diff --git a/components/usb/include/usb/usb_helpers.h b/components/usb/include/usb/usb_helpers.h index d8405dd5fc..834d02ae25 100644 --- a/components/usb/include/usb/usb_helpers.h +++ b/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 +#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 diff --git a/components/usb/include/usb/usb_types_ch9.h b/components/usb/include/usb/usb_types_ch9.h index 7d40bc46e0..d49b81eb33 100644 --- a/components/usb/include/usb/usb_types_ch9.h +++ b/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 -------------------- /** diff --git a/components/usb/usb_helpers.c b/components/usb/usb_helpers.c index e657c789b4..ac1caf1425 100644 --- a/components/usb/usb_helpers.c +++ b/components/usb/usb_helpers.c @@ -8,8 +8,14 @@ #include #include #include +#include +#include #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 --------------------------------------------------------- diff --git a/examples/peripherals/usb/host/msc/CMakeLists.txt b/examples/peripherals/usb/host/msc/CMakeLists.txt new file mode 100644 index 0000000000..2f83da6686 --- /dev/null +++ b/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) diff --git a/examples/peripherals/usb/host/msc/README.md b/examples/peripherals/usb/host/msc/README.md new file mode 100644 index 0000000000..934ba54771 --- /dev/null +++ b/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 +``` diff --git a/examples/peripherals/usb/host/msc/components/msc/CMakeLists.txt b/examples/peripherals/usb/host/msc/components/msc/CMakeLists.txt new file mode 100644 index 0000000000..36848f1682 --- /dev/null +++ b/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 ) diff --git a/examples/peripherals/usb/host/msc/components/msc/README.md b/examples/peripherals/usb/host/msc/components/msc/README.md new file mode 100644 index 0000000000..4f722d1fcd --- /dev/null +++ b/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` diff --git a/examples/peripherals/usb/host/msc/components/msc/include/msc_host.h b/examples/peripherals/usb/host/msc/components/msc/include/msc_host.h new file mode 100644 index 0000000000..058037649d --- /dev/null +++ b/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 +#include +#include "esp_err.h" +#include + +#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 diff --git a/examples/peripherals/usb/host/msc/components/msc/include/msc_host_vfs.h b/examples/peripherals/usb/host/msc/components/msc/include/msc_host_vfs.h new file mode 100644 index 0000000000..af9137f9d9 --- /dev/null +++ b/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 diff --git a/examples/peripherals/usb/host/msc/components/msc/private_include/diskio_usb.h b/examples/peripherals/usb/host/msc/components/msc/private_include/diskio_usb.h new file mode 100644 index 0000000000..6327d6eee5 --- /dev/null +++ b/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 diff --git a/examples/peripherals/usb/host/msc/components/msc/private_include/msc_common.h b/examples/peripherals/usb/host/msc/components/msc/private_include/msc_common.h new file mode 100644 index 0000000000..a5bed31e1d --- /dev/null +++ b/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 +#include +#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 diff --git a/examples/peripherals/usb/host/msc/components/msc/private_include/msc_scsi_bot.h b/examples/peripherals/usb/host/msc/components/msc/private_include/msc_scsi_bot.h new file mode 100644 index 0000000000..d4ee210347 --- /dev/null +++ b/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 +#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 diff --git a/examples/peripherals/usb/host/msc/components/msc/src/diskio_usb.c b/examples/peripherals/usb/host/msc/components/msc/src/diskio_usb.c new file mode 100644 index 0000000000..0191f289c9 --- /dev/null +++ b/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; +} diff --git a/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c b/examples/peripherals/usb/host/msc/components/msc/src/msc_host.c new file mode 100644 index 0000000000..ad4b3f642f --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/examples/peripherals/usb/host/msc/components/msc/src/msc_host_vfs.c b/examples/peripherals/usb/host/msc/components/msc/src/msc_host_vfs.c new file mode 100644 index 0000000000..21638635ed --- /dev/null +++ b/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 +#include +#include +#include +#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; +} diff --git a/examples/peripherals/usb/host/msc/components/msc/src/msc_scsi_bot.c b/examples/peripherals/usb/host/msc/components/msc/src/msc_scsi_bot.c new file mode 100644 index 0000000000..2e548f0b9e --- /dev/null +++ b/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 +#include +#include "esp_log.h" +#include +#include +#include +#include +#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); +} diff --git a/examples/peripherals/usb/host/msc/components/msc/test/CMakeLists.txt b/examples/peripherals/usb/host/msc/components/msc/test/CMakeLists.txt new file mode 100644 index 0000000000..829f046e33 --- /dev/null +++ b/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) diff --git a/examples/peripherals/usb/host/msc/components/msc/test/msc_device.c b/examples/peripherals/usb/host/msc/components/msc/test/msc_device.c new file mode 100644 index 0000000000..6f244287d3 --- /dev/null +++ b/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 +#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 diff --git a/examples/peripherals/usb/host/msc/components/msc/test/test_common.h b/examples/peripherals/usb/host/msc/components/msc/test/test_common.h new file mode 100644 index 0000000000..b9acfaa9c9 --- /dev/null +++ b/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); diff --git a/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c b/examples/peripherals/usb/host/msc/components/msc/test/test_msc.c new file mode 100644 index 0000000000..98d47aac69 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#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 diff --git a/examples/peripherals/usb/host/msc/main/CMakeLists.txt b/examples/peripherals/usb/host/msc/main/CMakeLists.txt new file mode 100644 index 0000000000..1503b83c6d --- /dev/null +++ b/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) diff --git a/examples/peripherals/usb/host/msc/main/msc_example_main.c b/examples/peripherals/usb/host/msc/main/msc_example_main.c new file mode 100644 index 0000000000..6514f9174d --- /dev/null +++ b/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 +#include +#include +#include +#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"); +} diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index ee7f3a6594..690bb6877c 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -2801,6 +2801,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 diff --git a/tools/test_apps/peripherals/usb/CMakeLists.txt b/tools/test_apps/peripherals/usb/CMakeLists.txt index bb32fc5fe5..f593023413 100644 --- a/tools/test_apps/peripherals/usb/CMakeLists.txt +++ b/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) diff --git a/tools/test_apps/peripherals/usb/README.md b/tools/test_apps/peripherals/usb/README.md index b8eafc684a..245f71285a 100644 --- a/tools/test_apps/peripherals/usb/README.md +++ b/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). diff --git a/tools/test_apps/peripherals/usb/main/usb_test_main.c b/tools/test_apps/peripherals/usb/main/usb_test_main.c index 2f57becfe4..65c83a567c 100644 --- a/tools/test_apps/peripherals/usb/main/usb_test_main.c +++ b/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(); } diff --git a/tools/test_apps/peripherals/usb/sdkconfig.defaults b/tools/test_apps/peripherals/usb/sdkconfig.defaults new file mode 100644 index 0000000000..e395540cf3 --- /dev/null +++ b/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