diff --git a/src/class/net/ncm.h b/src/class/net/ncm.h index 96ba11fbc..313237747 100644 --- a/src/class/net/ncm.h +++ b/src/class/net/ncm.h @@ -30,9 +30,46 @@ #include "common/tusb_common.h" -#ifdef __cplusplus - extern "C" { + +#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE + /// must be >> MTU + #define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200 #endif +#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE + /// must be >> MTU + #define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200 +#endif + +#ifndef CFG_TUD_NCM_OUT_NTB_N + /// number of ntb buffers for reception side + /// 1 - good performance + /// 2 - up to 30% more performance with iperf with small packets + /// >2 - no performance gain + #define CFG_TUD_NCM_OUT_NTB_N 2 +#endif + +#ifndef CFG_TUD_NCM_IN_NTB_N + /// number of ntb buffers for transmission side + /// 1 - good performance but SystemView shows lost events (on load test) + /// 2 - up to 50% more performance with iperf with small packets, "tud_network_can_xmit: request blocked" + /// happens from time to time with SystemView + /// 3 - "tud_network_can_xmit: request blocked" never happens + /// >2 - no performance gain + #define CFG_TUD_NCM_IN_NTB_N 3 +#endif + +#ifndef CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + /// this is for the transmission size for allocation of \a ndp16_datagram_t + #define CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB 8 +#endif + +#ifndef CFG_TUD_NCM_ALIGNMENT + #define CFG_TUD_NCM_ALIGNMENT 4 +#endif +#if (CFG_TUD_NCM_ALIGNMENT != 4) + #error "CFG_TUD_NCM_ALIGNMENT must be 4, otherwise the headers and start of datagrams have to be aligned (which they are currently not)" +#endif + // Table 4.3 Data Class Interface Protocol Codes typedef enum @@ -62,8 +99,67 @@ typedef enum NCM_SET_CRC_MODE = 0x8A, } ncm_request_code_t; -#ifdef __cplusplus - } -#endif + +#define NTH16_SIGNATURE 0x484D434E +#define NDP16_SIGNATURE_NCM0 0x304D434E +#define NDP16_SIGNATURE_NCM1 0x314D434E + +typedef struct TU_ATTR_PACKED { + uint16_t wLength; + uint16_t bmNtbFormatsSupported; + uint32_t dwNtbInMaxSize; + uint16_t wNdbInDivisor; + uint16_t wNdbInPayloadRemainder; + uint16_t wNdbInAlignment; + uint16_t wReserved; + uint32_t dwNtbOutMaxSize; + uint16_t wNdbOutDivisor; + uint16_t wNdbOutPayloadRemainder; + uint16_t wNdbOutAlignment; + uint16_t wNtbOutMaxDatagrams; +} ntb_parameters_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wHeaderLength; + uint16_t wSequence; + uint16_t wBlockLength; + uint16_t wNdpIndex; +} nth16_t; + +typedef struct TU_ATTR_PACKED { + uint16_t wDatagramIndex; + uint16_t wDatagramLength; +} ndp16_datagram_t; + +typedef struct TU_ATTR_PACKED { + uint32_t dwSignature; + uint16_t wLength; + uint16_t wNextNdpIndex; + //ndp16_datagram_t datagram[]; +} ndp16_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + ndp16_t ndp; + ndp16_datagram_t ndp_datagram[CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1]; + }; + uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE]; +} xmit_ntb_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + // only the header is at a guaranteed position + }; + uint8_t data[CFG_TUD_NCM_OUT_NTB_MAX_SIZE]; +} recv_ntb_t; + +struct ncm_notify_t { + tusb_control_request_t header; + uint32_t downlink, uplink; +}; + #endif diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c index 226c42c4e..f7a236e69 100644 --- a/src/class/net/ncm_device.c +++ b/src/class/net/ncm_device.c @@ -1,9 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2020 Jacob Berg Potter - * Copyright (c) 2020 Peter Lawrence - * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2023 Hardy Griech * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,493 +24,1004 @@ * 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 ) +#if ECLIPSE_GUI || ( CFG_TUD_ENABLED && CFG_TUD_NCM ) + +#include +#include +#include #include "device/usbd.h" #include "device/usbd_pvt.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 +#include "net_device.h" +#include "ncm.h" + + +#if !defined(tu_static) || ECLIPSE_GUI + // TinyUSB <=0.15.0 does not know "tu_static" + #define tu_static static #endif -#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_NCM_LOG_LEVEL, __VA_ARGS__) +#if 0 + #define DEBUG_OUT(...) printf(__VA_ARGS__) + #define DEBUG_OUT_ENABLED +#else + #define DEBUG_OUT(...) +#endif -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ +#if 0 + #define INFO_OUT(...) printf(__VA_ARGS__) +#else + #define INFO_OUT(...) +#endif -#define NTH16_SIGNATURE 0x484D434E -#define NDP16_SIGNATURE_NCM0 0x304D434E -#define NDP16_SIGNATURE_NCM1 0x314D434E +#if 1 + #define ERROR_OUT(...) printf(__VA_ARGS__) +#else + #define ERROR_OUT(...) +#endif -typedef struct TU_ATTR_PACKED -{ - uint16_t wLength; - uint16_t bmNtbFormatsSupported; - uint32_t dwNtbInMaxSize; - uint16_t wNdbInDivisor; - uint16_t wNdbInPayloadRemainder; - uint16_t wNdbInAlignment; - uint16_t wReserved; - uint32_t dwNtbOutMaxSize; - uint16_t wNdbOutDivisor; - uint16_t wNdbOutPayloadRemainder; - uint16_t wNdbOutAlignment; - uint16_t wNtbOutMaxDatagrams; -} ntb_parameters_t; +// calculate alignment of xmit datagrams within an NTB +#define XMIT_ALIGN_OFFSET(x) ((CFG_TUD_NCM_ALIGNMENT - ((x) & (CFG_TUD_NCM_ALIGNMENT - 1))) & (CFG_TUD_NCM_ALIGNMENT - 1)) -typedef struct TU_ATTR_PACKED -{ - uint32_t dwSignature; - uint16_t wHeaderLength; - uint16_t wSequence; - uint16_t wBlockLength; - uint16_t wNdpIndex; -} nth16_t; +//----------------------------------------------------------------------------- +// +// 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 TU_ATTR_PACKED -{ - uint16_t wDatagramIndex; - uint16_t wDatagramLength; -} ndp16_datagram_t; +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 -typedef struct TU_ATTR_PACKED -{ - uint32_t dwSignature; - uint16_t wLength; - uint16_t wNextNdpIndex; - ndp16_datagram_t datagram[]; -} ndp16_t; + // recv handling + recv_ntb_t recv_ntb[RECV_NTB_N]; //!< actual recv NTBs + 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 + 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 -typedef union TU_ATTR_PACKED { - struct { - nth16_t nth; - ndp16_t ndp; - }; - uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE]; -} transmit_ntb_t; - -struct ecm_notify_struct -{ - tusb_control_request_t header; - uint32_t downlink, uplink; -}; - -typedef struct -{ - uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface - uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active - - uint8_t ep_notif; - uint8_t ep_in; - uint8_t ep_out; - - const ndp16_t *ndp; - uint8_t num_datagrams, current_datagram_index; - - enum { - REPORT_SPEED, - REPORT_CONNECTED, - REPORT_DONE - } report_state; - bool report_pending; - - uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams - uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb] - uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram - uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE - uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB - - uint16_t nth_sequence; // Sequence number counter for transmitted NTBs - - bool transferring; + // xmit handling + xmit_ntb_t xmit_ntb[XMIT_NTB_N]; //!< actual xmit NTBs + 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 + 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 } ncm_interface_t; -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static const ntb_parameters_t ntb_parameters = { - .wLength = sizeof(ntb_parameters_t), - .bmNtbFormatsSupported = 0x01, - .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE, - .wNdbInDivisor = 4, - .wNdbInPayloadRemainder = 0, - .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT, - .wReserved = 0, - .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, - .wNdbOutDivisor = 4, - .wNdbOutPayloadRemainder = 0, - .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT, - .wNtbOutMaxDatagrams = 0 -}; +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static ncm_interface_t ncm_interface; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static transmit_ntb_t transmit_ntb[2]; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static uint8_t receive_ntb[CFG_TUD_NCM_OUT_NTB_MAX_SIZE]; - -tu_static ncm_interface_t ncm_interface; - -/* - * Set up the NTB state in ncm_interface to be ready to add datagrams. +/** + * This is the NTB parameter structure + * + * \attention + * We are lucky, that byte order is correct */ -static void ncm_prepare_for_tx(void) { - ncm_interface.datagram_count = 0; - // datagrams start after all the headers - ncm_interface.next_datagram_offset = sizeof(nth16_t) + sizeof(ndp16_t) - + ((CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1) * sizeof(ndp16_datagram_t)); -} +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_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 = CFG_TUD_NCM_ALIGNMENT, + .wReserved = 0, + .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, + .wNdbOutDivisor = 4, + .wNdbOutPayloadRemainder = 0, + .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT, + .wNtbOutMaxDatagrams = 6 // 0=no limit +}; -/* - * If not already transmitting, start sending the current NTB to the host and swap buffers - * to start filling the other one with datagrams. +// 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? +// switch \a INFO_OUT on to see interesting values for this. +// +// 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 +// +tu_static struct ncm_notify_t ncm_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 = 1 /* Connected */, + .wLength = 0, + }, +}; + +tu_static struct ncm_notify_t ncm_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, + .wLength = 8, + }, + .downlink = 1000000, + .uplink = 1000000, +}; + + + +static void notification_xmit(uint8_t rhport, bool force_next) +/** + * Transmit next notification to the host (if appropriate). + * Notifications are transferred to the host once during connection setup. */ -static void ncm_start_tx(void) { - if (ncm_interface.transferring) { - return; - } - - transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb]; - size_t ntb_length = ncm_interface.next_datagram_offset; - - // Fill in NTB header - ntb->nth.dwSignature = NTH16_SIGNATURE; - ntb->nth.wHeaderLength = sizeof(nth16_t); - ntb->nth.wSequence = ncm_interface.nth_sequence++; - ntb->nth.wBlockLength = ntb_length; - ntb->nth.wNdpIndex = sizeof(nth16_t); - - // Fill in NDP16 header and terminator - ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0; - ntb->ndp.wLength = sizeof(ndp16_t) + (ncm_interface.datagram_count + 1) * sizeof(ndp16_datagram_t); - ntb->ndp.wNextNdpIndex = 0; - ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = 0; - ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = 0; - - // Kick off an endpoint transfer - usbd_edpt_xfer(0, ncm_interface.ep_in, ntb->data, ntb_length); - ncm_interface.transferring = true; - - // Swap to the other NTB and clear it out - ncm_interface.current_ntb = 1 - ncm_interface.current_ntb; - ncm_prepare_for_tx(); -} - -tu_static struct ecm_notify_struct ncm_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 = 1 /* Connected */, - .wLength = 0, - }, -}; + DEBUG_OUT("notification_xmit(%d, %d) - %d %d\n", force_next, rhport, ncm_interface.notification_xmit_state, ncm_interface.notification_xmit_is_running); -tu_static struct ecm_notify_struct ncm_notify_speed_change = + if ( !force_next && ncm_interface.notification_xmit_is_running) { + return; + } + + if (ncm_interface.notification_xmit_state == NOTIFICATION_SPEED) { + DEBUG_OUT(" NOTIFICATION_SPEED\n"); + ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t*) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change)); + ncm_interface.notification_xmit_state = NOTIFICATION_CONNECTED; + ncm_interface.notification_xmit_is_running = true; + } + else if (ncm_interface.notification_xmit_state == NOTIFICATION_CONNECTED) { + DEBUG_OUT(" NOTIFICATION_CONNECTED\n"); + ncm_notify_connected.header.wIndex = ncm_interface.itf_num; + usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t*) &ncm_notify_connected, sizeof(ncm_notify_connected)); + ncm_interface.notification_xmit_state = NOTIFICATION_DONE; + ncm_interface.notification_xmit_is_running = true; + } + else { + DEBUG_OUT(" NOTIFICATION_FINISHED\n"); + } +} // notification_xmit + + +//----------------------------------------------------------------------------- +// +// everything about packet transmission (driver -> TinyUSB) +// + + +static void xmit_put_ntb_into_free_list(xmit_ntb_t *free_ntb) +/** + * Put NTB into the transmitter free list. + */ { - .header = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_IN - }, - .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE, - .wLength = 8, - }, - .downlink = 10000000, - .uplink = 10000000, -}; + DEBUG_OUT("xmit_put_ntb_into_free_list() - %p\n", ncm_interface.xmit_tinyusb_ntb); -void tud_network_recv_renew(void) -{ - if (!ncm_interface.num_datagrams) - { - usbd_edpt_xfer(0, ncm_interface.ep_out, receive_ntb, sizeof(receive_ntb)); - return; - } + if (free_ntb == NULL) { + // can happen due to ZLPs + return; + } - const ndp16_t *ndp = ncm_interface.ndp; - const int i = ncm_interface.current_datagram_index; - ncm_interface.current_datagram_index++; - ncm_interface.num_datagrams--; - - tud_network_recv_cb(receive_ntb + ndp->datagram[i].wDatagramIndex, ndp->datagram[i].wDatagramLength); -} - -//--------------------------------------------------------------------+ -// USBD Driver API -//--------------------------------------------------------------------+ - -void netd_init(void) -{ - tu_memclr(&ncm_interface, sizeof(ncm_interface)); - ncm_interface.ntb_in_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE; - ncm_interface.max_datagrams_per_ntb = CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB; - ncm_prepare_for_tx(); -} - -void netd_reset(uint8_t rhport) -{ - (void) rhport; - - netd_init(); -} - -uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) -{ - // confirm interface hasn't already been allocated - TU_ASSERT(0 == ncm_interface.ep_notif, 0); - - //------------- Management Interface -------------// - ncm_interface.itf_num = itf_desc->bInterfaceNumber; - - uint16_t drv_len = sizeof(tusb_desc_interface_t); - uint8_t const * p_desc = tu_desc_next( itf_desc ); - - // Communication Functional Descriptors - while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) - { - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); - } - - // notification endpoint (if any) - if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) - { - 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); - } - - //------------- Data Interface -------------// - // - CDC-NCM data interface has 2 alternate settings - // - 0 : zero endpoints for inactive (default) - // - 1 : IN & OUT endpoints for transfer of NTBs - TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); - - do - { - tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; - TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); - - drv_len += tu_desc_len(p_desc); - p_desc = tu_desc_next(p_desc); - } while((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len)); - - // Pair of endpoints - TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); - - TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) ); - - drv_len += 2*sizeof(tusb_desc_endpoint_t); - - return drv_len; -} - -static void ncm_report(void) -{ - uint8_t const rhport = 0; - if (ncm_interface.report_state == REPORT_SPEED) { - ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num; - usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change)); - ncm_interface.report_state = REPORT_CONNECTED; - ncm_interface.report_pending = true; - } else if (ncm_interface.report_state == REPORT_CONNECTED) { - ncm_notify_connected.header.wIndex = ncm_interface.itf_num; - usbd_edpt_xfer(rhport, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_connected, sizeof(ncm_notify_connected)); - ncm_interface.report_state = REPORT_DONE; - ncm_interface.report_pending = true; - } -} - -TU_ATTR_WEAK void tud_network_link_state_cb(bool state) -{ - (void)state; -} - -// Handle class control request -// return false to stall control endpoint (e.g unsupported request) -bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) -{ - if ( stage != CONTROL_STAGE_SETUP ) return true; - - switch ( request->bmRequestType_bit.type ) - { - case TUSB_REQ_TYPE_STANDARD: - switch ( request->bRequest ) - { - case TUSB_REQ_GET_INTERFACE: - { - uint8_t const req_itfnum = (uint8_t) request->wIndex; - TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum); - - tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1); + 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; } - break; + } + ERROR_OUT("xmit_put_ntb_into_free_list - no entry in free list\n"); // this should not happen +} // xmit_put_ntb_into_free_list - case TUSB_REQ_SET_INTERFACE: - { - uint8_t const req_itfnum = (uint8_t) request->wIndex; - uint8_t const req_alt = (uint8_t) request->wValue; - // Only valid for Data Interface with Alternate is either 0 or 1 - TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2); - if (req_alt != ncm_interface.itf_data_alt) { - ncm_interface.itf_data_alt = req_alt; +static xmit_ntb_t *xmit_get_free_ntb(void) +/** + * Get an NTB from the free list + */ +{ + DEBUG_OUT("xmit_get_free_ntb()\n"); - if (ncm_interface.itf_data_alt) { - if (!usbd_edpt_busy(rhport, ncm_interface.ep_out)) { - tud_network_recv_renew(); // prepare for incoming datagrams - } - if (!ncm_interface.report_pending) { - ncm_report(); - } + 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 + + + +static void xmit_put_ntb_into_ready_list(xmit_ntb_t *ready_ntb) +/** + * Put a filled NTB into the ready list + */ +{ + INFO_OUT("xmit_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->len); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + if (ncm_interface.xmit_ready_ntb[i] == NULL) { + ncm_interface.xmit_ready_ntb[i] = ready_ntb; + return; + } + } + ERROR_OUT("xmit_put_ntb_into_ready_list: ready list full\n"); // this should not happen +} // xmit_put_ntb_into_ready_list + + + +static xmit_ntb_t *xmit_get_next_ready_ntb(void) +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +{ + xmit_ntb_t *r = NULL; + + r = ncm_interface.xmit_ready_ntb[0]; + memmove(ncm_interface.xmit_ready_ntb + 0, ncm_interface.xmit_ready_ntb + 1, sizeof(ncm_interface.xmit_ready_ntb) - sizeof(ncm_interface.xmit_ready_ntb[0])); + + DEBUG_OUT("recv_get_next_ready_ntb: %p\n", r); + return r; +} // xmit_get_next_ready_ntb + + + +static bool xmit_insert_required_zlp(uint8_t rhport, uint16_t xferred_bytes) +/** + * 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 + */ +{ + DEBUG_OUT("xmit_insert_required_zlp(%d,%d)\n", rhport, xferred_bytes); + + if (xferred_bytes == 0 || xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE != 0) { + return false; + } + + TU_ASSERT(ncm_interface.itf_data_alt == 1, false); + TU_ASSERT( !usbd_edpt_busy(rhport, ncm_interface.ep_in), false); + + INFO_OUT("xmit_insert_required_zlp! (%u)\n", (unsigned)xferred_bytes); + + // start transmission of the ZLP + usbd_edpt_xfer(rhport, ncm_interface.ep_in, NULL, 0); + + return true; +} // xmit_insert_required_zlp + + + +static void xmit_start_if_possible(uint8_t rhport) +/** + * Start transmission if it there is a waiting packet and if can be done from interface side. + */ +{ + DEBUG_OUT("xmit_start_if_possible()\n"); + + if (ncm_interface.xmit_tinyusb_ntb != NULL) { + DEBUG_OUT(" !xmit_start_if_possible 1\n"); + return; + } + if (ncm_interface.itf_data_alt != 1) { + ERROR_OUT(" !xmit_start_if_possible 2\n"); + return; + } + if (usbd_edpt_busy(rhport, ncm_interface.ep_in)) { + INFO_OUT(" !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 DEBUG_OUT_ENABLED + { + uint16_t len = ncm_interface.xmit_tinyusb_ntb->ntb.nth.wBlockLength; + DEBUG_OUT(" %d\n", len); + for (int i = 0; i < len; ++i) { + DEBUG_OUT(" %02x", ncm_interface.xmit_tinyusb_ntb->data[i]); + } + DEBUG_OUT("\n"); + } +#endif + + if (ncm_interface.xmit_glue_ntb_datagram_ndx != 1) { + DEBUG_OUT(">> %d %d\n", ncm_interface.xmit_tinyusb_ntb->len, 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); +} // xmit_start_if_possible + + + +static bool xmit_requested_datagram_fits_into_current_ntb(uint16_t datagram_size) +/** + * check if a new datagram fits into the current NTB + */ +{ + DEBUG_OUT("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 >= CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB) { + return false; + } + if (ncm_interface.xmit_glue_ntb->nth.wBlockLength + datagram_size + XMIT_ALIGN_OFFSET(datagram_size) > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { + return false; + } + return true; +} // xmit_requested_datagram_fits_into_current_ntb + + + +static bool xmit_setup_next_glue_ntb(void) +/** + * Setup an NTB for the glue logic + */ +{ + DEBUG_OUT("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) { + DEBUG_OUT(" 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) +// + + +static recv_ntb_t *recv_get_free_ntb(void) +/** + * Return pointer to an available receive buffer or NULL. + * Returned buffer (if any) has the size \a CFG_TUD_NCM_OUT_NTB_MAX_SIZE. + */ +{ + DEBUG_OUT("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 + + + +static recv_ntb_t *recv_get_next_ready_ntb(void) +/** + * Get the next NTB from the ready list (and remove it from the list). + * If the ready list is empty, return NULL. + */ +{ + recv_ntb_t *r = NULL; + + r = ncm_interface.recv_ready_ntb[0]; + memmove(ncm_interface.recv_ready_ntb + 0, ncm_interface.recv_ready_ntb + 1, sizeof(ncm_interface.recv_ready_ntb) - sizeof(ncm_interface.recv_ready_ntb[0])); + + DEBUG_OUT("recv_get_next_ready_ntb: %p\n", r); + return r; +} // recv_get_next_ready_ntb + + + +static void recv_put_ntb_into_free_list(recv_ntb_t *free_ntb) +/** + * + */ +{ + DEBUG_OUT("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; + } + } + ERROR_OUT("recv_put_ntb_into_free_list - no entry in free list\n"); // this should not happen +} // recv_put_ntb_into_free_list + + + +static void recv_put_ntb_into_ready_list(recv_ntb_t *ready_ntb) +/** + * The \a ncm_interface.recv_tinyusb_ntb is filled, + * put this buffer into the waiting list and free the receive logic. + */ +{ + DEBUG_OUT("recv_put_ntb_into_ready_list(%p) %d\n", ready_ntb, ready_ntb->len); + + for (int i = 0; i < RECV_NTB_N; ++i) { + if (ncm_interface.recv_ready_ntb[i] == NULL) { + ncm_interface.recv_ready_ntb[i] = ready_ntb; + return; + } + } + ERROR_OUT("recv_put_ntb_into_ready_list: ready list full\n"); // this should not happen +} // recv_put_ntb_into_ready_list + + + +static void recv_try_to_start_new_reception(uint8_t rhport) +/** + * If possible, start a new reception TinyUSB -> driver. + * Return value is actually not of interest. + */ +{ + DEBUG_OUT("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(ncm_interface.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 + DEBUG_OUT(" start reception\n"); + bool r = usbd_edpt_xfer(0, ncm_interface.ep_out, ncm_interface.recv_tinyusb_ntb->data, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); + 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 + + + +static bool recv_validate_datagram(const recv_ntb_t *ntb, uint16_t len) +/** + * Validate incoming datagram. + * \return true if valid + * + * \note + * \a ndp16->wNextNdpIndex != 0 is not supported + */ +{ + const nth16_t *nth16 = &(ntb->nth); + + DEBUG_OUT("recv_validate_datagram(%p)\n", ntb); + + // + // check header + // + if (nth16->wHeaderLength != sizeof(nth16_t)) + { + ERROR_OUT(" ill nth16 length: %d\n", nth16->wHeaderLength); + return false; + } + if (nth16->dwSignature != NTH16_SIGNATURE) { + ERROR_OUT(" ill signature: 0x%08x\n", (unsigned)nth16->dwSignature); + return false; + } + if (len < sizeof(nth16_t) + sizeof(ndp16_t) + 2*sizeof(ndp16_datagram_t)) { + ERROR_OUT(" ill min len: %d\n", len); + return false; + } + if (nth16->wBlockLength > len) { + ERROR_OUT(" ill block length: %d > %d\n", nth16->wBlockLength, len); + return false; + } + if (nth16->wBlockLength > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { + ERROR_OUT(" ill block length2: %d > %d\n", nth16->wBlockLength, CFG_TUD_NCM_OUT_NTB_MAX_SIZE); + return false; + } + if (nth16->wNdpIndex < sizeof(nth16) || nth16->wNdpIndex > len - (sizeof(ndp16_t) + 2*sizeof(ndp16_datagram_t))) { + ERROR_OUT(" ill position of first ndp: %d (%d)\n", nth16->wNdpIndex, len); + return false; + } + + // + // check (first) NDP(16) + // + const ndp16_t *ndp16 = (ndp16_t *)(ntb->data + nth16->wNdpIndex); + + if (ndp16->wLength < sizeof(ndp16_t) + 2*sizeof(ndp16_datagram_t)) { + ERROR_OUT(" ill ndp16 length: %d\n", ndp16->wLength); + return false; + } + if (ndp16->dwSignature != NDP16_SIGNATURE_NCM0 && ndp16->dwSignature != NDP16_SIGNATURE_NCM1) { + ERROR_OUT(" ill signature: 0x%08x\n", (unsigned)ndp16->dwSignature); + return false; + } + if (ndp16->wNextNdpIndex != 0) { + ERROR_OUT(" cannot handle wNextNdpIndex!=0 (%d)\n", ndp16->wNextNdpIndex); + return false; + } + + const ndp16_datagram_t *ndp16_datagram = (ndp16_datagram_t *)(ntb->data + nth16->wNdpIndex + sizeof(ndp16_t)); + int ndx = 0; + int max_ndx = (ndp16->wLength - sizeof(ndp16_t)) / sizeof(ndp16_datagram_t); + + if (max_ndx > 2) { + // number of datagrams in NTB > 1 + INFO_OUT("<< %d (%d)\n", max_ndx - 1, ntb->len); + } + if (ndp16_datagram[max_ndx-1].wDatagramIndex != 0 || ndp16_datagram[max_ndx-1].wDatagramLength != 0) { + INFO_OUT(" max_ndx != 0\n"); + return false; + } + while (ndp16_datagram[ndx].wDatagramIndex != 0 && ndp16_datagram[ndx].wDatagramLength != 0) { + DEBUG_OUT(" << %d %d\n", ndp16_datagram[ndx].wDatagramIndex, ndp16_datagram[ndx].wDatagramLength); + if (ndp16_datagram[ndx].wDatagramIndex > len) { + ERROR_OUT(" ill start of datagram[%d]: %d (%d)\n", ndx, ndp16_datagram[ndx].wDatagramIndex, len); + return false; + } + if (ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength > len) { + ERROR_OUT(" ill end of datagram[%d]: %d (%d)\n", ndx, ndp16_datagram[ndx].wDatagramIndex + ndp16_datagram[ndx].wDatagramLength, len); + return false; + } + ++ndx; + } + + for (int i = 0; i < len; ++i) { + DEBUG_OUT(" %02x", ntb->data[i]); + } + DEBUG_OUT("\n"); + + // -> ntb contains a valid packet structure + // ok... I did not check for garbage within the datagram indices... + return true; +} // recv_validate_datagram + + + +static void recv_transfer_datagram_to_glue_logic(void) +/** + * Transfer the next (pending) datagram to the glue logic and return receive buffer if empty. + */ +{ + DEBUG_OUT("recv_transfer_datagram_to_glue_logic()\n"); + + if (ncm_interface.recv_glue_ntb == NULL) { + ncm_interface.recv_glue_ntb = recv_get_next_ready_ntb(); + DEBUG_OUT(" 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) { + ERROR_OUT(" SOMETHING WENT WRONG 1\n"); + } + else if (ndp16_datagram[ncm_interface.recv_glue_ntb_datagram_ndx].wDatagramLength == 0) { + ERROR_OUT(" 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; + + DEBUG_OUT(" 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 + // + DEBUG_OUT(" 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; + } } - - tud_network_link_state_cb(ncm_interface.itf_data_alt); - } - - 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); - - if (NCM_GET_NTB_PARAMETERS == request->bRequest) - { - tud_control_xfer(rhport, request, (void*)(uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); - } - - break; - - // unsupported request - default: return false; - } - - return true; -} - -static void handle_incoming_datagram(uint32_t len) -{ - uint32_t size = len; - - if (len == 0) { - return; - } - - TU_ASSERT(size >= sizeof(nth16_t), ); - - const nth16_t *hdr = (const nth16_t *)receive_ntb; - TU_ASSERT(hdr->dwSignature == NTH16_SIGNATURE, ); - TU_ASSERT(hdr->wNdpIndex >= sizeof(nth16_t) && (hdr->wNdpIndex + sizeof(ndp16_t)) <= len, ); - - const ndp16_t *ndp = (const ndp16_t *)(receive_ntb + hdr->wNdpIndex); - TU_ASSERT(ndp->dwSignature == NDP16_SIGNATURE_NCM0 || ndp->dwSignature == NDP16_SIGNATURE_NCM1, ); - TU_ASSERT(hdr->wNdpIndex + ndp->wLength <= len, ); - - int num_datagrams = (ndp->wLength - 12) / 4; - ncm_interface.current_datagram_index = 0; - ncm_interface.num_datagrams = 0; - ncm_interface.ndp = ndp; - for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++) - { - ncm_interface.num_datagrams++; - } - - tud_network_recv_renew(); -} - -bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) rhport; - (void) result; - - /* new datagram receive_ntb */ - if (ep_addr == ncm_interface.ep_out ) - { - handle_incoming_datagram(xferred_bytes); - } - - /* data transmission finished */ - if (ep_addr == ncm_interface.ep_in ) - { - if (ncm_interface.transferring) { - ncm_interface.transferring = false; } +} // recv_transfer_datagram_to_glue_logic - // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now - if (ncm_interface.datagram_count && ncm_interface.itf_data_alt == 1) { - ncm_start_tx(); - } - } - if (ep_addr == ncm_interface.ep_notif ) - { - ncm_interface.report_pending = false; - ncm_report(); - } +//----------------------------------------------------------------------------- +// +// all the tud_network_*() stuff (glue logic -> driver) +// - return true; -} -// poll network driver for its ability to accept another packet to transmit bool tud_network_can_xmit(uint16_t size) +/** + * 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. + */ { - TU_VERIFY(ncm_interface.itf_data_alt == 1); + DEBUG_OUT("tud_network_can_xmit(%d)\n", size); - if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) { - TU_LOG_DRV("NTB full [by count]\r\n"); + TU_ASSERT(size <= CFG_TUD_NCM_OUT_NTB_MAX_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); + ERROR_OUT(" 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 - size_t next_datagram_offset = ncm_interface.next_datagram_offset; - if (next_datagram_offset + size > ncm_interface.ntb_in_size) { - TU_LOG_DRV("ntb full [by size]\r\n"); - return false; - } - return true; -} void tud_network_xmit(void *ref, uint16_t arg) +/** + * Put a datagram into a waiting NTB. + * If currently no transmission is started, then initiate transmission. + */ { - transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb]; - size_t next_datagram_offset = ncm_interface.next_datagram_offset; + DEBUG_OUT("tud_network_xmit(%p, %d)\n", ref, arg); - uint16_t size = tud_network_xmit_cb(ntb->data + next_datagram_offset, ref, arg); + if (ncm_interface.xmit_glue_ntb == NULL) { + ERROR_OUT("tud_network_xmit: no buffer\n"); // must not happen (really) + return; + } - ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = ncm_interface.next_datagram_offset; - ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = size; + xmit_ntb_t *ntb = ncm_interface.xmit_glue_ntb; - ncm_interface.datagram_count++; - next_datagram_offset += size; + // copy new datagram to the end of the current NTB + uint16_t size = tud_network_xmit_cb(ntb->data + ntb->nth.wBlockLength, ref, arg); - // round up so the next datagram is aligned correctly - next_datagram_offset += (CFG_TUD_NCM_ALIGNMENT - 1); - next_datagram_offset -= (next_datagram_offset % CFG_TUD_NCM_ALIGNMENT); + // 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; - ncm_interface.next_datagram_offset = next_datagram_offset; + ntb->nth.wBlockLength += size + XMIT_ALIGN_OFFSET(size); - ncm_start_tx(); -} + if (ntb->nth.wBlockLength > CFG_TUD_NCM_OUT_NTB_MAX_SIZE) { + ERROR_OUT("tud_network_xmit: buffer overflow\n"); // must not happen (really) + return; + } -#endif + xmit_start_if_possible(ncm_interface.rhport); +} // tud_network_xmit + + + +void tud_network_recv_renew(void) +/** + * Keep the receive logic busy and transfer pending packets to the glue logic. + */ +{ + DEBUG_OUT("tud_network_recv_renew()\n"); + + recv_transfer_datagram_to_glue_logic(); + recv_try_to_start_new_reception(ncm_interface.rhport); +} // tud_network_recv_renew + + + +void tud_network_recv_renew_r(uint8_t rhport) +/** + * Same as tud_network_recv_renew() but knows \a rhport + */ +{ + DEBUG_OUT("tud_network_recv_renew_r(%d)\n", rhport); + + ncm_interface.rhport = rhport; + tud_network_recv_renew(); +} // tud_network_recv_renew + + +//----------------------------------------------------------------------------- +// +// all the netd_*() stuff (interface TinyUSB -> driver) +// +void netd_init(void) +/** + * Initialize the driver data structures. + * Might be called several times. + */ +{ + DEBUG_OUT("netd_init()\n"); + + memset( &ncm_interface, 0, sizeof(ncm_interface)); + + for (int i = 0; i < XMIT_NTB_N; ++i) { + ncm_interface.xmit_free_ntb[i] = ncm_interface.xmit_ntb + i; + } + for (int i = 0; i < RECV_NTB_N; ++i) { + ncm_interface.recv_free_ntb[i] = ncm_interface.recv_ntb + i; + } +} // netd_init + + + +void netd_reset(uint8_t rhport) +/** + * Resets the port. + * In this driver this is the same as netd_init() + */ +{ + DEBUG_OUT("netd_reset(%d)\n", rhport); + + netd_init(); +} // netd_reset + + + +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) +/** + * 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 + */ +{ + DEBUG_OUT("netd_open(%d,%p,%d)\n", rhport, itf_desc, 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 + + // + // skip the two first entries and the following TUSB_DESC_CS_INTERFACE entries + // + 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) { + 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)); + drv_len += 2 * sizeof(tusb_desc_endpoint_t); + + return drv_len; +} // netd_open + + + +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +/** + * Handle TinyUSB requests to process transfer events. + */ +{ + DEBUG_OUT("netd_xfer_cb(%d,%d,%d,%u)\n", rhport, ep_addr, result, (unsigned)xferred_bytes); + + 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 + // + DEBUG_OUT(" EP_OUT %d %d %d %u\n", rhport, ep_addr, result, (unsigned)xferred_bytes); + if ( !recv_validate_datagram( ncm_interface.recv_tinyusb_ntb, xferred_bytes)) { + // verification failed: ignore NTB and return it to free + ERROR_OUT(" VALIDATION FAILED. WHAT CAN WE DO IN THIS CASE?\n"); + } + 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 + // + DEBUG_OUT(" EP_IN %d %u\n", ncm_interface.itf_data_alt, (unsigned)xferred_bytes); + 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 + // + DEBUG_OUT(" EP_NOTIF\n"); + notification_xmit(rhport, true); + } + + return true; +} // netd_xfer_cb + + + +bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +/** + * Respond to TinyUSB control requests. + * At startup transmission of notification packets are done here. + */ +{ + DEBUG_OUT("netd_control_xfer_cb(%d, %d, %p)\n", rhport, stage, request); + + if (stage != CONTROL_STAGE_SETUP) { + return true; + } + + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: { + TU_VERIFY(ncm_interface.itf_num + 1 == request->wIndex, false); + + DEBUG_OUT(" TUSB_REQ_GET_INTERFACE - %d\n", ncm_interface.itf_data_alt); + 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 = request->wValue; + DEBUG_OUT(" TUSB_REQ_SET_INTERFACE - %d %d %d\n", ncm_interface.itf_data_alt, request->wIndex, ncm_interface.itf_num); + + if (ncm_interface.itf_data_alt == 1) { + tud_network_recv_renew_r(rhport); + notification_xmit(rhport, false); + } + 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); + + DEBUG_OUT(" TUSB_REQ_TYPE_CLASS: %d\n", request->bRequest); + + if (request->bRequest == NCM_GET_NTB_PARAMETERS) { + // transfer NTB parameters to host. + // TODO can one assume, that tud_control_xfer() succeeds? + DEBUG_OUT(" NCM_GET_NTB_PARAMETERS\n"); + tud_control_xfer(rhport, request, (void*) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); + } + break; + + // unsupported request + default: + return false ; + } + + return true; +} // netd_control_xfer_cb + +#endif // ECLIPSE_GUI || ( CFG_TUD_ENABLED && CFG_TUD_NCM ) diff --git a/src/class/net/net_device.h b/src/class/net/net_device.h index 399916355..111c822fb 100644 --- a/src/class/net/net_device.h +++ b/src/class/net/net_device.h @@ -28,14 +28,13 @@ #ifndef _TUSB_NET_DEVICE_H_ #define _TUSB_NET_DEVICE_H_ +#include #include "class/cdc/cdc.h" #if CFG_TUD_ECM_RNDIS && CFG_TUD_NCM #error "Cannot enable both ECM_RNDIS and NCM network drivers" #endif -#include "ncm.h" - /* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */ #define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) @@ -44,21 +43,6 @@ #define CFG_TUD_NET_MTU 1514 #endif -#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE -#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200 -#endif - -#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE -#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200 -#endif - -#ifndef CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB -#define CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB 8 -#endif - -#ifndef CFG_TUD_NCM_ALIGNMENT -#define CFG_TUD_NCM_ALIGNMENT 4 -#endif #ifdef __cplusplus extern "C" { @@ -92,12 +76,6 @@ uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg); // client must provide this: initialize any network state back to the beginning void tud_network_init_cb(void); -// client must provide this: 48-bit MAC address -// TODO removed later since it is not part of tinyusb stack -extern uint8_t tud_network_mac_address[6]; - -//------------- NCM -------------// - // callback to client providing optional indication of internal state of network driver void tud_network_link_state_cb(bool state);