From 3367b106bb802d33ab2d594e6c2ca75ebc53d8f6 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Fri, 30 Sep 2022 06:59:08 +0200 Subject: [PATCH] tests: add tinyUSB CDC and MSC device test application --- Makefile.features | 2 +- pkg/tinyusb/Kconfig | 2 +- pkg/tinyusb/Makefile.dep | 4 +- pkg/tinyusb/contrib/include/tinyusb.h | 4 +- tests/pkg_tinyusb_cdc_msc/Makefile | 13 + tests/pkg_tinyusb_cdc_msc/README.md | 43 +++ tests/pkg_tinyusb_cdc_msc/app.config.test | 5 + tests/pkg_tinyusb_cdc_msc/main.c | 195 +++++++++++ tests/pkg_tinyusb_cdc_msc/msc_disk.c | 360 ++++++++++++++++++++ tests/pkg_tinyusb_cdc_msc/tusb_config.h | 41 +++ tests/pkg_tinyusb_cdc_msc/usb_descriptors.c | 340 ++++++++++++++++++ 11 files changed, 1003 insertions(+), 6 deletions(-) create mode 100644 tests/pkg_tinyusb_cdc_msc/Makefile create mode 100644 tests/pkg_tinyusb_cdc_msc/README.md create mode 100644 tests/pkg_tinyusb_cdc_msc/app.config.test create mode 100644 tests/pkg_tinyusb_cdc_msc/main.c create mode 100644 tests/pkg_tinyusb_cdc_msc/msc_disk.c create mode 100644 tests/pkg_tinyusb_cdc_msc/tusb_config.h create mode 100644 tests/pkg_tinyusb_cdc_msc/usb_descriptors.c diff --git a/Makefile.features b/Makefile.features index 086d677bd8..87ce629e94 100644 --- a/Makefile.features +++ b/Makefile.features @@ -29,4 +29,4 @@ FEATURES_CONFLICT += periph_gpio_irq:periph_gpio_ll_irq FEATURES_CONFLICT_MSG += "Only one GPIO IRQ implementation can be used" FEATURES_CONFLICT += periph_usbdev:tinyusb -FEATURES_CONFLICT_MSG += "Package tinyUSB is not yet compatible with periph/usdev" +FEATURES_CONFLICT_MSG += "Package tinyUSB is not yet compatible with periph/usbdev" diff --git a/pkg/tinyusb/Kconfig b/pkg/tinyusb/Kconfig index a7ab07e9e9..c40fb3b3bb 100644 --- a/pkg/tinyusb/Kconfig +++ b/pkg/tinyusb/Kconfig @@ -1,4 +1,4 @@ -# Copyright (c) 2021 HAW Hamburg +# Copyright (c) 2022 Gunar Schorcht # # This file is subject to the terms and conditions of the GNU Lesser # General Public License v2.1. See the file LICENSE in the top level diff --git a/pkg/tinyusb/Makefile.dep b/pkg/tinyusb/Makefile.dep index e60b33b55d..705af2001b 100644 --- a/pkg/tinyusb/Makefile.dep +++ b/pkg/tinyusb/Makefile.dep @@ -1,10 +1,10 @@ # Package tinyUSB has its own USB device driver. Therefore, it cannot be used # together with periph/usbdev for now. ifneq (,$(filter periph_usbdev,$(USEMODULE))) - $(error "Package tinyUSB is not yet compatible with periph/usdev") + $(error "Package tinyUSB is not yet compatible with periph/usbdev") endif -# tinyUSB muteces use priority inheritance +# tinyUSB mutexes use priority inheritance # USEMODULE += core_mutex_priority_inheritance # tinyUSB modules always needed diff --git a/pkg/tinyusb/contrib/include/tinyusb.h b/pkg/tinyusb/contrib/include/tinyusb.h index 8667429a9b..e6a62e44fa 100644 --- a/pkg/tinyusb/contrib/include/tinyusb.h +++ b/pkg/tinyusb/contrib/include/tinyusb.h @@ -32,12 +32,12 @@ #endif #ifndef TINYUSB_TUD_RHPORT -/** tinyUSB RHPort number used for device, default value is 0 */ +/** tinyUSB RHPort number used for the device stack, default value is 0 */ #define TINYUSB_TUD_RHPORT 0 #endif #ifndef TINYUSB_TUH_RHPORT -/** tinyUSB RHPort number used for host, defaults value is 0 */ +/** tinyUSB RHPort number used for the host stack, defaults value is 0 */ #define TINYUSB_TUH_RHPORT 0 #endif diff --git a/tests/pkg_tinyusb_cdc_msc/Makefile b/tests/pkg_tinyusb_cdc_msc/Makefile new file mode 100644 index 0000000000..846cd4130d --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/Makefile @@ -0,0 +1,13 @@ +include ../Makefile.tests_common + +USB_VID ?= $(USB_VID_TESTING) +USB_PID ?= $(USB_PID_TESTING) + +USEPKG += tinyusb +USEMODULE += tinyusb_class_cdc +USEMODULE += tinyusb_class_msc +USEMODULE += tinyusb_device + +include $(RIOTBASE)/Makefile.include + +INCLUDES += -I$(APPDIR) diff --git a/tests/pkg_tinyusb_cdc_msc/README.md b/tests/pkg_tinyusb_cdc_msc/README.md new file mode 100644 index 0000000000..a3b596fddc --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/README.md @@ -0,0 +1,43 @@ +# TinyUSB package test application + +## Overview + +This application uses the tinyUSB device stack to emulate a mass storage +device (MSC) with a communication interface (CDC). + +**Please note:** RIOT doesn't own any USB vendor and product ID. The test +application therefore uses `USB_VID_TESTING=0x1209` as manufacturer ID and +`USB_PID_TESTING=0x7d01` as product ID. Do not use these IDs outside of +test environments! They MUST NOT be used on any device that is redistributed, +sold or manufactured, as they are not unique! + +To compile this application with your own vendor and product ID, set the +variables `USB_VID` and `USB_PID` in the makefile or at the command line, +for example + +``` +USB_VID=1234 USB_PID=5678 BOARD=... make -C tests/pkg_tinyusb_cdc_msc +``` + +## Usage + +Once the application is flashed, the device should be mounted when it is +connected to a host. That is, + +- the mass storage interface is mounted as volume `TinyUSB MSC` in the + operating system and +- the communication interface is mounted as a serial device, for example + as `/dev/ttyACM0` on Linux. + +It should then be possible + +1. to read from and write to the mass storage with the usual file operations + of the operating system +2. to connect to the serial interface of the device with a terminal program, e.g. + ``` + python -m serial.tools.miniterm /dev/ttyACM0 115200 + ``` + and get the entered characters sent back. + +The test application uses LED0, if present, to indicate the status of the +device by blinking at different frequencies. diff --git a/tests/pkg_tinyusb_cdc_msc/app.config.test b/tests/pkg_tinyusb_cdc_msc/app.config.test new file mode 100644 index 0000000000..efd8b538ab --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/app.config.test @@ -0,0 +1,5 @@ +CONFIG_PACKAGE_TINYUSB=y +CONFIG_MODULE_TINYUSB_CLASS_CDC=y +CONFIG_MODULE_TINYUSB_CLASS_MSC=y +CONFIG_MODULE_TINYUSB_COMMON=y +CONFIG_MODULE_TINYUSB_DEVICE=y diff --git a/tests/pkg_tinyusb_cdc_msc/main.c b/tests/pkg_tinyusb_cdc_msc/main.c new file mode 100644 index 0000000000..5b59c6124f --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/main.c @@ -0,0 +1,195 @@ +/* + * 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 +#include +#include + +#include "board.h" +#include "log.h" +#include "ztimer.h" + +#include "tusb.h" +#include "tinyusb.h" + +/* + * -------------------------------------------------------------------- + * MACRO CONSTANT TYPEDEF PROTYPES + * -------------------------------------------------------------------- + */ + +/* + * Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +/* + * -------------------------------------------------------------------- + * BLINKING TASK + * -------------------------------------------------------------------- + */ +char led_thread_stack[THREAD_STACKSIZE_MAIN]; + +void *led_thread_impl(void *arg) +{ + (void)arg; + + while (1) { + ztimer_sleep(ZTIMER_MSEC, blink_interval_ms); +#ifdef LED0_TOGGLE + LED0_TOGGLE; +#else + printf("Blinking with %"PRIu32" msec!\n", blink_interval_ms); +#endif + } +} + +void cdc_task(void); + +/* ------------- MAIN ------------- */ + +int main(void) +{ + ztimer_sleep(ZTIMER_MSEC, 200); + + thread_create(led_thread_stack, sizeof(led_thread_stack), + THREAD_PRIORITY_MAIN + 1, + THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, + led_thread_impl, NULL, "led"); + + /* initialize the tinyUSB stack including used peripherals and + * start the tinyUSB thread */ + tinyusb_setup(); + + while (1) { + ztimer_sleep(ZTIMER_MSEC, 10); + cdc_task(); + } + + return 0; +} + +/* + * -------------------------------------------------------------------- + * Device callbacks to be implemented + * -------------------------------------------------------------------- + */ + +/* + * Invoked when device is mounted + */ +void tud_mount_cb(void) +{ + printf("tinyUSB %s\n", __func__); + blink_interval_ms = BLINK_MOUNTED; +} + +/* + * Invoked when device is unmounted + */ +void tud_umount_cb(void) +{ + printf("tinyUSB %s\n", __func__); + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +/* + * Invoked when usb bus is suspended + * remote_wakeup_en : if host allow us to perform remote wakeup + * Within 7ms, device must draw an average of current less than 2.5 mA from bus + */ +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + printf("tinyUSB %s\n", __func__); + blink_interval_ms = BLINK_SUSPENDED; +} + +/* + * Invoked when usb bus is resumed + */ +void tud_resume_cb(void) +{ + printf("tinyUSB %s\n", __func__); + blink_interval_ms = BLINK_MOUNTED; +} + +/* + * --------------------------------------------------------------------+ + * USB CDC + * --------------------------------------------------------------------+ + */ +void cdc_task(void) +{ + /* + * connected() check for DTR bit + * Most but not all terminal client set this when making connection + */ + if ( tud_cdc_connected() ) { + /* connected and there are data available */ + if ( tud_cdc_available() ) + { + /* read data */ + char buf[64]; + uint32_t count = tud_cdc_read(buf, sizeof(buf)); + (void) count; + + /* + * Echo back + * Note: Skip echo by commenting out write() and write_flush() + * for throughput test e.g + * $ dd if=/dev/zero of=/dev/ttyACM0 count=10000 + */ + tud_cdc_write(buf, count); + tud_cdc_write_flush(); + } + } +} + +/* + * Invoked when cdc when line state changed e.g connected/disconnected + */ +void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + (void) itf; + (void) rts; + + /* TODO set some indicator */ + if ( dtr ) { + /* Terminal connected */ + } + else { + /* Terminal disconnected */ + } +} diff --git a/tests/pkg_tinyusb_cdc_msc/msc_disk.c b/tests/pkg_tinyusb_cdc_msc/msc_disk.c new file mode 100644 index 0000000000..2e8fd29bcb --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/msc_disk.c @@ -0,0 +1,360 @@ +/* + * 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" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#if CFG_TUD_MSC + +/* whether host does safe-eject */ +static bool ejected = false; + +/* Some MCU doesn't have enough 8KB SRAM to store the whole disk + * We will use Flash as read-only disk with board that has + * CFG_EXAMPLE_MSC_READONLY defined + */ + +#define README_CONTENTS \ + "This is tinyusb's MassStorage Class demo.\r\n\r\n" \ + "If you find any bugs or get any questions, feel free to file an\r\n" \ + "issue at github.com/hathach/tinyusb" + +enum { + DISK_BLOCK_NUM = 16, /* 8KB is the smallest size that windows allow to mount */ + DISK_BLOCK_SIZE = 512 +}; + +#ifdef CFG_EXAMPLE_MSC_READONLY +const +#endif +uint8_t msc_disk[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 MSC"; + * 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' , ' ' , 'M' , 'S' , '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 */ + 'T' , 'i' , 'n' , 'y' , 'U' , 'S' , 'B' , ' ' , + 'M' , 'S' , 'C' , 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x6D, + 0x65, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* second entry is readme file */ + 'R' , 'E' , 'A' , 'D' , 'M' , 'E' , ' ' , ' ' , + 'T' , 'X' , 'T' , 0x20, 0x00, 0xC6, 0x52, 0x6D, + 0x65, 0x43, 0x65, 0x43, 0x00, 0x00, 0x88, 0x6D, + 0x65, 0x43, 0x02, 0x00, + sizeof(README_CONTENTS)-1, 0x00, 0x00, 0x00 /* readme's files size (4 Bytes) */ + }, + + /* ------------- Block3: Readme Content ------------- */ + README_CONTENTS +}; + +/* + * 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; + + DEBUG("tinyUSB %s\n", __func__); + + 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; + + DEBUG("tinyUSB %s\n", __func__); + + /* RAM disk is ready until ejected */ + if (ejected) { + /* Additional Sense 3A-00 is NOT_FOUND */ + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); + return false; + } + + return true; +} + +/* + * 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; + + DEBUG("tinyUSB %s\n", __func__); + + *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; + + DEBUG("tinyUSB %s\n", __func__); + + if (load_eject) { + if (start) { + /* load disk storage */ + } + else { + /* unload disk storage */ + ejected = true; + } + } + 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) +{ + (void)lun; + + DEBUG("tinyUSB %s\n", __func__); + + /* out of ramdisk */ + if (lba >= DISK_BLOCK_NUM) { + return -1; + } + + uint8_t const* addr = msc_disk[lba] + offset; + memcpy(buffer, addr, bufsize); + + return (int32_t)bufsize; +} + +bool tud_msc_is_writable_cb(uint8_t lun) +{ + (void)lun; + + DEBUG("tinyUSB %s\n", __func__); + +#ifdef CFG_EXAMPLE_MSC_READONLY + return false; +#else + return true; +#endif +} + +/* + * 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) +{ + (void)lun; + + DEBUG("tinyUSB %s\n", __func__); + + /* out of ramdisk */ + if (lba >= DISK_BLOCK_NUM) { + return -1; + } + +#ifndef CFG_EXAMPLE_MSC_READONLY + uint8_t* addr = msc_disk[lba] + offset; + memcpy(addr, buffer, bufsize); +#else + (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 */ + + DEBUG("tinyUSB %s\n", __func__); + + 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 (int32_t)resplen; +} + +#endif diff --git a/tests/pkg_tinyusb_cdc_msc/tusb_config.h b/tests/pkg_tinyusb_cdc_msc/tusb_config.h new file mode 100644 index 0000000000..dcde62b0b6 --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/tusb_config.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#ifndef TUSB_CONFIG_H +#define TUSB_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * By default, the number of `CFG_TUD_*` device class and `CFG_TUH_*` + * host class interfaces is defined to 1 if the corresponding `tinyusb_class_*` + * and `tinyusb_device`/`tinyusb_host` module are enabled, and 0 otherwise. + * That is, there is one interface of each class. + * + * For example, if the `tinyusb_device` and `tinyusb_class_cdc` modules are + * enabled, `CFG_TUD_CDC` is defined to 1 by default. The number of all other + * `CFG_TUD_*` device class interfaces are 0. + * + * To define a different number of device class or host class interfaces, + * just define them here to override these default values, for example: + * ```c + * #define CFG_TUD_CDC 2 + * #define CFG_TUD_HID 3 + * ``` + */ + +/* Default configuration defined by RIOT package tinyUSB has to be included last */ +#include "tinyusb_config.h" + +#ifdef __cplusplus +} +#endif + +#endif /* TUSB_CONFIG_H */ diff --git a/tests/pkg_tinyusb_cdc_msc/usb_descriptors.c b/tests/pkg_tinyusb_cdc_msc/usb_descriptors.c new file mode 100644 index 0000000000..3bd2523449 --- /dev/null +++ b/tests/pkg_tinyusb_cdc_msc/usb_descriptors.c @@ -0,0 +1,340 @@ +/* + * 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" + +#ifdef CONFIG_USB_PID +#define USB_PID CONFIG_USB_PID +#else +/* + * 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] VIDEO | AUDIO | 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(AUDIO, 4) \ + | _PID_MAP(VIDEO, 5) ) +#endif + +#ifdef CONFIG_USB_VID +#define USB_VID CONFIG_USB_VID +#else +#error USB_VID has to be defined +#endif + +#define USB_BCD 0x0200 + +/* + * --------------------------------------------------------------------+ + * Device Descriptors + * --------------------------------------------------------------------+ + */ +static tusb_desc_device_t const desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + + /* Use Interface Association Descriptor (IAD) for CDC + * As required by USB Specs IAD's subclass must be common class (2) + * and protocol must be IAD (1) */ + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +/* + * 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 + *--------------------------------------------------------------------+ + */ +enum { + ITF_NUM_CDC = 0, + ITF_NUM_CDC_DATA, + ITF_NUM_MSC, + ITF_NUM_TOTAL +}; + +#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, 5 Bulk etc ... + */ + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_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, e.g EP1 OUT & EP1 IN cannot exist together + */ + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x83 + + #define EPNUM_MSC_OUT 0x04 + #define EPNUM_MSC_IN 0x85 + +#elif CFG_TUSB_MCU == OPT_MCU_CXD56 + /* + * CXD56 doesn't support a same endpoint number with different direction IN + * and OUT, e.g EP1 OUT & EP1 IN cannot exist together + * CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and + * direction (IN/OUT) by its number + * 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), + * 5 Bulk (OUT), 6 In (IN) + */ + #define EPNUM_CDC_NOTIF 0x83 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x81 + + #define EPNUM_MSC_OUT 0x05 + #define EPNUM_MSC_IN 0x84 + +#elif CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X + /* + * FT9XX doesn't support a same endpoint number with different direction + * IN and OUT, e.g EP1 OUT & EP1 IN cannot exist together + */ + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x83 + + #define EPNUM_MSC_OUT 0x04 + #define EPNUM_MSC_IN 0x85 + +#else + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x82 + + #define EPNUM_MSC_OUT 0x03 + #define EPNUM_MSC_IN 0x83 + +#endif + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN) + +/* full speed configuration */ +uint8_t const desc_fs_configuration[] = { + /* Config number, interface count, string index, total length, attribute, + * power in mA */ + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + /* Interface number, string index, EP notification address and size, EP data + * address (out, in) and size. */ + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), + + /* Interface number, string index, EP Out & EP In address, EP size */ + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), +}; + +#if TUD_OPT_HIGH_SPEED + /* Per USB specs: high speed capable device must report device_qualifier + * and other_speed_configuration */ + +/* high speed configuration */ +uint8_t const desc_hs_configuration[] = +{ + /* Config number, interface count, string index, total length, attribute, + * power in mA */ + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + + /* Interface number, string index, EP notification address and size, EP data + * address (out, in) and size. */ + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512), + + /* Interface number, string index, EP Out & EP In address, EP size */ + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512), +}; + +/* other speed configuration */ +uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; + +/* device qualifier is mostly similar to device descriptor since we don't + * change configuration based on speed */ +tusb_desc_device_qualifier_t const desc_device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = USB_BCD, + + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +/* + * Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request + * Application return pointer to descriptor, whose contents must exist long + * enough for transfer to complete. Device_qualifier descriptor describes + * information about a high-speed capable device that would change if the + * device were operating at the other speed. If not highspeed capable stall + * this request. + */ +uint8_t const* tud_descriptor_device_qualifier_cb(void) +{ + return (uint8_t const*)&desc_device_qualifier; +} + +/* + * Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request + * Application return pointer to descriptor, whose contents must exist long + * enough for transfer to complete. Configuration descriptor in the other + * speed e.g if high speed then this is for full speed and vice versa + */ +uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) +{ + (void)index; /* for multiple configurations */ + + /* if link speed is high return fullspeed config, and vice versa + * Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG */ + memcpy(desc_other_speed_config, + (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration + : desc_hs_configuration, + CONFIG_TOTAL_LEN); + + desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; + + return desc_other_speed_config; +} + +#endif /* highspeed */ + +/* + * 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) +{ + (void)index; /* for multiple configurations */ + +#if TUD_OPT_HIGH_SPEED + /* Although we are highspeed, host may be fullspeed. */ + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration + : desc_fs_configuration; +#else + return desc_fs_configuration; +#endif +} + +/* + *--------------------------------------------------------------------+ + * String Descriptors + *--------------------------------------------------------------------+ + */ + +/* array of pointer to string descriptors */ +char const* string_desc_arr [] = { + (const char[]){ 0x09, 0x04 }, /* 0: is supported language is English (0x0409) */ + "RIOT-OS", /* 1: Manufacturer */ + "TinyUSB Device", /* 2: Product */ + "123456789012", /* 3: Serials, should use chip ID */ + "TinyUSB CDC", /* 4: CDC Interface */ + "TinyUSB MSC", /* 5: MSC Interface */ +}; + +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; + + uint8_t chr_count; + + if ( index == 0) { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + } + 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 = (uint8_t) strlen(str); + if (chr_count > 31) { + chr_count = 31; + } + + /* Convert ASCII string into UTF-16 */ + for (uint8_t i=0; i