Explorar o código

USB: Add MSC example.

Saurabh Kumar Bansal %!s(int64=3) %!d(string=hai) anos
pai
achega
fac23b90ae

+ 63 - 1
docs/en/api-reference/peripherals/usb_device.rst

@@ -77,7 +77,7 @@ However, the driver also provides default descriptors. You can install the drive
 - bcdDevice
 - Manufacturer
 - Product name
-- Name of CDC device if it is On
+- Name of CDC or MSC device if it is On
 - Serial number
 
 If you want to use your own descriptors with extended modification, you can define them during the driver installation process.
@@ -141,6 +141,66 @@ USB Serial Console
 
 The driver allows to redirect all standard application streams (stdinm stdout, stderr) to the USB Serial Device and return them to UART using :cpp:func:`esp_tusb_init_console`/:cpp:func:`esp_tusb_deinit_console` functions.
 
+USB Mass Storage Device (MSC)
+-----------------------------
+
+If the MSC CONFIG_TINYUSB_MSC_ENABLED option is enabled and SPI Flash Wear Levelling WL_SECTOR_SIZE is set to 512 and WL_SECTOR_MODE is set to PERF in Menuconfig, the USB MSC Device can be initialized as shown below (see example below).
+
+.. code-block:: c
+
+    static uint8_t const desc_configuration[] = {
+        // Config number, interface count, string index, total length, attribute, power in mA
+        TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+        // Interface number, string index, EP Out & EP In address, EP size
+        TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64),
+    };
+
+    static tusb_desc_device_t descriptor_config = {
+        .bLength = sizeof(descriptor_config),
+        .bDescriptorType = TUSB_DESC_DEVICE,
+        .bcdUSB = 0x0200,
+        .bDeviceClass = TUSB_CLASS_MISC,
+        .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+        .bDeviceProtocol = MISC_PROTOCOL_IAD,
+        .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+        .idVendor = 0x303A,
+        .idProduct = 0x4002,
+        .bcdDevice = 0x100,
+        .iManufacturer = 0x01,
+        .iProduct = 0x02,
+        .iSerialNumber = 0x03,
+        .bNumConfigurations = 0x01
+    };
+
+    static char const *string_desc_arr[] = {
+        (const char[]) { 0x09, 0x04 },  // 0: is supported language is English (0x0409)
+        "TinyUSB",                      // 1: Manufacturer
+        "TinyUSB Device",               // 2: Product
+        "123456",                       // 3: Serials
+        "Example MSC",                  // 4. MSC
+    };
+
+    const tinyusb_config_t tusb_cfg = {
+        .device_descriptor = &descriptor_config,
+        .string_descriptor = string_desc_arr,
+        .external_phy = false,
+        .configuration_descriptor = desc_configuration,
+    };
+    tinyusb_driver_install(&tusb_cfg);
+
+The mandatory callbacks that are required to be implemented are
+
+.. code-block:: c
+
+    void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
+    bool tud_msc_test_unit_ready_cb(uint8_t lun)
+    void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
+    bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
+    int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
+    int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
+    int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
+
 Application Examples
 --------------------
 
@@ -160,3 +220,5 @@ The table below describes the code examples available in the directory :example:
      - How to set up {IDF_TARGET_NAME} chip to work as a USB MIDI Device
    * - :example:`peripherals/usb/device/tusb_hid`
      - How to set up {IDF_TARGET_NAME} chip to work as a USB Human Interface Device
+   * - :example:`peripherals/usb/device/tusb_msc`
+     - How to set up {IDF_TARGET_NAME} chip to work as a USB Mass Storage Device

+ 8 - 0
examples/peripherals/usb/device/tusb_msc/CMakeLists.txt

@@ -0,0 +1,8 @@
+# For more information about build system see
+# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
+# The following five lines of boilerplate have to be in your project's
+# CMakeLists in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(tusb_msc)

+ 180 - 0
examples/peripherals/usb/device/tusb_msc/README.md

@@ -0,0 +1,180 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# TinyUSB Mass Storage Device Example
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+Mass Storage Devices are one of the most common USB devices. It use Mass Storage Class (MSC) that allow access to their internal data storage.
+This example contains code to make ESP based device recognizable by USB-hosts as a USB Mass Storage Device.
+It either allows the embedded application ie example to access the partition or Host PC accesses the partition over USB MSC.
+They can't be allowed to access the partition at the same time.
+The access to the underlying block device is provided by functions in tusb_msc_storage.c
+
+In this example, data is read/written from/to SPI Flash through wear-levelling APIs. Wear leveling is a technique that helps to distribute wear and tear among sectors more evenly without requiring any attention from the user. As a result, it helps in extending the life of each sector of the Flash memory.
+
+As a USB stack, a TinyUSB component is used.
+
+## How to use example
+
+### Scenarios
+1. USB which accesses the ESP MSC Partition is unplugged initially and the board is powered-on.
+     - Result: Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition.
+2. USB which accesses the ESP MSC Partition is already plugged-in at boot time.
+     - Result: Host PC recongnize it as removable device and can access the partition over USB MSC. Application example can't perform any operation on partition.
+3. USB which accesses the ESP MSC Partition is plugged-in at boo-up. After boot-up, it is ejected on Host PC manually by user.
+     - Result: Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition.
+4. USB which accesses the ESP MSC Partition is plugged-in at boot-up. It is then unplugged(removed) from Host PC manually by user.
+     - Result: The behaviour is different for bus-powered devices and self-powered devices
+          - (a) Bus-Powered devices - Both Host PC as well as application example can't access the partition over USB MSC. Here, the device will be Powered-off.
+          - (b) Self-Powered devices - Here, the device can be powered-on even after unplugging the device from Host PC. These behaviour can be further categorize in two ways:
+               - (i) Self-Powered Devices without VBUS monitoring - Both Host PC as well as application example can't access the partition over USB MSC.
+               - (ii) Self-Powered Devices with VBUS monitoring - Host PC can't access the partition over USB MSC. Application example can perform operations (read, write) on partition. Here, in ``tinyusb_config_t`` user must set ``self_powered`` to ``true`` and ``vbus_monitor_io`` to GPIO number (``VBUS_MONITORING_GPIO_NUM``) that will be used for VBUS monitoring.
+
+### Hardware Required
+
+Any ESP board that have USB-OTG supported.
+
+### Pin Assignment
+
+_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
+
+See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
+
+Next, for Self-Powered Devices with VBUS monitoring, user must set ``self_powered`` to ``true`` and ``vbus_monitor_io`` to GPIO number (``VBUS_MONITORING_GPIO_NUM``) that will be used for VBUS monitoring.
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```bash
+idf.py -p PORT flash monitor
+```
+
+(Replace PORT with the name of the serial port to use.)
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+After the flashing you should see the output at idf monitor:
+
+```
+I (311) cpu_start: Starting scheduler on PRO CPU.
+I (0) cpu_start: Starting scheduler on APP CPU.
+I (332) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
+I (332) example_msc_main: Initializing storage...
+I (342) example_msc_storage: Initializing wear levelling
+I (372) example_msc_main: USB MSC initialization
+I (372) tusb_desc: 
+┌─────────────────────────────────┐
+│  USB Device Descriptor Summary  │
+├───────────────────┬─────────────┤
+│bDeviceClass       │ 239         │
+├───────────────────┼─────────────┤
+│bDeviceSubClass    │ 2           │
+├───────────────────┼─────────────┤
+│bDeviceProtocol    │ 1           │
+├───────────────────┼─────────────┤
+│bMaxPacketSize0    │ 64          │
+├───────────────────┼─────────────┤
+│idVendor           │ 0x303a      │
+├───────────────────┼─────────────┤
+│idProduct          │ 0x4002      │
+├───────────────────┼─────────────┤
+│bcdDevice          │ 0x100       │
+├───────────────────┼─────────────┤
+│iManufacturer      │ 0x1         │
+├───────────────────┼─────────────┤
+│iProduct           │ 0x2         │
+├───────────────────┼─────────────┤
+│iSerialNumber      │ 0x3         │
+├───────────────────┼─────────────┤
+│bNumConfigurations │ 0x1         │
+└───────────────────┴─────────────┘
+I (532) TinyUSB: TinyUSB Driver installed
+I (532) example_msc_main: USB MSC initialization DONE
+I (542) example_msc_main: Mount storage...
+I (542) example_msc_storage: Initializing FAT
+I (552) example_msc_main: 
+ls command output:
+README.MD
+.fseventsd
+
+Type 'help' to get the list of commands.
+Use UP/DOWN arrows to navigate through command history.
+Press TAB when typing command name to auto-complete.
+esp32s3> I (912) example_msc_main: tud_mount_cb MSC START: Expose Over USB
+I (912) example_msc_main: Unmount storage...
+I (2032) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
+I (2032) example_msc_main: tud_msc_capacity_cb() size(1024000), sec_size(512)
+esp32s3> 
+esp32s3> 
+esp32s3> help
+help 
+  Print the list of registered commands
+
+read 
+  read BASE_PATH/README.MD and print its contents
+
+write 
+  create file BASE_PATH/README.MD if it does not exist
+
+size 
+  show storage size and sector size
+
+expose 
+  Expose Storage to Host
+
+status 
+  Status of storage exposure over USB
+
+exit 
+  exit from application
+
+esp32s3> 
+esp32s3> read
+E (19102) example_msc_main: storage exposed over USB. Application can't read from storage.
+Command returned non-zero error code: 0xffffffff (ESP_FAIL)
+esp32s3> write
+E (22412) example_msc_main: storage exposed over USB. Application can't write to storage.
+Command returned non-zero error code: 0xffffffff (ESP_FAIL)
+esp32s3> size
+E (24962) example_msc_main: storage exposed over USB. Application can't access storage
+Command returned non-zero error code: 0xffffffff (ESP_FAIL)
+esp32s3> status
+storage exposed over USB: Yes
+esp32s3> 
+esp32s3> 
+esp32s3> I (49692) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
+I (49692) example_msc_main: tud_msc_start_stop_cb() invoked, power_condition=0, start=0, load_eject=1
+I (49702) example_msc_main: tud_msc_start_stop_cb: MSC EJECT: Mount on Example
+I (49712) example_msc_main: Mount storage...
+I (49712) example_msc_storage: Initializing FAT
+I (49712) example_msc_main: 
+ls command output:
+README.MD
+esp32s3> 
+esp32s3> 
+esp32s3> status
+storage exposed over USB: No
+esp32s3> read
+Mass Storage Devices are one of the most common USB devices. It use Mass Storage Class (MSC) that allow access to their internal data storage.
+In this example, ESP chip will be recognised by host (PC) as Mass Storage Device.
+Upon connection to USB host (PC), the example application will initialize the storage module and then the storage will be seen as removable device on PC.
+esp32s3> write
+esp32s3> size
+storage size(1024000), sec_size(512)
+esp32s3> 
+esp32s3> expose
+I (76402) example_msc_main: Unmount storage...
+esp32s3> I (76772) example_msc_main: tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL
+I (76772) example_msc_main: tud_msc_capacity_cb() size(1024000), sec_size(512)
+esp32s3> 
+esp32s3> status
+storage exposed over USB: Yes
+esp32s3> 
+esp32s3>
+```

+ 5 - 0
examples/peripherals/usb/device/tusb_msc/main/CMakeLists.txt

@@ -0,0 +1,5 @@
+idf_component_register(
+    SRCS "tusb_msc_storage.c" "tusb_msc_main.c"
+    INCLUDE_DIRS .
+    REQUIRES fatfs wear_levelling console
+    )

+ 4 - 0
examples/peripherals/usb/device/tusb_msc/main/idf_component.yml

@@ -0,0 +1,4 @@
+## IDF Component Manager Manifest File
+dependencies:
+  espressif/esp_tinyusb: "0.0.1"
+  idf: "^5.1"

+ 455 - 0
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_main.c

@@ -0,0 +1,455 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+/* DESCRIPTION:
+ * This example contains code to make ESP32-S3 based device recognizable by USB-hosts as a USB Mass Storage Device.
+ * It either allows the embedded application ie example to access the partition or Host PC accesses the partition over USB MSC.
+ * They can't be allowed to access the partition at the same time.
+ * The access to the underlying block device is provided by functions in tusb_msc_storage.c
+ * For different scenarios and behaviour, Refer to README of this example.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include "esp_log.h"
+#include "esp_console.h"
+#include "tinyusb.h"
+#include "class/msc/msc_device.h"
+#include "tusb_msc_storage.h"
+#include "driver/gpio.h"
+
+static const char *TAG = "example_msc_main";
+#define PROMPT_STR CONFIG_IDF_TARGET
+
+/********* TinyUSB MSC callbacks ***************/
+
+/** SCSI ASC/ASCQ codes. **/
+/** User can add and use more codes as per the need of the application **/
+#define SCSI_CODE_ASC_MEDIUM_NOT_PRESENT 0x3A /** SCSI ASC code for 'MEDIUM NOT PRESENT' **/
+#define SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE 0x20 /** SCSI ASC code for 'INVALID COMMAND OPERATION CODE' **/
+#define SCSI_CODE_ASCQ 0x00
+
+static void _mount(void);
+static void _unmount(void);
+static bool is_eject = false;
+
+// 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;
+    ESP_LOGD(TAG, "tud_msc_inquiry_cb() invoked");
+
+    const char vid[] = "TinyUSB";
+    const char pid[] = "Flash Storage";
+    const char rev[] = "0.1";
+
+    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;
+    ESP_LOGD(TAG, "tud_msc_test_unit_ready_cb() invoked");
+
+    if (is_eject) {
+        tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, SCSI_CODE_ASC_MEDIUM_NOT_PRESENT, SCSI_CODE_ASCQ);
+        return false;
+    } else {
+        ESP_LOGD(TAG, "tud_msc_test_unit_ready_cb: MSC START: Expose Over USB");
+        _unmount();
+        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;
+
+    size_t size = storage_get_size();
+    size_t sec_size = storage_get_sector_size();
+    ESP_LOGI(TAG, "tud_msc_capacity_cb() size(%d), sec_size(%d)", size, sec_size);
+    *block_count = size / sec_size;
+    *block_size  = sec_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;
+    ESP_LOGI(TAG, "tud_msc_start_stop_cb() invoked, power_condition=%d, start=%d, load_eject=%d", power_condition, start, load_eject);
+
+    if (load_eject && !start) {
+        is_eject = true;
+        ESP_LOGI(TAG, "tud_msc_start_stop_cb: MSC EJECT: Mount on Example");
+        _mount();
+    }
+    return true;
+}
+
+// Invoked when received SCSI READ10 command
+// - Address = lba * BLOCK_SIZE + offset
+// - Application fill the buffer (up to bufsize) with address contents and return number of read byte.
+int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
+{
+    ESP_LOGD(TAG, "tud_msc_read10_cb() invoked, lun=%d, lba=%lu, offset=%lu, bufsize=%lu", lun, lba, offset, bufsize);
+
+    size_t addr = lba * storage_get_sector_size() + offset;
+    esp_err_t err = storage_read_sector(addr, bufsize, buffer);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "storage_read_sector failed: 0x%x", err);
+        return 0;
+    }
+    return bufsize;
+}
+
+// Invoked when received SCSI WRITE10 command
+// - Address = lba * BLOCK_SIZE + offset
+// - Application write data from buffer to address contents (up to bufsize) and return number of written byte.
+int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
+{
+    ESP_LOGD(TAG, "tud_msc_write10_cb() invoked, lun=%d, lba=%lu, offset=%lu", lun, lba, offset);
+
+    size_t addr = lba * storage_get_sector_size() + offset;
+    esp_err_t err = storage_write_sector(addr, bufsize, buffer);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "storage_write_sector failed: 0x%x", err);
+        return 0;
+    }
+    return bufsize;
+}
+
+/**
+ * Invoked when received an SCSI command not in built-in list below.
+ * - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE
+ * - READ10 and WRITE10 has their own callbacks
+ *
+ * \param[in]   lun         Logical unit number
+ * \param[in]   scsi_cmd    SCSI command contents which application must examine to response accordingly
+ * \param[out]  buffer      Buffer for SCSI Data Stage.
+ *                            - For INPUT: application must fill this with response.
+ *                            - For OUTPUT it holds the Data from host
+ * \param[in]   bufsize     Buffer's length.
+ *
+ * \return      Actual bytes processed, can be zero for no-data command.
+ * \retval      negative    Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding
+ *                          endpoint and return failed status in command status wrapper phase.
+ */
+int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
+{
+    int32_t ret;
+
+    ESP_LOGD(TAG, "tud_msc_scsi_cb() invoked. bufsize=%d", bufsize);
+
+    switch (scsi_cmd[0]) {
+    case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
+        /* SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL is the Prevent/Allow Medium Removal
+        command (1Eh) that requests the library to enable or disable user access to
+        the storage media/partition. */
+        ESP_LOGI(TAG, "tud_msc_scsi_cb() invoked: SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL");
+        ret = 0;
+        break;
+    default:
+        ESP_LOGW(TAG, "tud_msc_scsi_cb() invoked: %d", scsi_cmd[0]);
+        tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ);
+        ret = -1;
+        break;
+    }
+    return ret;
+}
+
+// Invoked when device is unmounted
+void tud_umount_cb(void)
+{
+    is_eject = true;
+    ESP_LOGI(TAG, "tud_umount_cb: Mount on Example");
+    _mount();
+}
+
+// Invoked when device is mounted (configured)
+void tud_mount_cb(void)
+{
+    ESP_LOGI(TAG, "tud_mount_cb MSC START: Expose Over USB");
+    _unmount();
+}
+
+/************* Application Code *******************/
+
+/************* TinyUSB descriptors ****************/
+#define EPNUM_MSC       1
+#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
+#define VBUS_MONITORING_GPIO_NUM GPIO_NUM_4
+
+enum {
+    ITF_NUM_MSC = 0,
+    ITF_NUM_TOTAL
+};
+
+enum {
+    EDPT_CTRL_OUT = 0x00,
+    EDPT_CTRL_IN  = 0x80,
+
+    EDPT_MSC_OUT  = 0x01,
+    EDPT_MSC_IN   = 0x81,
+};
+
+static uint8_t const desc_configuration[] = {
+    // Config number, interface count, string index, total length, attribute, power in mA
+    TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+    // Interface number, string index, EP Out & EP In address, EP size
+    TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64),
+};
+
+static tusb_desc_device_t descriptor_config = {
+    .bLength = sizeof(descriptor_config),
+    .bDescriptorType = TUSB_DESC_DEVICE,
+    .bcdUSB = 0x0200,
+    .bDeviceClass = TUSB_CLASS_MISC,
+    .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+    .bDeviceProtocol = MISC_PROTOCOL_IAD,
+    .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+    .idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
+    .idProduct = 0x4002,
+    .bcdDevice = 0x100,
+    .iManufacturer = 0x01,
+    .iProduct = 0x02,
+    .iSerialNumber = 0x03,
+    .bNumConfigurations = 0x01
+};
+
+static char const *string_desc_arr[] = {
+    (const char[]) { 0x09, 0x04 },  // 0: is supported language is English (0x0409)
+    "TinyUSB",                      // 1: Manufacturer
+    "TinyUSB Device",               // 2: Product
+    "123456",                       // 3: Serials
+    "Example MSC",                  // 4. MSC
+};
+
+#define BASE_PATH "/data" // base path to mount the partition
+static bool is_mount = false;
+
+// mount the partition and show all the files in BASE_PATH
+static void _mount(void)
+{
+    ESP_LOGI(TAG, "Mount storage...");
+    if (!is_mount) {
+        ESP_ERROR_CHECK(storage_mount(BASE_PATH));
+        is_mount = true;
+    }
+
+    // List all the files in this directory
+    ESP_LOGI(TAG, "\nls command output:");
+    struct dirent *d;
+    DIR *dh = opendir(BASE_PATH);
+    if (!dh) {
+        if (errno = ENOENT) {
+            //If the directory is not found
+            ESP_LOGE(TAG, "Directory doesn't exist %s", BASE_PATH);
+        } else {
+            //If the directory is not readable then throw error and exit
+            ESP_LOGE(TAG, "Unable to read directory %s", BASE_PATH);
+        }
+        return;
+    }
+    //While the next entry is not readable we will print directory files
+    while ((d = readdir(dh)) != NULL) {
+        printf("%s\n", d->d_name);
+    }
+    return;
+}
+
+// unmount the partition
+static void _unmount(void)
+{
+    if (!is_mount) {
+        ESP_LOGD(TAG, "storage exposed over USB...");
+        return;
+    }
+    ESP_LOGI(TAG, "Unmount storage...");
+    ESP_ERROR_CHECK(storage_unmount());
+    is_mount = false;
+    is_eject = false;
+}
+
+static int f_unmount(int argc, char **argv)
+{
+    _unmount();
+    return 0;
+}
+
+// read BASE_PATH/README.MD and print its contents
+static int f_read(int argc, char **argv)
+{
+    if (!is_mount) {
+        ESP_LOGE(TAG, "storage exposed over USB. Application can't read from storage.");
+        return -1;
+    }
+    ESP_LOGD(TAG, "read from storage:");
+    const char *filename = BASE_PATH "/README.MD";
+    FILE *ptr = fopen(filename, "r");
+    if (ptr == NULL) {
+        ESP_LOGE(TAG, "Filename not present - %s", filename);
+        return -1;
+    }
+    char buf[1024];
+    while (fgets(buf, 1000, ptr) != NULL) {
+        printf("%s", buf);
+    }
+    fclose(ptr);
+    return 0;
+}
+
+// create file BASE_PATH/README.MD if it does not exist
+static int f_write(int argc, char **argv)
+{
+    if (!is_mount) {
+        ESP_LOGE(TAG, "storage exposed over USB. Application can't write to storage.");
+        return -1;
+    }
+    ESP_LOGD(TAG, "write to storage:");
+    const char *filename = BASE_PATH "/README.MD";
+    FILE *fd = fopen(filename, "r");
+    if (!fd) {
+        ESP_LOGW(TAG, "README.MD doesn't exist yet, creating");
+        fd = fopen(filename, "w");
+        fprintf(fd, "Mass Storage Devices are one of the most common USB devices. It use Mass Storage Class (MSC) that allow access to their internal data storage.\n");
+        fprintf(fd, "In this example, ESP chip will be recognised by host (PC) as Mass Storage Device.\n");
+        fprintf(fd, "Upon connection to USB host (PC), the example application will initialize the storage module and then the storage will be seen as removable device on PC.\n");
+        fclose(fd);
+    }
+    return 0;
+}
+
+// Show storage size and sector size
+static int f_size(int argc, char **argv)
+{
+    if (!is_mount) {
+        ESP_LOGE(TAG, "storage exposed over USB. Application can't access storage");
+        return -1;
+    }
+    size_t size = storage_get_size();
+    size_t sec_size = storage_get_sector_size();
+    printf("storage size(%d), sec_size(%d)\n", size, sec_size);
+    return 0;
+}
+
+// exit from application
+static int f_status(int argc, char **argv)
+{
+    printf("storage exposed over USB: %s\n", is_mount ? "No" : "Yes");
+    return 0;
+}
+
+// exit from application
+static int f_exit(int argc, char **argv)
+{
+    printf("Application Exiting\n");
+    exit(0);
+    return 0;
+}
+
+void app_main(void)
+{
+    // Configure GPIO Pin for vbus monitorung
+    const gpio_config_t vbus_gpio_config = {
+        .pin_bit_mask = BIT64(VBUS_MONITORING_GPIO_NUM),
+        .mode = GPIO_MODE_INPUT,
+        .intr_type = GPIO_INTR_DISABLE,
+        .pull_up_en = true,
+        .pull_down_en = false,
+    };
+    ESP_ERROR_CHECK(gpio_config(&vbus_gpio_config));
+
+    ESP_LOGI(TAG, "Initializing storage...");
+    ESP_ERROR_CHECK(storage_init());
+
+    ESP_LOGI(TAG, "USB MSC initialization");
+    const tinyusb_config_t tusb_cfg = {
+        .device_descriptor = &descriptor_config,
+        .string_descriptor = string_desc_arr,
+        .external_phy = false,
+        .configuration_descriptor = desc_configuration,
+        .self_powered = true,
+        .vbus_monitor_io = VBUS_MONITORING_GPIO_NUM,
+    };
+    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
+    ESP_LOGI(TAG, "USB MSC initialization DONE");
+
+    //mounted in the app by default
+    _mount();
+
+    esp_console_repl_t *repl = NULL;
+    esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
+    /* Prompt to be printed before each line.
+     * This can be customized, made dynamic, etc.
+     */
+    repl_config.prompt = PROMPT_STR ">";
+    repl_config.max_cmdline_length = 64;
+    esp_console_register_help_command();
+    esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
+
+    const esp_console_cmd_t cmd_read = {
+        .command = "read",
+        .help = "read BASE_PATH/README.MD and print its contents",
+        .hint = NULL,
+        .func = &f_read,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_read) );
+
+    const esp_console_cmd_t cmd_write = {
+        .command = "write",
+        .help = "create file BASE_PATH/README.MD if it does not exist",
+        .hint = NULL,
+        .func = &f_write,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_write) );
+
+    const esp_console_cmd_t cmd_size = {
+        .command = "size",
+        .help = "show storage size and sector size",
+        .hint = NULL,
+        .func = &f_size,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_size) );
+
+    const esp_console_cmd_t cmd_umount = {
+        .command = "expose",
+        .help = "Expose Storage to Host",
+        .hint = NULL,
+        .func = &f_unmount,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_umount) );
+
+    const esp_console_cmd_t cmd_status = {
+        .command = "status",
+        .help = "Status of storage exposure over USB",
+        .hint = NULL,
+        .func = &f_status,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_status) );
+
+    const esp_console_cmd_t cmd_exit = {
+        .command = "exit",
+        .help = "exit from application",
+        .hint = NULL,
+        .func = &f_exit,
+    };
+    ESP_ERROR_CHECK( esp_console_cmd_register(&cmd_exit) );
+
+    ESP_ERROR_CHECK(esp_console_start_repl(repl));
+}

+ 201 - 0
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.c

@@ -0,0 +1,201 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+// DESCRIPTION:
+// This file contains the code for accessing the storage medium ie SPI Flash.
+
+#include "esp_log.h"
+#include "esp_err.h"
+#include "esp_vfs_fat.h"
+#include "diskio_impl.h"
+#include "diskio_wl.h"
+#include "wear_levelling.h"
+#include "esp_partition.h"
+
+static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
+static bool s_fat_mounted;
+static const char *s_base_path;
+
+static const char *TAG = "example_msc_storage";
+
+esp_err_t storage_init(void)
+{
+    ESP_LOGI(TAG, "Initializing wear levelling");
+    esp_err_t err;
+
+    const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
+    if (data_partition == NULL) {
+        ESP_LOGE(TAG, "Failed to find FATFS partition. Check the partition table.");
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    err = wl_mount(data_partition, &s_wl_handle);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "failed to mount wear levelling layer (0x%x)", err);
+        return err;
+    }
+
+    return ESP_OK;
+}
+
+static inline size_t esp_vfs_fat_get_allocation_unit_size(
+    size_t sector_size, size_t requested_size)
+{
+    size_t alloc_unit_size = requested_size;
+    const size_t max_sectors_per_cylinder = 128;
+    const size_t max_size = sector_size * max_sectors_per_cylinder;
+    alloc_unit_size = MAX(alloc_unit_size, sector_size);
+    alloc_unit_size = MIN(alloc_unit_size, max_size);
+    return alloc_unit_size;
+}
+
+esp_err_t storage_mount(const char *base_path)
+{
+    const size_t workbuf_size = 4096;
+    void *workbuf = NULL;
+    esp_err_t err;
+
+    if (s_fat_mounted) {
+        return ESP_OK;
+    }
+
+    ESP_LOGI(TAG, "Initializing FAT");
+
+    // connect driver to FATFS
+    BYTE pdrv = 0xFF;
+    if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
+        ESP_LOGE(TAG, "the maximum count of volumes is already mounted");
+        return ESP_ERR_NO_MEM;
+    }
+    ESP_LOGD(TAG, "using pdrv=%i", pdrv);
+    char drv[3] = {(char)('0' + pdrv), ':', 0};
+
+    err = ff_diskio_register_wl_partition(pdrv, s_wl_handle);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "ff_diskio_register_wl_partition failed pdrv=%d (0x%x)", pdrv, err);
+        goto fail;
+    }
+    FATFS *fs;
+    err = esp_vfs_fat_register(base_path, drv, 2, &fs);
+    if (err == ESP_ERR_INVALID_STATE) {
+        // it's okay, already registered with VFS
+    } else if (err != ESP_OK) {
+        ESP_LOGE(TAG, "esp_vfs_fat_register failed (0x%x)", err);
+        goto fail;
+    }
+
+    // Try to mount partition
+    FRESULT fresult = f_mount(fs, drv, 1);
+    if (fresult != FR_OK) {
+        ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
+        if (!((fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR))) {
+            err = ESP_FAIL;
+            goto fail;
+        }
+        workbuf = ff_memalloc(workbuf_size);
+        if (workbuf == NULL) {
+            err = ESP_ERR_NO_MEM;
+            goto fail;
+        }
+        size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
+                                     CONFIG_WL_SECTOR_SIZE,
+                                     4096);
+
+        ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
+        const MKFS_PARM opt = {(BYTE)FM_FAT, 0, 0, 0, 0};
+        fresult = f_mkfs("", &opt, workbuf, workbuf_size); // Use default volume
+        if (fresult != FR_OK) {
+            err = ESP_FAIL;
+            ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult);
+            goto fail;
+        }
+        free(workbuf);
+        workbuf = NULL;
+        ESP_LOGI(TAG, "Mounting again");
+        fresult = f_mount(fs, drv, 0);
+        if (fresult != FR_OK) {
+            err = ESP_FAIL;
+            ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult);
+            goto fail;
+        }
+    }
+    s_fat_mounted = true;
+    s_base_path = base_path;
+
+    return ESP_OK;
+
+fail:
+    free(workbuf);
+    esp_vfs_fat_unregister_path(base_path);
+    ff_diskio_unregister(pdrv);
+    s_fat_mounted = false;
+    ESP_LOGW(TAG, "Failed to mount storage (0x%x)", err);
+    return err;
+}
+
+esp_err_t storage_unmount(void)
+{
+    if (!s_fat_mounted) {
+        return ESP_OK;
+    }
+
+    BYTE pdrv = ff_diskio_get_pdrv_wl(s_wl_handle);
+    if (pdrv == 0xff) {
+        return ESP_ERR_INVALID_STATE;
+    }
+    char drv[3] = {(char)('0' + pdrv), ':', 0};
+
+    f_mount(0, drv, 0);
+    ff_diskio_unregister(pdrv);
+    ff_diskio_clear_pdrv_wl(s_wl_handle);
+    esp_err_t err = esp_vfs_fat_unregister_path(s_base_path);
+    s_base_path = NULL;
+    s_fat_mounted = false;
+
+    return err;
+
+}
+
+size_t storage_get_size(void)
+{
+    assert(s_wl_handle != WL_INVALID_HANDLE);
+
+    return wl_size(s_wl_handle);
+}
+
+size_t storage_get_sector_size(void)
+{
+    assert(s_wl_handle != WL_INVALID_HANDLE);
+
+    return wl_sector_size(s_wl_handle);
+}
+
+esp_err_t storage_read_sector(size_t addr, size_t size, void *dest)
+{
+    assert(s_wl_handle != WL_INVALID_HANDLE);
+
+    return wl_read(s_wl_handle, addr, dest, size);
+}
+
+esp_err_t storage_write_sector(size_t addr, size_t size, const void *src)
+{
+    assert(s_wl_handle != WL_INVALID_HANDLE);
+
+    if (s_fat_mounted) {
+        ESP_LOGE(TAG, "can't write, FAT mounted");
+        return ESP_ERR_INVALID_STATE;
+    }
+    size_t sector_size = wl_sector_size(s_wl_handle);
+    if (addr % sector_size != 0 || size % sector_size != 0) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    esp_err_t err = wl_erase_range(s_wl_handle, addr, size);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "wl_erase_range failed (0x%x)", err);
+        return err;
+    }
+    return wl_write(s_wl_handle, addr, src, size);
+}

+ 118 - 0
examples/peripherals/usb/device/tusb_msc/main/tusb_msc_storage.h

@@ -0,0 +1,118 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Unlicense OR CC0-1.0
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include "esp_err.h"
+
+/* Public functions
+   ********************************************************************* */
+
+/**
+ * @brief Initialize the Storage.
+ *
+ * First find 'first partition' based on one or more parameters (ie DATA, FAT).
+ * Mount WL for defined partition.
+ * Initialize the instance of WL instance.
+ * Once the storage is initialized, other storage functions can be used.
+ *
+ * @return esp_err_t
+ *       - ESP_OK, if success;
+ *       - ESP_ERR_NOT_FOUND, if Failed to find FATFS partition;
+ *       - ESP_ERR_INVALID_ARG, if WL allocation was unsuccessful;
+ *       - ESP_ERR_NO_MEM, if there was no memory to allocate WL components;
+ */
+esp_err_t storage_init(void);
+
+/**
+ * @brief Mount the storage partition locally on the firmware application.
+ *
+ * Get the available drive number. Register spi flash partition.
+ * Connect POSIX and C standard library IO function with FATFS.
+ * Mounts the partition.
+ * This API is used by the firmware application. If the storage partition is
+ * mounted by this API, host (PC) can't access the storage via MSC.
+ *
+ * @param base_path  path prefix where FATFS should be registered
+ * @return esp_err_t
+ *       - ESP_OK, if success;
+ *       - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered;
+ */
+esp_err_t storage_mount(const char *base_path);
+
+/**
+ * @brief Unmount the storage partition from the firmware application.
+ *
+ * Unmount the partition. Unregister diskio driver.
+ * Unregister the SPI flash partition.
+ * Finally, Un-register FATFS from VFS.
+ * After this function is called, storage device can be seen (recongnized) by host (PC).
+ *
+ * @return esp_err_t
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
+ */
+esp_err_t storage_unmount(void);
+
+/**
+ * @brief Get size of the WL storage
+ *
+ * @return usable size, in bytes
+ */
+size_t storage_get_size(void);
+
+/**
+ * @brief Get sector size of the WL instance
+ *
+ * @return sector size, in bytes
+ */
+size_t storage_get_sector_size(void);
+
+/**
+ * @brief Read data from the WL storage
+ *
+ * @param addr Address of the data to be read, relative to the
+ *            beginning of the partition.
+ * @param size Size of data to be read, in bytes.
+ * @param dest Pointer to the buffer where data should be stored.
+ *            Pointer must be non-NULL and buffer must be at least 'size' bytes long.
+ * @return esp_err_t
+ *       - ESP_OK, if data was read successfully;
+ *       - ESP_ERR_INVALID_ARG, if src_offset exceeds partition size;
+ *       - ESP_ERR_INVALID_SIZE, if read would go out of bounds of the partition;
+ *       - or one of error codes from lower-level flash driver.
+ */
+esp_err_t storage_read_sector(size_t addr, size_t size, void *dest);
+
+/**
+ * @brief Write data to the WL storage
+ *
+ * Before writing data to flash, corresponding region of flash needs to be erased.
+ * This is done internally using wl_erase_range function.
+ *
+ * @param addr Address where the data should be written, relative to the
+ *            beginning of the partition.
+ * @param size Size of data to be written, in bytes.
+ * @param src Pointer to the source buffer.  Pointer must be non-NULL and
+ *            buffer must be at least 'size' bytes long.
+ * @return esp_err_t
+ *       - ESP_OK, if data was written successfully;
+ *       - ESP_ERR_INVALID_ARG, if dst_offset exceeds partition size;
+ *       - ESP_ERR_INVALID_SIZE, if write would go out of bounds of the partition;
+ *       - or one of error codes from lower-level flash driver.
+ */
+esp_err_t storage_write_sector(size_t addr, size_t size, const void *src);
+
+/*********************************************************************** Public functions*/
+
+#ifdef __cplusplus
+}
+#endif

+ 6 - 0
examples/peripherals/usb/device/tusb_msc/partitions.csv

@@ -0,0 +1,6 @@
+# Name,   Type, SubType, Offset,  Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs,      data, nvs,     0x9000,  0x6000,
+phy_init, data, phy,     0xf000,  0x1000,
+factory,  app,  factory, 0x10000, 1M,
+storage,  data, fat,     ,        1M,

+ 20 - 0
examples/peripherals/usb/device/tusb_msc/pytest_usb_device_msc.py

@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+import pytest
+from pytest_embedded import Dut
+
+
+@pytest.mark.esp32s2
+@pytest.mark.usb_device
+def test_usb_device_msc_example(dut: Dut) -> None:
+    dut.expect('USB MSC initialization DONE')
+    dut.expect('Mount storage')
+    dut.expect('Initializing FAT')
+    dut.write(' help')
+    dut.expect('read')
+    dut.expect('write')
+    dut.expect('size')
+    dut.expect('expose')
+    dut.expect('status')
+    dut.write(' status')
+    dut.expect('storage exposed over USB')

+ 14 - 0
examples/peripherals/usb/device/tusb_msc/sdkconfig.defaults

@@ -0,0 +1,14 @@
+# This file was generated using idf.py save-defconfig. It can be edited manually.
+# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
+#
+CONFIG_TINYUSB=y
+CONFIG_TINYUSB_MSC_ENABLED=y
+
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+CONFIG_WL_SECTOR_SIZE_512=y
+CONFIG_WL_SECTOR_MODE_PERF=y
+
+CONFIG_FATFS_LFN_HEAP=y