Add USB NCM link state control support
This adds the ability to dynamically control the network link state for NCM devices. The host OS will see the network interface as connected/disconnected based on the link state. New API: - tud_network_link_state(rhport, is_up): Set link up/down state Example updates: - Added button control to toggle link state - Fixed LWIP integration to properly handle link state changes - Added printf to show correct protocol (NCM vs RNDIS/ECM) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,12 @@ this appears as either a RNDIS or CDC-ECM USB virtual network adapter; the OS pi
|
||||
RNDIS should be valid on Linux and Windows hosts, and CDC-ECM should be valid on Linux and macOS hosts
|
||||
|
||||
The MCU appears to the host as IP address 192.168.7.1, and provides a DHCP server, DNS server, and web server.
|
||||
|
||||
Link State Control:
|
||||
- Press the user button to toggle the network link state (UP/DOWN)
|
||||
- This simulates "ethernet cable unplugged/plugged" events
|
||||
- The host OS will see the network interface as disconnected/connected accordingly
|
||||
- Use this to test network error handling and recovery in host applications
|
||||
*/
|
||||
/*
|
||||
Some smartphones *may* work with this implementation as well, but likely have limited (broken) drivers,
|
||||
@@ -137,6 +143,12 @@ static err_t netif_init_cb(struct netif *netif) {
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
/* notifies the USB host about the link state change. */
|
||||
static void usbnet_netif_link_callback(struct netif *netif) {
|
||||
bool link_up = netif_is_link_up(netif);
|
||||
tud_network_link_state(BOARD_TUD_RHPORT, link_up);
|
||||
}
|
||||
|
||||
static void init_lwip(void) {
|
||||
struct netif *netif = &netif_data;
|
||||
|
||||
@@ -147,11 +159,19 @@ static void init_lwip(void) {
|
||||
memcpy(netif->hwaddr, tud_network_mac_address, sizeof(tud_network_mac_address));
|
||||
netif->hwaddr[5] ^= 0x01;
|
||||
|
||||
netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, netif_init_cb, ip_input);
|
||||
netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, netif_init_cb, ethernet_input);
|
||||
#if LWIP_IPV6
|
||||
netif_create_ip6_linklocal_address(netif, 1);
|
||||
#endif
|
||||
netif_set_default(netif);
|
||||
|
||||
#if LWIP_NETIF_LINK_CALLBACK
|
||||
// Set the link callback to notify USB host about link state changes
|
||||
netif_set_link_callback(netif, usbnet_netif_link_callback);
|
||||
netif_set_link_up(netif);
|
||||
#else
|
||||
tud_network_link_state(BOARD_TUD_RHPORT, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* handle any DNS requests from dns-server */
|
||||
@@ -171,13 +191,16 @@ bool tud_network_recv_cb(const uint8_t *src, uint16_t size) {
|
||||
if (size) {
|
||||
struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL);
|
||||
|
||||
if (p) {
|
||||
/* pbuf_alloc() has already initialized struct; all we need to do is copy the data */
|
||||
memcpy(p->payload, src, size);
|
||||
|
||||
/* store away the pointer for service_traffic() to later handle */
|
||||
received_frame = p;
|
||||
if (p == NULL) {
|
||||
printf("ERROR: Failed to allocate pbuf of size %d\n", size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy buf to pbuf */
|
||||
pbuf_take(p, src, size);
|
||||
|
||||
/* store away the pointer for service_traffic() to later handle */
|
||||
received_frame = p;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -194,12 +217,14 @@ uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) {
|
||||
static void service_traffic(void) {
|
||||
/* handle any packet received by tud_network_recv_cb() */
|
||||
if (received_frame) {
|
||||
struct netif *netif = &netif_data;
|
||||
// Surrender ownership of our pbuf unless there was an error
|
||||
// Only call pbuf_free if not Ok else it will panic with "pbuf_free: p->ref > 0"
|
||||
// or steal it from whatever took ownership of it with undefined consequences.
|
||||
// See: https://savannah.nongnu.org/patch/index.php?10121
|
||||
if (ethernet_input(received_frame, &netif_data)!=ERR_OK) {
|
||||
pbuf_free(received_frame);
|
||||
if (netif->input(received_frame, netif) != ERR_OK) {
|
||||
printf("ERROR: netif input failed\n");
|
||||
pbuf_free(received_frame);
|
||||
}
|
||||
received_frame = NULL;
|
||||
tud_network_recv_renew();
|
||||
@@ -216,6 +241,28 @@ void tud_network_init_cb(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_link_state_switch(void) {
|
||||
/* Check for button press to toggle link state */
|
||||
static bool last_link_state = true;
|
||||
static bool last_button_state = false;
|
||||
bool current_button_state = board_button_read();
|
||||
|
||||
if (current_button_state && !last_button_state) {
|
||||
/* Button pressed - toggle link state */
|
||||
last_link_state = !last_link_state;
|
||||
if (last_link_state) {
|
||||
printf("Link state: UP\n");
|
||||
netif_set_link_up(&netif_data);
|
||||
} else {
|
||||
printf("Link state: DOWN\n");
|
||||
netif_set_link_down(&netif_data);
|
||||
}
|
||||
/* LWIP callback will notify USB host about the change */
|
||||
}
|
||||
last_button_state = current_button_state;
|
||||
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* initialize TinyUSB */
|
||||
board_init();
|
||||
@@ -243,15 +290,23 @@ int main(void) {
|
||||
lwiperf_start_tcp_server_default(NULL, NULL);
|
||||
#endif
|
||||
|
||||
#if CFG_TUD_NCM
|
||||
printf("USB NCM network interface initialized\n");
|
||||
#elif CFG_TUD_ECM_RNDIS
|
||||
printf("USB RNDIS/ECM network interface initialized\n");
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
tud_task();
|
||||
service_traffic();
|
||||
handle_link_state_switch();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* lwip has provision for using a mutex, when applicable */
|
||||
/* This implementation is for single-threaded use only */
|
||||
sys_prot_t sys_arch_protect(void) {
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user