From 5de4a23abe315ecc5f9387ced910f54e29e0e69e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Tue, 27 May 2025 15:06:05 +1000 Subject: [PATCH] Add USB NCM link state control support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../device/net_lwip_webserver/src/lwipopts.h | 1 + examples/device/net_lwip_webserver/src/main.c | 73 ++++++++++++++++--- .../net_lwip_webserver/src/tusb_config.h | 2 + src/class/net/ncm_device.c | 36 ++++++++- src/class/net/net_device.h | 5 ++ 5 files changed, 107 insertions(+), 10 deletions(-) diff --git a/examples/device/net_lwip_webserver/src/lwipopts.h b/examples/device/net_lwip_webserver/src/lwipopts.h index 41e8f0d67..04949cef9 100644 --- a/examples/device/net_lwip_webserver/src/lwipopts.h +++ b/examples/device/net_lwip_webserver/src/lwipopts.h @@ -58,6 +58,7 @@ #define LWIP_HTTPD_SSI_INCLUDE_TAG 0 #define LWIP_SINGLE_NETIF 1 +#define LWIP_NETIF_LINK_CALLBACK 1 #define PBUF_POOL_SIZE 4 diff --git a/examples/device/net_lwip_webserver/src/main.c b/examples/device/net_lwip_webserver/src/main.c index 36f402332..41f02576f 100644 --- a/examples/device/net_lwip_webserver/src/main.c +++ b/examples/device/net_lwip_webserver/src/main.c @@ -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; } diff --git a/examples/device/net_lwip_webserver/src/tusb_config.h b/examples/device/net_lwip_webserver/src/tusb_config.h index 22082fc81..c774f59ff 100644 --- a/examples/device/net_lwip_webserver/src/tusb_config.h +++ b/examples/device/net_lwip_webserver/src/tusb_config.h @@ -85,6 +85,7 @@ extern "C" { #endif // Use different configurations to test all net devices (also due to resource limitations) +#ifndef USE_ECM #if TU_CHECK_MCU(OPT_MCU_LPC15XX, OPT_MCU_LPC40XX, OPT_MCU_LPC51UXX, OPT_MCU_LPC54) #define USE_ECM 1 #elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAML21, OPT_MCU_SAML22) @@ -97,6 +98,7 @@ extern "C" { #define USE_ECM 0 #define INCLUDE_IPERF #endif +#endif //-------------------------------------------------------------------- // NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c index f9fda0698..02833c5f1 100644 --- a/src/class/net/ncm_device.c +++ b/src/class/net/ncm_device.c @@ -110,6 +110,7 @@ typedef struct { NOTIFICATION_DONE } notification_xmit_state; // state of notification transmission bool notification_xmit_is_running; // notification is currently transmitted + bool link_is_up; // current link state // misc bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) @@ -218,7 +219,7 @@ static void notification_xmit(uint8_t rhport, bool force_next) { .direction = TUSB_DIR_IN }, .bRequest = CDC_NOTIF_NETWORK_CONNECTION, - .wValue = 1 /* Connected */, + .wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */ .wIndex = ncm_interface.itf_num, .wLength = 0, }, @@ -232,6 +233,7 @@ static void notification_xmit(uint8_t rhport, bool force_next) { ncm_interface.notification_xmit_is_running = true; } else { TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); + ncm_interface.notification_xmit_is_running = false; } } // notification_xmit @@ -755,6 +757,32 @@ static void tud_network_recv_renew_r(uint8_t rhport) { tud_network_recv_renew(); } // tud_network_recv_renew +/** + * Set the link state and send notification to host + */ +void tud_network_link_state(uint8_t rhport, bool is_up) { + TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); + + if (ncm_interface.link_is_up == is_up) { + // No change in link state + return; + } + + ncm_interface.link_is_up = is_up; + + // Only send notification if we have an active data interface + if (ncm_interface.itf_data_alt != 1) { + TU_LOG_DRV(" link state notification skipped (interface not active)\n"); + return; + } + + // Reset notification state to send link state update + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + + // Trigger notification transmission + notification_xmit(rhport, false); +} + //----------------------------------------------------------------------------- // // all the netd_*() stuff (interface TinyUSB -> driver) @@ -774,6 +802,12 @@ void netd_init(void) { for (int i = 0; i < RECV_NTB_N; ++i) { ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb; } + // Default link state - can be configured via CFG_TUD_NCM_DEFAULT_LINK_UP + #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP + ncm_interface.link_is_up = CFG_TUD_NCM_DEFAULT_LINK_UP; + #else + ncm_interface.link_is_up = true; // Default to link up if not set. + #endif } // netd_init /** diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h index 4c9a92f2d..fff2623b7 100644 --- a/src/class/net/net_device.h +++ b/src/class/net/net_device.h @@ -87,6 +87,11 @@ void tud_network_init_cb(void); // TODO removed later since it is not part of tinyusb stack extern uint8_t tud_network_mac_address[6]; +//------------- NCM -------------// + +// Set the network link state (up/down) and notify the host +void tud_network_link_state(uint8_t rhport, bool is_up); + //--------------------------------------------------------------------+ // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+