diff --git a/examples/rp2040/pico-rndis-dashboard/CMakeLists.txt b/examples/rp2040/pico-rndis-dashboard/CMakeLists.txt index 45ccf348..9de6b2e5 100644 --- a/examples/rp2040/pico-rndis-dashboard/CMakeLists.txt +++ b/examples/rp2040/pico-rndis-dashboard/CMakeLists.txt @@ -6,10 +6,11 @@ pico_sdk_init() add_executable(firmware main.c + msc_disk.c + usb_descriptors.c ../../../mongoose.c ../../device-dashboard/net.c ../../device-dashboard/packed_fs.c - pico-sdk//lib/tinyusb/examples/device/net_lwip_webserver/src/usb_descriptors.c pico-sdk/lib/tinyusb/lib/networking/rndis_reports.c) target_include_directories(firmware PUBLIC @@ -28,3 +29,6 @@ add_definitions(-DMG_ENABLE_TCPIP=1) add_definitions(-DMG_ENABLE_PACKED_FS=1) add_definitions(-DMG_ENABLE_FILE=0) add_definitions(-DDISABLE_ROUTING=1) + +# Extra build flags (enable if needed) +add_definitions(-DDUAL_CONFIG=0) diff --git a/examples/rp2040/pico-rndis-dashboard/Makefile b/examples/rp2040/pico-rndis-dashboard/Makefile index 65afdd92..584371e2 100644 --- a/examples/rp2040/pico-rndis-dashboard/Makefile +++ b/examples/rp2040/pico-rndis-dashboard/Makefile @@ -1,10 +1,10 @@ SDK_VERSION ?= 1.4.0 SDK_REPO ?= https://github.com/raspberrypi/pico-sdk -example: +all example: true -all build: pico-sdk +build: pico-sdk test -d build || mkdir build cd build && cmake .. && make diff --git a/examples/rp2040/pico-rndis-dashboard/README.md b/examples/rp2040/pico-rndis-dashboard/README.md index fbd493b7..5ed95940 100644 --- a/examples/rp2040/pico-rndis-dashboard/README.md +++ b/examples/rp2040/pico-rndis-dashboard/README.md @@ -15,7 +15,11 @@ make build This will generate a firmware file: `build/firmware.uf2`. Reboot your Pico board in bootloader mode, and copy it to the RPI disk. -The device will reboot, register as a USB RNDIS device, and add a network to your computer. -Run a browser, and open 192.168.3.1, you should see a device dashboard +The device will reboot, register as a USB RNDIS device, and add a network and a removable read-only disk to your computer. +Open the new drive, named `Mongoose`, and double click on its `INDEX.HTM` file; your browser should open and you should see a device dashboard. + +Alternatively, you may just run a browser, and open 192.168.3.1, Note: USB stdio in the Pico-SDK is done in background with TinyUSB and an interrupt to hide it from the user and periodically call tusb_task(). When we use TinyUSB, that code is removed from the compilation list; so this example uses UART stdio (UART 0) to keep things simple and focused on the RNDIS example. If you want to see the console output, connect an USB-to-UART or a low-voltage-TTL-to-RS-232 adapter to GPIO0 + +Note for Mac users: For this example to run on Mac, please set `DUAL_CONFIG=1` in `CMakeLists.txt` before building; this enables CDC-ECM. Please notice that Windows may not recognize the mass-storage device in that case. diff --git a/examples/rp2040/pico-rndis-dashboard/msc_disk.c b/examples/rp2040/pico-rndis-dashboard/msc_disk.c new file mode 100644 index 00000000..b9b4db83 --- /dev/null +++ b/examples/rp2040/pico-rndis-dashboard/msc_disk.c @@ -0,0 +1,252 @@ +/* + * 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. + * + */ +// Modified by Cesanta Software + +#include "tusb.h" + +// Some MCU doesn't have enough 8KB SRAM to store the whole disk + +enum +{ + DISK_BLOCK_NUM = 16, // 8KB is the smallest size that Windows allows to mount + DISK_BLOCK_SIZE = 512 +}; + + +#define INDEX_CONTENTS \ +"
" + +// readonly +const uint8_t msc_disk0[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = +{ + //------------- Block0: Boot Sector -------------// + // byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = DISK_BLOCK_NUM; + // sector_per_cluster = 1; reserved_sectors = 1; + // fat_num = 1; fat12_root_entry_num = 16; + // sector_per_fat = 1; sector_per_track = 1; head_num = 1; hidden_sectors = 0; + // drive_number = 0x80; media_type = 0xf8; extended_boot_signature = 0x29; + // filesystem_type = "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB 0 "; + // FAT magic code at offset 510-511 + { + 0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, 0x02, 0x01, 0x01, 0x00, + 0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T' , 'i' , 'n' , 'y' , 'U' , + 'S' , 'B' , ' ' , 'C' , ' ' , ' ' , 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00, + + // Zero up to 2 last bytes of FAT magic code + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA + }, + + //------------- Block1: FAT12 Table -------------// + { + 0xF8, 0xFF, 0xFF, 0xFF, 0x0F // // first 2 entries must be F8FF, third entry is cluster end of readme file + }, + + //------------- Block2: Root Directory -------------// + { + // first entry is volume label + 'M' , 'o' , 'n' , 'g' , 'o' , 'o' , 's' , 'e' , ' ' , ' ' , ' ' , 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, 0x65, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // second entry is our file + 'I' , 'N' , 'D' , 'E' , 'X' , ' ' , ' ' , ' ' , 'H' , 'T' , 'M' , 0x20, 0x00, 0xC6, 0x52, 0x6D, + 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, 0x65, 0x43, 0x02, 0x00, + sizeof(INDEX_CONTENTS)-1, 0x00, 0x00, 0x00 // readme's files size (4 Bytes) + }, + + //------------- Block3: file Content -------------// + INDEX_CONTENTS +}; + + +// Invoked to determine max LUN +uint8_t tud_msc_get_maxlun_cb(void) +{ + return 1; // single LUN +} + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) +{ + (void) lun; // use same ID for both LUNs + + const char vid[] = "TinyUSB"; + const char pid[] = "Mass Storage"; + const char rev[] = "1.0"; + + memcpy(vendor_id , vid, strlen(vid)); + memcpy(product_id , pid, strlen(pid)); + memcpy(product_rev, rev, strlen(rev)); +} + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun) +{ + (void) lun; + return true; // Flash/RAM disks are always ready +} + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) +{ + (void) lun; + + *block_count = DISK_BLOCK_NUM; + *block_size = DISK_BLOCK_SIZE; +} + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) +{ + (void) lun; + (void) power_condition; + + if ( load_eject ) + { + if (start) + { + // load disk storage + }else + { + // unload disk storage + } + } + + return true; +} + +// Callback invoked when received READ10 command. +// Copy disk's data to buffer (up to bufsize) and return number of copied bytes. +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) +{ + // out of ramdisk + if ( lba >= DISK_BLOCK_NUM ) return -1; + + (void) lun; + uint8_t const* addr = msc_disk0[lba] + offset; + memcpy(buffer, addr, bufsize); + + return (int32_t) bufsize; +} + +bool tud_msc_is_writable_cb (uint8_t lun) +{ + (void) lun; + +// readonly + return false; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and return number of written bytes +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) +{ + // out of ramdisk + if ( lba >= DISK_BLOCK_NUM ) return -1; + +#if 0 + uint8_t* addr = (lun ? msc_disk1[lba] : msc_disk0[lba]) + offset; + memcpy(addr, buffer, bufsize); +#else // readonly + (void) lun; (void) lba; (void) offset; (void) buffer; +#endif + + return (int32_t) bufsize; +} + +// Callback invoked when received an SCSI command not in built-in list below +// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE +// - READ10 and WRITE10 has their own callbacks +int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) +{ + // read10 & write10 has their own callback and MUST not be handled here + + void const* response = NULL; + int32_t resplen = 0; + + // most scsi handled is input + bool in_xfer = true; + + switch (scsi_cmd[0]) + { + default: + // Set Sense = Invalid Command Operation + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // negative means error -> tinyusb could stall and/or response with failed status + resplen = -1; + break; + } + + // return resplen must not larger than bufsize + if ( resplen > bufsize ) resplen = bufsize; + + if ( response && (resplen > 0) ) + { + if(in_xfer) + { + memcpy(buffer, response, (size_t) resplen); + }else + { + // SCSI output + } + } + + return resplen; +} diff --git a/examples/rp2040/pico-rndis-dashboard/tusb_config.h b/examples/rp2040/pico-rndis-dashboard/tusb_config.h index 5fc52c53..8393b2e8 100644 --- a/examples/rp2040/pico-rndis-dashboard/tusb_config.h +++ b/examples/rp2040/pico-rndis-dashboard/tusb_config.h @@ -1,10 +1,11 @@ #pragma once +// Windows only supports RNDIS, and apparently doesn't like composite devices with two configurations. +// Mac only supports CDC-ECM, which we present as the second config in a dual configuration device +// Linux supports either RNDIS, CDC-ECM or CDC-NCM #define CFG_TUD_ECM_RNDIS 1 -#if 0 #define CFG_TUD_MSC 1 // This enables mass storage -#define CFG_TUD_MSC_EP_BUFSIZE 512 // Also Add msc_disk.c and descritprs -#endif +#define CFG_TUD_MSC_EP_BUFSIZE 512 // Also Add msc_disk.c and descritors #define CFG_TUD_NCM 0 #define BOARD_DEVICE_RHPORT_NUM 0 #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED diff --git a/examples/rp2040/pico-rndis-dashboard/usb_descriptors.c b/examples/rp2040/pico-rndis-dashboard/usb_descriptors.c index bee51790..87176f88 100644 --- a/examples/rp2040/pico-rndis-dashboard/usb_descriptors.c +++ b/examples/rp2040/pico-rndis-dashboard/usb_descriptors.c @@ -22,6 +22,7 @@ * THE SOFTWARE. * */ +// Modified by Cesanta Software #include "tusb.h" @@ -50,6 +51,7 @@ enum { ITF_NUM_CDC = 0, ITF_NUM_CDC_DATA, + ITF_NUM_MSC, ITF_NUM_TOTAL }; @@ -57,7 +59,9 @@ enum { #if CFG_TUD_ECM_RNDIS CONFIG_ID_RNDIS = 0, +#if DUAL_CONFIG CONFIG_ID_ECM = 1, +#endif #else CONFIG_ID_NCM = 0, #endif @@ -101,16 +105,18 @@ uint8_t const * tud_descriptor_device_cb(void) //--------------------------------------------------------------------+ // 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) -#define NCM_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_NCM_DESC_LEN) +#define MAIN_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN + TUD_RNDIS_DESC_LEN) +#define ALT_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN + TUD_CDC_ECM_DESC_LEN) +#define NCM_CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN + TUD_CDC_NCM_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 ... + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... #define EPNUM_NET_NOTIF 0x81 #define EPNUM_NET_OUT 0x02 #define EPNUM_NET_IN 0x82 + #define EPNUM_MSC_OUT 0x05 + #define EPNUM_MSC_IN 0x85 #elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT @@ -118,13 +124,20 @@ uint8_t const * tud_descriptor_device_cb(void) #define EPNUM_NET_NOTIF 0x81 #define EPNUM_NET_OUT 0x02 #define EPNUM_NET_IN 0x83 + #define EPNUM_MSC_OUT 0x04 + #define EPNUM_MSC_IN 0x85 #else #define EPNUM_NET_NOTIF 0x81 #define EPNUM_NET_OUT 0x02 #define EPNUM_NET_IN 0x82 + #define EPNUM_MSC_OUT 0x03 + #define EPNUM_MSC_IN 0x83 #endif + // Interface number, string index, EP Out & EP In address, EP size +# define MSCDESC TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64) + #if CFG_TUD_ECM_RNDIS static uint8_t const rndis_configuration[] = @@ -134,8 +147,11 @@ static uint8_t const rndis_configuration[] = // 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), + + MSCDESC }; +#if DUAL_CONFIG static uint8_t const ecm_configuration[] = { // Config number (index+1), interface count, string index, total length, attribute, power in mA @@ -143,7 +159,10 @@ static uint8_t const ecm_configuration[] = // 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), + + MSCDESC }; +#endif #else @@ -154,6 +173,8 @@ static uint8_t const ncm_configuration[] = // 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(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), + + MSCDESC }; #endif @@ -165,8 +186,10 @@ static uint8_t const ncm_configuration[] = static uint8_t const * const configuration_arr[2] = { #if CFG_TUD_ECM_RNDIS - [CONFIG_ID_RNDIS] = rndis_configuration, - [CONFIG_ID_ECM ] = ecm_configuration + [CONFIG_ID_RNDIS] = rndis_configuration +#if DUAL_CONFIG + , [CONFIG_ID_ECM ] = ecm_configuration +#endif #else [CONFIG_ID_NCM ] = ncm_configuration #endif