diff --git a/cores/rp2040/USB.cpp b/cores/rp2040/USB.cpp index 52aa4e073..6b399e2b6 100644 --- a/cores/rp2040/USB.cpp +++ b/cores/rp2040/USB.cpp @@ -610,7 +610,12 @@ extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t pr product_rev[0] = 0; } - +extern "C" bool tud_network_recv_cb(const uint8_t *src, uint16_t size) __attribute__((weak)); +extern "C" bool tud_network_recv_cb(const uint8_t *src, uint16_t size) { + (void) src; + (void) size; + return false; +} #ifdef ENABLE_PICOTOOL_USB // Support for Microsoft OS 2.0 descriptor diff --git a/cores/rp2040/sdkoverride/ncm_device.c b/cores/rp2040/sdkoverride/ncm_device.c new file mode 100644 index 000000000..d28dd18f2 --- /dev/null +++ b/cores/rp2040/sdkoverride/ncm_device.c @@ -0,0 +1,74 @@ +// Dummy shim to be overridden by lwip_usb_ncm +#include +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_NCM) + +#include +#include +#include + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "../../../pico-sdk/lib/tinyusb/src/class/net/ncm.h" +#include "../../../pico-sdk/lib/tinyusb/src/class/net/net_device.h" + + +extern bool tud_network_can_xmit(uint16_t size) __attribute((weak)); +bool tud_network_can_xmit(uint16_t size) { + (void) size; + return false; +} + +extern void tud_network_xmit(void *ref, uint16_t arg) __attribute((weak)); +void tud_network_xmit(void *ref, uint16_t arg) { + (void) ref; + (void) arg; + return; +} + +extern void tud_network_recv_renew(void) __attribute((weak)); +void tud_network_recv_renew(void) { +} + +extern void netd_init(void) __attribute((weak)); +void netd_init(void) { +} + +extern bool netd_deinit(void) __attribute((weak)); +bool netd_deinit(void) { + return true; +} + +extern void netd_reset(uint8_t rhport) __attribute((weak)); +void netd_reset(uint8_t rhport) { + (void) rhport; +} + +extern uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) __attribute((weak)); +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { + (void) rhport; + (void) itf_desc; + (void) max_len; + return 0; +} + +extern bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) __attribute((weak)); +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { + (void) rhport; + (void) ep_addr; + (void) result; + (void) xferred_bytes; + return false; +} + +extern bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) __attribute((weak)); +bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { + (void) rhport; + (void) stage; + (void) request; + return false; +} + +#endif // ( CFG_TUD_ENABLED && CFG_TUD_NCM ) diff --git a/include/tusb_config.h b/include/tusb_config.h index e867b2074..87a29f27e 100644 --- a/include/tusb_config.h +++ b/include/tusb_config.h @@ -75,6 +75,7 @@ #define CFG_TUD_MSC (1) #define CFG_TUD_MIDI (1) #define CFG_TUD_VENDOR (0) +#define CFG_TUD_NCM (1) #define CFG_TUD_CDC_RX_BUFSIZE (256) #define CFG_TUD_CDC_TX_BUFSIZE (256) @@ -84,10 +85,33 @@ // HID buffer size Should be sufficient to hold ID (if any) + Data #define CFG_TUD_HID_EP_BUFSIZE (64) + // MIDI #define CFG_TUD_MIDI_RX_BUFSIZE (64) #define CFG_TUD_MIDI_TX_BUFSIZE (64) +//-------------------------------------------------------------------- +// NCM CLASS CONFIGURATION, SEE "ncm.h" FOR PERFORMANCE TUNING +//-------------------------------------------------------------------- +#include "lwipopts.h" +// Must be >> MTU +// Can be set to 2048 without impact +#define CFG_TUD_NCM_IN_NTB_MAX_SIZE (2 * TCP_MSS + 100) + +// Must be >> MTU +// Can be set to smaller values if wNtbOutMaxDatagrams==1 +#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE (2 * TCP_MSS + 100) + +// Number of NCM transfer blocks for reception side +#ifndef CFG_TUD_NCM_OUT_NTB_N +#define CFG_TUD_NCM_OUT_NTB_N 1 +#endif + +// Number of NCM transfer blocks for transmission side +#ifndef CFG_TUD_NCM_IN_NTB_N +#define CFG_TUD_NCM_IN_NTB_N 1 +#endif + #ifdef __cplusplus } #endif diff --git a/lib/rp2040/liblwip-bt.a b/lib/rp2040/liblwip-bt.a index 6c0e63425..3c6b184a2 100644 Binary files a/lib/rp2040/liblwip-bt.a and b/lib/rp2040/liblwip-bt.a differ diff --git a/lib/rp2040/liblwip.a b/lib/rp2040/liblwip.a index b6b044ee9..8dbcbea2c 100644 Binary files a/lib/rp2040/liblwip.a and b/lib/rp2040/liblwip.a differ diff --git a/lib/rp2040/libpico.a b/lib/rp2040/libpico.a index 872dce81c..aba93df4b 100644 Binary files a/lib/rp2040/libpico.a and b/lib/rp2040/libpico.a differ diff --git a/lib/rp2350-riscv/liblwip-bt.a b/lib/rp2350-riscv/liblwip-bt.a index f972f7ec6..76bb1a34b 100644 Binary files a/lib/rp2350-riscv/liblwip-bt.a and b/lib/rp2350-riscv/liblwip-bt.a differ diff --git a/lib/rp2350-riscv/liblwip.a b/lib/rp2350-riscv/liblwip.a index 9b19a2957..d1469ef83 100644 Binary files a/lib/rp2350-riscv/liblwip.a and b/lib/rp2350-riscv/liblwip.a differ diff --git a/lib/rp2350-riscv/libpico.a b/lib/rp2350-riscv/libpico.a index 59fbefd87..c23d9dae9 100644 Binary files a/lib/rp2350-riscv/libpico.a and b/lib/rp2350-riscv/libpico.a differ diff --git a/lib/rp2350/liblwip-bt.a b/lib/rp2350/liblwip-bt.a index 2cefd22ad..cc8c23be8 100644 Binary files a/lib/rp2350/liblwip-bt.a and b/lib/rp2350/liblwip-bt.a differ diff --git a/lib/rp2350/liblwip.a b/lib/rp2350/liblwip.a index c84fdae7e..bcc6e02d8 100644 Binary files a/lib/rp2350/liblwip.a and b/lib/rp2350/liblwip.a differ diff --git a/lib/rp2350/libpico.a b/lib/rp2350/libpico.a index dfc5348b6..359edd552 100644 Binary files a/lib/rp2350/libpico.a and b/lib/rp2350/libpico.a differ diff --git a/libraries/lwIP_USB_NCM/.gitignore b/libraries/lwIP_USB_NCM/.gitignore new file mode 100644 index 000000000..cf5b839ea --- /dev/null +++ b/libraries/lwIP_USB_NCM/.gitignore @@ -0,0 +1,2 @@ +**/.pio/ +**/.vscode/ diff --git a/libraries/lwIP_USB_NCM/.gitkeep b/libraries/lwIP_USB_NCM/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/.gitignore b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/.gitignore new file mode 100644 index 000000000..9fccbae7c --- /dev/null +++ b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/.gitignore @@ -0,0 +1,7 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +*/README + diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/include/.gitkeep b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/include/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/lib/.gitkeep b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/lib/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/platformio.ini b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/platformio.ini new file mode 100644 index 000000000..950e85670 --- /dev/null +++ b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/platformio.ini @@ -0,0 +1,31 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = pico + +[env:pico] +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +board = rpipico +framework = arduino +board_build.core = earlephilhower +platform_packages = + framework-arduinopico@symlink://path/to/arduino-pico + +;build_flags = -DLWIP_DEBUG=1 -DDEBUG_RP2040_PORT=Serial1 + +[env:pico2] +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +board = rpipico2 +framework = arduino +board_build.core = earlephilhower +platform_packages = + framework-arduinopico@symlink://path/to/arduino-pico + diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/src/main.cpp b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/src/main.cpp new file mode 100644 index 000000000..d5976f8e5 --- /dev/null +++ b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/src/main.cpp @@ -0,0 +1,127 @@ +/* + This sketch establishes a TCP connection to a "quote of the day" service. + It sends a "hello" message, and then prints received data. +*/ + +#include +#include + +const char* host = "djxmmx.net"; +const uint16_t port = 17; + +NCMEthernetlwIP eth; +IPAddress my_static_ip_addr(192, 168, 137, 100); +IPAddress my_static_gateway_and_dns_addr(192, 168, 137, 1); + +#define USE_REAL_UART + +#if defined(USE_REAL_UART) +#define SER Serial1 +#else +#define SER Serial +#endif + +void setup() { + // enable Serial1 so it can be used by USE_REAL_UART or by DEBUG_RP2040_PORT + Serial1.end(); + Serial1.setTX(16); + Serial1.setRX(17); + Serial1.begin(115200); + + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + + Serial.begin(115200); + delay(3000); + SER.println(); + SER.println(); + SER.println("Starting NCM Ethernet port"); + + + //optional static config + // eth.config(my_static_ip_addr, my_static_gateway_and_dns_addr, IPAddress(255, 255, 255, 0), my_static_gateway_and_dns_addr); + + // Start the Ethernet port + // This starts DHCP in case config() was not called before + bool ok = eth.begin(); + delay(1000); + if (!ok) { + while (1) { + SER.println("Failed to initialize NCM Ethernet."); + delay(1000); + } + } else { + SER.println("NCM Ethernet started successfully."); + } + +} + +void loop() { + static unsigned long next_msg = 0; + static bool led_on = false; + if(millis() > next_msg) { + SER.println("."); + next_msg = millis() + 1000; + digitalWrite(LED_BUILTIN, led_on); + led_on ^=1; + } + + static bool connected = false; + if(!eth.connected()) { + connected = false; + return; + } else if(!connected){ + SER.println(""); + SER.println("Ethernet connected"); + SER.println("IP address: "); + SER.println(eth.localIP()); + connected = true; + } + + static bool wait = false; + + SER.printf("connecting to %s:%i\n", host, port); + + // Use WiFiClient class to create TCP connections + WiFiClient client; + if (!client.connect(host, port)) { + SER.println("connection failed"); + delay(5000); + return; + } + + // This will send a string to the server + SER.println("sending data to server"); + if (client.connected()) { + client.println("hello from RP2040"); + } + + // wait for data to be available + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + SER.println(">>> Client Timeout !"); + client.stop(); + delay(60000); + return; + } + } + + // Read all the lines of the reply from server and print them to Serial + SER.println("receiving from remote server"); + // not testing 'client.connected()' since we do not need to send data here + while (client.available()) { + char ch = static_cast(client.read()); + SER.print(ch); + } + + // Close the connection + SER.println(); + SER.println("closing connection"); + client.stop(); + + if (wait) { + delay(300000); // execute once every 5 minutes, don't flood remote service + } + wait = true; +} diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/test/.gitkeep b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet-platformio/test/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet/WiFiClient-NCMEthernet.ino b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet/WiFiClient-NCMEthernet.ino new file mode 100644 index 000000000..e741b26c0 --- /dev/null +++ b/libraries/lwIP_USB_NCM/examples/WiFiClient-NCMEthernet/WiFiClient-NCMEthernet.ino @@ -0,0 +1,126 @@ +/* + This sketch establishes a TCP connection to a "quote of the day" service. + It sends a "hello" message, and then prints received data. +*/ + +#include + +const char* host = "djxmmx.net"; +const uint16_t port = 17; + +NCMEthernetlwIP eth; +IPAddress my_static_ip_addr(192, 168, 137, 100); +IPAddress my_static_gateway_and_dns_addr(192, 168, 137, 1); + +#define USE_REAL_UART + +#if defined(USE_REAL_UART) +#define SER Serial1 +#else +#define SER Serial +#endif + +void setup() { + // enable Serial1 so it can be used by USE_REAL_UART or by DEBUG_RP2040_PORT + Serial1.end(); + Serial1.setTX(16); + Serial1.setRX(17); + Serial1.begin(115200); + + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + + Serial.begin(115200); + delay(3000); + SER.println(); + SER.println(); + SER.println("Starting NCM Ethernet port"); + + + //optional static config + // eth.config(my_static_ip_addr, my_static_gateway_and_dns_addr, IPAddress(255, 255, 255, 0), my_static_gateway_and_dns_addr); + + // Start the Ethernet port + // This starts DHCP in case config() was not called before + bool ok = eth.begin(); + delay(1000); + if (!ok) { + while (1) { + SER.println("Failed to initialize NCM Ethernet."); + delay(1000); + } + } else { + SER.println("NCM Ethernet started successfully."); + } + +} + +void loop() { + static unsigned long next_msg = 0; + static bool led_on = false; + if(millis() > next_msg) { + SER.println("."); + next_msg = millis() + 1000; + digitalWrite(LED_BUILTIN, led_on); + led_on ^=1; + } + + static bool connected = false; + if(!eth.connected()) { + connected = false; + return; + } else if(!connected){ + SER.println(""); + SER.println("Ethernet connected"); + SER.println("IP address: "); + SER.println(eth.localIP()); + connected = true; + } + + static bool wait = false; + + SER.printf("connecting to %s:%i\n", host, port); + + // Use WiFiClient class to create TCP connections + WiFiClient client; + if (!client.connect(host, port)) { + SER.println("connection failed"); + delay(5000); + return; + } + + // This will send a string to the server + SER.println("sending data to server"); + if (client.connected()) { + client.println("hello from RP2040"); + } + + // wait for data to be available + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + SER.println(">>> Client Timeout !"); + client.stop(); + delay(60000); + return; + } + } + + // Read all the lines of the reply from server and print them to Serial + SER.println("receiving from remote server"); + // not testing 'client.connected()' since we do not need to send data here + while (client.available()) { + char ch = static_cast(client.read()); + SER.print(ch); + } + + // Close the connection + SER.println(); + SER.println("closing connection"); + client.stop(); + + if (wait) { + delay(300000); // execute once every 5 minutes, don't flood remote service + } + wait = true; +} diff --git a/libraries/lwIP_USB_NCM/keywords.txt b/libraries/lwIP_USB_NCM/keywords.txt new file mode 100644 index 000000000..adf07ba4b --- /dev/null +++ b/libraries/lwIP_USB_NCM/keywords.txt @@ -0,0 +1,18 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Library (KEYWORD1) +####################################### + +USB_NCM_lwIP KEYWORD1 +NCMEthernet KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/libraries/lwIP_USB_NCM/library.properties b/libraries/lwIP_USB_NCM/library.properties new file mode 100644 index 000000000..d7415dda9 --- /dev/null +++ b/libraries/lwIP_USB_NCM/library.properties @@ -0,0 +1,10 @@ +name=lwIP_USB_NCM +version=1 +author=functionpointer +maintainer=functionpointer +sentence=Ethernet over USB driver based on tinyUSB implementation of CDC-NCM (Network Control Model) +paragraph=Ethernet over USB driver based on tinyUSB implementation of CDC-NCM (Network Control Model) +category=Communication +url=https://github.com/functionpointer/arduino-pico +architectures=rp2040 +dot_a_linkage=true diff --git a/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.cpp b/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.cpp new file mode 100644 index 000000000..5aea70153 --- /dev/null +++ b/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.cpp @@ -0,0 +1,18 @@ +#include "NCMEthernetlwIP.h" +#include +#include +#include +#include + +NCMEthernetlwIP::NCMEthernetlwIP() { +} + +bool NCMEthernetlwIP::begin(const uint8_t *macAddress, const uint16_t mtu) { + // super call + return LwipIntfDev::begin(macAddress, mtu); +} + +void NCMEthernetlwIP::packetReceivedIRQWorker(NCMEthernet *instance) { + NCMEthernetlwIP *d = static_cast(instance); + d->_irq(instance); +} diff --git a/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.h b/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.h new file mode 100644 index 000000000..b0e453517 --- /dev/null +++ b/libraries/lwIP_USB_NCM/src/NCMEthernetlwIP.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include +#include + +class NCMEthernetlwIP: public LwipIntfDev { +public: + NCMEthernetlwIP(); + + bool begin(const uint8_t* macAddress = nullptr, const uint16_t mtu = DEFAULT_MTU); + void packetReceivedIRQWorker(NCMEthernet *instance) override; + +}; diff --git a/libraries/lwIP_USB_NCM/src/ncm_device.c b/libraries/lwIP_USB_NCM/src/ncm_device.c new file mode 100644 index 000000000..6dc593cc9 --- /dev/null +++ b/libraries/lwIP_USB_NCM/src/ncm_device.c @@ -0,0 +1,946 @@ +// Taken from TinyUSB library to override the empty handlers and save memory +// when not actually using USB_NCM + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2024 Hardy Griech + * Copyright (c) 2020 Jacob Berg Potter + * Copyright (c) 2020 Peter Lawrence + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * 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 +#include +#include + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "../../../pico-sdk/lib/tinyusb/src/class/net/ncm.h" +#include "../../../pico-sdk/lib/tinyusb/src/class/net/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 + + // 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 + 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 + 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 + + // 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; + +/** + * This is the NTB parameter structure + * + * \attention + * We are lucky, that byte order is correct + */ +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 = 1, + .wNdbInPayloadRemainder = 0, + .wNdbInAlignment = TUD_NCM_ALIGNMENT, + .wReserved = 0, + .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, + .wNdbOutDivisor = 1, + .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); + + 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 = 1 /* Connected */, + .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); + + ncm_interface.notification_xmit_state = NOTIFICATION_DONE; + ncm_interface.notification_xmit_is_running = true; + } else { + TU_LOG_DRV(" NOTIFICATION_FINISHED\n"); + } +} // 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); + + 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; + } + } + TU_LOG_DRV("(EE) xmit_put_ntb_into_ready_list: ready list full\n");// this should not happen +} // 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) { + 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])); + ncm_interface.xmit_ready_ntb[XMIT_NTB_N - 1] = NULL; + + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +} // 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); + + 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); + + 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); + + 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); +} // 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 >= CFG_TUD_NCM_IN_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 + +/** + * 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) { + 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])); + ncm_interface.recv_ready_ntb[RECV_NTB_N - 1] = NULL; + + TU_LOG_DRV("recv_get_next_ready_ntb: %p\n", r); + return r; +} // 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); + + 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; + } + } + TU_LOG_DRV("(EE) recv_put_ntb_into_ready_list: ready list full\n");// this should not happen +} // 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); + 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) || 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; + } + 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 <= 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); + 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_OUT_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 + +//----------------------------------------------------------------------------- +// +// 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)); + + 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; + } +} // 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 + + // 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 + +/** + * 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("(EE) 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 + 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) { + 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); + + 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); + } + 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: { + // transfer NTB parameters to host. + tud_control_xfer(rhport, request, (void *) (uintptr_t) &ntb_parameters, sizeof(ntb_parameters)); + } 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 ) diff --git a/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.cpp b/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.cpp new file mode 100644 index 000000000..2106883cc --- /dev/null +++ b/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.cpp @@ -0,0 +1,187 @@ +/* + Copyright (c) 2024 functionpointer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "NCMEthernet.h" +#include +#include +#include "USB.h" + +#define USBD_NCM_EPSIZE 64 + +NCMEthernet::NCMEthernet(int8_t cs, arduino::SPIClass &spi, int8_t intrpin) { + (void) cs; + (void) spi; + (void) intrpin; +} + +bool NCMEthernet::begin(const uint8_t* mac_address, netif *net) { + (void) net; + memcpy(tud_network_mac_address, mac_address, 6); + + if (!critical_section_is_initialized(&this->_recv_critical_section)) { + critical_section_init(&this->_recv_critical_section); + critical_section_enter_blocking(&this->_recv_critical_section); + this->_recv_pkg.size = 0; + this->_recv_pkg.src = nullptr; + critical_section_exit(&this->_recv_critical_section); + } + + async_context_threadsafe_background_config_t config = async_context_threadsafe_background_default_config(); + if (!async_context_threadsafe_background_init(&this->_async_context, &config)) { + return false; + } + + this->_recv_irq_worker.user_data = this; + this->_recv_irq_worker.do_work = &NCMEthernet::_recv_irq_work; + async_context_add_when_pending_worker(&this->_async_context.core, &this->_recv_irq_worker); + + _ncm_ethernet_instance = this; + + USB.disconnect(); + + _epIn = USB.registerEndpointIn(); + _epOut = USB.registerEndpointOut(); + _epNotif = USB.registerEndpointIn(); + _strID = USB.registerString("Pico NCM"); + + // mac address we give to the PC + // must be different than our own + uint8_t len = 0; + for (unsigned i=0; i<6; i++) { + uint8_t mac_byte = tud_network_mac_address[i]; + if(i==5) { // invert last byte + mac_byte^=0xFF; + } + macAddrStr[len++] = "0123456789ABCDEF"[(mac_byte >> 4) & 0xf]; + macAddrStr[len++] = "0123456789ABCDEF"[(mac_byte >> 0) & 0xf]; + } + _strMac = USB.registerString(macAddrStr); + + _id = USB.registerInterface(2, _usb_interface_cb, (void *)this, TUD_CDC_NCM_DESC_LEN, 3, 0); + + USB.connect(); + + return true; +} + +void NCMEthernet::end() { + USB.disconnect(); + USB.unregisterInterface(_id); + USB.unregisterEndpointIn(_epIn); + USB.unregisterEndpointIn(_epNotif); + USB.unregisterEndpointOut(_epOut); + USB.connect(); +} + +// Need to define here so we don't have to include tusb.h in global header (causes problemw w/BT redefining things) +void NCMEthernet::usbInterfaceCB(int itf, uint8_t *dst, int len) { + uint8_t desc[TUD_CDC_NCM_DESC_LEN] = { + // Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. + TUD_CDC_NCM_DESCRIPTOR((uint8_t)itf, _strID, _strMac, _epNotif, USBD_NCM_EPSIZE, _epOut, _epIn, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU) + }; + memcpy(dst, desc, len); +} + +uint16_t NCMEthernet::readFrame(uint8_t* buffer, uint16_t bufsize) { + uint16_t data_len = this->readFrameSize(); + + if (data_len == 0) { + return 0; + } + + if (data_len > bufsize) { + // Packet is bigger than buffer - drop the packet + discardFrame(data_len); + return 0; + } + + return readFrameData(buffer, data_len); +} + +uint16_t NCMEthernet::readFrameSize() { + return this->_recv_pkg.size; +} + +uint16_t NCMEthernet::readFrameData(uint8_t* buffer, uint16_t framesize) { + critical_section_enter_blocking(&this->_recv_critical_section); + + size_t size = this->_recv_pkg.size; + memcpy(buffer, (const void*)this->_recv_pkg.src, size); + this->_recv_pkg.size = 0; + + critical_section_exit(&this->_recv_critical_section); + tud_network_recv_renew(); + return size; +} + +uint16_t NCMEthernet::sendFrame(const uint8_t* buf, uint16_t len) { + // this is basically linkoutput_fn + + for (;;) { + /* if TinyUSB isn't ready, we must signal back to lwip that there is nothing we can do */ + if (!tud_ready()) + return 0; + + /* if the network driver can accept another packet, we make it happen */ + if (tud_network_can_xmit(len)) { + tud_network_xmit((void*)const_cast(buf), len); + return len; + } + + /* transfer execution to TinyUSB in the hopes that it will finish transmitting the prior packet */ + tud_task(); + } +} + +extern "C" { + // data transfer between tinyUSB callbacks and NCMEthernet class + NCMEthernet *_ncm_ethernet_instance = nullptr; + + /* + * Interface to tinyUSB. + */ + uint8_t tud_network_mac_address[6] = {0}; + + void tud_network_init_cb(void) { + } + + bool tud_network_recv_cb(const uint8_t *src, uint16_t size) { + if(_ncm_ethernet_instance == nullptr || _ncm_ethernet_instance->_recv_pkg.size > 0) { + return false; + } + + critical_section_enter_blocking(&_ncm_ethernet_instance->_recv_critical_section); + _ncm_ethernet_instance->_recv_pkg.src = src; + _ncm_ethernet_instance->_recv_pkg.size = size; + critical_section_exit(&_ncm_ethernet_instance->_recv_critical_section); + + async_context_set_work_pending(&_ncm_ethernet_instance->_async_context.core, &_ncm_ethernet_instance->_recv_irq_worker); + + return true; + } + + uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) { + // this is called by tud_network_xmit, which is called by NCMEthernet::sendFrame, + // which is called by LwipIntfDev::linkoutput_s + // linkoutput_s gives us pbuf->payload and pbuf->len + + memcpy(dst, ref, arg); + return arg; + } +} \ No newline at end of file diff --git a/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.h b/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.h new file mode 100644 index 000000000..d8a8903d1 --- /dev/null +++ b/libraries/lwIP_USB_NCM/src/utility/NCMEthernet.h @@ -0,0 +1,116 @@ +/* + Copyright (c) 2024 functionpointer + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef NCM_ETHERNET_H +#define NCM_ETHERNET_H + +#include +#include +#include "pico/util/queue.h" +#include +#include +#include +#include + +extern "C" { + typedef struct _ncmethernet_packet_t { + const uint8_t *src; + uint16_t size; + } ncmethernet_packet_t; +} + +/** +* incoming packet flow: +* tinyUSB calls tud_network_recv_cb +* that stores the packet in _ncmethernet_pkg and sets _ncm_ethernet_recv_irq_worker pending +* _ncm_ethernet_recv_irq_worker, in different execution context, calls _recv_irq_work +* _recv_irq_work uses _ncm_ethernet_instance to call packetReceivedIRQWorker +* in NCMEthernetlwIP packetReceivedIRQWorker is overridden to call LwipIntfDev::_irq() +* LwipIntfDev::_irq() calls readFrameSize() and readFrameData() and _netif.input +* +* outgoing paket flow: +* LwipIntfDev calls sendFrame() +*/ + +class NCMEthernet { +public: + // constructor and methods as required by LwipIntfDev + + NCMEthernet(int8_t cs, arduino::SPIClass &spi, int8_t intrpin); + + bool begin(const uint8_t *address, netif *netif); + void end(); + + uint16_t sendFrame(const uint8_t *data, uint16_t datalen); + + uint16_t readFrameSize(); + + uint16_t readFrameData(uint8_t *buffer, uint16_t bufsize); + + uint16_t readFrame(uint8_t* buffer, uint16_t bufsize); + + void discardFrame(uint16_t ign) { + (void) ign; + } + + bool interruptIsPossible() { + return false; + } + + PinStatus interruptMode() { + return HIGH; + } + + constexpr bool needsSPI() const { + return false; + } + + void usbInterfaceCB(int itf, uint8_t *dst, int len); + + virtual void packetReceivedIRQWorker(NCMEthernet *instance) {}; + + async_context_threadsafe_background_t _async_context; + async_when_pending_worker_t _recv_irq_worker; + + critical_section_t _recv_critical_section; + ncmethernet_packet_t _recv_pkg; +protected: + netif *_netif; + uint8_t _id; + uint8_t _epIn; + uint8_t _epNotif; + uint8_t _epOut; + uint8_t _strID; + uint8_t _strMac; + char macAddrStr[6*2+2] = {0}; + + static void _usb_interface_cb(int itf, uint8_t *dst, int len, void *param) { + ((NCMEthernet *)param)->usbInterfaceCB(itf, dst, len); + } + static void _recv_irq_work(async_context_t *context, async_when_pending_worker_t *worker) { + NCMEthernet *d = static_cast(worker->user_data); + d->packetReceivedIRQWorker(d); + } +}; + +extern "C" { + extern NCMEthernet *_ncm_ethernet_instance; +} + +#endif // NCM_ETHERNET_H diff --git a/tools/libpico/CMakeLists.txt b/tools/libpico/CMakeLists.txt index eae887c8b..d158ad30c 100644 --- a/tools/libpico/CMakeLists.txt +++ b/tools/libpico/CMakeLists.txt @@ -275,6 +275,7 @@ foreach(tgt pico lwip lwip-bt) add_custom_command(TARGET ${tgt}-${cpu} POST_BUILD COMMAND arm-none-eabi-ar d lib${tgt}-${cpu}.a pico_malloc.c.o newlib_interface.c.o stdlib.c.o stdio.c.o stdio_uart.c.o new_delete.cpp.o COMMAND arm-none-eabi-ar d lib${tgt}-${cpu}.a btstack_flash_bank.c.o # Need to override with our own implementation + COMMAND arm-none-eabi-ar d lib${tgt}-${cpu}.a ncm_device.c.o # Need to override with our own implementation COMMAND cp lib${tgt}-${cpu}.a ../../../lib/${cpu}/lib${tgt}.a ) endforeach()