| /* | |
| * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org) | |
| * SPDX-FileCopyrightText: Copyright (c) 2024 Hardy Griech | |
| * SPDX-FileCopyrightText: Copyright (c) 2020 Jacob Berg Potter | |
| * SPDX-FileCopyrightText: Copyright (c) 2020 Peter Lawrence | |
| * SPDX-License-Identifier: MIT | |
| * | |
| * This file is part of the TinyUSB stack. | |
| */ | |
| /** | |
| * Small Glossary (from the spec) | |
| * -------------- | |
| * Datagram - A collection of bytes forming a single item of information, passed as a unit from source to destination. | |
| * NCM - Network Control Model | |
| * NDP - NCM Datagram Pointer: NTB structure that delineates Datagrams (typically Ethernet frames) within an NTB | |
| * NTB - NCM Transfer Block: a data structure for efficient USB encapsulation of one or more datagrams | |
| * Each NTB is designed to be a single USB transfer | |
| * NTH - NTB Header: a data structure at the front of each NTB, which provides the information needed to validate | |
| * the NTB and begin decoding | |
| * | |
| * Some explanations | |
| * ----------------- | |
| * - rhport is the USB port of the device, in most cases "0" | |
| * - itf_data_alt if != 0 -> data xmit/recv are allowed (see spec) | |
| * - ep_in IN endpoints take data from the device intended to go in to the host (the device transmits) | |
| * - ep_out OUT endpoints send data out of the host to the device (the device receives) | |
| */ | |
| #include "tusb_option.h" | |
| #if (CFG_TUD_ENABLED && CFG_TUD_NCM) | |
| #include "device/usbd.h" | |
| #include "device/usbd_pvt.h" | |
| #include "ncm.h" | |
| #include "net_device.h" | |
| // Level where CFG_TUSB_DEBUG must be at least for this driver is logged | |
| #ifndef CFG_TUD_NCM_LOG_LEVEL | |
| #define CFG_TUD_NCM_LOG_LEVEL CFG_TUD_LOG_LEVEL | |
| #endif | |
| #define TU_LOG_DRV(...) TU_LOG(CFG_TUD_NCM_LOG_LEVEL, __VA_ARGS__) | |
| // Alignment must be 4 | |
| #define TUD_NCM_ALIGNMENT 4 | |
| // calculate alignment of xmit datagrams within an NTB | |
| #define XMIT_ALIGN_OFFSET(x) ((TUD_NCM_ALIGNMENT - ((x) & (TUD_NCM_ALIGNMENT - 1))) & (TUD_NCM_ALIGNMENT - 1)) | |
| //----------------------------------------------------------------------------- | |
| // | |
| // Module global things | |
| // | |
| #define XMIT_NTB_N CFG_TUD_NCM_IN_NTB_N | |
| #define RECV_NTB_N CFG_TUD_NCM_OUT_NTB_N | |
| typedef struct { | |
| // general | |
| uint8_t ep_in; // endpoint for outgoing datagrams (naming is a little bit confusing) | |
| uint8_t ep_out; // endpoint for incoming datagrams (naming is a little bit confusing) | |
| uint8_t ep_notif; // endpoint for notifications | |
| uint8_t itf_num; // interface number | |
| uint8_t itf_data_alt; // ==0 -> no endpoints, i.e. no network traffic, ==1 -> normal operation with two endpoints (spec, chapter 5.3) | |
| uint8_t rhport; // storage of \a rhport because some callbacks are done without it | |
| uint16_t ep_size; // bulk endpoint max packet size (IN and OUT assumed equal) | |
| // recv handling | |
| recv_ntb_t *recv_free_ntb[RECV_NTB_N]; // free list of recv NTBs | |
| recv_ntb_t *recv_ready_ntb[RECV_NTB_N]; // NTBs waiting for transmission to glue logic (circular buffer) | |
| #if RECV_NTB_N > 1 | |
| uint8_t recv_ready_head; // head index for recv_ready_ntb circular buffer | |
| uint8_t recv_ready_tail; // tail index for recv_ready_ntb circular buffer | |
| uint8_t recv_ready_count; // number of elements in recv_ready_ntb circular buffer | |
| #endif | |
| recv_ntb_t *recv_tinyusb_ntb; // buffer for the running transfer TinyUSB -> driver | |
| recv_ntb_t *recv_glue_ntb; // buffer for the running transfer driver -> glue logic | |
| uint16_t recv_glue_ntb_datagram_ndx; // index into \a recv_glue_ntb_datagram | |
| // xmit handling | |
| xmit_ntb_t *xmit_free_ntb[XMIT_NTB_N]; // free list of xmit NTBs | |
| xmit_ntb_t *xmit_ready_ntb[XMIT_NTB_N]; // NTBs waiting for transmission to TinyUSB (circular buffer) | |
| #if XMIT_NTB_N > 1 | |
| uint8_t xmit_ready_head; // head index for xmit_ready_ntb circular buffer | |
| uint8_t xmit_ready_tail; // tail index for xmit_ready_ntb circular buffer | |
| uint8_t xmit_ready_count; // number of elements in xmit_ready_ntb circular buffer | |
| #endif | |
| xmit_ntb_t *xmit_tinyusb_ntb; // buffer for the running transfer driver -> TinyUSB | |
| xmit_ntb_t *xmit_glue_ntb; // buffer for the running transfer glue logic -> driver | |
| uint16_t xmit_sequence; // NTB sequence counter | |
| uint16_t xmit_glue_ntb_datagram_ndx; // index into \a xmit_glue_ntb_datagram | |
| // notification handling | |
| enum { | |
| NOTIFICATION_SPEED, | |
| NOTIFICATION_CONNECTED, | |
| 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 | |
| // host-configured transmit limits | |
| uint8_t bm_capabilities; | |
| uint16_t xmit_max_ntb_size; // maximum NTB size device may send | |
| uint16_t xmit_max_datagrams; // maximum datagrams per NTB device may send | |
| ncm_ntb_input_size_t ntb_input_size; | |
| // misc | |
| bool tud_network_recv_renew_active; // tud_network_recv_renew() is active (avoid recursive invocations) | |
| bool tud_network_recv_renew_process_again; // tud_network_recv_renew() should process again | |
| } ncm_interface_t; | |
| typedef struct { | |
| struct { | |
| TUD_EPBUF_TYPE_DEF(recv_ntb_t, ntb); | |
| } recv[RECV_NTB_N]; | |
| struct { | |
| TUD_EPBUF_TYPE_DEF(xmit_ntb_t, ntb); | |
| } xmit[XMIT_NTB_N]; | |
| TUD_EPBUF_TYPE_DEF(ncm_notify_t, epnotif); | |
| } ncm_epbuf_t; | |
| static ncm_interface_t ncm_interface; | |
| CFG_TUD_MEM_SECTION static ncm_epbuf_t ncm_epbuf; | |
| //--------------------------------------------------------------------+ | |
| // Weak stubs: invoked if no strong implementation is available | |
| //--------------------------------------------------------------------+ | |
| TU_ATTR_WEAK void tud_network_set_packet_filter_cb(uint16_t packet_filter) { | |
| (void) packet_filter; | |
| } | |
| TU_ATTR_WEAK bool tud_network_default_link_state_cb(void) { | |
| #ifdef CFG_TUD_NCM_DEFAULT_LINK_UP | |
| return CFG_TUD_NCM_DEFAULT_LINK_UP; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| /** | |
| * This is the NTB parameter structure | |
| * | |
| * \attention | |
| * We are lucky, that byte order is correct | |
| */ | |
| // wNdbInDivisor/wNdbOutDivisor: a Windows Update (KB5124008, Sept 2026) | |
| // enabled NTB-parameter validation in usbncm.sys requiring these to be | |
| // >=4 and a power of 2. 1 is spec-legal but now rejected with | |
| // STATUS_DEVICE_HARDWARE_ERROR (Code 10) during enumeration. 4 satisfies | |
| // the check and is spec-legal on the device side too. | |
| TU_ATTR_ALIGNED(4) static const ntb_parameters_t ntb_parameters = { | |
| .wLength = sizeof(ntb_parameters_t), | |
| .bmNtbFormatsSupported = 0x01,// 16-bit NTB supported | |
| .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE, | |
| .wNdbInDivisor = 4, | |
| .wNdbInPayloadRemainder = 0, | |
| .wNdbInAlignment = TUD_NCM_ALIGNMENT, | |
| .wReserved = 0, | |
| .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, | |
| .wNdbOutDivisor = 4, | |
| .wNdbOutPayloadRemainder = 0, | |
| .wNdbOutAlignment = TUD_NCM_ALIGNMENT, | |
| .wNtbOutMaxDatagrams = CFG_TUD_NCM_OUT_MAX_DATAGRAMS_PER_NTB, | |
| }; | |
| // Some confusing remarks about wNtbOutMaxDatagrams... | |
| // ==1 -> SystemView packets/s goes up to 2000 and events are lost during startup | |
| // ==0 -> SystemView runs fine, iperf shows in wireshark a lot of error | |
| // ==6 -> SystemView runs fine, iperf also | |
| // >6 -> iperf starts to show errors | |
| // -> 6 seems to be the best value. Why? Don't know, perhaps only on my system? | |
| // | |
| // iperf: for MSS in 100 200 400 800 1200 1450 1500; do iperf -c 192.168.14.1 -e -i 1 -M $MSS -l 8192 -P 1; sleep 2; done | |
| // sysview: SYSTICKS_PER_SEC=35000, IDLE_US=1000, PRINT_MOD=1000 | |
| // | |
| //----------------------------------------------------------------------------- | |
| // | |
| // everything about notifications | |
| // | |
| /** | |
| * Transmit next notification to the host (if appropriate). | |
| * Notifications are transferred to the host once during connection setup. | |
| */ | |
| static void notification_xmit(uint8_t rhport, bool force_next) { | |
| TU_LOG_DRV("notification_xmit(%d, %d) - %d %d\n", force_next, rhport, ncm_interface.notification_xmit_state, ncm_interface.notification_xmit_is_running); | |
| if (!force_next && ncm_interface.notification_xmit_is_running) { | |
| return; | |
| } | |
| if (ncm_interface.notification_xmit_state == NOTIFICATION_SPEED) { | |
| TU_LOG_DRV(" NOTIFICATION_SPEED\n"); | |
| ncm_notify_t notify_speed_change = { | |
| .header = { | |
| .bmRequestType_bit = { | |
| .recipient = TUSB_REQ_RCPT_INTERFACE, | |
| .type = TUSB_REQ_TYPE_CLASS, | |
| .direction = TUSB_DIR_IN | |
| }, | |
| .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE, | |
| .wValue = 0, | |
| .wIndex = ncm_interface.itf_num, | |
| .wLength = 8 | |
| } | |
| }; | |
| if (tud_speed_get() == TUSB_SPEED_HIGH) { | |
| notify_speed_change.downlink = 480000000; | |
| notify_speed_change.uplink = 480000000; | |
| } else { | |
| notify_speed_change.downlink = 12000000; | |
| notify_speed_change.uplink = 12000000; | |
| } | |
| uint16_t notif_len = sizeof(notify_speed_change.header) + notify_speed_change.header.wLength; | |
| ncm_epbuf.epnotif = notify_speed_change; | |
| usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t*) &ncm_epbuf.epnotif, notif_len, false); | |
| ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; | |
| ncm_interface.notification_xmit_is_running = true; | |
| } else if (ncm_interface.notification_xmit_state == NOTIFICATION_CONNECTED) { | |
| TU_LOG_DRV(" NOTIFICATION_CONNECTED\n"); | |
| ncm_notify_t notify_connected = { | |
| .header = { | |
| .bmRequestType_bit = { | |
| .recipient = TUSB_REQ_RCPT_INTERFACE, | |
| .type = TUSB_REQ_TYPE_CLASS, | |
| .direction = TUSB_DIR_IN | |
| }, | |
| .bRequest = CDC_NOTIF_NETWORK_CONNECTION, | |
| .wValue = ncm_interface.link_is_up ? 1 : 0, /* Dynamic link state */ | |
| .wIndex = ncm_interface.itf_num, | |
| .wLength = 0, | |
| }, | |
| }; | |
| uint16_t notif_len = sizeof(notify_connected.header) + notify_connected.header.wLength; | |
| ncm_epbuf.epnotif = notify_connected; | |
| usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t *) &ncm_epbuf.epnotif, notif_len, false); | |
| ncm_interface.notification_xmit_state = NOTIFICATION_DONE; | |
| ncm_interface.notification_xmit_is_running = true; | |
| } else { | |
| TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); | |
| ncm_interface.notification_xmit_is_running = false; | |
| } | |
| } // notification_xmit | |
| //----------------------------------------------------------------------------- | |
| // | |
| // everything about packet transmission (driver -> TinyUSB) | |
| // | |
| /** | |
| * Put NTB into the transmitter free list. | |
| */ | |
| static void xmit_put_ntb_into_free_list(xmit_ntb_t *free_ntb) { | |
| TU_LOG_DRV("xmit_put_ntb_into_free_list() - %p\n", ncm_interface.xmit_tinyusb_ntb); | |
| if (free_ntb == NULL) { // can happen due to ZLPs | |
| return; | |
| } | |
| for (int i = 0; i < XMIT_NTB_N; ++i) { | |
| if (ncm_interface.xmit_free_ntb[i] == NULL) { | |
| ncm_interface.xmit_free_ntb[i] = free_ntb; | |
| return; | |
| } | |
| } | |
| TU_LOG_DRV("(EE) xmit_put_ntb_into_free_list - no entry in free list\n");// this should not happen | |
| } // xmit_put_ntb_into_free_list | |
| /** | |
| * Get an NTB from the free list | |
| */ | |
| static xmit_ntb_t *xmit_get_free_ntb(void) { | |
| TU_LOG_DRV("xmit_get_free_ntb()\n"); | |
| for (int i = 0; i < XMIT_NTB_N; ++i) { | |
| if (ncm_interface.xmit_free_ntb[i] != NULL) { | |
| xmit_ntb_t *free = ncm_interface.xmit_free_ntb[i]; | |
| ncm_interface.xmit_free_ntb[i] = NULL; | |
| return free; | |
| } | |
| } | |
| return NULL; | |
| } // xmit_get_free_ntb | |
| /** | |
| * Put a filled NTB into the ready list | |
| */ | |
| static void xmit_put_ntb_into_ready_list(xmit_ntb_t *ready_ntb) { | |
| TU_LOG_DRV("xmit_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); | |
| #if XMIT_NTB_N == 1 | |
| ncm_interface.xmit_ready_ntb[0] = ready_ntb; | |
| #else | |
| if (ncm_interface.xmit_ready_count >= XMIT_NTB_N) { | |
| TU_LOG_DRV("(EE) xmit_put_ntb_into_ready_list: ready list full\n");// this should not happen | |
| return; | |
| } | |
| ncm_interface.xmit_ready_ntb[ncm_interface.xmit_ready_head] = ready_ntb; | |
| ncm_interface.xmit_ready_head = (ncm_interface.xmit_ready_head + 1) % XMIT_NTB_N; | |
| ncm_interface.xmit_ready_count++; | |
| #endif | |
| } // xmit_put_ntb_into_ready_list | |
| /** | |
| * Get the next NTB from the ready list (and remove it from the list). | |
| * If the ready list is empty, return NULL. | |
| */ | |
| static xmit_ntb_t *xmit_get_next_ready_ntb(void) { | |
| #if XMIT_NTB_N == 1 | |
| xmit_ntb_t *r = ncm_interface.xmit_ready_ntb[0]; | |
| ncm_interface.xmit_ready_ntb[0] = NULL; | |
| TU_LOG_DRV("xmit_get_next_ready_ntb: %p\n", r); | |
| return r; | |
| #else | |
| if (ncm_interface.xmit_ready_count == 0) { | |
| return NULL; // empty | |
| } | |
| xmit_ntb_t *r = ncm_interface.xmit_ready_ntb[ncm_interface.xmit_ready_tail]; | |
| ncm_interface.xmit_ready_tail = (ncm_interface.xmit_ready_tail + 1) % XMIT_NTB_N; | |
| ncm_interface.xmit_ready_count--; | |
| TU_LOG_DRV("xmit_get_next_ready_ntb: %p\n", r); | |
| return r; | |
| #endif | |
| } // xmit_get_next_ready_ntb | |
| /** | |
| * Transmit a ZLP if required | |
| * | |
| * \note | |
| * Insertion of the ZLPs is a little bit different then described in the spec. | |
| * But the below implementation actually works. Don't know if this is a spec | |
| * or TinyUSB issue. | |
| * | |
| * \pre | |
| * This must be called from netd_xfer_cb() so that ep_in is ready | |
| */ | |
| static bool xmit_insert_required_zlp(uint8_t rhport, uint32_t xferred_bytes) { | |
| TU_LOG_DRV("xmit_insert_required_zlp(%d,%ld)\n", rhport, xferred_bytes); | |
| uint16_t const ep_size = ncm_interface.ep_size; | |
| if (xferred_bytes == 0 || (xferred_bytes & (ep_size-1)) != 0) { | |
| return false; | |
| } | |
| TU_ASSERT(ncm_interface.itf_data_alt == 1, false); | |
| TU_ASSERT(!usbd_edpt_busy(rhport, ncm_interface.ep_in), false); | |
| TU_LOG_DRV("xmit_insert_required_zlp! (%u)\n", (unsigned) xferred_bytes); | |
| // start transmission of the ZLP | |
| usbd_edpt_xfer(rhport, ncm_interface.ep_in, NULL, 0, false); | |
| return true; | |
| } // xmit_insert_required_zlp | |
| /** | |
| * Start transmission if it there is a waiting packet and if can be done from interface side. | |
| */ | |
| static void xmit_start_if_possible(uint8_t rhport) { | |
| TU_LOG_DRV("xmit_start_if_possible()\n"); | |
| if (ncm_interface.xmit_tinyusb_ntb != NULL) { | |
| TU_LOG_DRV(" !xmit_start_if_possible 1\n"); | |
| return; | |
| } | |
| if (ncm_interface.itf_data_alt != 1) { | |
| TU_LOG_DRV("(EE) !xmit_start_if_possible 2\n"); | |
| return; | |
| } | |
| if (usbd_edpt_busy(rhport, ncm_interface.ep_in)) { | |
| TU_LOG_DRV(" !xmit_start_if_possible 3\n"); | |
| return; | |
| } | |
| ncm_interface.xmit_tinyusb_ntb = xmit_get_next_ready_ntb(); | |
| if (ncm_interface.xmit_tinyusb_ntb == NULL) { | |
| if (ncm_interface.xmit_glue_ntb == NULL || ncm_interface.xmit_glue_ntb_datagram_ndx == 0) { | |
| // -> really nothing is waiting | |
| return; | |
| } | |
| ncm_interface.xmit_tinyusb_ntb = ncm_interface.xmit_glue_ntb; | |
| ncm_interface.xmit_glue_ntb = NULL; | |
| } | |
| #if CFG_TUD_NCM_LOG_LEVEL >= 3 | |
| { | |
| uint16_t len = ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength; | |
| TU_LOG_BUF(3, ncm_interface.xmit_tinyusb_ntb->data[i], len); | |
| } | |
| #endif | |
| if (ncm_interface.xmit_glue_ntb_datagram_ndx != 1) { | |
| TU_LOG_DRV(">> %d %d\n", ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength, ncm_interface.xmit_glue_ntb_datagram_ndx); | |
| } | |
| // Kick off an endpoint transfer | |
| usbd_edpt_xfer(0, ncm_interface.ep_in, ncm_interface.xmit_tinyusb_ntb->data, ncm_interface.xmit_tinyusb_ntb->nth.wBlockLength, false); | |
| } // xmit_start_if_possible | |
| /** | |
| * check if a new datagram fits into the current NTB | |
| */ | |
| static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size) { | |
| TU_LOG_DRV("xmit_requested_datagram_fits_into_current_ntb(%d) - %p %p\n", datagram_size, ncm_interface.xmit_tinyusb_ntb, ncm_interface.xmit_glue_ntb); | |
| if (ncm_interface.xmit_glue_ntb == NULL) { | |
| return false; | |
| } | |
| if (ncm_interface.xmit_glue_ntb_datagram_ndx >= ncm_interface.xmit_max_datagrams) { | |
| return false; | |
| } | |
| if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + (uint32_t)XMIT_ALIGN_OFFSET(datagram_size) > (uint32_t)ncm_interface.xmit_max_ntb_size) { | |
| return false; | |
| } | |
| return true; | |
| } // xmit_requested_datagram_fits_into_current_ntb | |
| /** | |
| * Setup an NTB for the glue logic | |
| */ | |
| static bool xmit_setup_next_glue_ntb(void) { | |
| TU_LOG_DRV("xmit_setup_next_glue_ntb - %p\n", ncm_interface.xmit_glue_ntb); | |
| if (ncm_interface.xmit_glue_ntb != NULL) { | |
| // put NTB into waiting list (the new datagram did not fit in) | |
| xmit_put_ntb_into_ready_list(ncm_interface.xmit_glue_ntb); | |
| } | |
| ncm_interface.xmit_glue_ntb = xmit_get_free_ntb();// get next buffer (if any) | |
| if (ncm_interface.xmit_glue_ntb == NULL) { | |
| TU_LOG_DRV(" xmit_setup_next_glue_ntb - nothing free\n");// should happen rarely | |
| return false; | |
| } | |
| ncm_interface.xmit_glue_ntb_datagram_ndx = 0; | |
| xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; | |
| // Fill in NTB header | |
| ntb->nth.dwSignature = NTH16_SIGNATURE; | |
| ntb->nth.wHeaderLength = sizeof(ntb->nth); | |
| ntb->nth.wSequence = ncm_interface.xmit_sequence++; | |
| ntb->nth.wBlockLength = sizeof(ntb->nth) + sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); | |
| ntb->nth.wNdpIndex = sizeof(ntb->nth); | |
| // Fill in NDP16 header and terminator | |
| ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0; | |
| ntb->ndp.wLength = sizeof(ntb->ndp) + sizeof(ntb->ndp_datagram); | |
| ntb->ndp.wNextNdpIndex = 0; | |
| memset(ntb->ndp_datagram, 0, sizeof(ntb->ndp_datagram)); | |
| return true; | |
| } // xmit_setup_next_glue_ntb | |
| //----------------------------------------------------------------------------- | |
| // | |
| // all the recv_*() stuff (TinyUSB -> driver -> glue logic) | |
| // | |
| /** | |
| * Return pointer to an available receive buffer or NULL. | |
| * Returned buffer (if any) has the size \a CFG_TUD_NCM_OUT_NTB_MAX_SIZE. | |
| */ | |
| static recv_ntb_t *recv_get_free_ntb(void) { | |
| TU_LOG_DRV("recv_get_free_ntb()\n"); | |
| for (int i = 0; i < RECV_NTB_N; ++i) { | |
| if (ncm_interface.recv_free_ntb[i] != NULL) { | |
| recv_ntb_t *free = ncm_interface.recv_free_ntb[i]; | |
| ncm_interface.recv_free_ntb[i] = NULL; | |
| return free; | |
| } | |
| } | |
| return NULL; | |
| } // recv_get_free_ntb | |
| /** | |
| * Get the next NTB from the ready list (and remove it from the list). | |
| * If the ready list is empty, return NULL. | |
| */ | |
| static recv_ntb_t *recv_get_next_ready_ntb(void) { | |
| #if RECV_NTB_N == 1 | |
| recv_ntb_t *r = ncm_interface.recv_ready_ntb[0]; | |
| ncm_interface.recv_ready_ntb[0] = NULL; | |
| TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); | |
| return r; | |
| #else | |
| if (ncm_interface.recv_ready_count == 0) { | |
| return NULL; // empty | |
| } | |
| recv_ntb_t *r = ncm_interface.recv_ready_ntb[ncm_interface.recv_ready_tail]; | |
| ncm_interface.recv_ready_tail = (ncm_interface.recv_ready_tail + 1) % RECV_NTB_N; | |
| ncm_interface.recv_ready_count--; | |
| TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); | |
| return r; | |
| #endif | |
| } // recv_get_next_ready_ntb | |
| /** | |
| * Put NTB into the receiver free list. | |
| */ | |
| static void recv_put_ntb_into_free_list(recv_ntb_t *free_ntb) { | |
| TU_LOG_DRV("recv_put_ntb_into_free_list(%p)\n", free_ntb); | |
| for (int i = 0; i < RECV_NTB_N; ++i) { | |
| if (ncm_interface.recv_free_ntb[i] == NULL) { | |
| ncm_interface.recv_free_ntb[i] = free_ntb; | |
| return; | |
| } | |
| } | |
| TU_LOG_DRV("(EE) recv_put_ntb_into_free_list - no entry in free list\n");// this should not happen | |
| } // recv_put_ntb_into_free_list | |
| /** | |
| * \a ready_ntb holds a validated NTB, | |
| * put this buffer into the waiting list. | |
| */ | |
| static void recv_put_ntb_into_ready_list(recv_ntb_t *ready_ntb) { | |
| TU_LOG_DRV("recv_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->nth.wBlockLength); | |
| #if RECV_NTB_N == 1 | |
| ncm_interface.recv_ready_ntb[0] = ready_ntb; | |
| #else | |
| if (ncm_interface.recv_ready_count >= RECV_NTB_N) { | |
| TU_LOG_DRV("(EE) recv_put_ntb_into_ready_list: ready list full\n");// this should not happen | |
| return; | |
| } | |
| ncm_interface.recv_ready_ntb[ncm_interface.recv_ready_head] = ready_ntb; | |
| ncm_interface.recv_ready_head = (ncm_interface.recv_ready_head + 1) % RECV_NTB_N; | |
| ncm_interface.recv_ready_count++; | |
| #endif | |
| } // recv_put_ntb_into_ready_list | |
| /** | |
| * If possible, start a new reception TinyUSB -> driver. | |
| */ | |
| static void recv_try_to_start_new_reception(uint8_t rhport) { | |
| TU_LOG_DRV("recv_try_to_start_new_reception(%d)\n", rhport); | |
| if (ncm_interface.itf_data_alt != 1) { | |
| return; | |
| } | |
| if (ncm_interface.recv_tinyusb_ntb != NULL) { | |
| return; | |
| } | |
| if (usbd_edpt_busy(rhport, ncm_interface.ep_out)) { | |
| return; | |
| } | |
| ncm_interface.recv_tinyusb_ntb = recv_get_free_ntb(); | |
| if (ncm_interface.recv_tinyusb_ntb == NULL) { | |
| return; | |
| } | |
| // initiate transfer | |
| TU_LOG_DRV(" start reception\n"); | |
| bool r = usbd_edpt_xfer(rhport, ncm_interface.ep_out, ncm_interface.recv_tinyusb_ntb->data, CFG_TUD_NCM_OUT_NTB_MAX_SIZE, false); | |
| if (!r) { | |
| recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); | |
| ncm_interface.recv_tinyusb_ntb = NULL; | |
| } | |
| } // recv_try_to_start_new_reception | |
| /** | |
| * Validate incoming datagram. | |
| * \return true if valid | |
| * | |
| * \note | |
| * \a ndp16->wNextNdpIndex != 0 is not supported | |
| */ | |
| static bool recv_validate_datagram(const recv_ntb_t *ntb, uint32_t len) { | |
| const nth16_t *nth16 = &(ntb->nth); | |
| TU_LOG_DRV("recv_validate_datagram(%p, %d)\n", ntb, (int) len); | |
| // check header | |
| if (nth16->wHeaderLength != sizeof(nth16_t)) { | |
| TU_LOG_DRV("(EE) ill nth16 length: %d\n", nth16->wHeaderLength); | |
| return false; | |
| } | |
| if (nth16->dwSignature != NTH16_SIGNATURE) { | |
| TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) nth16->dwSignature); | |
| return false; | |
| } | |
| if (len < sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { | |
| TU_LOG_DRV("(EE) ill min len: %lu\n", len); | |
| return false; | |
| } | |
| if (nth16->wBlockLength > len) { | |
| TU_LOG_DRV("(EE) ill block length: %d > %lu\n", nth16->wBlockLength, len); | |
| return false; | |
| } | |
| if (nth16->wBlockLength > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { | |
| TU_LOG_DRV("(EE) ill block length2: %d > %d\n", nth16->wBlockLength, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); | |
| return false; | |
| } | |
| if (nth16->wNdpIndex < sizeof(nth16_t) || nth16->wNdpIndex > len - (sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t))) { | |
| TU_LOG_DRV("(EE) ill position of first ndp: %d (%lu)\n", nth16->wNdpIndex, len); | |
| return false; | |
| } | |
| // check (first) NDP(16) | |
| const ndp16_t *ndp16 = (const ndp16_t *) (ntb->data + nth16->wNdpIndex); | |
| if (ndp16->wLength < sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)) { | |
| TU_LOG_DRV("(EE) ill ndp16 length: %d\n", ndp16->wLength); | |
| return false; | |
| } | |
| // the NDP block (wLength bytes from wNdpIndex) must fit within the received NTB, otherwise the | |
| // datagram pointer array walked below (max_ndx is derived from wLength) runs past ntb->data | |
| if ((uint32_t) nth16->wNdpIndex + ndp16->wLength > len) { | |
| TU_LOG_DRV("(EE) ill ndp16 length: %d (%lu)\n", ndp16->wLength, len); | |
| return false; | |
| } | |
| if (ndp16->dwSignature != NDP16_SIGNATURE_NCM0 && ndp16->dwSignature != NDP16_SIGNATURE_NCM1) { | |
| TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) ndp16->dwSignature); | |
| return false; | |
| } | |
| if (ndp16->wNextNdpIndex != 0) { | |
| TU_LOG_DRV("(EE) cannot handle wNextNdpIndex!=0 (%d)\n", ndp16->wNextNdpIndex); | |
| return false; | |
| } | |
| const ndp16_datagram_t *ndp16_datagram = (const ndp16_datagram_t *) (ntb->data + nth16->wNdpIndex + sizeof(ndp16_t)); | |
| int ndx = 0; | |
| uint16_t max_ndx = (uint16_t) ((ndp16->wLength - sizeof(ndp16_t)) / sizeof(ndp16_datagram_t)); | |
| if (max_ndx > 2) { // number of datagrams in NTB > 1 | |
| TU_LOG_DRV("<< %d (%d)\n", max_ndx - 1, ntb->nth.wBlockLength); | |
| } | |
| if (ndp16_datagram[max_ndx - 1].wDatagramIndex != 0 || ndp16_datagram[max_ndx - 1].wDatagramLength != 0) { | |
| TU_LOG_DRV(" max_ndx != 0\n"); | |
| return false; | |
| } | |
| while (ndp16_datagram[ndx].wDatagramIndex != 0 && ndp16_datagram[ndx].wDatagramLength != 0) { | |
| TU_LOG_DRV(" << %d %d\n", ndp16_datagram[ndx].wDatagramIndex, ndp16_datagram[ndx].wDatagramLength); | |
| if (ndp16_datagram[ndx].wDatagramIndex > len) { | |
| TU_LOG_DRV("(EE) ill start of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex, len); | |
| return false; | |
| } | |
| if (ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength > len) { | |
| TU_LOG_DRV("(EE) ill end of datagram[%d]: %d (%lu)\n", ndx, ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength, len); | |
| return false; | |
| } | |
| ++ndx; | |
| } | |
| #if CFG_TUD_NCM_LOG_LEVEL >= 3 | |
| TU_LOG_BUF(3, ntb->data[i], len); | |
| #endif | |
| // -> ntb contains a valid packet structure | |
| // ok... I did not check for garbage within the datagram indices... | |
| return true; | |
| } // recv_validate_datagram | |
| /** | |
| * Transfer the next (pending) datagram to the glue logic and return receive buffer if empty. | |
| */ | |
| static void recv_transfer_datagram_to_glue_logic(void) { | |
| TU_LOG_DRV("recv_transfer_datagram_to_glue_logic()\n"); | |
| if (ncm_interface.recv_glue_ntb == NULL) { | |
| ncm_interface.recv_glue_ntb = recv_get_next_ready_ntb(); | |
| TU_LOG_DRV(" new buffer for glue logic: %p\n", ncm_interface.recv_glue_ntb); | |
| ncm_interface.recv_glue_ntb_datagram_ndx = 0; | |
| } | |
| if (ncm_interface.recv_glue_ntb != NULL) { | |
| const ndp16_datagram_t *ndp16_datagram = (ndp16_datagram_t *) (ncm_interface.recv_glue_ntb->data + ncm_interface.recv_glue_ntb->nth.wNdpIndex + sizeof(ndp16_t)); | |
| if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex == 0) { | |
| TU_LOG_DRV("(EE) SOMETHING WENT WRONG 1\n"); | |
| } else if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength == 0) { | |
| TU_LOG_DRV("(EE) SOMETHING WENT WRONG 2\n"); | |
| } else { | |
| uint16_t datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramIndex; | |
| uint16_t datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength; | |
| TU_LOG_DRV(" recv[%d] - %d %d\n", ncm_interface.recv_glue_ntb_datagram_ndx, datagramIndex, datagramLength); | |
| if (tud_network_recv_cb(ncm_interface.recv_glue_ntb->data + datagramIndex, datagramLength)) { | |
| // send datagram successfully to glue logic | |
| TU_LOG_DRV(" OK\n"); | |
| datagramIndex = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramIndex; | |
| datagramLength = ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx + 1].wDatagramLength; | |
| if (datagramIndex != 0 && datagramLength != 0) { | |
| // -> next datagram | |
| ++ncm_interface.recv_glue_ntb_datagram_ndx; | |
| } else { | |
| // end of datagrams reached | |
| recv_put_ntb_into_free_list(ncm_interface.recv_glue_ntb); | |
| ncm_interface.recv_glue_ntb = NULL; | |
| } | |
| } | |
| } | |
| } | |
| } // recv_transfer_datagram_to_glue_logic | |
| //----------------------------------------------------------------------------- | |
| // | |
| // all the tud_network_*() stuff (glue logic -> driver) | |
| // | |
| /** | |
| * Check if the glue logic is allowed to call tud_network_xmit(). | |
| * This function also fetches a next buffer if required, so that tud_network_xmit() is ready for copy | |
| * and transmission operation. | |
| */ | |
| bool tud_network_can_xmit(uint16_t size) { | |
| TU_LOG_DRV("tud_network_can_xmit(%d)\n", size); | |
| TU_ASSERT(size <= ncm_interface.xmit_max_ntb_size - (sizeof(nth16_t) + sizeof(ndp16_t) + 2 * sizeof(ndp16_datagram_t)), false); | |
| if (xmit_requested_datagram_fits_into_current_ntb(size) || xmit_setup_next_glue_ntb()) { | |
| // -> everything is fine | |
| return true; | |
| } | |
| xmit_start_if_possible(ncm_interface.rhport); | |
| TU_LOG_DRV("(II) tud_network_can_xmit: request blocked\n");// could happen if all xmit buffers are full (but should happen rarely) | |
| return false; | |
| } // tud_network_can_xmit | |
| /** | |
| * Put a datagram into a waiting NTB. | |
| * If currently no transmission is started, then initiate transmission. | |
| */ | |
| void tud_network_xmit(void *ref, uint16_t arg) { | |
| TU_LOG_DRV("tud_network_xmit(%p, %d)\n", ref, arg); | |
| if (ncm_interface.xmit_glue_ntb == NULL) { | |
| TU_LOG_DRV("(EE) tud_network_xmit: no buffer\n");// must not happen (really) | |
| return; | |
| } | |
| xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; | |
| // copy new datagram to the end of the current NTB | |
| uint16_t size = tud_network_xmit_cb(ntb->data + ntb->nth.wBlockLength, ref, arg); | |
| // correct NTB internals | |
| ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramIndex = ntb->nth.wBlockLength; | |
| ntb->ndp_datagram[ncm_interface.xmit_glue_ntb_datagram_ndx].wDatagramLength = size; | |
| ncm_interface.xmit_glue_ntb_datagram_ndx += 1; | |
| ntb->nth.wBlockLength += (uint16_t) (size + XMIT_ALIGN_OFFSET(size)); | |
| if (ntb->nth.wBlockLength > CFG_TUD_NCM_IN_NTB_MAX_SIZE) { | |
| TU_LOG_DRV("(EE) tud_network_xmit: buffer overflow\n"); // must not happen (really) | |
| return; | |
| } | |
| xmit_start_if_possible(ncm_interface.rhport); | |
| } // tud_network_xmit | |
| /** | |
| * Keep the receive logic busy and transfer pending packets to the glue logic. | |
| * Avoid recursive calls due to wrong expectations of the net glue logic, | |
| * see https://github.com/hathach/tinyusb/issues/2711 | |
| */ | |
| void tud_network_recv_renew(void) { | |
| TU_LOG_DRV("tud_network_recv_renew()\n"); | |
| ncm_interface.tud_network_recv_renew_process_again = true; | |
| if (ncm_interface.tud_network_recv_renew_active) { | |
| TU_LOG_DRV("Re-entrant into tud_network_recv_renew, will process later\n"); | |
| return; | |
| } | |
| while (ncm_interface.tud_network_recv_renew_process_again) { | |
| ncm_interface.tud_network_recv_renew_process_again = false; | |
| // If the current function is called within recv_transfer_datagram_to_glue_logic, | |
| // tud_network_recv_renew_process_again will become true, and the loop will run again | |
| // Otherwise the loop will not run again | |
| ncm_interface.tud_network_recv_renew_active = true; | |
| recv_transfer_datagram_to_glue_logic(); | |
| ncm_interface.tud_network_recv_renew_active = false; | |
| } | |
| recv_try_to_start_new_reception(ncm_interface.rhport); | |
| } // tud_network_recv_renew | |
| /** | |
| * Same as tud_network_recv_renew() but knows \a rhport | |
| */ | |
| static void tud_network_recv_renew_r(uint8_t rhport) { | |
| TU_LOG_DRV("tud_network_recv_renew_r(%d)\n", rhport); | |
| ncm_interface.rhport = rhport; | |
| tud_network_recv_renew(); | |
| } // tud_network_recv_renew | |
| /** | |
| * usbd-task trampoline for tud_network_link_state(), packing rhport and is_up | |
| * into a single pointer-sized argument. | |
| * | |
| * Runs entirely in the usbd task context, so it cannot race the notify | |
| * xfer-completion callback over the notification state machine. Re-arming | |
| * notification_xmit_state and kicking notification_xmit() (rather than | |
| * sending NETWORK_CONNECTION directly) means a state change that collides | |
| * with an in-flight notification is picked up by the existing completion | |
| * callback instead of being silently dropped - which would otherwise leave | |
| * the host stuck at NO-CARRIER after a link-state change. | |
| */ | |
| static void ncm_link_state_task(void *param) { | |
| uintptr_t const arg = (uintptr_t) param; | |
| uint8_t const rhport = (uint8_t) (arg >> 1); | |
| bool const is_up = (arg & 1u) != 0; | |
| if (ncm_interface.link_is_up == is_up) { | |
| return; // no change in link state | |
| } | |
| ncm_interface.link_is_up = is_up; | |
| if (ncm_interface.itf_data_alt != 1) { | |
| TU_LOG_DRV(" link state notification deferred (interface not active)\n"); | |
| return; // data interface not active yet; SET_INTERFACE(alt=1) will notify | |
| } | |
| // A link toggle does not change the link speed, so strictly only the | |
| // NETWORK_CONNECTION notification would need (re)sending. Re-running the | |
| // speed-then-connection sequence keeps this on the same state machine the | |
| // completion callback already drives, at the cost of a redundant speed | |
| // notification on every toggle. | |
| ncm_interface.notification_xmit_state = NOTIFICATION_SPEED; | |
| notification_xmit(rhport, false); | |
| } | |
| /** | |
| * Set the link state and notify the host. | |
| * | |
| * Defers onto the usbd task so a caller running in a different task than | |
| * tud_task() cannot race the notification state machine against the notify | |
| * xfer-completion callback. | |
| */ | |
| void tud_network_link_state(uint8_t rhport, bool is_up) { | |
| TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); | |
| uintptr_t const arg = ((uintptr_t) rhport << 1) | (is_up ? 1u : 0u); | |
| usbd_defer_func(ncm_link_state_task, (void *) arg, false); | |
| } | |
| //----------------------------------------------------------------------------- | |
| // | |
| // all the netd_*() stuff (interface TinyUSB -> driver) | |
| // | |
| /** | |
| * Initialize the driver data structures. | |
| * Might be called several times. | |
| */ | |
| void netd_init(void) { | |
| TU_LOG_DRV("netd_init()\n"); | |
| memset(&ncm_interface, 0, sizeof(ncm_interface)); | |
| ncm_interface.xmit_max_ntb_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE; | |
| ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; | |
| for (int i = 0; i < XMIT_NTB_N; ++i) { | |
| ncm_interface.xmit_free_ntb[i] = &ncm_epbuf.xmit[i].ntb; | |
| } | |
| for (int i = 0; i < RECV_NTB_N; ++i) { | |
| ncm_interface.recv_free_ntb[i] = &ncm_epbuf.recv[i].ntb; | |
| } | |
| ncm_interface.link_is_up = tud_network_default_link_state_cb(); | |
| } // netd_init | |
| /** | |
| * Deinit driver | |
| */ | |
| bool netd_deinit(void) { | |
| return true; | |
| } | |
| /** | |
| * Resets the port. | |
| * In this driver this is the same as netd_init() | |
| */ | |
| void netd_reset(uint8_t rhport) { | |
| (void) rhport; | |
| netd_init(); | |
| } // netd_reset | |
| /** | |
| * Open the USB interface. | |
| * - parse the USB descriptor \a TUD_CDC_NCM_DESCRIPTOR for itfnum and endpoints | |
| * - a specific order of elements in the descriptor is tested. | |
| * | |
| * \note | |
| * Actually all of the information could be read directly from \a itf_desc, because the | |
| * structure and the values are well known. But we do it this way. | |
| * | |
| * \post | |
| * - \a itf_num set | |
| * - \a ep_notif, \a ep_in and \a ep_out are set | |
| * - USB interface is open | |
| */ | |
| uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { | |
| TU_ASSERT(ncm_interface.ep_notif == 0, 0);// assure that the interface is only opened once | |
| ncm_interface.itf_num = itf_desc->bInterfaceNumber;// management interface | |
| uint16_t drv_len = sizeof(tusb_desc_interface_t); | |
| uint8_t const *p_desc = tu_desc_next(itf_desc); | |
| while (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && drv_len <= max_len) { | |
| if (tu_desc_subtype(p_desc) == CDC_FUNC_DESC_NCM) { | |
| TU_ASSERT(tu_desc_len(p_desc) >= sizeof(tusb_desc_cdc_ncm_func_t), 0); | |
| tusb_desc_cdc_ncm_func_t const *ncm_func = (tusb_desc_cdc_ncm_func_t const *) p_desc; | |
| ncm_interface.bm_capabilities = ncm_func->bmCapabilities; | |
| } | |
| drv_len += tu_desc_len(p_desc); | |
| p_desc = tu_desc_next(p_desc); | |
| } | |
| // get notification endpoint | |
| TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); | |
| TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); | |
| ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; | |
| drv_len += tu_desc_len(p_desc); | |
| p_desc = tu_desc_next(p_desc); | |
| // skip the following TUSB_DESC_INTERFACE entries (which must be TUSB_CLASS_CDC_DATA) | |
| while (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && drv_len <= max_len) { | |
| tusb_desc_interface_t const *data_itf_desc = (tusb_desc_interface_t const *) p_desc; | |
| TU_ASSERT(data_itf_desc->bInterfaceClass == TUSB_CLASS_CDC_DATA, 0); | |
| drv_len += tu_desc_len(p_desc); | |
| p_desc = tu_desc_next(p_desc); | |
| } | |
| // a TUSB_DESC_ENDPOINT (actually two) must follow, open these endpoints | |
| TU_ASSERT(tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT, 0); | |
| TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in)); | |
| ncm_interface.ep_size = tu_edpt_packet_size((tusb_desc_endpoint_t const *) p_desc); | |
| drv_len += 2 * sizeof(tusb_desc_endpoint_t); | |
| return drv_len; | |
| } // netd_open | |
| /** | |
| * Handle TinyUSB requests to process transfer events. | |
| */ | |
| bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { | |
| (void) result; | |
| if (ep_addr == ncm_interface.ep_out) { | |
| // new NTB received | |
| // - make the NTB valid | |
| // - if ready transfer datagrams to the glue logic for further processing | |
| // - if there is a free receive buffer, initiate reception | |
| if (!recv_validate_datagram(ncm_interface.recv_tinyusb_ntb, xferred_bytes)) { | |
| // verification failed: ignore NTB and return it to free | |
| TU_LOG_DRV("Invalid datatagram. Ignoring NTB\n"); | |
| recv_put_ntb_into_free_list(ncm_interface.recv_tinyusb_ntb); | |
| } else { | |
| // packet ok -> put it into ready list | |
| recv_put_ntb_into_ready_list(ncm_interface.recv_tinyusb_ntb); | |
| } | |
| ncm_interface.recv_tinyusb_ntb = NULL; | |
| tud_network_recv_renew_r(rhport); | |
| } else if (ep_addr == ncm_interface.ep_in) { | |
| // transmission of an NTB finished | |
| // - free the transmitted NTB buffer | |
| // - insert ZLPs when necessary | |
| // - if there is another transmit NTB waiting, try to start transmission | |
| xmit_put_ntb_into_free_list(ncm_interface.xmit_tinyusb_ntb); | |
| ncm_interface.xmit_tinyusb_ntb = NULL; | |
| if (!xmit_insert_required_zlp(rhport, xferred_bytes)) { | |
| xmit_start_if_possible(rhport); | |
| } | |
| } else if (ep_addr == ncm_interface.ep_notif) { | |
| // next transfer on notification channel | |
| notification_xmit(rhport, true); | |
| } | |
| return true; | |
| } // netd_xfer_cb | |
| /** | |
| * Respond to TinyUSB control requests. | |
| * At startup transmission of notification packets are done here. | |
| */ | |
| bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { | |
| switch (request->bmRequestType_bit.type) { | |
| case TUSB_REQ_TYPE_STANDARD: | |
| if (stage != CONTROL_STAGE_SETUP) { | |
| return true; | |
| } | |
| switch (request->bRequest) { | |
| case TUSB_REQ_GET_INTERFACE: { | |
| TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex, false); | |
| tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1); | |
| } break; | |
| case TUSB_REQ_SET_INTERFACE: { | |
| TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex && request->wValue < 2, false); | |
| ncm_interface.itf_data_alt = (uint8_t) request->wValue; | |
| if (ncm_interface.itf_data_alt == 1) { | |
| tud_network_recv_renew_r(rhport); | |
| notification_xmit(rhport, false); | |
| } else { | |
| // Reset notification state to send link state update when interface is re-activated | |
| ncm_interface.notification_xmit_state = NOTIFICATION_SPEED; | |
| } | |
| tud_control_status(rhport, request); | |
| } break; | |
| // unsupported request | |
| default: | |
| return false; | |
| } | |
| break; | |
| case TUSB_REQ_TYPE_CLASS: | |
| TU_VERIFY(ncm_interface.itf_num == request->wIndex, false); | |
| switch (request->bRequest) { | |
| case NCM_GET_NTB_PARAMETERS: { | |
| if (stage != CONTROL_STAGE_SETUP) { | |
| return true; | |
| } | |
| // transfer NTB parameters to host. | |
| tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); | |
| } break; | |
| case NCM_SET_ETHERNET_PACKET_FILTER: { | |
| if (stage != CONTROL_STAGE_SETUP) { | |
| return true; | |
| } | |
| // Some hosts issue this request even if ETH_FILTER is not advertised, | |
| // see https://bugzilla.kernel.org/show_bug.cgi?id=217290 | |
| tud_network_set_packet_filter_cb(request->wValue); | |
| tud_control_xfer(rhport, request, NULL, 0); | |
| } break; | |
| case NCM_GET_NTB_INPUT_SIZE: { | |
| if (stage != CONTROL_STAGE_SETUP) { | |
| return true; | |
| } | |
| TU_VERIFY(request->wLength >=4, false); | |
| uint8_t resp_len = (request->wLength >= 8 && (ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE)) ? 8 : 4; | |
| ncm_ntb_input_size_t ntb_input_size = { | |
| .dwNtbInMaxSize = ncm_interface.xmit_max_ntb_size, | |
| .wNtbInMaxDatagrams = ncm_interface.xmit_max_datagrams | |
| }; | |
| tud_control_xfer(rhport, request, &ntb_input_size, resp_len); | |
| } break; | |
| case NCM_SET_NTB_INPUT_SIZE: { | |
| if (stage == CONTROL_STAGE_SETUP) { | |
| /* wLength == 8 -> the NTB Input Size Structure (if NCM_NETWORK_CAPS_NTB_INPUT_SIZE is set) | |
| wLength == 4 -> dwNtbInMaxSize field of the NTB Input Size Structure. */ | |
| TU_VERIFY(request->wLength == 4 || request->wLength == 8, false); | |
| if (request->wLength == 8) { | |
| TU_VERIFY(ncm_interface.bm_capabilities & NCM_NETWORK_CAPS_NTB_INPUT_SIZE, false); | |
| } | |
| tu_memclr(&ncm_interface.ntb_input_size, sizeof(ncm_interface.ntb_input_size)); | |
| tud_control_xfer(rhport, request, &ncm_interface.ntb_input_size, request->wLength); | |
| } else if (stage == CONTROL_STAGE_DATA) { | |
| /* CDC-NCM 1.0 Table 6-4, up to NTB16 size */ | |
| const uint32_t requested_size = ncm_interface.ntb_input_size.dwNtbInMaxSize; | |
| if (requested_size < 2048u || requested_size > 65535u) { | |
| return false; | |
| } | |
| ncm_interface.xmit_max_ntb_size = tu_min16(requested_size, CFG_TUD_NCM_IN_NTB_MAX_SIZE); | |
| if (ncm_interface.ntb_input_size.wNtbInMaxDatagrams == 0 || ncm_interface.ntb_input_size.wNtbInMaxDatagrams > CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB) { | |
| ncm_interface.xmit_max_datagrams = CFG_TUD_NCM_IN_MAX_DATAGRAMS_PER_NTB; | |
| } else { | |
| ncm_interface.xmit_max_datagrams = ncm_interface.ntb_input_size.wNtbInMaxDatagrams; | |
| } | |
| } | |
| } break; | |
| // unsupported request | |
| default: | |
| return false; | |
| } | |
| break; | |
| // unsupported request | |
| default: | |
| return false; | |
| } | |
| return true; | |
| } // netd_control_xfer_cb | |
| #endif // ( CFG_TUD_ENABLED && CFG_TUD_NCM ) |