diff options
| author | 54fire <[email protected]> | 2026-08-17 14:48:32 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-08-17 14:48:32 +0800 |
| commit | 60911ffd9ed6aaacf768dfd557508d0b40170664 (patch) | |
| tree | 7e38cdec71f07b3afc42d206585ba4809d522516 | |
| parent | 1bfc83ad46301553c8b626446417273b18abca6e (diff) | |
feat(class/cdc/usbd_ncm): 添加usb device ncm功能 (#433)
* 添加usb device ncm功能
* fix(class/cdc/usbd_ncm): address review findings
- fix compile failure when CONFIG_USBDEV_CDC_NCM_USING_LWIP is disabled
- fix GET/SET_NTB_INPUT_SIZE operating on the OUT direction size, and
honor the negotiated IN limit in the transmit path
- set tx busy flag before submitting the transfer to avoid a race that
could block transmit permanently
- follow the ndp chain via wNextNdpIndex so chained datagrams are not
dropped, and access ntb fields with LE helpers instead of assuming a
little-endian target
- reject oversized frames instead of transmitting truncated ones
- register endpoints only once when initializing both interfaces
- wait for tx completion with a timeout in the demo instead of an
unbounded busy wait
- make usbd_cdc_ncm.h self-contained
Co-Authored-By: Claude <[email protected]>
---------
Co-authored-by: Claude <[email protected]>
| -rw-r--r-- | Kconfig | 1 | ||||
| -rw-r--r-- | class/cdc/usb_cdc.h | 3 | ||||
| -rw-r--r-- | class/cdc/usbd_cdc_ncm.c | 545 | ||||
| -rw-r--r-- | class/cdc/usbd_cdc_ncm.h | 41 | ||||
| -rw-r--r-- | common/usb_util.h | 20 | ||||
| -rw-r--r-- | demo/cdc_ncm_template.c | 384 |
6 files changed, 993 insertions, 1 deletions
@@ -122,7 +122,6 @@ if CHERRYUSB config CHERRYUSB_DEVICE_CDC_NCM bool prompt "Enable usb cdc ncm device" - depends on !IDF_CMAKE default n config CHERRYUSB_DEVICE_MTP diff --git a/class/cdc/usb_cdc.h b/class/cdc/usb_cdc.h index a21bc3b2..c80a107e 100644 --- a/class/cdc/usb_cdc.h +++ b/class/cdc/usb_cdc.h @@ -275,6 +275,9 @@ #define CDC_ECM_NOTIFY_CODE_RESPONSE_AVAILABLE 0x01 #define CDC_ECM_NOTIFY_CODE_CONNECTION_SPEED_CHANGE 0x2A +#define CDC_NCM_CONNECT_SPEED_UPSTREAM 0x004C4B40U /* 5Mbps */ +#define CDC_NCM_CONNECT_SPEED_DOWNSTREAM 0x004C4B40U /* 5Mbps */ + #define CDC_NCM_NTH16_SIGNATURE 0x484D434E #define CDC_NCM_NDP16_SIGNATURE_NCM0 0x304D434E #define CDC_NCM_NDP16_SIGNATURE_NCM1 0x314D434E diff --git a/class/cdc/usbd_cdc_ncm.c b/class/cdc/usbd_cdc_ncm.c new file mode 100644 index 00000000..68a272ee --- /dev/null +++ b/class/cdc/usbd_cdc_ncm.c @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2026, 54fire + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include <string.h> +#include "usbd_core.h" +#include "usbd_cdc_ncm.h" + +#define CDC_NCM_OUT_EP_IDX 0 +#define CDC_NCM_IN_EP_IDX 1 +#define CDC_NCM_INT_EP_IDX 2 + +#define CONFIG_CDC_NCM_ETH_MAX_SEGSZE 1514U +#define CONFIG_CDC_NCM_NTB_MAX_SIZE 2048U +#define CDC_NCM_NTH16_LEN 12U +#define CDC_NCM_NDP16_LEN 16U +#define CDC_NCM_DATAGRAM_OFFSET 16U + +static struct usbd_endpoint cdc_ncm_ep_data[3]; + +#ifdef CONFIG_USBDEV_CDC_NCM_USING_LWIP +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_ncm_rx_buffer[USB_ALIGN_UP(CONFIG_CDC_NCM_NTB_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)]; +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_ncm_tx_buffer[USB_ALIGN_UP(CONFIG_CDC_NCM_NTB_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)]; +#endif +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_ncm_ctrl_buf[USB_ALIGN_UP(32, CONFIG_USB_ALIGN_SIZE)]; +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_ncm_notify_buf[USB_ALIGN_UP(16, CONFIG_USB_ALIGN_SIZE)]; + +static volatile uint32_t g_cdc_ncm_rx_ntb_length = 0; +static volatile uint32_t g_cdc_ncm_tx_ntb_length = 0; +static volatile bool g_cdc_ncm_data_alt_active = false; +static uint8_t *g_cdc_ncm_rx_data_buffer = NULL; +static uint32_t g_cdc_ncm_rx_total_length = 0; +static uint16_t g_cdc_ncm_rx_datagram_pos = 0; +static uint16_t g_cdc_ncm_rx_ndp_index = 0; +static uint16_t g_cdc_ncm_rx_sequence = 0; +static uint16_t g_cdc_ncm_tx_sequence = 0; +static uint16_t g_cdc_ncm_max_datagram_size = CONFIG_CDC_NCM_ETH_MAX_SEGSZE; +static uint32_t g_cdc_ncm_ntb_in_max_size = CONFIG_CDC_NCM_NTB_MAX_SIZE; +static uint32_t g_cdc_ncm_ntb_out_max_size = CONFIG_CDC_NCM_NTB_MAX_SIZE; +static uint16_t g_cdc_ncm_ntb_format = 0; +static uint16_t g_cdc_ncm_packet_filter = 0; +static volatile uint8_t g_current_net_status = 0; +static volatile uint8_t g_cmd_intf = 0; +static uint8_t g_cdc_ncm_mac[6] = { 0 }; +static bool g_cdc_ncm_ep_registered = false; + +static uint32_t g_connect_speed_table[2] = { CDC_NCM_CONNECT_SPEED_UPSTREAM, CDC_NCM_CONNECT_SPEED_DOWNSTREAM }; + +static void usbd_cdc_ncm_send_notify(uint8_t notifycode, uint8_t value, uint32_t *speed) +{ + struct cdc_eth_notification *notify = (struct cdc_eth_notification *)g_cdc_ncm_notify_buf; + uint8_t bytes2send = 0; + + memset(g_cdc_ncm_notify_buf, 0, 16); + notify->bmRequestType = CDC_ECM_BMREQUEST_TYPE_ECM; + notify->bNotificationType = notifycode; + notify->wIndex = g_cmd_intf; + + switch (notifycode) { + case CDC_ECM_NOTIFY_CODE_NETWORK_CONNECTION: + notify->wValue = value; + bytes2send = 8; + break; + case CDC_ECM_NOTIFY_CODE_RESPONSE_AVAILABLE: + bytes2send = 8; + break; + case CDC_ECM_NOTIFY_CODE_CONNECTION_SPEED_CHANGE: + notify->wLength = 8; + if (speed) { + memcpy(notify->data, speed, 8); + } + bytes2send = 16; + break; + default: + break; + } + + if (usb_device_is_configured(0) && bytes2send) { + usbd_ep_start_write(0, cdc_ncm_ep_data[CDC_NCM_INT_EP_IDX].ep_addr, g_cdc_ncm_notify_buf, bytes2send); + } +} + +static void usbd_cdc_ncm_fill_ntb_parameters(void) +{ + memset(g_cdc_ncm_ctrl_buf, 0, 32); + SET_LE16(&g_cdc_ncm_ctrl_buf[0], 28); /* wLength */ + SET_LE16(&g_cdc_ncm_ctrl_buf[2], 0x0001); /* NTB16 only */ + SET_LE32(&g_cdc_ncm_ctrl_buf[4], g_cdc_ncm_ntb_in_max_size); /* device-to-host NTB */ + SET_LE16(&g_cdc_ncm_ctrl_buf[8], 4); /* wNdbInDivisor */ + SET_LE16(&g_cdc_ncm_ctrl_buf[10], 0); /* wNdbInPayloadRemainder */ + SET_LE16(&g_cdc_ncm_ctrl_buf[12], 4); /* wNdbInAlignment */ + SET_LE16(&g_cdc_ncm_ctrl_buf[14], 0); /* wReserved */ + SET_LE32(&g_cdc_ncm_ctrl_buf[16], g_cdc_ncm_ntb_out_max_size);/* host-to-device NTB */ + SET_LE16(&g_cdc_ncm_ctrl_buf[20], 4); /* wNdbOutDivisor */ + SET_LE16(&g_cdc_ncm_ctrl_buf[22], 0); /* wNdbOutPayloadRemainder */ + SET_LE16(&g_cdc_ncm_ctrl_buf[24], 4); /* wNdbOutAlignment */ + SET_LE16(&g_cdc_ncm_ctrl_buf[26], 1); /* one datagram preferred */ +} + +static int cdc_ncm_class_interface_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len) +{ + (void)busid; + + g_cmd_intf = LO_BYTE(setup->wIndex); + + switch (setup->bRequest) { + case CDC_REQUEST_GET_NTB_PARAMETERS: + usbd_cdc_ncm_fill_ntb_parameters(); + *data = g_cdc_ncm_ctrl_buf; + *len = 28; + break; + case CDC_REQUEST_GET_NTB_FORMAT: + SET_LE16(g_cdc_ncm_ctrl_buf, g_cdc_ncm_ntb_format); + *data = g_cdc_ncm_ctrl_buf; + *len = 2; + break; + case CDC_REQUEST_SET_NTB_FORMAT: + if (setup->wValue != 0) { + return -1; + } + g_cdc_ncm_ntb_format = 0; + break; + case CDC_REQUEST_GET_NTB_INPUT_SIZE: + SET_LE32(g_cdc_ncm_ctrl_buf, g_cdc_ncm_ntb_in_max_size); + *data = g_cdc_ncm_ctrl_buf; + *len = 4; + break; + case CDC_REQUEST_SET_NTB_INPUT_SIZE: + if (setup->wLength >= 4 && data && *data) { + uint32_t ntb_size = GET_LE32(*data); + if (ntb_size >= (CDC_NCM_DATAGRAM_OFFSET + CDC_NCM_NDP16_LEN) && ntb_size <= CONFIG_CDC_NCM_NTB_MAX_SIZE) { + g_cdc_ncm_ntb_in_max_size = ntb_size; + } + } + break; + case CDC_REQUEST_GET_MAX_DATAGRAM_SIZE: + SET_LE16(g_cdc_ncm_ctrl_buf, g_cdc_ncm_max_datagram_size); + *data = g_cdc_ncm_ctrl_buf; + *len = 2; + break; + case CDC_REQUEST_SET_MAX_DATAGRAM_SIZE: + if (setup->wLength >= 2 && data && *data) { + uint16_t max_datagram_size = GET_LE16(*data); + if (max_datagram_size >= 64 && max_datagram_size <= CONFIG_CDC_NCM_ETH_MAX_SEGSZE) { + g_cdc_ncm_max_datagram_size = max_datagram_size; + } + } else if (setup->wValue >= 64 && setup->wValue <= CONFIG_CDC_NCM_ETH_MAX_SEGSZE) { + g_cdc_ncm_max_datagram_size = setup->wValue; + } + break; + case CDC_REQUEST_GET_CRC_MODE: + SET_LE16(g_cdc_ncm_ctrl_buf, 0); + *data = g_cdc_ncm_ctrl_buf; + *len = 2; + break; + case CDC_REQUEST_SET_CRC_MODE: + if (setup->wValue != 0) { + return -1; + } + break; + case CDC_REQUEST_GET_NET_ADDRESS: + memcpy(g_cdc_ncm_ctrl_buf, g_cdc_ncm_mac, 6); + *data = g_cdc_ncm_ctrl_buf; + *len = 6; + break; + case CDC_REQUEST_SET_NET_ADDRESS: + if (setup->wLength >= 6 && data && *data) { + memcpy(g_cdc_ncm_mac, *data, 6); + } + break; + case CDC_REQUEST_SET_ETHERNET_PACKET_FILTER: + g_cdc_ncm_packet_filter = setup->wValue; + g_connect_speed_table[0] = 100000000; + g_connect_speed_table[1] = 100000000; + usbd_cdc_ncm_set_connect(g_cdc_ncm_packet_filter != 0, g_connect_speed_table); + break; + case CDC_REQUEST_SET_ETHERNET_MULTICAST_FILTERS: + case CDC_REQUEST_SET_ETHERNET_PMP_FILTER: + break; + default: + USB_LOG_WRN("Unhandled CDC NCM Class bRequest 0x%02x\r\n", setup->bRequest); + return -1; + } + + return 0; +} + +static void cdc_ncm_notify_handler(uint8_t busid, uint8_t event, void *arg) +{ + (void)busid; + + switch (event) { + case USBD_EVENT_RESET: + case USBD_EVENT_DISCONNECTED: + g_current_net_status = 0; + g_cdc_ncm_rx_ntb_length = 0; + g_cdc_ncm_tx_ntb_length = 0; + g_cdc_ncm_data_alt_active = false; + g_cdc_ncm_rx_datagram_pos = 0; + g_cdc_ncm_rx_ndp_index = 0; + break; + case USBD_EVENT_SET_INTERFACE: { +#ifdef CONFIG_USBDEV_CDC_NCM_USING_LWIP + struct usb_interface_descriptor *desc = (struct usb_interface_descriptor *)arg; + + if (desc && desc->bAlternateSetting == 1) { + g_cdc_ncm_data_alt_active = true; + usbd_cdc_ncm_start_read(g_cdc_ncm_rx_buffer, g_cdc_ncm_ntb_out_max_size); + } else { + g_cdc_ncm_data_alt_active = false; + g_cdc_ncm_tx_ntb_length = 0; + } +#else + (void)arg; +#endif + break; + } + default: + break; + } +} + +static void cdc_ncm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + (void)busid; + (void)ep; + + g_cdc_ncm_rx_ntb_length = nbytes; + g_cdc_ncm_rx_datagram_pos = 0; + g_cdc_ncm_rx_ndp_index = 0; + usbd_cdc_ncm_data_recv_done(g_cdc_ncm_rx_ntb_length); +} + +static void cdc_ncm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + (void)busid; + + if ((nbytes % usbd_get_ep_mps(0, ep)) == 0 && nbytes) { + usbd_ep_start_write(0, ep, NULL, 0); + } else { + usbd_cdc_ncm_data_send_done(g_cdc_ncm_tx_ntb_length); + g_cdc_ncm_tx_ntb_length = 0; + } +} + +static void cdc_ncm_int_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + (void)busid; + (void)ep; + (void)nbytes; + + if (g_current_net_status == 2) { + g_current_net_status = 3; + usbd_cdc_ncm_send_notify(CDC_ECM_NOTIFY_CODE_CONNECTION_SPEED_CHANGE, 0, g_connect_speed_table); + } else { + g_current_net_status = 0; + } +} + +int usbd_cdc_ncm_start_write(uint8_t *buf, uint32_t len) +{ + int ret; + + if (!usb_device_is_configured(0) || !g_cdc_ncm_data_alt_active) { + return -USB_ERR_NOTCONN; + } + + if (g_cdc_ncm_tx_ntb_length > 0) { + return -USB_ERR_BUSY; + } + + /* mark busy before submitting: the transfer may complete in interrupt + context before usbd_ep_start_write() returns */ + g_cdc_ncm_tx_ntb_length = len; + ret = usbd_ep_start_write(0, cdc_ncm_ep_data[CDC_NCM_IN_EP_IDX].ep_addr, buf, len); + if (ret != 0) { + g_cdc_ncm_tx_ntb_length = 0; + return ret; + } + + USB_LOG_DBG("txlen:%d\r\n", g_cdc_ncm_tx_ntb_length); + return 0; +} + +int usbd_cdc_ncm_start_read(uint8_t *buf, uint32_t len) +{ + if (!usb_device_is_configured(0)) { + return -USB_ERR_NOTCONN; + } + + g_cdc_ncm_rx_data_buffer = buf; + g_cdc_ncm_rx_total_length = len; + g_cdc_ncm_rx_ntb_length = 0; + g_cdc_ncm_rx_datagram_pos = 0; + g_cdc_ncm_rx_ndp_index = 0; + return usbd_ep_start_read(0, cdc_ncm_ep_data[CDC_NCM_OUT_EP_IDX].ep_addr, buf, len); +} + +#ifdef CONFIG_USBDEV_CDC_NCM_USING_LWIP +static bool cdc_ncm_check_ndp16(uint16_t ndp_index) +{ + struct cdc_ncm_ndp16 *ndp16; + uint32_t signature; + uint16_t ndp_len; + + if (ndp_index == 0 || (uint32_t)ndp_index + sizeof(struct cdc_ncm_ndp16) > g_cdc_ncm_rx_ntb_length) { + return false; + } + + ndp16 = (struct cdc_ncm_ndp16 *)&g_cdc_ncm_rx_data_buffer[ndp_index]; + signature = GET_LE32((uint8_t *)&ndp16->dwSignature); + ndp_len = GET_LE16((uint8_t *)&ndp16->wLength); + + if ((signature != CDC_NCM_NDP16_SIGNATURE_NCM0) && + (signature != CDC_NCM_NDP16_SIGNATURE_NCM1)) { + USB_LOG_WRN("invalid ncm ndp16 signature\r\n"); + return false; + } + + if (ndp_len < 12 || (uint32_t)ndp_index + ndp_len > g_cdc_ncm_rx_ntb_length) { + USB_LOG_WRN("invalid ncm ndp16 len\r\n"); + return false; + } + + return true; +} + +static bool cdc_ncm_prepare_rx_ndp(void) +{ + struct cdc_ncm_nth16 *nth16; + uint16_t ndp_index; + + if (g_cdc_ncm_rx_ntb_length < (CDC_NCM_NTH16_LEN + CDC_NCM_NDP16_LEN)) { + return false; + } + + nth16 = (struct cdc_ncm_nth16 *)g_cdc_ncm_rx_data_buffer; + ndp_index = GET_LE16((uint8_t *)&nth16->wNdpIndex); + if (GET_LE32((uint8_t *)&nth16->dwSignature) != CDC_NCM_NTH16_SIGNATURE || + GET_LE16((uint8_t *)&nth16->wHeaderLength) != CDC_NCM_NTH16_LEN || + (uint32_t)GET_LE16((uint8_t *)&nth16->wBlockLength) > g_cdc_ncm_rx_ntb_length || + !cdc_ncm_check_ndp16(ndp_index)) { + USB_LOG_WRN("invalid ncm nth16\r\n"); + return false; + } + + g_cdc_ncm_rx_ndp_index = ndp_index; + g_cdc_ncm_rx_sequence = GET_LE16((uint8_t *)&nth16->wSequence); + (void)g_cdc_ncm_rx_sequence; + return true; +} + +struct pbuf *usbd_cdc_ncm_eth_rx(void) +{ + struct cdc_ncm_ndp16 *ndp16; + uint16_t ndp_len; + uint16_t next_ndp_index; + + if (g_cdc_ncm_rx_ntb_length == 0 || g_cdc_ncm_rx_data_buffer == NULL) { + return NULL; + } + + if (g_cdc_ncm_rx_ndp_index == 0 && !cdc_ncm_prepare_rx_ndp()) { + usbd_cdc_ncm_start_read(g_cdc_ncm_rx_buffer, g_cdc_ncm_ntb_out_max_size); + return NULL; + } + + for (;;) { + ndp16 = (struct cdc_ncm_ndp16 *)&g_cdc_ncm_rx_data_buffer[g_cdc_ncm_rx_ndp_index]; + ndp_len = GET_LE16((uint8_t *)&ndp16->wLength); + + while ((8 + 4 * (g_cdc_ncm_rx_datagram_pos + 1)) <= ndp_len) { + struct cdc_ncm_ndp16_datagram *datagram; + uint16_t index; + uint16_t length; + struct pbuf *p; + + datagram = (struct cdc_ncm_ndp16_datagram *)&g_cdc_ncm_rx_data_buffer[g_cdc_ncm_rx_ndp_index + 8 + 4 * g_cdc_ncm_rx_datagram_pos]; + g_cdc_ncm_rx_datagram_pos++; + index = GET_LE16((uint8_t *)&datagram->wDatagramIndex); + length = GET_LE16((uint8_t *)&datagram->wDatagramLength); + + if (index == 0 || length == 0) { + break; + } + if ((uint32_t)index + length > g_cdc_ncm_rx_ntb_length || length > g_cdc_ncm_max_datagram_size) { + USB_LOG_WRN("invalid ncm datagram index:%u len:%u\r\n", index, length); + continue; + } + + p = pbuf_alloc(PBUF_RAW, length, PBUF_POOL); + if (p == NULL) { + USB_LOG_WRN("ncm pbuf alloc failed\r\n"); + usbd_cdc_ncm_start_read(g_cdc_ncm_rx_buffer, g_cdc_ncm_ntb_out_max_size); + return NULL; + } + if (pbuf_take(p, &g_cdc_ncm_rx_data_buffer[index], length) != ERR_OK) { + pbuf_free(p); + continue; + } + + USB_LOG_DBG("rxlen:%d\r\n", length); + return p; + } + + /* current ndp is exhausted, follow the ndp chain if present */ + next_ndp_index = GET_LE16((uint8_t *)&ndp16->wNextNdpIndex); + if (next_ndp_index == 0 || !cdc_ncm_check_ndp16(next_ndp_index)) { + break; + } + g_cdc_ncm_rx_ndp_index = next_ndp_index; + g_cdc_ncm_rx_datagram_pos = 0; + } + + usbd_cdc_ncm_start_read(g_cdc_ncm_rx_buffer, g_cdc_ncm_ntb_out_max_size); + return NULL; +} + +int usbd_cdc_ncm_eth_tx(struct pbuf *p) +{ + struct cdc_ncm_nth16 *nth16; + struct cdc_ncm_ndp16 *ndp16; + struct cdc_ncm_ndp16_datagram *datagram; + struct pbuf *q; + uint8_t *buffer; + uint16_t payload_len; + uint16_t aligned_payload_len; + uint16_t ndp_index; + uint16_t block_len; + + if (!usb_device_is_configured(0)) { + return -USB_ERR_NOTCONN; + } + + if (g_cdc_ncm_tx_ntb_length > 0) { + return -USB_ERR_BUSY; + } + + if (p->tot_len > g_cdc_ncm_max_datagram_size) { + USB_LOG_WRN("ncm tx frame too large:%u\r\n", p->tot_len); + return -USB_ERR_INVAL; + } + + payload_len = p->tot_len; + aligned_payload_len = USB_ALIGN_UP(payload_len, 4); + ndp_index = CDC_NCM_DATAGRAM_OFFSET + aligned_payload_len; + block_len = ndp_index + CDC_NCM_NDP16_LEN; + if (block_len > sizeof(g_cdc_ncm_tx_buffer) || block_len > g_cdc_ncm_ntb_in_max_size) { + return -USB_ERR_INVAL; + } + + memset(g_cdc_ncm_tx_buffer, 0, block_len); + buffer = &g_cdc_ncm_tx_buffer[CDC_NCM_DATAGRAM_OFFSET]; + for (q = p; q != NULL && payload_len > 0; q = q->next) { + uint16_t copy_len = MIN(q->len, payload_len); + usb_memcpy(buffer, q->payload, copy_len); + buffer += copy_len; + payload_len -= copy_len; + } + payload_len = p->tot_len; + + nth16 = (struct cdc_ncm_nth16 *)&g_cdc_ncm_tx_buffer[0]; + SET_LE32((uint8_t *)&nth16->dwSignature, CDC_NCM_NTH16_SIGNATURE); + SET_LE16((uint8_t *)&nth16->wHeaderLength, CDC_NCM_NTH16_LEN); + SET_LE16((uint8_t *)&nth16->wSequence, g_cdc_ncm_tx_sequence++); + SET_LE16((uint8_t *)&nth16->wBlockLength, block_len); + SET_LE16((uint8_t *)&nth16->wNdpIndex, ndp_index); + + ndp16 = (struct cdc_ncm_ndp16 *)&g_cdc_ncm_tx_buffer[ndp_index]; + SET_LE32((uint8_t *)&ndp16->dwSignature, CDC_NCM_NDP16_SIGNATURE_NCM0); + SET_LE16((uint8_t *)&ndp16->wLength, CDC_NCM_NDP16_LEN); + SET_LE16((uint8_t *)&ndp16->wNextNdpIndex, 0); + + datagram = (struct cdc_ncm_ndp16_datagram *)&g_cdc_ncm_tx_buffer[ndp_index + 8]; + SET_LE16((uint8_t *)&datagram[0].wDatagramIndex, CDC_NCM_DATAGRAM_OFFSET); + SET_LE16((uint8_t *)&datagram[0].wDatagramLength, payload_len); + datagram[1].wDatagramIndex = 0; + datagram[1].wDatagramLength = 0; + + return usbd_cdc_ncm_start_write(g_cdc_ncm_tx_buffer, block_len); +} +#endif + +struct usbd_interface *usbd_cdc_ncm_init_intf(struct usbd_interface *intf, + const uint8_t int_ep, + const uint8_t out_ep, + const uint8_t in_ep, + uint8_t mac[6]) +{ + memcpy(g_cdc_ncm_mac, mac, 6); + + intf->class_interface_handler = cdc_ncm_class_interface_request_handler; + intf->class_endpoint_handler = NULL; + intf->vendor_handler = NULL; + intf->notify_handler = cdc_ncm_notify_handler; + + /* control and data interfaces share the same endpoints, register them only once */ + if (!g_cdc_ncm_ep_registered) { + g_cdc_ncm_ep_registered = true; + + cdc_ncm_ep_data[CDC_NCM_OUT_EP_IDX].ep_addr = out_ep; + cdc_ncm_ep_data[CDC_NCM_OUT_EP_IDX].ep_cb = cdc_ncm_bulk_out; + cdc_ncm_ep_data[CDC_NCM_IN_EP_IDX].ep_addr = in_ep; + cdc_ncm_ep_data[CDC_NCM_IN_EP_IDX].ep_cb = cdc_ncm_bulk_in; + cdc_ncm_ep_data[CDC_NCM_INT_EP_IDX].ep_addr = int_ep; + cdc_ncm_ep_data[CDC_NCM_INT_EP_IDX].ep_cb = cdc_ncm_int_in; + + usbd_add_endpoint(0, &cdc_ncm_ep_data[CDC_NCM_OUT_EP_IDX]); + usbd_add_endpoint(0, &cdc_ncm_ep_data[CDC_NCM_IN_EP_IDX]); + usbd_add_endpoint(0, &cdc_ncm_ep_data[CDC_NCM_INT_EP_IDX]); + } + + return intf; +} + +int usbd_cdc_ncm_set_connect(bool connect, uint32_t speed[2]) +{ + if (!usb_device_is_configured(0)) { + return -USB_ERR_NOTCONN; + } + + if (connect) { + g_current_net_status = 2; + if (speed) { + memcpy(g_connect_speed_table, speed, 8); + } + usbd_cdc_ncm_send_notify(CDC_ECM_NOTIFY_CODE_NETWORK_CONNECTION, CDC_ECM_NET_CONNECTED, NULL); + } else { + g_current_net_status = 1; + usbd_cdc_ncm_send_notify(CDC_ECM_NOTIFY_CODE_NETWORK_CONNECTION, CDC_ECM_NET_DISCONNECTED, NULL); + } + + return 0; +} + +__WEAK void usbd_cdc_ncm_data_recv_done(uint32_t len) +{ + (void)len; +} + +__WEAK void usbd_cdc_ncm_data_send_done(uint32_t len) +{ + (void)len; +} diff --git a/class/cdc/usbd_cdc_ncm.h b/class/cdc/usbd_cdc_ncm.h new file mode 100644 index 00000000..7a232c32 --- /dev/null +++ b/class/cdc/usbd_cdc_ncm.h @@ -0,0 +1,41 @@ +/* + * CherryUSB CDC NCM device class glue + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef USBD_CDC_NCM_H +#define USBD_CDC_NCM_H + +#include <stdint.h> +#include <stdbool.h> +#include "usb_cdc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct usbd_interface *usbd_cdc_ncm_init_intf(struct usbd_interface *intf, + const uint8_t int_ep, + const uint8_t out_ep, + const uint8_t in_ep, + uint8_t mac[6]); + +int usbd_cdc_ncm_set_connect(bool connect, uint32_t speed[2]); + +void usbd_cdc_ncm_data_recv_done(uint32_t len); +void usbd_cdc_ncm_data_send_done(uint32_t len); +int usbd_cdc_ncm_start_write(uint8_t *buf, uint32_t len); +int usbd_cdc_ncm_start_read(uint8_t *buf, uint32_t len); + +#ifdef CONFIG_USBDEV_CDC_NCM_USING_LWIP +#include "lwip/netif.h" +#include "lwip/pbuf.h" +struct pbuf *usbd_cdc_ncm_eth_rx(void); +int usbd_cdc_ncm_eth_tx(struct pbuf *p); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* USBD_CDC_NCM_H */ diff --git a/common/usb_util.h b/common/usb_util.h index 521bf27f..97e0917c 100644 --- a/common/usb_util.h +++ b/common/usb_util.h @@ -185,6 +185,26 @@ (field)[3] = (uint8_t)((value) >> 0); \ } while (0) +#define GET_LE16(field) \ + (((uint16_t)(field)[0] << 0) | ((uint16_t)(field)[1] << 8)) + +#define GET_LE32(field) \ + (((uint32_t)(field)[0] << 0) | ((uint32_t)(field)[1] << 8) | ((uint32_t)(field)[2] << 16) | ((uint32_t)(field)[3] << 24)) + +#define SET_LE16(field, value) \ + do { \ + (field)[0] = (uint8_t)((value) >> 0); \ + (field)[1] = (uint8_t)((value) >> 8); \ + } while (0) + +#define SET_LE32(field, value) \ + do { \ + (field)[0] = (uint8_t)((value) >> 0); \ + (field)[1] = (uint8_t)((value) >> 8); \ + (field)[2] = (uint8_t)((value) >> 16); \ + (field)[3] = (uint8_t)((value) >> 24); \ + } while (0) + #define WBVAL(x) ((x) & 0xFF), (((x) >> 8) & 0xFF) #define DBVAL(x) ((x) & 0xFF), (((x) >> 8) & 0xFF), (((x) >> 16) & 0xFF), (((x) >> 24) & 0xFF) diff --git a/demo/cdc_ncm_template.c b/demo/cdc_ncm_template.c new file mode 100644 index 00000000..17327716 --- /dev/null +++ b/demo/cdc_ncm_template.c @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2026, 54fire + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_cdc_ncm.h" + +#ifndef CONFIG_USBDEV_CDC_NCM_USING_LWIP +#error "Please enable CONFIG_USBDEV_CDC_NCM_USING_LWIP for this demo" +#endif + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x02 +#define CDC_INT_EP 0x83 + +#define USBD_VID 0xFFFF +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + CDC_NCM_DESCRIPTOR_LEN) + +#ifdef CONFIG_USB_HS +#define CDC_MAX_MPS 512 +#else +#define CDC_MAX_MPS 64 +#endif + +/* str idx = 4 is for mac address: aa:bb:cc:dd:ee:ff*/ +#define CDC_NCM_MAC_STRING_INDEX 4 + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01) +}; + +static const uint8_t config_descriptor[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_NCM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, CDC_NCM_MAC_STRING_INDEX) +}; + +static const uint8_t device_quality_descriptor[] = { + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB CDC NCM DEMO", /* Product */ + "2026123456", /* Serial Number */ + "aabbccddeeff", /* ncm mac address */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + return config_descriptor; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + if (index >= (sizeof(string_descriptors) / sizeof(char *))) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor cdc_ncm_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback +}; + +uint8_t mac[6] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + +#ifdef RT_USING_LWIP + +#ifndef RT_LWIP_DHCP +#error cdc_ncm must enable RT_LWIP_DHCP +#endif + +#ifndef LWIP_USING_DHCPD +#error cdc_ncm must enable LWIP_USING_DHCPD +#endif + +#include <rtthread.h> +#include <rtdevice.h> +#include <netif/ethernetif.h> +#include <dhcp_server.h> + +#define CDC_NCM_TX_TIMEOUT_MS 1000 + +struct eth_device cdc_ncm_dev; +static struct rt_semaphore cdc_ncm_tx_sem; + +void usbd_cdc_ncm_data_send_done(uint32_t len) +{ + (void)len; + rt_sem_release(&cdc_ncm_tx_sem); +} + +static rt_err_t rt_usbd_cdc_ncm_control(rt_device_t dev, int cmd, void *args) +{ + switch (cmd) { + case NIOCTL_GADDR: + + /* get mac address */ + if (args) { + uint8_t *mac_dev = (uint8_t *)args; + rt_memcpy(mac_dev, mac, 6); + mac_dev[5] = ~mac_dev[5]; /* device mac can't same as host. */ + } else + return -RT_ERROR; + + break; + + default: + break; + } + + return RT_EOK; +} + +struct pbuf *rt_usbd_cdc_ncm_eth_rx(rt_device_t dev) +{ + return usbd_cdc_ncm_eth_rx(); +} + +rt_err_t rt_usbd_cdc_ncm_eth_tx(rt_device_t dev, struct pbuf *p) +{ + int ret; + + ret = usbd_cdc_ncm_eth_tx(p); + if (ret == 0) { + if (rt_sem_take(&cdc_ncm_tx_sem, rt_tick_from_millisecond(CDC_NCM_TX_TIMEOUT_MS)) != RT_EOK) { + return -RT_ETIMEOUT; + } + return RT_EOK; + } else + return -RT_ERROR; +} + +void cdc_ncm_lwip_init(void) +{ + rt_sem_init(&cdc_ncm_tx_sem, "ncm_tx", 0, RT_IPC_FLAG_PRIO); + + cdc_ncm_dev.parent.control = rt_usbd_cdc_ncm_control; + cdc_ncm_dev.eth_rx = rt_usbd_cdc_ncm_eth_rx; + cdc_ncm_dev.eth_tx = rt_usbd_cdc_ncm_eth_tx; + + eth_device_init(&cdc_ncm_dev, "u0"); + + eth_device_linkchange(&cdc_ncm_dev, RT_TRUE); + dhcpd_start("u0"); +} + +void usbd_cdc_ncm_data_recv_done(uint32_t len) +{ + eth_device_ready(&cdc_ncm_dev); +} + +#else +#include "netif/etharp.h" +#include "lwip/init.h" +#include "lwip/netif.h" +#include "lwip/pbuf.h" + +#include "dhserver.h" +#include "dnserver.h" + +/* rough bounded wait for the polled demo; in a real application use an os semaphore with timeout */ +#define CDC_NCM_TX_TIMEOUT_LOOPS 1000000UL + +volatile bool cdc_ncm_tx_done = false; + +void usbd_cdc_ncm_data_send_done(uint32_t len) +{ + (void)len; + cdc_ncm_tx_done = true; +} + +/*Static IP ADDRESS: IP_ADDR0.IP_ADDR1.IP_ADDR2.IP_ADDR3 */ +#define IP_ADDR0 (uint8_t)192 +#define IP_ADDR1 (uint8_t)168 +#define IP_ADDR2 (uint8_t)7 +#define IP_ADDR3 (uint8_t)1 + +/*NETMASK*/ +#define NETMASK_ADDR0 (uint8_t)255 +#define NETMASK_ADDR1 (uint8_t)255 +#define NETMASK_ADDR2 (uint8_t)255 +#define NETMASK_ADDR3 (uint8_t)0 + +/*Gateway Address*/ +#define GW_ADDR0 (uint8_t)0 +#define GW_ADDR1 (uint8_t)0 +#define GW_ADDR2 (uint8_t)0 +#define GW_ADDR3 (uint8_t)0 + +const ip_addr_t ipaddr = IPADDR4_INIT_BYTES(IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); +const ip_addr_t netmask = IPADDR4_INIT_BYTES(NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); +const ip_addr_t gateway = IPADDR4_INIT_BYTES(GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); + +#define NUM_DHCP_ENTRY 3 + +static dhcp_entry_t entries[NUM_DHCP_ENTRY] = { + /* mac ip address subnet mask lease time */ + { { 0 }, { 192, 168, 7, 2 }, { 255, 255, 255, 0 }, 24 * 60 * 60 }, + { { 0 }, { 192, 168, 7, 3 }, { 255, 255, 255, 0 }, 24 * 60 * 60 }, + { { 0 }, { 192, 168, 7, 4 }, { 255, 255, 255, 0 }, 24 * 60 * 60 } +}; + +static dhcp_config_t dhcp_config = { + { 192, 168, 7, 1 }, /* server address */ + 67, /* port */ + { 192, 168, 7, 1 }, /* dns server */ + "cherry", /* dns suffix */ + NUM_DHCP_ENTRY, /* num entry */ + entries /* entries */ +}; + +static bool dns_query_proc(const char *name, ip_addr_t *addr) +{ + if (strcmp(name, "cdc_ncm.cherry") == 0 || strcmp(name, "www.cdc_ncm.cherry") == 0) { + addr->addr = ipaddr.addr; + return true; + } + return false; +} + +static struct netif cdc_ncm_netif; //network interface + +/* Network interface name */ +#define IFNAME0 'N' +#define IFNAME1 'X' + +err_t linkoutput_fn(struct netif *netif, struct pbuf *p) +{ + int ret; + uint32_t timeout = CDC_NCM_TX_TIMEOUT_LOOPS; + + cdc_ncm_tx_done = false; + ret = usbd_cdc_ncm_eth_tx(p); + if (ret == 0) { + while (!cdc_ncm_tx_done) { + if (--timeout == 0) { + return ERR_TIMEOUT; + } + } + return ERR_OK; + } else + return ERR_BUF; +} + +err_t cdc_ncm_if_init(struct netif *netif) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + + netif->mtu = 1500; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; + netif->state = NULL; + netif->name[0] = IFNAME0; + netif->name[1] = IFNAME1; + netif->output = etharp_output; + netif->linkoutput = linkoutput_fn; + return ERR_OK; +} + +err_t cdc_ncm_if_input(struct netif *netif) +{ + err_t err; + struct pbuf *p; + + p = usbd_cdc_ncm_eth_rx(); + if (p != NULL) { + err = netif->input(p, netif); + if (err != ERR_OK) { + pbuf_free(p); + } + } else { + return ERR_BUF; + } + return err; +} + +void cdc_ncm_lwip_init(void) +{ + struct netif *netif = &cdc_ncm_netif; + + lwip_init(); + + netif->hwaddr_len = 6; + memcpy(netif->hwaddr, mac, 6); + netif->hwaddr[5] = ~netif->hwaddr[5]; /* device mac can't same as host. */ + + netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, cdc_ncm_if_init, netif_input); + netif_set_default(netif); + while (!netif_is_up(netif)) { + } + + while (dhserv_init(&dhcp_config)) { + } + + while (dnserv_init(IP_ADDR_ANY, 53, dns_query_proc)) { + } +} + +void usbd_cdc_ncm_data_recv_done(uint32_t len) +{ +} + +void cdc_ncm_input_poll(void) +{ + cdc_ncm_if_input(&cdc_ncm_netif); +} +#endif + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +struct usbd_interface intf0; +struct usbd_interface intf1; + +/* ncm is supported in linux, windows and macos. In linux, you should input the following command + * + * sudo ifconfig enxaabbccddeeff up + * sudo dhclient enxaabbccddeeff +*/ +void cdc_ncm_init(uint8_t busid, uintptr_t reg_base) +{ + cdc_ncm_lwip_init(); + + usbd_desc_register(busid, &cdc_ncm_descriptor); + + usbd_add_interface(busid, usbd_cdc_ncm_init_intf(&intf0, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, mac)); + usbd_add_interface(busid, usbd_cdc_ncm_init_intf(&intf1, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, mac)); + usbd_initialize(busid, reg_base, usbd_event_handler); +} |
