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:
@@ -58,6 +58,7 @@
|
|||||||
#define LWIP_HTTPD_SSI_INCLUDE_TAG 0
|
#define LWIP_HTTPD_SSI_INCLUDE_TAG 0
|
||||||
|
|
||||||
#define LWIP_SINGLE_NETIF 1
|
#define LWIP_SINGLE_NETIF 1
|
||||||
|
#define LWIP_NETIF_LINK_CALLBACK 1
|
||||||
|
|
||||||
#define PBUF_POOL_SIZE 4
|
#define PBUF_POOL_SIZE 4
|
||||||
|
|
||||||
|
@@ -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
|
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.
|
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,
|
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;
|
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) {
|
static void init_lwip(void) {
|
||||||
struct netif *netif = &netif_data;
|
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));
|
memcpy(netif->hwaddr, tud_network_mac_address, sizeof(tud_network_mac_address));
|
||||||
netif->hwaddr[5] ^= 0x01;
|
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
|
#if LWIP_IPV6
|
||||||
netif_create_ip6_linklocal_address(netif, 1);
|
netif_create_ip6_linklocal_address(netif, 1);
|
||||||
#endif
|
#endif
|
||||||
netif_set_default(netif);
|
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 */
|
/* 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) {
|
if (size) {
|
||||||
struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL);
|
struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL);
|
||||||
|
|
||||||
if (p) {
|
if (p == NULL) {
|
||||||
/* pbuf_alloc() has already initialized struct; all we need to do is copy the data */
|
printf("ERROR: Failed to allocate pbuf of size %d\n", size);
|
||||||
memcpy(p->payload, src, size);
|
return false;
|
||||||
|
|
||||||
/* store away the pointer for service_traffic() to later handle */
|
|
||||||
received_frame = p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Copy buf to pbuf */
|
||||||
|
pbuf_take(p, src, size);
|
||||||
|
|
||||||
|
/* store away the pointer for service_traffic() to later handle */
|
||||||
|
received_frame = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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) {
|
static void service_traffic(void) {
|
||||||
/* handle any packet received by tud_network_recv_cb() */
|
/* handle any packet received by tud_network_recv_cb() */
|
||||||
if (received_frame) {
|
if (received_frame) {
|
||||||
|
struct netif *netif = &netif_data;
|
||||||
// Surrender ownership of our pbuf unless there was an error
|
// 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"
|
// 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.
|
// or steal it from whatever took ownership of it with undefined consequences.
|
||||||
// See: https://savannah.nongnu.org/patch/index.php?10121
|
// See: https://savannah.nongnu.org/patch/index.php?10121
|
||||||
if (ethernet_input(received_frame, &netif_data)!=ERR_OK) {
|
if (netif->input(received_frame, netif) != ERR_OK) {
|
||||||
pbuf_free(received_frame);
|
printf("ERROR: netif input failed\n");
|
||||||
|
pbuf_free(received_frame);
|
||||||
}
|
}
|
||||||
received_frame = NULL;
|
received_frame = NULL;
|
||||||
tud_network_recv_renew();
|
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) {
|
int main(void) {
|
||||||
/* initialize TinyUSB */
|
/* initialize TinyUSB */
|
||||||
board_init();
|
board_init();
|
||||||
@@ -243,15 +290,23 @@ int main(void) {
|
|||||||
lwiperf_start_tcp_server_default(NULL, NULL);
|
lwiperf_start_tcp_server_default(NULL, NULL);
|
||||||
#endif
|
#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) {
|
while (1) {
|
||||||
tud_task();
|
tud_task();
|
||||||
service_traffic();
|
service_traffic();
|
||||||
|
handle_link_state_switch();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* lwip has provision for using a mutex, when applicable */
|
/* lwip has provision for using a mutex, when applicable */
|
||||||
|
/* This implementation is for single-threaded use only */
|
||||||
sys_prot_t sys_arch_protect(void) {
|
sys_prot_t sys_arch_protect(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -85,6 +85,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use different configurations to test all net devices (also due to resource limitations)
|
// 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)
|
#if TU_CHECK_MCU(OPT_MCU_LPC15XX, OPT_MCU_LPC40XX, OPT_MCU_LPC51UXX, OPT_MCU_LPC54)
|
||||||
#define USE_ECM 1
|
#define USE_ECM 1
|
||||||
#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAML21, OPT_MCU_SAML22)
|
#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAML21, OPT_MCU_SAML22)
|
||||||
@@ -97,6 +98,7 @@ extern "C" {
|
|||||||
#define USE_ECM 0
|
#define USE_ECM 0
|
||||||
#define INCLUDE_IPERF
|
#define INCLUDE_IPERF
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
//--------------------------------------------------------------------
|
||||||
// NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING
|
// NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING
|
||||||
|
@@ -110,6 +110,7 @@ typedef struct {
|
|||||||
NOTIFICATION_DONE
|
NOTIFICATION_DONE
|
||||||
} notification_xmit_state; // state of notification transmission
|
} notification_xmit_state; // state of notification transmission
|
||||||
bool notification_xmit_is_running; // notification is currently transmitted
|
bool notification_xmit_is_running; // notification is currently transmitted
|
||||||
|
bool link_is_up; // current link state
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations)
|
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
|
.direction = TUSB_DIR_IN
|
||||||
},
|
},
|
||||||
.bRequest = CDC_NOTIF_NETWORK_CONNECTION,
|
.bRequest = CDC_NOTIF_NETWORK_CONNECTION,
|
||||||
.wValue = 1 /* Connected */,
|
.wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */
|
||||||
.wIndex = ncm_interface.itf_num,
|
.wIndex = ncm_interface.itf_num,
|
||||||
.wLength = 0,
|
.wLength = 0,
|
||||||
},
|
},
|
||||||
@@ -232,6 +233,7 @@ static void notification_xmit(uint8_t rhport, bool force_next) {
|
|||||||
ncm_interface.notification_xmit_is_running = true;
|
ncm_interface.notification_xmit_is_running = true;
|
||||||
} else {
|
} else {
|
||||||
TU_LOG_DRV(" NOTIFICATION_FINISHED\n");
|
TU_LOG_DRV(" NOTIFICATION_FINISHED\n");
|
||||||
|
ncm_interface.notification_xmit_is_running = false;
|
||||||
}
|
}
|
||||||
} // notification_xmit
|
} // notification_xmit
|
||||||
|
|
||||||
@@ -755,6 +757,32 @@ static void tud_network_recv_renew_r(uint8_t rhport) {
|
|||||||
tud_network_recv_renew();
|
tud_network_recv_renew();
|
||||||
} // 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)
|
// all the netd_*() stuff (interface TinyUSB -> driver)
|
||||||
@@ -774,6 +802,12 @@ void netd_init(void) {
|
|||||||
for (int i = 0; i < RECV_NTB_N; ++i) {
|
for (int i = 0; i < RECV_NTB_N; ++i) {
|
||||||
ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb;
|
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
|
} // netd_init
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -87,6 +87,11 @@ void tud_network_init_cb(void);
|
|||||||
// TODO removed later since it is not part of tinyusb stack
|
// TODO removed later since it is not part of tinyusb stack
|
||||||
extern uint8_t tud_network_mac_address[6];
|
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
|
// INTERNAL USBD-CLASS DRIVER API
|
||||||
//--------------------------------------------------------------------+
|
//--------------------------------------------------------------------+
|
||||||
|
Reference in New Issue
Block a user