diff --git a/.gitmodules b/.gitmodules index 2157efa..7ea9dc8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "pico-extras"] path = pico-extras url = git@github.com:raspberrypi/pico-extras.git +[submodule "pico-playground"] + path = pico-playground + url = git@github.com:raspberrypi/pico-playground.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d836938..1c7c4d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,11 @@ cmake_minimum_required(VERSION 3.12) include(pico_sdk_import.cmake) +include(pico_extras_import.cmake) project(pico-stuff) pico_sdk_init() -add_subdirectory(bmp180) \ No newline at end of file +add_subdirectory(bmp180) +add_subdirectory(cdc_ecm_device) \ No newline at end of file diff --git a/iperf_server/CMakeLists.txt b/iperf_server/CMakeLists.txt new file mode 100644 index 0000000..1c1e8f9 --- /dev/null +++ b/iperf_server/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.12) + +project(pico-cdc_ecm_device) + +add_executable(cdc_ecm_device + usb_descriptors.c + main.c +) + +target_link_libraries(cdc_ecm_device + pico_stdlib + pico_stdio + tinyusb_host + tinyusb_board + tinyusb_net + lwip +) + +target_include_directories(cdc_ecm_device PRIVATE .) + +pico_enable_stdio_usb(cdc_ecm_device 1) +pico_enable_stdio_uart(cdc_ecm_device 0) + +pico_add_extra_outputs(cdc_ecm_device) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) \ No newline at end of file diff --git a/iperf_server/lwipopts.h b/iperf_server/lwipopts.h new file mode 100644 index 0000000..5a8096f --- /dev/null +++ b/iperf_server/lwipopts.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Simon Goldschmidt + * + */ +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */ +#define NO_SYS 1 +#define MEM_ALIGNMENT 4 +#define LWIP_RAW 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_DHCP 0 +#define LWIP_ICMP 1 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define ETH_PAD_SIZE 0 +#define LWIP_IP_ACCEPT_UDP_PORT(p) ((p) == PP_NTOHS(67)) + +#define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/) +#define TCP_SND_BUF (2 * TCP_MSS) + +#define ETHARP_SUPPORT_STATIC_ENTRIES 1 + +#define LWIP_HTTPD_CGI 0 +#define LWIP_HTTPD_SSI 0 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 + +#define LWIP_SINGLE_NETIF 1 + +#endif /* __LWIPOPTS_H__ */ diff --git a/iperf_server/main.c b/iperf_server/main.c new file mode 100644 index 0000000..acda694 --- /dev/null +++ b/iperf_server/main.c @@ -0,0 +1,246 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * + * influenced by lrndis https://github.com/fetisov/lrndis + * + * 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 appears as either a RNDIS or CDC-ECM USB virtual network adapter; the OS picks its preference + +RNDIS should be valid on Linux and Windows hosts, and CDC-ECM should be valid on Linux and macOS hosts + +The MCU appears to the host as IP address 192.168.7.1, and provides a DHCP server, DNS server, and web server. +*/ +/* +Some smartphones *may* work with this implementation as well, but likely have limited (broken) drivers, +and likely their manufacturer has not tested such functionality. Some code workarounds could be tried: + +The smartphone may only have an ECM driver, but refuse to automatically pick ECM (unlike the OSes above); +try modifying ./examples/devices/net_lwip_webserver/usb_descriptors.c so that CONFIG_ID_ECM is default. + +The smartphone may be artificially picky about which Ethernet MAC address to recognize; if this happens, +try changing the first byte of tud_network_mac_address[] below from 0x02 to 0x00 (clearing bit 1). +*/ + +#include "bsp/board.h" +#include "tusb.h" + +#include "dhserver.h" +#include "dnserver.h" +#include "lwip/init.h" +#include "lwip/timeouts.h" + +#include "lwiperf.h" +#include "httpd.h" + +/* lwip context */ +static struct netif netif_data; + +/* shared between tud_network_recv_cb() and service_traffic() */ +static struct pbuf *received_frame; + +/* this is used by this code, ./class/net/net_driver.c, and usb_descriptors.c */ +/* ideally speaking, this should be generated from the hardware's unique ID (if available) */ +/* it is suggested that the first byte is 0x02 to indicate a link-local address */ +const uint8_t tud_network_mac_address[6] = {0x02,0x02,0x84,0x6A,0x96,0x00}; + +/* network parameters of this MCU */ +static const ip_addr_t ipaddr = IPADDR4_INIT_BYTES(192, 168, 7, 1); +static const ip_addr_t netmask = IPADDR4_INIT_BYTES(255, 255, 255, 0); +static const ip_addr_t gateway = IPADDR4_INIT_BYTES(0, 0, 0, 0); + +/* database IP addresses that can be offered to the host; this must be in RAM to store assigned MAC addresses */ +static dhcp_entry_t entries[] = +{ + /* mac ip address lease time */ + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 2), 24 * 60 * 60 }, + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 3), 24 * 60 * 60 }, + { {0}, IPADDR4_INIT_BYTES(192, 168, 7, 4), 24 * 60 * 60 }, +}; + +static const dhcp_config_t dhcp_config = +{ + .router = IPADDR4_INIT_BYTES(0, 0, 0, 0), /* router address (if any) */ + .port = 67, /* listen port */ + .dns = IPADDR4_INIT_BYTES(192, 168, 7, 1), /* dns server (if any) */ + "usb", /* dns suffix */ + TU_ARRAY_SIZE(entries), /* num entry */ + entries /* entries */ +}; +static err_t linkoutput_fn(struct netif *netif, struct pbuf *p) +{ + (void)netif; + + for (;;) + { + /* if TinyUSB isn't ready, we must signal back to lwip that there is nothing we can do */ + if (!tud_ready()) + return ERR_USE; + + /* if the network driver can accept another packet, we make it happen */ + if (tud_network_can_xmit()) + { + tud_network_xmit(p, 0 /* unused for this example */); + return ERR_OK; + } + + /* transfer execution to TinyUSB in the hopes that it will finish transmitting the prior packet */ + tud_task(); + } +} + +static err_t output_fn(struct netif *netif, struct pbuf *p, const ip_addr_t *addr) +{ + return etharp_output(netif, p, addr); +} + +static err_t netif_init_cb(struct netif *netif) +{ + LWIP_ASSERT("netif != NULL", (netif != NULL)); + netif->mtu = CFG_TUD_NET_MTU; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; + netif->state = NULL; + netif->name[0] = 'E'; + netif->name[1] = 'X'; + netif->linkoutput = linkoutput_fn; + netif->output = output_fn; + return ERR_OK; +} + +static void init_lwip(void) +{ + struct netif *netif = &netif_data; + + lwip_init(); + + /* the lwip virtual MAC address must be different from the host's; to ensure this, we toggle the LSbit */ + netif->hwaddr_len = sizeof(tud_network_mac_address); + memcpy(netif->hwaddr, tud_network_mac_address, sizeof(tud_network_mac_address)); + netif->hwaddr[5] ^= 0x01; + + netif = netif_add(netif, &ipaddr, &netmask, &gateway, NULL, netif_init_cb, ip_input); + netif_set_default(netif); +} + +/* handle any DNS requests from dns-server */ +bool dns_query_proc(const char *name, ip_addr_t *addr) +{ + if (0 == strcmp(name, "tiny.usb")) + { + *addr = ipaddr; + return true; + } + return false; +} + +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) +{ + /* this shouldn't happen, but if we get another packet before + parsing the previous, we must signal our inability to accept it */ + if (received_frame) return false; + + if (size) + { + struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); + + if (p) + { + /* pbuf_alloc() has already initialized struct; all we need to do is copy the data */ + memcpy(p->payload, src, size); + + /* store away the pointer for service_traffic() to later handle */ + received_frame = p; + } + } + + return true; +} + +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) +{ + struct pbuf *p = (struct pbuf *)ref; + struct pbuf *q; + uint16_t len = 0; + + (void)arg; /* unused for this example */ + + /* traverse the "pbuf chain"; see ./lwip/src/core/pbuf.c for more info */ + for(q = p; q != NULL; q = q->next) + { + memcpy(dst, (char *)q->payload, q->len); + dst += q->len; + len += q->len; + if (q->len == q->tot_len) break; + } + + return len; +} + +static void service_traffic(void) +{ + /* handle any packet received by tud_network_recv_cb() */ + if (received_frame) + { + ethernet_input(received_frame, &netif_data); + pbuf_free(received_frame); + received_frame = NULL; + tud_network_recv_renew(); + } + + sys_check_timeouts(); +} + +void tud_network_init_cb(void) +{ + /* if the network is re-initializing and we have a leftover packet, we must do a cleanup */ + if (received_frame) + { + pbuf_free(received_frame); + received_frame = NULL; + } +} + +int main(void) +{ + /* initialize TinyUSB */ + board_init(); + tusb_init(); + + /* initialize lwip, dhcp-server, dns-server, and http */ + init_lwip(); + while (!netif_is_up(&netif_data)); + while (dhserv_init(&dhcp_config) != ERR_OK); + while (dnserv_init(&ipaddr, 53, dns_query_proc) != ERR_OK); + httpd_init(); + + lwiperf_start_tcp_server_default(NULL, NULL); + + while (1) + { + tud_task(); + service_traffic(); + } + + return 0; +} \ No newline at end of file diff --git a/iperf_server/tusb_config.h b/iperf_server/tusb_config.h new file mode 100644 index 0000000..262d4eb --- /dev/null +++ b/iperf_server/tusb_config.h @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 +#define CFG_TUD_NET 1 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/iperf_server/usb_descriptors.c b/iperf_server/usb_descriptors.c new file mode 100644 index 0000000..f1c0b47 --- /dev/null +++ b/iperf_server/usb_descriptors.c @@ -0,0 +1,225 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * 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. + * + */ + +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] NET | VENDOR | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) | _PID_MAP(NET, 5) ) + +// String Descriptor Index +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_INTERFACE, + STRID_MAC +}; + +enum +{ + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, + ITF_NUM_TOTAL +}; + +enum +{ + CONFIG_ID_RNDIS = 0, + CONFIG_ID_ECM = 1, + CONFIG_ID_COUNT +}; + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // Use Interface Association Descriptor (IAD) device class + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0101, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = CONFIG_ID_COUNT // multiple configurations +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ +#define MAIN_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_RNDIS_DESC_LEN) +#define ALT_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_ECM_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In etc ... + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x82 + +#elif CFG_TUSB_MCU == OPT_MCU_SAMG + // SAMG doesn't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x83 + +#else + #define EPNUM_NET_NOTIF 0x81 + #define EPNUM_NET_OUT 0x02 + #define EPNUM_NET_IN 0x82 +#endif + +static uint8_t const rndis_configuration[] = +{ + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_RNDIS+1, ITF_NUM_TOTAL, 0, MAIN_CONFIG_TOTAL_LEN, 0, 100), + + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_RNDIS_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, EPNUM_NET_NOTIF, 8, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE), +}; + +static uint8_t const ecm_configuration[] = +{ + // Config number (index+1), interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(CONFIG_ID_ECM+1, ITF_NUM_TOTAL, 0, ALT_CONFIG_TOTAL_LEN, 0, 100), + + // 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_ECM_DESCRIPTOR(ITF_NUM_CDC, STRID_INTERFACE, STRID_MAC, EPNUM_NET_NOTIF, 64, EPNUM_NET_OUT, EPNUM_NET_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU), +}; + +// Configuration array: RNDIS and CDC-ECM +// - Windows only works with RNDIS +// - MacOS only works with CDC-ECM +// - Linux will work on both +// Note index is Num-1x +static uint8_t const * const configuration_arr[2] = +{ + [CONFIG_ID_RNDIS] = rndis_configuration, + [CONFIG_ID_ECM ] = ecm_configuration +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + return (index < CONFIG_ID_COUNT) ? configuration_arr[index] : NULL; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +static char const* string_desc_arr [] = +{ + [STRID_LANGID] = (const char[]) { 0x09, 0x04 }, // supported language is English (0x0409) + [STRID_MANUFACTURER] = "TinyUSB", // Manufacturer + [STRID_PRODUCT] = "TinyUSB Device", // Product + [STRID_SERIAL] = "123456", // Serial + [STRID_INTERFACE] = "TinyUSB Network Interface" // Interface Description + + // STRID_MAC index is handled separately +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + unsigned int chr_count = 0; + + if (STRID_LANGID == index) + { + memcpy(&_desc_str[1], string_desc_arr[STRID_LANGID], 2); + chr_count = 1; + } + else if (STRID_MAC == index) + { + // Convert MAC address into UTF-16 + + for (unsigned i=0; i> 4) & 0xf]; + _desc_str[1+chr_count++] = "0123456789ABCDEF"[(tud_network_mac_address[i] >> 0) & 0xf]; + } + } + else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > (TU_ARRAY_SIZE(_desc_str) - 1)) chr_count = TU_ARRAY_SIZE(_desc_str) - 1; + + // Convert ASCII string into UTF-16 + for (unsigned int i=0; i/external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + PICO_EXTRAS + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT PICO_EXTRAS) + message("Downloading PICO EXTRAS") + FetchContent_Populate(PICO_EXTRAS) + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + else() + message(FATAL_ERROR + "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." + ) + endif() + endif () +endif () + +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") +set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") +set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + +get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") +endif () + +set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + +add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) \ No newline at end of file