Przeglądaj źródła

Merge branch 'feature/usb-msc' into 'master'

USB MSC class

Closes IDFGH-4584

See merge request espressif/esp-idf!14573
Martin Válik 4 lat temu
rodzic
commit
4f7c36d859
27 zmienionych plików z 2703 dodań i 6 usunięć
  1. 21 0
      components/usb/include/usb/usb_helpers.h
  2. 11 0
      components/usb/include/usb/usb_types_ch9.h
  3. 133 0
      components/usb/usb_helpers.c
  4. 6 0
      examples/peripherals/usb/host/msc/CMakeLists.txt
  5. 60 0
      examples/peripherals/usb/host/msc/README.md
  6. 9 0
      examples/peripherals/usb/host/msc/components/msc/CMakeLists.txt
  7. 32 0
      examples/peripherals/usb/host/msc/components/msc/README.md
  8. 169 0
      examples/peripherals/usb/host/msc/components/msc/include/msc_host.h
  9. 44 0
      examples/peripherals/usb/host/msc/components/msc/include/msc_host_vfs.h
  10. 39 0
      examples/peripherals/usb/host/msc/components/msc/private_include/diskio_usb.h
  11. 61 0
      examples/peripherals/usb/host/msc/components/msc/private_include/msc_common.h
  12. 56 0
      examples/peripherals/usb/host/msc/components/msc/private_include/msc_scsi_bot.h
  13. 118 0
      examples/peripherals/usb/host/msc/components/msc/src/diskio_usb.c
  14. 547 0
      examples/peripherals/usb/host/msc/components/msc/src/msc_host.c
  15. 124 0
      examples/peripherals/usb/host/msc/components/msc/src/msc_host_vfs.c
  16. 434 0
      examples/peripherals/usb/host/msc/components/msc/src/msc_scsi_bot.c
  17. 3 0
      examples/peripherals/usb/host/msc/components/msc/test/CMakeLists.txt
  18. 295 0
      examples/peripherals/usb/host/msc/components/msc/test/msc_device.c
  19. 19 0
      examples/peripherals/usb/host/msc/components/msc/test/test_common.h
  20. 316 0
      examples/peripherals/usb/host/msc/components/msc/test/test_msc.c
  21. 3 0
      examples/peripherals/usb/host/msc/main/CMakeLists.txt
  22. 179 0
      examples/peripherals/usb/host/msc/main/msc_example_main.c
  23. 1 0
      tools/ci/check_copyright_ignore.txt
  24. 3 2
      tools/test_apps/peripherals/usb/CMakeLists.txt
  25. 15 3
      tools/test_apps/peripherals/usb/README.md
  26. 1 1
      tools/test_apps/peripherals/usb/main/usb_test_main.c
  27. 4 0
      tools/test_apps/peripherals/usb/sdkconfig.defaults

+ 21 - 0
components/usb/include/usb/usb_helpers.h

@@ -11,6 +11,8 @@ Warning: The USB Host Library API is still a beta version and may be subject to
 #pragma once
 
 #include <stdint.h>
+#include "esp_err.h"
+#include "usb/usb_types_stack.h"
 #include "usb/usb_types_ch9.h"
 
 #ifdef __cplusplus
@@ -129,6 +131,25 @@ static inline int usb_round_up_to_mps(int num_bytes, int mps)
     return ((num_bytes + mps - 1) / mps) * mps;
 }
 
+/**
+ * @brief Print class specific descriptor callback
+ *
+ * Optional callback to be provided to usb_print_descriptors() function.
+ * The callback is called when when a non-standard descriptor is encountered.
+ * The callback should decode the descriptor as print it.
+ */
+
+typedef void (*print_class_descriptor_cb)(const usb_standard_desc_t *);
+
+/**
+ * @brief Prints usb descriptors
+ *
+ * @param[in] device Handle to device
+ * @param[in] class_specific_cb Optional callback to print class specific descriptors
+ * @return esp_err_t
+ */
+esp_err_t usb_print_descriptors(usb_device_handle_t device, print_class_descriptor_cb class_specific_cb);
+
 #ifdef __cplusplus
 }
 #endif

+ 11 - 0
components/usb/include/usb/usb_types_ch9.h

@@ -214,6 +214,17 @@ _Static_assert(sizeof(usb_setup_packet_t) == USB_SETUP_PACKET_SIZE, "Size of usb
     (setup_pkt_ptr)->wLength = 0;   \
 })
 
+/**
+ * @brief Initializer for a request to get an string descriptor
+ */
+#define USB_SETUP_PACKET_INIT_GET_STR_DESC(setup_pkt_ptr, string_index, desc_len) ({ \
+    (setup_pkt_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | USB_BM_REQUEST_TYPE_TYPE_STANDARD | USB_BM_REQUEST_TYPE_RECIP_DEVICE; \
+    (setup_pkt_ptr)->bRequest = USB_B_REQUEST_GET_DESCRIPTOR; \
+    (setup_pkt_ptr)->wValue = (USB_W_VALUE_DT_STRING << 8) | ((string_index) & 0xFF); \
+    (setup_pkt_ptr)->wIndex = 0; \
+    (setup_pkt_ptr)->wLength = (desc_len); \
+})
+
 // ---------------- Standard Descriptor --------------------
 
 /**

+ 133 - 0
components/usb/usb_helpers.c

@@ -8,8 +8,14 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <assert.h>
+#include <stdio.h>
+#include <string.h>
 #include "usb/usb_helpers.h"
 #include "usb/usb_types_ch9.h"
+#include "esp_check.h"
+#include "usb/usb_host.h"
+
+static const char *TAG = "usb_helper";
 
 // ---------------------------------------- Configuration Descriptor Parsing -------------------------------------------
 
@@ -165,4 +171,131 @@ const usb_ep_desc_t *usb_parse_endpoint_descriptor_by_address(const usb_config_d
     return ep_desc;
 }
 
+// ------------------------------------------ Descriptor printing  ---------------------------------------------
+
+static void print_ep_desc(const usb_ep_desc_t *ep_desc)
+{
+    const char *ep_type_str;
+    int type = ep_desc->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK;
+
+    switch (type) {
+        case USB_BM_ATTRIBUTES_XFER_CONTROL:
+            ep_type_str = "CTRL";
+            break;
+        case USB_BM_ATTRIBUTES_XFER_ISOC:
+            ep_type_str = "ISOC";
+            break;
+        case USB_BM_ATTRIBUTES_XFER_BULK:
+            ep_type_str = "BULK";
+            break;
+        case USB_BM_ATTRIBUTES_XFER_INT:
+            ep_type_str = "INT";
+            break;
+        default:
+            ep_type_str = NULL;
+            break;
+    }
+
+    printf("\t\t*** Endpoint descriptor ***\n");
+    printf("\t\tbLength %d\n", ep_desc->bLength);
+    printf("\t\tbDescriptorType %d\n", ep_desc->bDescriptorType);
+    printf("\t\tbEndpointAddress 0x%x\tEP %d %s\n", ep_desc->bEndpointAddress,
+           USB_EP_DESC_GET_EP_NUM(ep_desc),
+           USB_EP_DESC_GET_EP_DIR(ep_desc) ? "IN" : "OUT");
+    printf("\t\tbmAttributes 0x%x\t%s\n", ep_desc->bmAttributes, ep_type_str);
+    printf("\t\twMaxPacketSize %d\n", ep_desc->wMaxPacketSize);
+    printf("\t\tbInterval %d\n", ep_desc->bInterval);
+}
+
+static void usbh_print_intf_desc(const usb_intf_desc_t *intf_desc)
+{
+    printf("\t*** Interface descriptor ***\n");
+    printf("\tbLength %d\n", intf_desc->bLength);
+    printf("\tbDescriptorType %d\n", intf_desc->bDescriptorType);
+    printf("\tbInterfaceNumber %d\n", intf_desc->bInterfaceNumber);
+    printf("\tbAlternateSetting %d\n", intf_desc->bAlternateSetting);
+    printf("\tbNumEndpoints %d\n", intf_desc->bNumEndpoints);
+    printf("\tbInterfaceClass 0x%x\n", intf_desc->bInterfaceProtocol);
+    printf("\tiInterface %d\n", intf_desc->iInterface);
+}
+
+static void usbh_print_cfg_desc(const usb_config_desc_t *cfg_desc)
+{
+    printf("*** Configuration descriptor ***\n");
+    printf("bLength %d\n", cfg_desc->bLength);
+    printf("bDescriptorType %d\n", cfg_desc->bDescriptorType);
+    printf("wTotalLength %d\n", cfg_desc->wTotalLength);
+    printf("bNumInterfaces %d\n", cfg_desc->bNumInterfaces);
+    printf("bConfigurationValue %d\n", cfg_desc->bConfigurationValue);
+    printf("iConfiguration %d\n", cfg_desc->iConfiguration);
+    printf("bmAttributes 0x%x\n", cfg_desc->bmAttributes);
+    printf("bMaxPower %dmA\n", cfg_desc->bMaxPower * 2);
+}
+
+static void print_device_descriptor(const usb_device_desc_t *devc_desc)
+{
+    printf("*** Device descriptor ***\n");
+    printf("bLength %d\n", devc_desc->bLength);
+    printf("bDescriptorType %d\n", devc_desc->bDescriptorType);
+    printf("bcdUSB %d.%d0\n", ((devc_desc->bcdUSB >> 8) & 0xF), ((devc_desc->bcdUSB >> 4) & 0xF));
+    printf("bDeviceClass 0x%x\n", devc_desc->bDeviceClass);
+    printf("bDeviceSubClass 0x%x\n", devc_desc->bDeviceSubClass);
+    printf("bDeviceProtocol 0x%x\n", devc_desc->bDeviceProtocol);
+    printf("bMaxPacketSize0 %d\n", devc_desc->bMaxPacketSize0);
+    printf("idVendor 0x%x\n", devc_desc->idVendor);
+    printf("idProduct 0x%x\n", devc_desc->idProduct);
+    printf("bcdDevice %d.%d0\n", ((devc_desc->bcdDevice >> 8) & 0xF), ((devc_desc->bcdDevice >> 4) & 0xF));
+    printf("iManufacturer %d\n", devc_desc->iManufacturer);
+    printf("iProduct %d\n", devc_desc->iProduct);
+    printf("iSerialNumber %d\n", devc_desc->iSerialNumber);
+    printf("bNumConfigurations %d\n", devc_desc->bNumConfigurations);
+}
+
+static void print_config_descriptor(const usb_config_desc_t *cfg_desc, print_class_descriptor_cb class_specific_cb)
+{
+    int offset = 0;
+    uint16_t wTotalLength = cfg_desc->wTotalLength;
+    const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)cfg_desc;
+
+    do {
+        switch (next_desc->bDescriptorType) {
+            case USB_W_VALUE_DT_CONFIG:
+                usbh_print_cfg_desc((const usb_config_desc_t *)next_desc);
+                break;
+            case USB_W_VALUE_DT_INTERFACE:
+                usbh_print_intf_desc((const usb_intf_desc_t *)next_desc);
+                break;
+            case USB_W_VALUE_DT_ENDPOINT:
+                print_ep_desc((const usb_ep_desc_t *)next_desc);
+                break;
+            default:
+                if(class_specific_cb) {
+                    class_specific_cb(next_desc);
+                }
+                break;
+        }
+
+        next_desc = usb_parse_next_descriptor(next_desc, wTotalLength, &offset);
+
+    } while (next_desc != NULL);
+}
+
+esp_err_t usb_print_descriptors(usb_device_handle_t device, print_class_descriptor_cb class_specific_cb)
+{
+    if (device == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    const usb_config_desc_t *config_desc;
+    const usb_device_desc_t *device_desc;
+
+    ESP_RETURN_ON_ERROR( usb_host_get_device_descriptor(device, &device_desc), TAG, "Failed to get devices descriptor" );
+    ESP_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(device, &config_desc), TAG, "Failed to get config descriptor" );
+
+    print_device_descriptor(device_desc);
+    print_config_descriptor(config_desc, class_specific_cb);
+
+    return ESP_OK;
+}
+
 // ------------------------------------------------------ Misc ---------------------------------------------------------

+ 6 - 0
examples/peripherals/usb/host/msc/CMakeLists.txt

@@ -0,0 +1,6 @@
+# The following 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.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(usb-msc)

+ 60 - 0
examples/peripherals/usb/host/msc/README.md

@@ -0,0 +1,60 @@
+| Supported Targets | ESP32-S2 | ESP32-S3 |
+| ----------------- | -------- | -------- |
+
+# USB Mass Storage Class example
+
+## Overview
+
+This example demonstrates usage of Mass Storage Class to get access to storage on USB memory stick.
+Example caries out read and write file operations, as USB storage is mounted to Virtual filesystem.
+
+### Hardware Required
+
+* Development board with USB capable ESP SoC (ESP32-S2/ESP32-S3)
+* A USB cable for Power supply and programming
+* A USB memory stick
+
+### Common Pin Assignments
+
+If your board doesn't have a USB A connector connected to the dedicated GPIOs, 
+you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
+
+```
+ESP BOARD    USB CONNECTOR (type A)
+                   --
+                  | || VCC
+[GPIO19]  ------> | || D-
+[GPIO20]  ------> | || D+
+                  | || GND
+                   --
+```
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+(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
+
+```
+...
+I (274) cpu_start: Starting scheduler on PRO CPU.
+I (339) APP: Waiting for USB stick to be connected
+Device info:
+         PID: 0x5678 
+         VID: 0xFFFF 
+         iProduct: Disk 2.0 
+         iManufacturer: USB 
+         iSerialNumber: 92072836B2589224378 
+I (719) APP: Writing file
+I (749) APP: Reading file
+I (749) APP: Read from file: 'Hello World!'
+I (759) APP: Done
+```

+ 9 - 0
examples/peripherals/usb/host/msc/components/msc/CMakeLists.txt

@@ -0,0 +1,9 @@
+set(sources src/msc_scsi_bot.c
+            src/diskio_usb.c
+            src/msc_host.c
+            src/msc_host_vfs.c)
+
+idf_component_register( SRCS ${sources}
+                        INCLUDE_DIRS include
+                        PRIV_INCLUDE_DIRS private_include
+                        REQUIRES usb fatfs )

+ 32 - 0
examples/peripherals/usb/host/msc/components/msc/README.md

@@ -0,0 +1,32 @@
+# USB Host MSC (Mass Storage Class) Driver
+
+This directory contains an implementation of a USB Mass Storage Class Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
+
+MSC driver allows access to USB flash drivers using the BOT “Bulk-Only Transport” protocol and the Transparent SCSI command set.
+
+## Usage
+
+- First, usb host library has to be initialized by calling `usb_host_install`
+- USB Host Library events have to be handled by invoking `usb_host_lib_handle_events` periodically.
+  In general, an application should spawn a dedicated task handle USB Host Library events.
+  However, in order to save RAM, an already existing task can also be used to call `usb_host_lib_handle_events`.
+- Mass Storage Class driver is installed by calling `usb_msc_install` function along side with configuration.
+- Supplied configuration contains user provided callback function invoked whenever MSC device is connected/disconnected
+  and optional parameters for creating background task handling MSC related events. 
+  Alternatively, user can call `usb_msc_handle_events` function from already existing task.
+- After receiving `MSC_DEVICE_CONNECTED` event, user has to install device with `usb_msc_install_device` function,
+  obtaining MSC device handle.
+- USB descriptors can be printed out with `usb_msc_print_descriptors` and general information about MSC device retrieved
+  with `from usb_msc_get_device_info` function.
+- Obtained device handle is then used in helper function `usb_msc_vfs_register` mounting USB Disk to Virtual filesystem.
+- At this point, standard C functions for accessing storage (`fopen`, `fwrite`, `fread`, `mkdir` etc.) can be carried out.
+- In order to uninstall the whole USB stack, deinitializing counterparts to functions above has to be called in reverse order. 
+
+## Known issues
+
+- Driver only supports USB 2.0 flash drives using the BOT “Bulk-Only Transport” protocol and the Transparent SCSI command set
+- Composite USB devices are not supported
+
+## Troubleshooting
+
+After connecting composite USB device, driver prints `COMPOSITE DEVICES UNSUPPORTED` 

+ 169 - 0
examples/peripherals/usb/host/msc/components/msc/include/msc_host.h

@@ -0,0 +1,169 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <wchar.h>
+#include <stdint.h>
+#include "esp_err.h"
+#include <freertos/FreeRTOS.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ESP_ERR_MSC_HOST_BASE        0x1700                      /*!< MSC host error code base */
+#define ESP_ERR_MSC_MOUNT_FAILED    (ESP_ERR_MSC_HOST_BASE + 1)  /*!< Failed to mount storage */
+#define ESP_ERR_MSC_FORMAT_FAILED   (ESP_ERR_MSC_HOST_BASE + 2)  /*!< Failed to format storage */
+#define ESP_ERR_MSC_INTERNAL        (ESP_ERR_MSC_HOST_BASE + 3)  /*!< MSC host internal error */
+
+#define MSC_STR_DESC_SIZE 32
+
+typedef struct msc_host_device *msc_host_device_handle_t;     /**< Handle to a Mass Storage Device */
+
+/**
+ * @brief USB Mass Storage event containing event type and associated device handle.
+*/
+typedef struct {
+    enum {
+        MSC_DEVICE_CONNECTED,       /**< MSC device has been connected to the system.*/
+        MSC_DEVICE_DISCONNECTED,    /**< MSC device has been disconnected from the system.*/
+    } event;
+    union {
+        uint8_t address;                /**< Address of connected MSC device.*/
+        msc_host_device_handle_t handle; /**< MSC device handle to disconnected device.*/
+    } device;
+} msc_host_event_t;
+
+/**
+ * @brief USB Mass Storage event callback.
+ *
+ * @param[in] event mass storage event
+*/
+typedef void (*msc_host_event_cb_t)(const msc_host_event_t *event, void *arg);
+
+/**
+ * @brief MSC configuration structure.
+*/
+typedef struct {
+    bool create_backround_task;     /**< When set to true, background task handling usb events is created.
+                                         Otherwise user has to periodically call msc_host_handle_events function */
+    size_t task_priority;           /**< Task priority of crated background task */
+    size_t stack_size;              /**< Stack size of crated background task */
+    BaseType_t core_id;             /**< Select core on which background task will run or tskNO_AFFINITY  */
+    msc_host_event_cb_t callback;   /**< Callback invoked when MSC event occurs. Must not be NULL. */
+    void *callback_arg;             /**< User provided argument passed to callback */
+} msc_host_driver_config_t;
+
+/**
+ * @brief MSC device info.
+*/
+typedef struct {
+    uint32_t sector_count;
+    uint32_t sector_size;
+    uint16_t idProduct;
+    uint16_t idVendor;
+    wchar_t iManufacturer[MSC_STR_DESC_SIZE];
+    wchar_t iProduct[MSC_STR_DESC_SIZE];
+    wchar_t iSerialNumber[MSC_STR_DESC_SIZE];
+} msc_host_device_info_t;
+
+/**
+ * @brief Install USB Host Mass Storage Class driver
+ *
+ * @param[in] config configuration structure MSC to create
+ * @return esp_err_r
+ */
+esp_err_t msc_host_install(const msc_host_driver_config_t *config);
+
+/**
+ * @brief Uninstall Mass Storage Class driver
+ * @return esp_err_t
+ */
+esp_err_t msc_host_uninstall(void);
+
+/**
+ * @brief Initialization of MSC device.
+ *
+ * @param[in]  device_address  Device address obtained from MSC callback provided upon connection and enumeration
+ * @param[out] device          Mass storage device handle to be used for subsequent calls.
+ * @return esp_err_t
+ */
+esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *device);
+
+/**
+ * @brief Deinitialization of MSC device.
+ *
+ * @param[in]  device  Device handle obtained from msc_host_install_device function
+ * @return esp_err_t
+ */
+esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device);
+
+/**
+ * @brief Helper function for reading sector from mass storage device.
+ *
+ * @warning This call is not thread safe and should not be combined
+ *          with accesses to storage through file system.
+ *
+ * @note  Provided sector and size cannot exceed
+ *        sector_count and sector_size obtained from msc_host_device_info_t
+ *
+ * @param[in]  device Device handle
+ * @param[in]  sector Number of sector to be read
+ * @param[out] data   Buffer into which data will be written
+ * @param[in]  size   Number of bytes to be read
+ * @return esp_err_t
+ */
+esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size);
+
+/**
+ * @brief Helper function for writing sector to mass storage device.
+ *
+ * @warning This call is not thread safe and should not be combined
+ *          with accesses to storare through file system.
+ *
+ * @note  Provided sector and size cannot exceed
+ *        sector_count and sector_size obtained from msc_host_device_info_t
+ *
+ * @param[in]  device Device handle
+ * @param[in]  sector Number of sector to be read
+ * @param[in]  data   Data to be written to the sector
+ * @param[in]  size   Number of bytes to be written
+ * @return esp_err_t
+ */
+esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size);
+
+/**
+ * @brief Handle MSC HOST events.
+ *
+ * @param[in]  timeout_ms  Timeout in miliseconds
+ * @return esp_err_t
+ */
+esp_err_t msc_host_handle_events(uint32_t timeout_ms);
+
+/**
+ * @brief Gets devices information.
+ *
+ * @warning This call is not thread safe and should not be combined
+ *          with accesses to storare through file system.
+ *
+ * @param[in]  device  Handle to device
+ * @param[out] info  Structure to be populated with device info
+ * @return esp_err_t
+ */
+esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info);
+
+/**
+ * @brief Print configuration descriptor.
+ *
+ * @param[in]  device  Handle of MSC device
+ * @return esp_err_t
+ */
+esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device);
+
+#ifdef __cplusplus
+}
+#endif //__cplusplus

+ 44 - 0
examples/peripherals/usb/host/msc/components/msc/include/msc_host_vfs.h

@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include "esp_vfs_fat.h"
+#include "msc_host.h"
+#include "esp_err.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct msc_host_vfs *msc_host_vfs_handle_t;           /**< VFS handle to attached Mass Storage device */
+
+/**
+ * @brief Register MSC device to Virtual filesystem.
+ *
+ * @param[in]  device  Device handle obtained from MSC callback provided upon initialization
+ * @param[in]  base_path Base VFS path to be used to access file storage
+ * @param[in]  mount_config Mount configuration.
+ * @param[out] vfs_handle Handle to MSC device associated with registered VFS
+ * @return esp_err_t
+ */
+esp_err_t msc_host_vfs_register(msc_host_device_handle_t device,
+                                const char *base_path,
+                                const esp_vfs_fat_mount_config_t *mount_config,
+                                msc_host_vfs_handle_t *vfs_handle);
+
+
+/**
+ * @brief Unregister MSC device from Virtual filesystem.
+ *
+ * @param[in]  vfs_handle  VFS handle obtained from MSC callback provided upon initialization
+ * @return esp_err_t
+ */
+esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle);
+
+#ifdef __cplusplus
+}
+#endif

+ 39 - 0
examples/peripherals/usb/host/msc/components/msc/private_include/diskio_usb.h

@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Mass storage disk initialization structure
+ */
+typedef struct {
+    uint32_t block_size;    /**< Block size */
+    uint32_t block_count;   /**< Block count */
+} usb_disk_t;
+
+/**
+ * @brief Register mass storage disk to fat file system
+ *
+ * @param[in] pdrv Number of free drive obtained from ff_diskio_get_drive() function
+ * @param[in] disk usb_disk_t structure
+ */
+void ff_diskio_register_msc(uint8_t pdrv, usb_disk_t *disk);
+
+/**
+ * @brief Obtains number of drive assigned to usb disk upon calling ff_diskio_register_msc()
+ *
+ * @param[in] disk usb_disk_t structure
+ * @return Drive number
+ */
+uint8_t ff_diskio_get_pdrv_disk(const usb_disk_t *disk);
+
+#ifdef __cplusplus
+}
+#endif //__cplusplus

+ 61 - 0
examples/peripherals/usb/host/msc/components/msc/private_include/msc_common.h

@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/queue.h>
+#include "esp_err.h"
+#include "esp_check.h"
+#include "diskio_usb.h"
+#include "usb/usb_host.h"
+#include "usb/usb_types_stack.h"
+#include "freertos/semphr.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef enum {
+    MSC_EP_OUT,
+    MSC_EP_IN
+} msc_endpoint_t;
+
+typedef struct {
+    uint16_t bulk_in_mps;
+    uint8_t bulk_in_ep;
+    uint8_t bulk_out_ep;
+    uint8_t iface_num;
+} msc_config_t;
+
+typedef struct msc_host_device {
+    STAILQ_ENTRY(msc_host_device) tailq_entry;
+    usb_transfer_status_t transfer_status;
+    SemaphoreHandle_t transfer_done;
+    usb_device_handle_t handle;
+    usb_transfer_t *xfer;
+    msc_config_t config;
+    usb_disk_t disk;
+} msc_device_t;
+
+esp_err_t msc_bulk_transfer(msc_device_t *device_handle, uint8_t *data, size_t size, msc_endpoint_t ep);
+
+esp_err_t msc_control_transfer(msc_device_t *device_handle, usb_transfer_t *xfer, size_t len);
+
+#define MSC_GOTO_ON_ERROR(exp) ESP_GOTO_ON_ERROR(exp, fail, TAG, "")
+
+#define MSC_GOTO_ON_FALSE(exp, err) ESP_GOTO_ON_FALSE( (exp), err, fail, TAG, "" )
+
+#define MSC_RETURN_ON_ERROR(exp) ESP_RETURN_ON_ERROR((exp), TAG, "")
+
+#define MSC_RETURN_ON_FALSE(exp, err) ESP_RETURN_ON_FALSE( (exp), (err), TAG, "")
+
+#define MSC_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "")
+
+#ifdef __cplusplus
+}
+#endif

+ 56 - 0
examples/peripherals/usb/host/msc/components/msc/private_include/msc_scsi_bot.h

@@ -0,0 +1,56 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include "esp_err.h"
+#include "msc_common.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct {
+    uint8_t key;
+    uint8_t code;
+    uint8_t code_q;
+} scsi_sense_data_t;
+
+esp_err_t scsi_cmd_read10(msc_device_t *device,
+                          uint8_t *data,
+                          uint32_t sector_address,
+                          uint32_t num_sectors,
+                          uint32_t sector_size);
+
+esp_err_t scsi_cmd_write10(msc_device_t *device,
+                           const uint8_t *data,
+                           uint32_t sector_address,
+                           uint32_t num_sectors,
+                           uint32_t sector_size);
+
+esp_err_t scsi_cmd_read_capacity(msc_device_t *device,
+                                 uint32_t *block_size,
+                                 uint32_t *block_count);
+
+esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense);
+
+esp_err_t scsi_cmd_unit_ready(msc_device_t *device);
+
+esp_err_t scsi_cmd_inquiry(msc_device_t *device);
+
+esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent);
+
+esp_err_t scsi_cmd_mode_sense(msc_device_t *device);
+
+esp_err_t msc_mass_reset(msc_device_t *device);
+
+esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun);
+
+#ifdef __cplusplus
+}
+#endif

+ 118 - 0
examples/peripherals/usb/host/msc/components/msc/src/diskio_usb.c

@@ -0,0 +1,118 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "diskio_impl.h"
+#include "ffconf.h"
+#include "ff.h"
+#include "esp_log.h"
+#include "diskio_usb.h"
+#include "msc_scsi_bot.h"
+#include "msc_common.h"
+#include "usb/usb_types_stack.h"
+
+static usb_disk_t *s_disks[FF_VOLUMES] = { NULL };
+
+static const char *TAG = "diskio_usb";
+
+static DSTATUS usb_disk_initialize (BYTE pdrv)
+{
+    return RES_OK;
+}
+
+static DSTATUS usb_disk_status (BYTE pdrv)
+{
+    return RES_OK;
+}
+
+static DRESULT usb_disk_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
+{
+    assert(pdrv < FF_VOLUMES);
+    assert(s_disks[pdrv]);
+
+    esp_err_t err;
+    usb_disk_t *disk = s_disks[pdrv];
+    size_t sector_size = disk->block_size;
+    msc_device_t *dev = __containerof(disk, msc_device_t, disk);
+
+    for (int i = 0; i < count; i++) {
+        err = scsi_cmd_read10(dev, &buff[i * sector_size], sector + i, 1, sector_size);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "scsi_cmd_read10 failed (%d)", err);
+            return RES_ERROR;
+        }
+
+    }
+
+    return RES_OK;
+}
+
+static DRESULT usb_disk_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
+{
+    assert(pdrv < FF_VOLUMES);
+    assert(s_disks[pdrv]);
+
+    esp_err_t err;
+    usb_disk_t *disk = s_disks[pdrv];
+    size_t sector_size = disk->block_size;
+    msc_device_t *dev = __containerof(disk, msc_device_t, disk);
+
+    for (int i = 0; i < count; i++) {
+        err = scsi_cmd_write10(dev, &buff[i * sector_size], sector + i, 1, sector_size);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "scsi_cmd_write10 failed (%d)", err);
+            return RES_ERROR;
+        }
+
+    }
+    return RES_OK;
+}
+
+static DRESULT usb_disk_ioctl (BYTE pdrv, BYTE cmd, void *buff)
+{
+    assert(pdrv < FF_VOLUMES);
+    assert(s_disks[pdrv]);
+
+    usb_disk_t *disk = s_disks[pdrv];
+
+    switch (cmd) {
+    case CTRL_SYNC:
+        return RES_OK;
+    case GET_SECTOR_COUNT:
+        *((DWORD *) buff) = disk->block_count;
+        return RES_OK;
+    case GET_SECTOR_SIZE:
+        *((WORD *) buff) = disk->block_size;
+        return RES_OK;
+    case GET_BLOCK_SIZE:
+        return RES_ERROR;
+    }
+    return RES_ERROR;
+}
+
+void ff_diskio_register_msc(BYTE pdrv, usb_disk_t *disk)
+{
+    assert(pdrv < FF_VOLUMES);
+
+    static const ff_diskio_impl_t usb_disk_impl = {
+        .init = &usb_disk_initialize,
+        .status = &usb_disk_status,
+        .read = &usb_disk_read,
+        .write = &usb_disk_write,
+        .ioctl = &usb_disk_ioctl
+    };
+    s_disks[pdrv] = disk;
+    ff_diskio_register(pdrv, &usb_disk_impl);
+}
+
+BYTE ff_diskio_get_pdrv_disk(const usb_disk_t *disk)
+{
+    for (int i = 0; i < FF_VOLUMES; i++) {
+        if (disk == s_disks[i]) {
+            return i;
+        }
+    }
+    return 0xff;
+}

+ 547 - 0
examples/peripherals/usb/host/msc/components/msc/src/msc_host.c

@@ -0,0 +1,547 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "usb/usb_host.h"
+#include "diskio_usb.h"
+#include "msc_common.h"
+#include "msc_host.h"
+#include "msc_scsi_bot.h"
+#include "usb/usb_types_ch9.h"
+#include "usb/usb_helpers.h"
+
+static portMUX_TYPE msc_lock = portMUX_INITIALIZER_UNLOCKED;
+
+#define MSC_ENTER_CRITICAL()    portENTER_CRITICAL(&msc_lock)
+#define MSC_EXIT_CRITICAL()     portEXIT_CRITICAL(&msc_lock)
+
+#define MSC_GOTO_ON_FALSE_CRITICAL(exp, err)    \
+    do {                                        \
+        if(!(exp)) {                            \
+            MSC_EXIT_CRITICAL();                \
+            ret = err;                          \
+            goto fail;                          \
+        }                                       \
+    } while(0)
+
+#define MSC_RETURN_ON_FALSE_CRITICAL(exp, err)  \
+    do {                                        \
+        if(!(exp)) {                            \
+            MSC_EXIT_CRITICAL();                \
+            return err;                         \
+        }                                       \
+    } while(0)
+
+#define WAIT_FOR_READY_TIMEOUT_MS 3000
+#define TAG "USB_MSC"
+
+#define SCSI_COMMAND_SET    0x06
+#define BULK_ONLY_TRANSFER  0x50
+#define MSC_NO_SENSE        0x00
+#define MSC_NOT_READY       0x02
+#define MSC_UNIT_ATTENTION  0x06
+
+typedef struct {
+    usb_host_client_handle_t client_handle;
+    msc_host_event_cb_t user_cb;
+    void *user_arg;
+    SemaphoreHandle_t all_events_handled;
+    volatile bool end_client_event_handling;
+} msc_driver_t;
+
+static msc_driver_t *s_msc_driver;
+
+STAILQ_HEAD(devices, msc_host_device) devices_tailq;
+
+static const usb_standard_desc_t *next_interface_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset)
+{
+    return usb_parse_next_descriptor_of_type(desc, len, USB_W_VALUE_DT_INTERFACE, (int *)offset);
+}
+
+static const usb_standard_desc_t *next_endpoint_desc(const usb_standard_desc_t *desc, size_t len, size_t *offset)
+{
+    return usb_parse_next_descriptor_of_type(desc, len, USB_B_DESCRIPTOR_TYPE_ENDPOINT, (int *)offset);
+}
+
+static const usb_intf_desc_t *find_msc_interface(const usb_config_desc_t *config_desc, size_t *offset)
+{
+    size_t total_length = config_desc->wTotalLength;
+    const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)config_desc;
+
+    next_desc = next_interface_desc(next_desc, total_length, offset);
+
+    while ( next_desc ) {
+
+        const usb_intf_desc_t *ifc_desc = (const usb_intf_desc_t *)next_desc;
+
+        if ( ifc_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE &&
+                ifc_desc->bInterfaceSubClass == SCSI_COMMAND_SET &&
+                ifc_desc->bInterfaceProtocol == BULK_ONLY_TRANSFER ) {
+            return ifc_desc;
+        }
+
+        next_desc = next_interface_desc(next_desc, total_length, offset);
+    };
+    return NULL;
+}
+
+/**
+ * @brief Extracts configuration from configuration descriptor.
+ *
+ * @note  Passes interface and endpoint descriptors to obtain:
+
+ *        - interface number, IN endpoint, OUT endpoint, max. packet size
+ *
+ * @param[in]  cfg_desc  Configuration descriptor
+ * @param[out] cfg       Obtained configuration
+ * @return esp_err_t
+ */
+static esp_err_t extract_config_from_descriptor(const usb_config_desc_t *cfg_desc, msc_config_t *cfg)
+{
+    size_t offset = 0;
+    size_t total_len = cfg_desc->wTotalLength;
+    const usb_intf_desc_t *ifc_desc = find_msc_interface(cfg_desc, &offset);
+    assert(ifc_desc);
+    const usb_standard_desc_t *next_desc = (const usb_standard_desc_t *)ifc_desc;
+    const usb_ep_desc_t *ep_desc = NULL;
+
+    cfg->iface_num = ifc_desc->bInterfaceNumber;
+
+    next_desc = next_endpoint_desc(next_desc, total_len, &offset);
+    MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED);
+    ep_desc = (const usb_ep_desc_t *)next_desc;
+
+    if (ep_desc->bEndpointAddress & 0x80) {
+        cfg->bulk_in_ep = ep_desc->bEndpointAddress;
+        cfg->bulk_in_mps = ep_desc->wMaxPacketSize;
+    } else {
+        cfg->bulk_out_ep = ep_desc->bEndpointAddress;
+    }
+
+    next_desc = next_endpoint_desc(next_desc, total_len, &offset);
+    MSC_RETURN_ON_FALSE(next_desc, ESP_ERR_NOT_SUPPORTED);
+    ep_desc = (const usb_ep_desc_t *)next_desc;
+
+    if (ep_desc->bEndpointAddress & 0x80) {
+        cfg->bulk_in_ep = ep_desc->bEndpointAddress;
+        cfg->bulk_in_mps = ep_desc->wMaxPacketSize;
+    } else {
+        cfg->bulk_out_ep = ep_desc->bEndpointAddress;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t msc_deinit_device(msc_device_t *dev, bool install_failed)
+{
+    MSC_ENTER_CRITICAL();
+    MSC_RETURN_ON_FALSE_CRITICAL( dev, ESP_ERR_INVALID_STATE );
+    STAILQ_REMOVE(&devices_tailq, dev, msc_host_device, tailq_entry);
+    MSC_EXIT_CRITICAL();
+
+    if (dev->transfer_done) {
+        vSemaphoreDelete(dev->transfer_done);
+    }
+    if (install_failed) {
+        // Error code is unchecked, as it's unknown at what point installation failed.
+        usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num);
+        usb_host_device_close(s_msc_driver->client_handle, dev->handle);
+        usb_host_transfer_free(dev->xfer);
+    } else {
+        MSC_RETURN_ON_ERROR( usb_host_interface_release(s_msc_driver->client_handle, dev->handle, dev->config.iface_num) );
+        MSC_RETURN_ON_ERROR( usb_host_device_close(s_msc_driver->client_handle, dev->handle) );
+        MSC_RETURN_ON_ERROR( usb_host_transfer_free(dev->xfer) );
+    }
+
+    free(dev);
+    return ESP_OK;
+}
+
+// Some MSC devices requires to change its internal state from non-ready to ready
+static esp_err_t msc_wait_for_ready_state(msc_device_t *dev, size_t timeout_ms)
+{
+    esp_err_t err;
+    scsi_sense_data_t sense;
+    uint32_t trials = MAX(1, timeout_ms / 100);
+
+    do {
+        err = scsi_cmd_unit_ready(dev);
+        if (err != ESP_OK) {
+            MSC_RETURN_ON_ERROR( scsi_cmd_sense(dev, &sense) );
+            if (sense.key != MSC_NOT_READY &&
+                    sense.key != MSC_UNIT_ATTENTION &&
+                    sense.key != MSC_NO_SENSE) {
+                return ESP_ERR_MSC_INTERNAL;
+            }
+        }
+        vTaskDelay( pdMS_TO_TICKS(100) );
+    } while (trials-- && err);
+
+    return err;
+}
+
+static bool is_mass_storage_device(uint8_t dev_addr)
+{
+    size_t dummy = 0;
+    bool is_msc_device = false;
+    usb_device_handle_t device;
+    const usb_config_desc_t *config_desc;
+
+    if ( usb_host_device_open(s_msc_driver->client_handle, dev_addr, &device) == ESP_OK) {
+        if ( usb_host_get_active_config_descriptor(device, &config_desc) == ESP_OK ) {
+            if ( find_msc_interface(config_desc, &dummy) ) {
+                is_msc_device = true;
+            } else {
+                ESP_LOGD(TAG, "Connected USB device is not MSC");
+            }
+        }
+        usb_host_device_close(s_msc_driver->client_handle, device);
+    }
+
+    return is_msc_device;
+}
+
+static void event_handler_task(void *arg)
+{
+    while (1) {
+        usb_host_client_handle_events(s_msc_driver->client_handle, pdMS_TO_TICKS(50));
+
+        if (s_msc_driver->end_client_event_handling) {
+            break;
+        }
+    }
+    usb_host_client_unblock(s_msc_driver->client_handle);
+    ESP_ERROR_CHECK( usb_host_client_deregister(s_msc_driver->client_handle) );
+    xSemaphoreGive(s_msc_driver->all_events_handled);
+    vTaskDelete(NULL);
+}
+
+static msc_device_t *find_msc_device(usb_device_handle_t device_handle)
+{
+    msc_host_device_handle_t device;
+
+    STAILQ_FOREACH(device, &devices_tailq, tailq_entry) {
+        if (device_handle == device->handle) {
+            return device;
+        }
+    }
+
+    return NULL;
+}
+
+static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg)
+{
+    if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) {
+        if (is_mass_storage_device(event->new_dev.address)) {
+            const msc_host_event_t msc_event = {
+                .event = MSC_DEVICE_CONNECTED,
+                .device.address = event->new_dev.address,
+            };
+            s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg);
+        }
+    } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) {
+        msc_device_t *msc_device = find_msc_device(event->dev_gone.dev_hdl);
+        if (msc_device) {
+            const msc_host_event_t msc_event = {
+                .event = MSC_DEVICE_DISCONNECTED,
+                .device.handle = msc_device,
+            };
+            s_msc_driver->user_cb(&msc_event, s_msc_driver->user_arg);
+        }
+    }
+}
+
+esp_err_t msc_host_install(const msc_host_driver_config_t *config)
+{
+    esp_err_t ret;
+
+    MSC_RETURN_ON_INVALID_ARG(config);
+    MSC_RETURN_ON_INVALID_ARG(config->callback);
+    if ( config->create_backround_task ) {
+        MSC_RETURN_ON_FALSE(config->stack_size != 0, ESP_ERR_INVALID_ARG);
+        MSC_RETURN_ON_FALSE(config->task_priority != 0, ESP_ERR_INVALID_ARG);
+    }
+    MSC_RETURN_ON_FALSE(!s_msc_driver, ESP_ERR_INVALID_STATE);
+
+    msc_driver_t *driver = calloc(1, sizeof(msc_driver_t));
+    MSC_RETURN_ON_FALSE(driver, ESP_ERR_NO_MEM);
+    driver->user_cb = config->callback;
+    driver->user_arg = config->callback_arg;
+
+    usb_host_client_config_t client_config = {
+        .async.client_event_callback = client_event_cb,
+        .async.callback_arg = NULL,
+        .max_num_event_msg = 10,
+    };
+
+    driver->end_client_event_handling = false;
+    driver->all_events_handled = xSemaphoreCreateBinary();
+    MSC_GOTO_ON_FALSE(driver->all_events_handled, ESP_ERR_NO_MEM);
+
+    MSC_GOTO_ON_ERROR( usb_host_client_register(&client_config, &driver->client_handle) );
+
+    MSC_ENTER_CRITICAL();
+    MSC_GOTO_ON_FALSE_CRITICAL(!s_msc_driver, ESP_ERR_INVALID_STATE);
+    s_msc_driver = driver;
+    STAILQ_INIT(&devices_tailq);
+    MSC_EXIT_CRITICAL();
+
+    if (config->create_backround_task) {
+        BaseType_t task_created = xTaskCreatePinnedToCore(
+            event_handler_task, "USB MSC", config->stack_size,
+            NULL, config->task_priority, NULL, config->core_id);
+        MSC_GOTO_ON_FALSE(task_created, ESP_ERR_NO_MEM);
+    }
+
+    return ESP_OK;
+
+fail:
+    s_msc_driver = NULL;
+    usb_host_client_deregister(driver->client_handle);
+    if (driver->all_events_handled) {
+        vSemaphoreDelete(driver->all_events_handled);
+    }
+    free(driver);
+    return ret;
+}
+
+esp_err_t msc_host_uninstall(void)
+{
+    // Make sure msc driver is installed,
+    // not being uninstalled from other task
+    // and no msc device is registered
+    MSC_ENTER_CRITICAL();
+    MSC_RETURN_ON_FALSE_CRITICAL( s_msc_driver != NULL, ESP_ERR_INVALID_STATE );
+    MSC_RETURN_ON_FALSE_CRITICAL( !s_msc_driver->end_client_event_handling, ESP_ERR_INVALID_STATE );
+    MSC_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&devices_tailq), ESP_ERR_INVALID_STATE );
+    s_msc_driver->end_client_event_handling = true;
+    MSC_EXIT_CRITICAL();
+
+    xSemaphoreTake(s_msc_driver->all_events_handled, portMAX_DELAY);
+    vSemaphoreDelete(s_msc_driver->all_events_handled);
+    free(s_msc_driver);
+    s_msc_driver = NULL;
+    return ESP_OK;
+}
+
+esp_err_t msc_host_install_device(uint8_t device_address, msc_host_device_handle_t *msc_device_handle)
+{
+    esp_err_t ret;
+    uint32_t block_size, block_count;
+    const usb_config_desc_t *config_desc;
+    msc_device_t *msc_device;
+    uint8_t lun;
+    size_t transfer_size = 512; // Normally the smallest block size
+
+    MSC_GOTO_ON_FALSE( msc_device = calloc(1, sizeof(msc_device_t)), ESP_ERR_NO_MEM );
+
+    MSC_ENTER_CRITICAL();
+    MSC_GOTO_ON_FALSE_CRITICAL( s_msc_driver, ESP_ERR_INVALID_STATE );
+    MSC_GOTO_ON_FALSE_CRITICAL( s_msc_driver->client_handle, ESP_ERR_INVALID_STATE );
+    STAILQ_INSERT_TAIL(&devices_tailq, msc_device, tailq_entry);
+    MSC_EXIT_CRITICAL();
+
+    MSC_GOTO_ON_FALSE( msc_device->transfer_done = xSemaphoreCreateBinary(), ESP_ERR_NO_MEM);
+    MSC_GOTO_ON_ERROR( usb_host_device_open(s_msc_driver->client_handle, device_address, &msc_device->handle) );
+    MSC_GOTO_ON_ERROR( usb_host_get_active_config_descriptor(msc_device->handle, &config_desc) );
+    MSC_GOTO_ON_ERROR( extract_config_from_descriptor(config_desc, &msc_device->config) );
+    MSC_GOTO_ON_ERROR( usb_host_transfer_alloc(transfer_size, 0, &msc_device->xfer) );
+    MSC_GOTO_ON_ERROR( usb_host_interface_claim(s_msc_driver->client_handle,
+                                                msc_device->handle,
+                                                msc_device->config.iface_num, 0) );
+
+    MSC_GOTO_ON_ERROR( msc_mass_reset(msc_device) );
+    MSC_GOTO_ON_ERROR( msc_get_max_lun(msc_device, &lun) );
+    MSC_GOTO_ON_ERROR( scsi_cmd_inquiry(msc_device) );
+    MSC_GOTO_ON_ERROR( msc_wait_for_ready_state(msc_device, WAIT_FOR_READY_TIMEOUT_MS) );
+    MSC_GOTO_ON_ERROR( scsi_cmd_read_capacity(msc_device, &block_size, &block_count) );
+
+    // Configuration descriptor size of simple MSC device is 32 bytes.
+    if (config_desc->wTotalLength != 32) {
+        ESP_LOGE(TAG, "COMPOSITE DEVICES UNSUPPORTED");
+    }
+
+    msc_device->disk.block_size = block_size;
+    msc_device->disk.block_count = block_count;
+
+    if (block_size > transfer_size) {
+        usb_transfer_t *larger_xfer;
+        MSC_GOTO_ON_ERROR( usb_host_transfer_alloc(block_size, 0, &larger_xfer) );
+        usb_host_transfer_free(msc_device->xfer);
+        msc_device->xfer = larger_xfer;
+    }
+
+    *msc_device_handle = msc_device;
+
+    return ESP_OK;
+
+fail:
+    msc_deinit_device(msc_device, true);
+    return ret;
+}
+
+esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device)
+{
+    MSC_RETURN_ON_INVALID_ARG(device);
+    return msc_deinit_device((msc_device_t *)device, false);
+}
+
+
+esp_err_t msc_host_read_sector(msc_host_device_handle_t device, size_t sector, void *data, size_t size)
+{
+    MSC_RETURN_ON_INVALID_ARG(device);
+    msc_device_t *dev = (msc_device_t *)device;
+
+    return scsi_cmd_read10(dev, data, sector, 1, dev->disk.block_size);
+}
+
+esp_err_t msc_host_write_sector(msc_host_device_handle_t device, size_t sector, const void *data, size_t size)
+{
+    MSC_RETURN_ON_INVALID_ARG(device);
+    msc_device_t *dev = (msc_device_t *)device;
+
+    return scsi_cmd_write10(dev, data, sector, 1, dev->disk.block_size);
+}
+
+esp_err_t msc_host_handle_events(uint32_t timeout_ms)
+{
+    MSC_RETURN_ON_FALSE(s_msc_driver != NULL, ESP_ERR_INVALID_STATE);
+
+    return usb_host_client_handle_events(s_msc_driver->client_handle, timeout_ms);
+}
+
+static esp_err_t msc_read_string_desc(msc_device_t *dev, uint8_t index, wchar_t *str)
+{
+    if (index == 0) {
+        // String descriptor not available
+        str[0] = 0;
+        return ESP_OK;
+    }
+
+    usb_transfer_t *xfer = dev->xfer;
+    USB_SETUP_PACKET_INIT_GET_STR_DESC((usb_setup_packet_t *)xfer->data_buffer, index, 64);
+    MSC_RETURN_ON_ERROR( msc_control_transfer(dev, xfer, USB_SETUP_PACKET_SIZE + 64) );
+
+    usb_standard_desc_t *desc = (usb_standard_desc_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE);
+    wchar_t *data = (wchar_t *)(xfer->data_buffer + USB_SETUP_PACKET_SIZE + 2);
+    size_t len = MIN((desc->bLength - USB_STANDARD_DESC_SIZE) / 2, MSC_STR_DESC_SIZE - 1);
+
+    wcsncpy(str, data, len);
+    str[len] = 0;
+
+    return ESP_OK;
+}
+
+esp_err_t msc_host_get_device_info(msc_host_device_handle_t device, msc_host_device_info_t *info)
+{
+    MSC_RETURN_ON_INVALID_ARG(device);
+    MSC_RETURN_ON_INVALID_ARG(info);
+
+    msc_device_t *dev = (msc_device_t *)device;
+    const usb_device_desc_t *desc;
+
+    MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &desc) );
+
+    info->idProduct = desc->idProduct;
+    info->idVendor = desc->idVendor;
+    info->sector_size = dev->disk.block_size;
+    info->sector_count = dev->disk.block_count;
+
+    MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iManufacturer, info->iManufacturer) );
+    MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iProduct, info->iProduct) );
+    MSC_RETURN_ON_ERROR( msc_read_string_desc(dev, desc->iSerialNumber, info->iSerialNumber) );
+
+    return ESP_OK;
+}
+
+esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device)
+{
+    return usb_print_descriptors(((msc_device_t *)device)->handle, NULL);
+}
+
+static void transfer_callback(usb_transfer_t *transfer)
+{
+    msc_device_t *device = (msc_device_t *)transfer->context;
+
+    if (transfer->status != USB_TRANSFER_STATUS_COMPLETED) {
+        ESP_LOGE("Transfer failed", "Status %d", transfer->status);
+    }
+
+    device->transfer_status = transfer->status;
+    xSemaphoreGive(device->transfer_done);
+}
+
+static esp_err_t wait_for_transfer_done(usb_transfer_t *xfer)
+{
+    msc_device_t *device = (msc_device_t *)xfer->context;
+    BaseType_t received = xSemaphoreTake(device->transfer_done, pdMS_TO_TICKS(xfer->timeout_ms));
+
+    if (received != pdTRUE) {
+        usb_host_endpoint_halt(xfer->device_handle, xfer->bEndpointAddress);
+        usb_host_endpoint_flush(xfer->device_handle, xfer->bEndpointAddress);
+        xSemaphoreTake(device->transfer_done, portMAX_DELAY);
+        return ESP_ERR_TIMEOUT;
+    }
+
+    return (device->transfer_status == USB_TRANSFER_STATUS_COMPLETED) ? ESP_OK : ESP_FAIL;
+}
+
+static inline bool is_in_endpoint(uint8_t endpoint)
+{
+    return endpoint & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK ? true : false;
+}
+
+esp_err_t msc_bulk_transfer(msc_device_t *device, uint8_t *data, size_t size, msc_endpoint_t ep)
+{
+    usb_transfer_t *xfer = device->xfer;
+    MSC_RETURN_ON_FALSE(size <= xfer->data_buffer_size, ESP_ERR_INVALID_SIZE);
+    uint8_t endpoint = (ep == MSC_EP_IN) ? device->config.bulk_in_ep : device->config.bulk_out_ep;
+
+    if (is_in_endpoint(endpoint)) {
+        xfer->num_bytes = usb_round_up_to_mps(size, device->config.bulk_in_mps);
+    } else {
+        memcpy(xfer->data_buffer, data, size);
+        xfer->num_bytes = size;
+    }
+
+    xfer->device_handle = device->handle;
+    xfer->bEndpointAddress = endpoint;
+    xfer->callback = transfer_callback;
+    xfer->timeout_ms = 1000;
+    xfer->context = device;
+
+    MSC_RETURN_ON_ERROR( usb_host_transfer_submit(xfer) );
+    MSC_RETURN_ON_ERROR( wait_for_transfer_done(xfer) );
+
+    if (is_in_endpoint(endpoint)) {
+        memcpy(data, xfer->data_buffer, size);
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t msc_control_transfer(msc_device_t *device, usb_transfer_t *xfer, size_t len)
+{
+    xfer->device_handle = device->handle;
+    xfer->bEndpointAddress = 0;
+    xfer->callback = transfer_callback;
+    xfer->timeout_ms = 1000;
+    xfer->num_bytes = len;
+    xfer->context = device;
+
+    MSC_RETURN_ON_ERROR( usb_host_transfer_submit_control(s_msc_driver->client_handle, xfer));
+    return wait_for_transfer_done(xfer);
+}

+ 124 - 0
examples/peripherals/usb/host/msc/components/msc/src/msc_host_vfs.c

@@ -0,0 +1,124 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+#include "msc_common.h"
+#include "msc_host_vfs.h"
+#include "diskio_impl.h"
+#include "ffconf.h"
+#include "ff.h"
+
+#define DRIVE_STR_LEN 3
+
+typedef struct msc_host_vfs {
+    char drive[DRIVE_STR_LEN];
+    char *base_path;
+    uint8_t pdrv;
+} msc_host_vfs_t;
+
+static const char *TAG = "MSC VFS";
+
+static esp_err_t msc_format_storage(size_t block_size, size_t allocation_size, const char *drv)
+{
+    void *workbuf = NULL;
+    const size_t workbuf_size = 4096;
+
+    MSC_RETURN_ON_FALSE( workbuf = ff_memalloc(workbuf_size), ESP_ERR_NO_MEM );
+
+    // Valid value of cluster size is between sector_size and 128 * sector_size.
+    size_t cluster_size = MIN(MAX(allocation_size, block_size), 128 * block_size);
+
+    FRESULT err = f_mkfs(drv, FM_ANY | FM_SFD, cluster_size, workbuf, workbuf_size);
+    if (err) {
+        ESP_LOGE(TAG, "Formatting failed with error: %d", err);
+        free(workbuf);
+        return ESP_ERR_MSC_FORMAT_FAILED;
+    }
+
+    free(workbuf);
+    return ESP_OK;
+}
+
+static void dealloc_msc_vfs(msc_host_vfs_t *vfs)
+{
+    free(vfs->base_path);
+    free(vfs);
+}
+
+esp_err_t msc_host_vfs_register(msc_host_device_handle_t device,
+                                const char *base_path,
+                                const esp_vfs_fat_mount_config_t *mount_config,
+                                msc_host_vfs_handle_t *vfs_handle)
+{
+    MSC_RETURN_ON_INVALID_ARG(device);
+    MSC_RETURN_ON_INVALID_ARG(base_path);
+    MSC_RETURN_ON_INVALID_ARG(mount_config);
+    MSC_RETURN_ON_INVALID_ARG(vfs_handle);
+
+    FATFS *fs = NULL;
+    BYTE pdrv;
+    bool diskio_registered = false;
+    esp_err_t ret = ESP_ERR_MSC_MOUNT_FAILED;
+    msc_device_t *dev = (msc_device_t *)device;
+    size_t block_size = dev->disk.block_size;
+    size_t alloc_size = mount_config->allocation_unit_size;
+
+    msc_host_vfs_t *vfs = calloc(1, sizeof(msc_host_vfs_t));
+    MSC_RETURN_ON_FALSE(vfs != NULL, ESP_ERR_NO_MEM);
+
+    MSC_GOTO_ON_ERROR( ff_diskio_get_drive(&pdrv) );
+
+    ff_diskio_register_msc(pdrv, &dev->disk);
+    char drive[DRIVE_STR_LEN] = {(char)('0' + pdrv), ':', 0};
+    diskio_registered = true;
+
+    strncpy(vfs->drive, drive, DRIVE_STR_LEN);
+    MSC_GOTO_ON_FALSE( vfs->base_path = strdup(base_path), ESP_ERR_NO_MEM );
+    vfs->pdrv = pdrv;
+
+    MSC_GOTO_ON_ERROR( esp_vfs_fat_register(base_path, drive, mount_config->max_files, &fs) );
+
+    FRESULT fresult = f_mount(fs, drive, 1);
+
+    if ( fresult != FR_OK) {
+        if (mount_config->format_if_mount_failed &&
+                (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR)) {
+            MSC_GOTO_ON_ERROR( msc_format_storage(block_size, alloc_size, drive) );
+            MSC_GOTO_ON_FALSE( f_mount(fs, drive, 0) == FR_OK, ESP_ERR_MSC_MOUNT_FAILED );
+        } else {
+            goto fail;
+        }
+    }
+
+    *vfs_handle = vfs;
+    return ESP_OK;
+
+fail:
+    if (diskio_registered) {
+        ff_diskio_unregister(pdrv);
+    }
+    esp_vfs_fat_unregister_path(base_path);
+    if(fs) {
+        f_mount(NULL, drive, 0);
+    }
+    dealloc_msc_vfs(vfs);
+    return ret;
+}
+
+esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle)
+{
+    MSC_RETURN_ON_INVALID_ARG(vfs_handle);
+    msc_host_vfs_t *vfs = (msc_host_vfs_t *)vfs_handle;
+
+    f_mount(NULL, vfs->drive, 0);
+    ff_diskio_unregister(vfs->pdrv);
+    esp_vfs_fat_unregister_path(vfs->base_path);
+    dealloc_msc_vfs(vfs);
+    return ESP_OK;
+}

+ 434 - 0
examples/peripherals/usb/host/msc/components/msc/src/msc_scsi_bot.c

@@ -0,0 +1,434 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "esp_log.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "esp_check.h"
+#include "esp_log.h"
+#include "msc_common.h"
+#include "msc_scsi_bot.h"
+
+#define TAG "USB_MSC_SCSI"
+
+/* --------------------------- SCSI Definitions ----------------------------- */
+#define CMD_SENSE_VALID_BIT (1 << 7)
+#define SCSI_FLAG_DPO (1<<4)
+#define SCSI_FLAG_FUA (1<<3)
+
+#define SCSI_CMD_FORMAT_UNIT 0x04
+#define SCSI_CMD_INQUIRY 0x12
+#define SCSI_CMD_MODE_SELECT 0x55
+#define SCSI_CMD_MODE_SENSE 0x5A
+#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E
+#define SCSI_CMD_READ10 0x28
+#define SCSI_CMD_READ12 0xA8
+#define SCSI_CMD_READ_CAPACITY 0x25
+#define SCSI_CMD_READ_FORMAT_CAPACITIES 0x23
+#define SCSI_CMD_REQUEST_SENSE 0x03
+#define SCSI_CMD_REZERO 0x01
+#define SCSI_CMD_SEEK10 0x2B
+#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D
+#define SCSI_CMD_START_STOP Unit 0x1B
+#define SCSI_CMD_TEST_UNIT_READY 0x00
+#define SCSI_CMD_VERIFY 0x2F
+#define SCSI_CMD_WRITE10 0x2A
+#define SCSI_CMD_WRITE12 0xAA
+#define SCSI_CMD_WRITE_AND_VERIFY 0x2E
+
+#define IN_DIR   CWB_FLAG_DIRECTION_IN
+#define OUT_DIR  0
+
+#define INQUIRY_VID_SIZE    8
+#define INQUIRY_PID_SIZE    16
+#define INQUIRY_REV_SIZE    4
+
+#define CBW_CMD_SIZE(cmd) (sizeof(cmd) - sizeof(msc_cbw_t))
+
+#define CBW_BASE_INIT(dir, cbw_len, data_len)   \
+    .base = {                                   \
+        .signature = 0x43425355,                \
+        .tag = ++cbw_tag,                       \
+        .flags = dir,                           \
+        .lun = 0,                               \
+        .data_length = data_len,                \
+        .cbw_length = cbw_len,                  \
+    }
+
+#define FEATURE_SELECTOR_ENDPOINT   0
+#define CSW_SIGNATURE   0x53425355
+#define CBW_SIZE        31
+
+#define USB_MASS_REQ_INIT_RESET(ctrl_req_ptr, intf_num) ({                  \
+    (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT |           \
+                                    USB_BM_REQUEST_TYPE_TYPE_CLASS |        \
+                                    USB_BM_REQUEST_TYPE_RECIP_INTERFACE;    \
+    (ctrl_req_ptr)->bRequest = 0xFF;                                        \
+    (ctrl_req_ptr)->wValue = 0;                                             \
+    (ctrl_req_ptr)->wIndex = (intf_num);                                    \
+    (ctrl_req_ptr)->wLength = 0;                                            \
+})
+
+#define USB_MASS_REQ_INIT_GET_MAX_LUN(ctrl_req_ptr, intf_num) ({            \
+    (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN |            \
+                                    USB_BM_REQUEST_TYPE_TYPE_CLASS |        \
+                                    USB_BM_REQUEST_TYPE_RECIP_INTERFACE;    \
+    (ctrl_req_ptr)->bRequest = 0xFE;                                        \
+    (ctrl_req_ptr)->wValue = 0;                                             \
+    (ctrl_req_ptr)->wIndex = (intf_num);                                    \
+    (ctrl_req_ptr)->wLength = 1;                                            \
+})
+
+#define USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP(ctrl_req_ptr, ep_num) ({     \
+    (ctrl_req_ptr)->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT |           \
+                                    USB_BM_REQUEST_TYPE_TYPE_STANDARD |     \
+                                    USB_BM_REQUEST_TYPE_RECIP_ENDPOINT;     \
+    (ctrl_req_ptr)->bRequest = USB_B_REQUEST_CLEAR_FEATURE;                 \
+    (ctrl_req_ptr)->wValue = FEATURE_SELECTOR_ENDPOINT;                     \
+    (ctrl_req_ptr)->wIndex = (ep_num);                                      \
+    (ctrl_req_ptr)->wLength = 0;                                            \
+})
+
+#define CWB_FLAG_DIRECTION_IN (1<<7) // device -> host
+
+/**
+ * @brief Command Block Wrapper structure
+ */
+typedef struct __attribute__((packed))
+{
+    uint32_t signature;
+    uint32_t tag;
+    uint32_t data_length;
+    uint8_t flags;
+    uint8_t lun;
+    uint8_t cbw_length;
+} msc_cbw_t;
+
+/**
+ * @brief Command Status Wrapper structure
+ */
+typedef struct __attribute__((packed))
+{
+    uint32_t signature;
+    uint32_t tag;
+    uint32_t dataResidue;
+    uint8_t status;
+} msc_csw_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint32_t address;
+    uint8_t reserved1;
+    uint16_t length;
+    uint8_t reserved2[3];
+} cbw_read10_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint32_t address;
+    uint8_t reserved1;
+    uint16_t length;
+    uint8_t reserved2[1];
+} cbw_write10_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint32_t address;
+    uint8_t reserved[6];
+} cbw_read_capacity_t;
+
+typedef struct __attribute__((packed))
+{
+    uint32_t block_count;
+    uint32_t block_size;
+} cbw_read_capacity_response_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint8_t reserved[10];
+} cbw_unit_ready_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint8_t reserved_0[2];
+    uint8_t allocation_length;
+    uint8_t reserved_1[7];
+} cbw_sense_t;
+
+typedef struct __attribute__((packed))
+{
+    uint8_t error_code;
+    uint8_t reserved_0;
+    uint8_t sense_key;
+    uint32_t info;
+    uint8_t sense_len;
+    uint32_t reserved_1;
+    uint8_t sense_code;
+    uint8_t sense_code_qualifier;
+    uint32_t reserved_2;
+} cbw_sense_response_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint8_t page_code;
+    uint8_t reserved_0;
+    uint8_t allocation_length;
+    uint8_t reserved_1[7];
+} cbw_inquiry_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint8_t pc_page_code;
+    uint8_t reserved_1[4];
+    uint16_t parameter_list_length;
+    uint8_t reserved_2[3];
+} mode_sense_t;
+
+typedef struct __attribute__((packed))
+{
+    uint8_t data[8];
+} mode_sense_response_t;
+
+typedef struct __attribute__((packed))
+{
+    msc_cbw_t base;
+    uint8_t opcode;
+    uint8_t flags;
+    uint8_t reserved_1[2];
+    uint8_t prevent;
+    uint8_t reserved_2[7];
+} prevent_allow_medium_removal_t;
+
+typedef struct __attribute__((packed))
+{
+    uint8_t data[36];
+} cbw_inquiry_response_t;
+
+// Unique number based on which MSC protocol pairs request and response
+static uint32_t cbw_tag;
+
+static esp_err_t check_csw(msc_csw_t *csw, uint32_t tag)
+{
+    bool csw_ok = csw->signature == CSW_SIGNATURE && csw->tag == tag &&
+                  csw->dataResidue == 0 && csw->status == 0;
+
+    if (!csw_ok) {
+        ESP_LOGD(TAG, "CSW failed: status %d", csw->status);
+    }
+
+    return csw_ok ? ESP_OK : ESP_FAIL;
+}
+
+static esp_err_t clear_feature(msc_device_t *device, uint8_t endpoint)
+{
+    usb_device_handle_t dev = device->handle;
+    usb_transfer_t *xfer = device->xfer;
+
+    MSC_RETURN_ON_ERROR( usb_host_endpoint_clear(dev, endpoint) );
+    USB_SETUP_PACKET_INIT_CLEAR_FEATURE_EP((usb_setup_packet_t *)xfer->data_buffer, endpoint);
+    MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) );
+
+    return ESP_OK;
+}
+
+esp_err_t msc_mass_reset(msc_device_t *device)
+{
+    usb_transfer_t *xfer = device->xfer;
+
+    USB_MASS_REQ_INIT_RESET((usb_setup_packet_t *)xfer->data_buffer, 0);
+    MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE) );
+
+    return ESP_OK;
+}
+
+esp_err_t msc_get_max_lun(msc_device_t *device, uint8_t *lun)
+{
+    usb_transfer_t *xfer = device->xfer;
+
+    USB_MASS_REQ_INIT_GET_MAX_LUN((usb_setup_packet_t *)xfer->data_buffer, 0);
+    MSC_RETURN_ON_ERROR( msc_control_transfer(device, xfer, USB_SETUP_PACKET_SIZE + 1) );
+
+    *lun = xfer->data_buffer[USB_SETUP_PACKET_SIZE];
+
+    return ESP_OK;
+}
+
+static esp_err_t bot_execute_command(msc_device_t *device, msc_cbw_t *cbw, void *data, size_t size)
+{
+    msc_csw_t csw;
+    msc_endpoint_t ep = (cbw->flags & CWB_FLAG_DIRECTION_IN) ? MSC_EP_IN : MSC_EP_OUT;
+
+    MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)cbw, CBW_SIZE, MSC_EP_OUT) );
+
+    if (data) {
+        MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)data, size, ep) );
+    }
+
+    esp_err_t  err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
+
+    if (err ==  ESP_FAIL && device->transfer_status == USB_TRANSFER_STATUS_STALL) {
+        ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" );
+        // Try to read csw again after clearing feature
+        err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
+        if (err) {
+            ESP_RETURN_ON_ERROR( clear_feature(device, MSC_EP_IN), TAG, "Clear feature failed" );
+            ESP_RETURN_ON_ERROR( msc_mass_reset(device), TAG, "Mass reset failed" );
+            return ESP_FAIL;
+        }
+    }
+
+    MSC_RETURN_ON_ERROR(err);
+
+    return check_csw(&csw, cbw->tag);
+}
+
+
+esp_err_t scsi_cmd_read10(msc_device_t *device,
+                          uint8_t *data,
+                          uint32_t sector_address,
+                          uint32_t num_sectors,
+                          uint32_t sector_size)
+{
+    cbw_read10_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read10_t), num_sectors * sector_size),
+        .opcode = SCSI_CMD_READ10,
+        .flags = 0, // lun
+        .address = __builtin_bswap32(sector_address),
+        .length = __builtin_bswap16(num_sectors),
+    };
+
+    return bot_execute_command(device, &cbw.base, data, num_sectors * sector_size);
+}
+
+esp_err_t scsi_cmd_write10(msc_device_t *device,
+                           const uint8_t *data,
+                           uint32_t sector_address,
+                           uint32_t num_sectors,
+                           uint32_t sector_size)
+{
+    cbw_write10_t cbw = {
+        CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(cbw_write10_t), num_sectors * sector_size),
+        .opcode = SCSI_CMD_WRITE10,
+        .address = __builtin_bswap32(sector_address),
+        .length = __builtin_bswap16(num_sectors),
+    };
+
+    return bot_execute_command(device, &cbw.base, (void *)data, num_sectors * sector_size);
+}
+
+esp_err_t scsi_cmd_read_capacity(msc_device_t *device, uint32_t *block_size, uint32_t *block_count)
+{
+    cbw_read_capacity_response_t response;
+
+    cbw_read_capacity_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_read_capacity_t), sizeof(response)),
+        .opcode = SCSI_CMD_READ_CAPACITY,
+    };
+
+    MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) );
+
+    *block_count = __builtin_bswap32(response.block_count);
+    *block_size = __builtin_bswap32(response.block_size);
+
+    return ESP_OK;
+}
+
+esp_err_t scsi_cmd_unit_ready(msc_device_t *device)
+{
+    cbw_unit_ready_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_unit_ready_t), 0),
+        .opcode = SCSI_CMD_TEST_UNIT_READY,
+    };
+
+    return bot_execute_command(device, &cbw.base, NULL, 0);
+}
+
+esp_err_t scsi_cmd_sense(msc_device_t *device, scsi_sense_data_t *sense)
+{
+    cbw_sense_response_t response;
+
+    cbw_sense_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_sense_t), sizeof(response)),
+        .opcode = SCSI_CMD_REQUEST_SENSE,
+        .allocation_length = sizeof(response),
+    };
+
+    MSC_RETURN_ON_ERROR( bot_execute_command(device, &cbw.base, &response, sizeof(response)) );
+
+    if (sense->key) {
+        ESP_LOGD(TAG, "sense_key: 0x%02X, code: 0x%02X, qualifier: 0x%02X",
+                 response.sense_key, response.sense_code, response.sense_code_qualifier);
+    }
+
+    sense->key = response.sense_key;
+    sense->code = response.sense_code;
+    sense->code_q = response.sense_code_qualifier;
+
+    return ESP_OK;
+}
+
+esp_err_t scsi_cmd_inquiry(msc_device_t *device)
+{
+    cbw_inquiry_response_t response = { 0 };
+
+    cbw_inquiry_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_inquiry_t), sizeof(response)),
+        .opcode = SCSI_CMD_INQUIRY,
+        .allocation_length = sizeof(response),
+    };
+
+    return bot_execute_command(device, &cbw.base, &response, sizeof(response) );
+}
+
+esp_err_t scsi_cmd_mode_sense(msc_device_t *device)
+{
+    mode_sense_response_t response = { 0 };
+
+    mode_sense_t cbw = {
+        CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(mode_sense_t), sizeof(response)),
+        .opcode = SCSI_CMD_MODE_SENSE,
+        .pc_page_code = 0x3F,
+        .parameter_list_length = sizeof(response),
+    };
+
+    return bot_execute_command(device, &cbw.base, &response, sizeof(response) );
+}
+
+esp_err_t scsi_cmd_prevent_removal(msc_device_t *device, bool prevent)
+{
+    prevent_allow_medium_removal_t cbw = {
+        CBW_BASE_INIT(OUT_DIR, CBW_CMD_SIZE(prevent_allow_medium_removal_t), 0),
+        .opcode = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL,
+        .prevent = 1,
+    };
+
+    return bot_execute_command(device, &cbw.base, NULL, 0);
+}

+ 3 - 0
examples/peripherals/usb/host/msc/components/msc/test/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRC_DIRS .
+                       INCLUDE_DIRS .
+                       REQUIRES unity usb msc tinyusb)

+ 295 - 0
examples/peripherals/usb/host/msc/components/msc/test/msc_device.c

@@ -0,0 +1,295 @@
+/*
+ * SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org)
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * SPDX-FileContributor: 2019-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ */
+
+/*
+ * 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 <stdint.h>
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "tinyusb.h"
+#include "test_common.h"
+#include "soc/soc_caps.h"
+
+#if SOC_USB_OTG_SUPPORTED
+
+#define MASS_STORAGE_CLASS  0x08
+#define SCSI_COMMAND_SET    0x06
+#define BULK_ONLY_TRANSFER  0x50
+
+static const char *TAG = "msc_example";
+
+
+/**** Kconfig driven Descriptor ****/
+tusb_desc_device_t device_descriptor = {
+    .bLength = sizeof(device_descriptor),
+    .bDescriptorType = TUSB_DESC_DEVICE,
+    .bcdUSB = 0x0200,
+    .bDeviceClass = MASS_STORAGE_CLASS,
+    .bDeviceSubClass = SCSI_COMMAND_SET,
+    .bDeviceProtocol = BULK_ONLY_TRANSFER,
+    .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+    .idVendor = USB_ESPRESSIF_VID,
+    .idProduct = 0x1234,
+    .bcdDevice = 0x0100,
+    .iManufacturer = 0x01,
+    .iProduct = 0x02,
+    .iSerialNumber = 0x03,
+    .bNumConfigurations = 0x01
+};
+
+void device_app(void)
+{
+    ESP_LOGI(TAG, "USB initialization");
+
+    tinyusb_config_t tusb_cfg = {
+        .descriptor = &device_descriptor
+    };
+
+    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
+    ESP_LOGI(TAG, "USB initialization DONE");
+
+    while (1) {
+        vTaskDelay(100);
+    }
+}
+
+
+// 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
+
+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;
+
+    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;
+
+    // RAM disk is ready until ejected
+    if (ejected) {
+        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;
+
+    *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
+            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;
+
+    uint8_t const *addr = msc_disk[lba] + offset;
+    memcpy(buffer, addr, bufsize);
+
+    return bufsize;
+}
+
+// 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;
+
+#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 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;
+    uint16_t resplen = 0;
+
+    // most scsi handled is input
+    bool in_xfer = true;
+
+    switch (scsi_cmd[0]) {
+    case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
+        // Host is about to read/write etc ... better not to disconnect disk
+        resplen = 0;
+        break;
+
+    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, resplen);
+        } else {
+            // SCSI output
+        }
+    }
+
+    return resplen;
+}
+
+#endif

+ 19 - 0
examples/peripherals/usb/host/msc/components/msc/test/test_common.h

@@ -0,0 +1,19 @@
+/*
+ * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+enum {
+    // FatFS only allows to format disks with number of blocks greater than 128
+    DISK_BLOCK_NUM  = 128 + 1,
+    DISK_BLOCK_SIZE = 512
+};
+
+#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"
+
+void device_app(void);

+ 316 - 0
examples/peripherals/usb/host/msc/components/msc/test/test_msc.c

@@ -0,0 +1,316 @@
+
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "unity.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "usb/usb_host.h"
+#include "msc_host.h"
+#include "msc_host_vfs.h"
+#include "ffconf.h"
+#include "ff.h"
+#include "esp_vfs.h"
+#include "test_common.h"
+#include "soc/usb_wrap_struct.h"
+#include "soc/soc_caps.h"
+
+#if SOC_USB_OTG_SUPPORTED
+
+static const char *TAG = "APP";
+
+#define ESP_OK_ASSERT(exp) TEST_ASSERT_EQUAL(ESP_OK, exp)
+
+static esp_vfs_fat_mount_config_t mount_config = {
+    .format_if_mount_failed = false,
+    .max_files = 3,
+    .allocation_unit_size = 1024,
+};
+
+static QueueHandle_t app_queue;
+static SemaphoreHandle_t ready_to_deinit_usb;
+static msc_host_device_handle_t device;
+static msc_host_vfs_handle_t vfs_handle;
+static volatile bool waiting_for_sudden_disconnect;
+
+static void test_usb_force_conn_state(bool connected, TickType_t delay_ticks)
+{
+    if (delay_ticks > 0) {
+        //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
+        vTaskDelay(delay_ticks);
+    }
+    usb_wrap_dev_t *wrap = &USB_WRAP;
+    if (connected) {
+        //Disable test mode to return to previous internal PHY configuration
+        wrap->test_conf.test_enable = 0;
+    } else {
+        /*
+        Mimic a disconnection by using the internal PHY's test mode.
+        Force Output Enable to 1 (even if the controller isn't outputting). With test_tx_dp and test_tx_dm set to 0,
+        this will look like a disconnection.
+        */
+        wrap->test_conf.val = 0;
+        wrap->test_conf.test_usb_wrap_oe = 1;
+        wrap->test_conf.test_enable = 1;
+    }
+}
+
+static void msc_event_cb(const msc_host_event_t *event, void *arg)
+{
+    if (waiting_for_sudden_disconnect) {
+        waiting_for_sudden_disconnect = false;
+        TEST_ASSERT(event->event == MSC_DEVICE_DISCONNECTED);
+    }
+
+    if (event->event == MSC_DEVICE_CONNECTED) {
+        printf("MSC_DEVICE_CONNECTED\n");
+    } else {
+        printf("MSC_DEVICE_DISCONNECTED\n");
+    }
+
+    xQueueSend(app_queue, event, 10);
+}
+
+static const char *TEST_STRING = "Hello World!";
+static const char *FILE_NAME = "/usb/ESP32.txt";
+
+static void write_read_file(const char *file_path)
+{
+    char line[64];
+
+    ESP_LOGI(TAG, "Writing file");
+    FILE *f = fopen(file_path, "w");
+    TEST_ASSERT( f != NULL);
+    fprintf(f, TEST_STRING);
+    fclose(f);
+
+    ESP_LOGI(TAG, "Reading file");
+    TEST_ASSERT( fopen(file_path, "r") != NULL);
+    fgets(line, sizeof(line), f);
+    fclose(f);
+    // strip newline
+    char *pos = strchr(line, '\n');
+    if (pos) {
+        *pos = '\0';
+    }
+    TEST_ASSERT_EQUAL_STRING(line, TEST_STRING);
+    ESP_LOGI(TAG, "Done");
+}
+
+static bool file_exists(const char *file_path)
+{
+    return ( access(file_path, F_OK) == 0 );
+}
+
+// Handles common USB host library events
+static void handle_usb_events(void *args)
+{
+    uint32_t end_flags = 0;
+
+    while (1) {
+        uint32_t event_flags;
+        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
+        // Release devices once all clients has deregistered
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
+            printf("USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS\n");
+            usb_host_device_free_all();
+            end_flags |= 1;
+        }
+        // Give ready_to_deinit_usb semaphore to indicate that USB Host library
+        // can be deinitialized, and terminate this task.
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
+            printf("USB_HOST_LIB_EVENT_FLAGS_ALL_FREE\n");
+            end_flags |= 2;
+        }
+
+        if (end_flags == 3) {
+            xSemaphoreGive(ready_to_deinit_usb);
+            break;
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+static void check_file_content(const char *file_path, const char *expected)
+{
+    ESP_LOGI(TAG, "Reading %s:", file_path);
+    FILE *file = fopen(file_path, "r");
+    TEST_ASSERT(file != NULL)
+
+    char content[200];
+    fread(content, 1, sizeof(content), file);
+    TEST_ASSERT_EQUAL_STRING(content, expected);
+    fclose(file);
+}
+
+static void check_sudden_disconnect(void)
+{
+    uint8_t data[512];
+    const size_t DATA_SIZE = sizeof(data);
+
+    ESP_LOGI(TAG, "Creating test.tx");
+    FILE *file = fopen("/usb/test.txt", "w");
+    TEST_ASSERT( file != NULL);
+
+    ESP_LOGI(TAG, "Write data");
+    TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
+    TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) == DATA_SIZE );
+    TEST_ASSERT( fflush(file) == 0 );
+
+    ESP_LOGI(TAG, "Trigger a disconnect");
+    //Trigger a disconnect
+    waiting_for_sudden_disconnect = true;
+    test_usb_force_conn_state(false, 0);
+
+    // Make sure flag was leared in callback
+    vTaskDelay( pdMS_TO_TICKS(100) );
+    TEST_ASSERT( waiting_for_sudden_disconnect == false );
+
+    ESP_LOGI(TAG, "Write data after disconnect");
+    TEST_ASSERT( fwrite(data, 1, DATA_SIZE, file) != DATA_SIZE );
+
+    fclose(file);
+}
+
+static void msc_setup(void)
+{
+    BaseType_t task_created;
+
+    ready_to_deinit_usb = xSemaphoreCreateBinary();
+
+    TEST_ASSERT( app_queue = xQueueCreate(5, sizeof(msc_host_event_t)) );
+
+    const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 };
+    ESP_OK_ASSERT( usb_host_install(&host_config) );
+
+    task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL);
+    TEST_ASSERT(task_created);
+
+    const msc_host_driver_config_t msc_config = {
+        .create_backround_task = true,
+        .callback = msc_event_cb,
+        .stack_size = 4096,
+        .task_priority = 5,
+    };
+    ESP_OK_ASSERT( msc_host_install(&msc_config) );
+
+    ESP_LOGI(TAG, "Waiting for USB stick to be connected");
+    msc_host_event_t app_event;
+    xQueueReceive(app_queue, &app_event, portMAX_DELAY);
+    TEST_ASSERT( app_event.event == MSC_DEVICE_CONNECTED );
+    uint8_t device_addr = app_event.device.address;
+
+    ESP_OK_ASSERT( msc_host_install_device(device_addr, &device) );
+    ESP_OK_ASSERT( msc_host_vfs_register(device, "/usb", &mount_config, &vfs_handle) );
+}
+
+static void msc_teardown(void)
+{
+    // Wait to finish any ongoing USB operations
+    vTaskDelay(100);
+
+    ESP_OK_ASSERT( msc_host_vfs_unregister(vfs_handle) );
+    ESP_OK_ASSERT( msc_host_uninstall_device(device) );
+    ESP_OK_ASSERT( msc_host_uninstall() );
+
+    xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY);
+    vSemaphoreDelete(ready_to_deinit_usb);
+    ESP_OK_ASSERT( usb_host_uninstall() );
+
+    vQueueDelete(app_queue);
+}
+
+static void write_read_sectors(void)
+{
+    uint8_t write_data[DISK_BLOCK_SIZE];
+    uint8_t read_data[DISK_BLOCK_SIZE];
+
+    memset(write_data, 0x55, DISK_BLOCK_SIZE);
+    memset(read_data, 0, DISK_BLOCK_SIZE);
+
+    msc_host_write_sector(device, 10, write_data, DISK_BLOCK_SIZE);
+    msc_host_read_sector(device, 10, read_data, DISK_BLOCK_SIZE);
+
+    TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, DISK_BLOCK_SIZE);
+}
+
+static void erase_storage(void)
+{
+    uint8_t data[DISK_BLOCK_SIZE];
+    memset(data, 0xFF, DISK_BLOCK_SIZE);
+
+    for (int block = 0; block < DISK_BLOCK_NUM; block++) {
+        msc_host_write_sector(device, block, data, DISK_BLOCK_SIZE);
+    }
+}
+
+static void check_readme_content(void)
+{
+    msc_setup();
+    check_file_content("/usb/README.TXT", README_CONTENTS);
+    msc_teardown();
+}
+
+TEST_CASE("Write and read file", "[usb_msc][ignore]")
+{
+    msc_setup();
+    write_read_file(FILE_NAME);
+    msc_teardown();
+}
+
+TEST_CASE("Sudden disconnect", "[usb_msc][ignore]")
+{
+    msc_setup();
+    check_sudden_disconnect();
+    msc_teardown();
+}
+
+void read_write_sectors(void)
+{
+    msc_setup();
+    write_read_sectors();
+    msc_teardown();
+}
+
+void check_formatting(void)
+{
+    printf("Create file\n");
+    msc_setup();
+    write_read_file(FILE_NAME);
+    msc_teardown();
+
+    printf("File exists after mounting again\n");
+    msc_setup();
+    TEST_ASSERT( file_exists(FILE_NAME) );
+    printf("Erase storage device\n");
+    erase_storage();
+    msc_teardown();
+
+    printf("Check file does not exist after formatting\n");
+    mount_config.format_if_mount_failed = true;
+    msc_setup();
+    TEST_ASSERT( !file_exists(FILE_NAME) );
+    msc_teardown();
+    mount_config.format_if_mount_failed = false;
+}
+
+TEST_CASE_MULTIPLE_DEVICES("Sectors can be written and read", "[usb_msc][ignore]", read_write_sectors, device_app);
+
+TEST_CASE_MULTIPLE_DEVICES("Can be Formated", "[usb_msc][ignore]", check_formatting, device_app);
+
+TEST_CASE_MULTIPLE_DEVICES("Check README content", "[usb_msc][ignore]", check_readme_content, device_app);
+
+#endif

+ 3 - 0
examples/peripherals/usb/host/msc/main/CMakeLists.txt

@@ -0,0 +1,3 @@
+idf_component_register(SRCS "msc_example_main.c"
+                    INCLUDE_DIRS ""
+                    REQUIRES usb msc fatfs)

+ 179 - 0
examples/peripherals/usb/host/msc/main/msc_example_main.c

@@ -0,0 +1,179 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/semphr.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "usb/usb_host.h"
+#include "msc_host.h"
+#include "msc_host_vfs.h"
+#include "ffconf.h"
+#include "ff.h"
+#include "esp_vfs.h"
+#include "errno.h"
+#include "hal/usb_hal.h"
+
+static const char* TAG = "example";
+
+static QueueHandle_t app_queue;
+static SemaphoreHandle_t ready_to_uninstall_usb;
+
+static void msc_event_cb(const msc_host_event_t *event, void *arg)
+{
+    if (event->event == MSC_DEVICE_CONNECTED) {
+        ESP_LOGI(TAG, "MSC device connected");
+    } else if (event->event == MSC_DEVICE_DISCONNECTED) {
+        ESP_LOGI(TAG, "MSC device disconnected");
+    }
+    xQueueSend(app_queue, event, 10);
+}
+
+static void print_device_info(msc_host_device_info_t *info)
+{
+    const size_t megabyte = 1024 * 1024;
+    uint64_t capacity = ((uint64_t)info->sector_size * info->sector_count) / megabyte;
+
+    printf("Device info:\n");
+    printf("\t Capacity: %llu MB\n", capacity);
+    printf("\t Sector size: %u\n", info->sector_size);
+    printf("\t Sector count: %u\n", info->sector_count);
+    printf("\t PID: 0x%4X \n", info->idProduct);
+    printf("\t VID: 0x%4X \n", info->idVendor);
+    wprintf(L"\t iProduct: %S \n", info->iProduct);
+    wprintf(L"\t iManufacturer: %S \n", info->iManufacturer);
+    wprintf(L"\t iSerialNumber: %S \n", info->iSerialNumber);
+}
+
+static void file_operations(void)
+{
+    const char *directory = "/usb/esp";
+    const char *file_path = "/usb/esp/test.txt";
+
+    struct stat s = {0};
+    bool directory_exists = stat(directory, &s) == 0;
+    if (!directory_exists) {
+        if (mkdir(directory, 0775) != 0) {
+            ESP_LOGE(TAG, "mkdir failed with errno: %s\n", strerror(errno));
+        }
+    }
+
+    ESP_LOGI(TAG, "Writing file");
+    FILE *f = fopen(file_path, "w");
+    if (f == NULL) {
+        ESP_LOGE(TAG, "Failed to open file for writing");
+        return;
+    }
+    fprintf(f, "Hello World!\n");
+    fclose(f);
+
+    ESP_LOGI(TAG, "Reading file");
+    f = fopen(file_path, "r");
+    if (f == NULL) {
+        ESP_LOGE(TAG, "Failed to open file for reading");
+        return;
+    }
+    char line[64];
+    fgets(line, sizeof(line), f);
+    fclose(f);
+    // strip newline
+    char *pos = strchr(line, '\n');
+    if (pos) {
+        *pos = '\0';
+    }
+    ESP_LOGI(TAG, "Read from file: '%s'", line);
+}
+
+// Handles common USB host library events
+static void handle_usb_events(void *args)
+{
+    while (1) {
+        uint32_t event_flags;
+        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
+        // Release devices once all clients has deregistered
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
+            usb_host_device_free_all();
+        }
+        // Give ready_to_uninstall_usb semaphore to indicate that USB Host library
+        // can be deinitialized, and terminate this task.
+        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
+            xSemaphoreGive(ready_to_uninstall_usb);
+            break;
+        }
+    }
+
+    vTaskDelete(NULL);
+}
+
+static uint8_t wait_for_msc_device(void)
+{
+    msc_host_event_t app_event;
+    ESP_LOGI(TAG, "Waiting for USB stick to be connected");
+    xQueueReceive(app_queue, &app_event, portMAX_DELAY);
+    assert( app_event.event == MSC_DEVICE_CONNECTED );
+    return app_event.device.address;
+}
+
+void app_main(void)
+{
+    msc_host_device_handle_t msc_device;
+    BaseType_t task_created;
+
+    ready_to_uninstall_usb = xSemaphoreCreateBinary();
+
+    app_queue = xQueueCreate(3, sizeof(msc_host_event_t));
+    assert(app_queue);
+
+    const usb_host_config_t host_config = { .intr_flags = ESP_INTR_FLAG_LEVEL1 };
+    ESP_ERROR_CHECK( usb_host_install(&host_config) );
+
+    task_created = xTaskCreate(handle_usb_events, "usb_events", 2048, NULL, 2, NULL);
+    assert(task_created);
+
+    const msc_host_driver_config_t msc_config = {
+        .create_backround_task = true,
+        .task_priority = 5,
+        .stack_size = 2048,
+        .callback = msc_event_cb,
+    };
+    ESP_ERROR_CHECK( msc_host_install(&msc_config) );
+
+    uint8_t device_address = wait_for_msc_device();
+
+    ESP_ERROR_CHECK( msc_host_install_device(device_address, &msc_device) );
+
+    msc_host_print_descriptors(msc_device);
+
+    msc_host_device_info_t info;
+    ESP_ERROR_CHECK( msc_host_get_device_info(msc_device, &info) );
+    print_device_info(&info);
+
+    msc_host_vfs_handle_t vfs_handle;
+    const esp_vfs_fat_mount_config_t mount_config = {
+        .format_if_mount_failed = false,
+        .max_files = 3,
+        .allocation_unit_size = 1024,
+    };
+
+    ESP_ERROR_CHECK( msc_host_vfs_register(msc_device, "/usb", &mount_config, &vfs_handle) );
+
+    file_operations();
+
+    ESP_ERROR_CHECK( msc_host_vfs_unregister(vfs_handle) );
+    ESP_ERROR_CHECK( msc_host_uninstall_device(msc_device) );
+    ESP_ERROR_CHECK( msc_host_uninstall() );
+
+    xSemaphoreTake(ready_to_uninstall_usb, portMAX_DELAY);
+    ESP_ERROR_CHECK( usb_host_uninstall() );
+
+    ESP_LOGI(TAG, "Done");
+}

+ 1 - 0
tools/ci/check_copyright_ignore.txt

@@ -2792,6 +2792,7 @@ examples/peripherals/uart/uart_echo_rs485/main/rs485_example.c
 examples/peripherals/uart/uart_events/main/uart_events_example_main.c
 examples/peripherals/uart/uart_repl/main/uart_repl_example_main.c
 examples/peripherals/uart/uart_select/main/uart_select_example_main.c
+examples/peripherals/usb/host/msc/components/msc/test/msc_device.c
 examples/peripherals/usb/tusb_console/main/tusb_console_main.c
 examples/peripherals/usb/tusb_sample_descriptor/main/tusb_sample_descriptor_main.c
 examples/peripherals/usb/tusb_serial_device/main/tusb_serial_device_main.c

+ 3 - 2
tools/test_apps/peripherals/usb/CMakeLists.txt

@@ -2,9 +2,10 @@
 # CMakeLists in this exact order for cmake to work correctly
 cmake_minimum_required(VERSION 3.5)
 
-set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common)
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/peripherals/usb/host/cdc/common
+                         $ENV{IDF_PATH}/examples/peripherals/usb/host/msc/components/)
 
 # Set the components to include the tests for.
-set(TEST_COMPONENTS "cdc_acm_host" CACHE STRING "List of components to test")
+set(TEST_COMPONENTS "cdc_acm_host" "msc" CACHE STRING "List of components to test")
 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
 project(usb_test_app)

+ 15 - 3
tools/test_apps/peripherals/usb/README.md

@@ -1,14 +1,26 @@
 | Supported Targets | ESP32-S2 | ESP32-S3 |
 | ----------------- | -------- | -------- |
 
-# USB Host CDC-ACM driver test project
+# USB Host Class driver test project
+Main purpose of this application is to test the USB Host Class drivers.
+
+## CDC-ACM driver
 
-Main purpose of this application is to test the USB Host CDC-ACM driver.
 It tests basic functionality of the driver like open/close/read/write operations,
 advanced features like CDC control request, multi-threaded or multi-device access,
 as well as reaction to sudden disconnection and other error states.
 
-## Hardware Required
+### Hardware Required
 
 This test expects that TinyUSB dual CDC device with VID = 0x303A and PID = 0x4002
 is connected to the USB host.
+
+## MSC driver
+
+Basic functionality such as MSC device install/uninstall, file operatons, 
+raw access to MSC device and sudden disconnect is tested.
+
+### Hardware Required
+
+This test requires two ESP32-S2/S3 boards with a interconnected USB perpherals,
+one acting as host running MSC host driver and another MSC device driver (tinyusb).

+ 1 - 1
tools/test_apps/peripherals/usb/main/usb_test_main.c

@@ -11,6 +11,6 @@
 void app_main(void)
 {
     UNITY_BEGIN();
-    unity_run_all_tests();
+    unity_run_menu();
     UNITY_END();
 }

+ 4 - 0
tools/test_apps/peripherals/usb/sdkconfig.defaults

@@ -0,0 +1,4 @@
+CONFIG_TINYUSB=y
+CONFIG_TINYUSB_MSC_ENABLED=y
+CONFIG_ESP_INT_WDT=n
+CONFIG_ESP_TASK_WDT=n