Initial public release of the 2024A software.

This commit is contained in:
Joe Kearney 2025-01-25 14:04:42 -06:00
parent 7b9ad3edfd
commit 303e9e1dad
361 changed files with 60083 additions and 2 deletions

View file

@ -0,0 +1 @@
efbf44743b0f1f1f808697a671064531ae4661ccbce84632637261f8f670b375

View file

@ -0,0 +1,39 @@
## 1.1.3
- Implemented request sense, to get sense data from USB device in case of an error
- Fixed initialization of some flash drives, which require more time to initialize (https://github.com/espressif/esp-idf/issues/14319)
## 1.1.2
- Added support for ESP32-P4
- Reverted zero-copy bulk transfers. Data are now copied to USB buffers with negligible effect on performance
## 1.1.1
- Fix `msc_host_get_device_info` for devices without Serial Number string descriptor https://github.com/espressif/esp-idf/issues/12163
- Fix regression from version 1.1.0 that files could not be opened in PSRAM https://github.com/espressif/idf-extra-components/issues/202
- Fix MSC driver event handling without background task
## 1.1.0 - yanked
- Significantly increase performance with Virtual File System by allowing longer transfers
- Optimize used heap memory by reusing the Virtual File System buffer
- Optimize CPU usage by putting the background MSC task to 'Blocked' state indefinitely when there is nothing to do
- Fix MSC commands for devices on interface numbers other than zero
- Replace unsafe debug functions for direct access of MSC sectors with private SCSI commands
## 1.0.4
- Claim support for USB composite devices
## 1.0.2
- Increase transfer timeout to 5 seconds to handle slower flash disks
## 1.0.1
- Fix compatibility with IDF v4.4
## 1.0.0
- Initial version

View file

@ -0,0 +1,10 @@
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 include/usb # 'include/usb' is here for backwards compatibility
PRIV_INCLUDE_DIRS private_include include/esp_private
REQUIRES usb fatfs
PRIV_REQUIRES heap )

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,42 @@
# USB Host MSC (Mass Storage Class) Driver
[![Component Registry](https://components.espressif.com/components/espressif/usb_host_msc/badge.svg)](https://components.espressif.com/components/espressif/usb_host_msc)
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.
## Performance tuning
The following performance tuning options have significant impact on data throughput in USB HighSpeed implementations.
For original FullSpeed implementations, the effects are negligible.
- By default, Newlib (the implementation of C Standard Library) creates cache for each opened file
- The greater the cache, the better performance for the cost of RAM
- Size of the cache can be set with C STD library function `setvbuf()`
- Sizes over 16kB do not improve the performance any more
## Known issues
- Driver only supports flash drives using the BOT (Bulk-Only Transport) protocol and the Transparent SCSI command set
## Examples
- For an example, refer to [msc_host_example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/msc) in ESP-IDF

View file

@ -0,0 +1,13 @@
dependencies:
idf: '>=4.4.1'
description: USB Host MSC driver
repository: git://github.com/espressif/esp-usb.git
repository_info:
commit_sha: 0d5b6e959b2ba6993f27c703f5b26f93557c9066
path: host/class/msc/usb_host_msc
targets:
- esp32s2
- esp32s3
- esp32p4
url: https://github.com/espressif/esp-usb/tree/master/host/class/msc/usb_host_msc
version: 1.1.3

View file

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "usb/msc_host.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_host_device_handle_t device,
uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size);
esp_err_t scsi_cmd_write10(msc_host_device_handle_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_host_device_handle_t device,
uint32_t *block_size,
uint32_t *block_count);
esp_err_t scsi_cmd_sense(msc_host_device_handle_t device, scsi_sense_data_t *sense);
esp_err_t scsi_cmd_unit_ready(msc_host_device_handle_t device);
esp_err_t scsi_cmd_inquiry(msc_host_device_handle_t device);
esp_err_t scsi_cmd_prevent_removal(msc_host_device_handle_t device, bool prevent);
esp_err_t scsi_cmd_mode_sense(msc_host_device_handle_t device);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,188 @@
/*
* SPDX-FileCopyrightText: 2015-2023 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 ESP_ERR_MSC_STALL (ESP_ERR_MSC_HOST_BASE + 4) /*!< USB transfer stalled */
#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 created background task */
size_t stack_size; /**< Stack size of created 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)
__attribute__((deprecated("use API from esp_private/msc_scsi_bot.h")));
/**
* @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 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[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)
__attribute__((deprecated("use API from esp_private/msc_scsi_bot.h")));
/**
* @brief Handle MSC HOST events.
*
* If MSC Host install was called with create_background_task=false configuration,
* application needs to handle USB Host events itself.
* Do not call this function if MSC host install was called with create_background_task=true configuration
*
* @param[in] timeout Timeout in FreeRTOS tick
* @return
* - ESP_OK: All events handled
* - ESP_ERR_TIMEOUT: No events handled within the timeout
* - ESP_FAIL: Event handling finished, driver uninstalled. You do not have to call this function further
*/
esp_err_t msc_host_handle_events(uint32_t timeout);
/**
* @brief Gets devices information.
*
* @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);
/**
* @brief MSC Bulk Only Transport Reset Recovery
*
* @see USB Mass Storage Class Bulk Only Transport, Chapter 5.3.4
*
* @param[in] device Handle of MSC device
* @return
* - ESP_OK: The device was recovered from reset
* - ESP_FAIL: Recovery unsuccessful, might indicate broken device
*/
esp_err_t msc_host_reset_recovery(msc_host_device_handle_t device);
#ifdef __cplusplus
}
#endif //__cplusplus

View file

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_vfs_fat.h"
#include "usb/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

View file

@ -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

View file

@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2015-2024 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;
SemaphoreHandle_t transfer_done;
usb_device_handle_t handle;
usb_transfer_t *xfer;
msc_config_t config;
usb_disk_t disk;
} msc_device_t;
/**
* @brief Trigger a BULK transfer to device
*
* Data buffer ownership is transferred to the MSC driver and the application cannot access it before the transfer finishes.
*
* @param[in] device_handle MSC device handle
* @param[inout] data Data buffer. Direction depends on 'ep'.
* @param[in] size Size of buffer in bytes
* @param[in] ep Direction of the transfer
* @return esp_err_t
*/
esp_err_t msc_bulk_transfer(msc_device_t *device_handle, uint8_t *data, size_t size, msc_endpoint_t ep);
/**
* @brief Trigger a CTRL transfer to device
*
* The request and data must be filled by accessing private device_handle->xfer before calling this function
*
* @param[in] device_handle MSC device handle
* @param[in] len Length of the transfer
* @return esp_err_t
*/
esp_err_t msc_control_transfer(msc_device_t *device_handle, size_t len);
/**
* @brief Reset endpoint and clear feature
*
* @param[in] device MSC device handle
* @param[in] endpoint Endpoint number
* @return esp_err_t
*/
esp_err_t clear_feature(msc_device_t *device, uint8_t endpoint);
#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

View file

@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2015-2023 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]);
usb_disk_t *disk = s_disks[pdrv];
size_t sector_size = disk->block_size;
msc_device_t *dev = __containerof(disk, msc_device_t, disk);
esp_err_t err = scsi_cmd_read10(dev, buff, sector, count, 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]);
usb_disk_t *disk = s_disks[pdrv];
size_t sector_size = disk->block_size;
msc_device_t *dev = __containerof(disk, msc_device_t, disk);
esp_err_t err = scsi_cmd_write10(dev, buff, sector, count, 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;
}

View file

@ -0,0 +1,698 @@
/*
* SPDX-FileCopyrightText: 2015-2024 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 "esp_heap_caps.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 "usb/msc_host.h"
#include "msc_scsi_bot.h"
#include "usb/usb_types_ch9.h"
#include "usb/usb_helpers.h"
#include "soc/soc_memory_layout.h"
// MSC driver spin lock
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)
// MSC Control requests
#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 FEATURE_SELECTOR_ENDPOINT 0
#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 DEFAULT_XFER_SIZE (64) // Transfer size used for all transfers apart from SCSI read/write
#define WAIT_FOR_READY_TIMEOUT_MS 5000
#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
static const char *TAG = "USB_MSC";
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;
bool event_handling_started;
STAILQ_HEAD(devices, msc_host_device) devices_tailq;
} msc_driver_t;
static msc_driver_t *s_msc_driver;
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 inline bool is_in_endpoint(uint8_t endpoint)
{
return endpoint & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK ? true : false;
}
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;
}
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_halt(dev, endpoint) );
esp_err_t err = usb_host_endpoint_flush(dev, endpoint);
if (ESP_OK != err ) {
// The endpoint cannot be flushed if it does not have STALL condition
// Return without ESP_LOGE
return err;
}
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, USB_SETUP_PACKET_SIZE) );
return ESP_OK;
}
/**
* @brief Bulk-Only Mass Storage Reset
*
* This class-specific request shall ready the device for the next CBW from the host.
* The device shall preserve the value of its bulk data toggle bits and endpoint STALL conditions despite the Bulk-Only Mass Storage Reset.
*
* @see USB Mass Storage Class Bulk Only Transport, Chapter 3.1
*
* @param[in] dev MSC device handle
* @return esp_err_t
*/
static esp_err_t msc_mass_reset(msc_host_device_handle_t dev)
{
msc_device_t *device = (msc_device_t *)dev;
usb_transfer_t *xfer = device->xfer;
USB_MASS_REQ_INIT_RESET((usb_setup_packet_t *)xfer->data_buffer, device->config.iface_num);
MSC_RETURN_ON_ERROR( msc_control_transfer(device, USB_SETUP_PACKET_SIZE) );
return ESP_OK;
}
/**
* @brief MSC get maximum Logical Unit Number
*
* If the device implements 3 LUNs, the returned value is 2. (LUN0, LUN1, LUN2).
*
* This driver does not support multiple LUNs yet.
*
* @see USB Mass Storage Class Bulk Only Transport, Chapter 3.2
*
* @param[in] dev MSC device handle
* @param[out] lun Maximum Logical Unit Number
* @return esp_err_t
*/
__attribute__((unused)) static esp_err_t msc_get_max_lun(msc_host_device_handle_t dev, uint8_t *lun)
{
msc_device_t *device = (msc_device_t *)dev;
usb_transfer_t *xfer = device->xfer;
USB_MASS_REQ_INIT_GET_MAX_LUN((usb_setup_packet_t *)xfer->data_buffer, device->config.iface_num);
MSC_RETURN_ON_ERROR( msc_control_transfer(device, USB_SETUP_PACKET_SIZE + 1) );
*lun = xfer->data_buffer[USB_SETUP_PACKET_SIZE];
return ESP_OK;
}
/**
* @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 (is_in_endpoint(ep_desc->bEndpointAddress)) {
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 (is_in_endpoint(ep_desc->bEndpointAddress)) {
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(&s_msc_driver->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) {
return ESP_OK;
} else {
// Some MSC devices report 'NOT READY TO READY TRANSITION - MEDIA CHANGED', which isn't cleared until a REQUEST SENSE is performed.
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;
}
esp_err_t msc_host_handle_events(uint32_t timeout)
{
MSC_RETURN_ON_FALSE(s_msc_driver != NULL, ESP_ERR_INVALID_STATE);
ESP_LOGV(TAG, "USB MSC handling");
s_msc_driver->event_handling_started = true;
esp_err_t ret = usb_host_client_handle_events(s_msc_driver->client_handle, timeout);
if (s_msc_driver->end_client_event_handling) {
xSemaphoreGive(s_msc_driver->all_events_handled);
return ESP_FAIL;
}
return ret;
}
/**
* @brief USB Client Event handler
*
* Handle all USB client events such as USB transfers and connections/disconnections
*
* @param[in] arg Argument, not used
*/
static void event_handler_task(void *arg)
{
ESP_LOGD(TAG, "USB MSC handling start");
while (msc_host_handle_events(portMAX_DELAY) == ESP_OK) {
}
ESP_LOGD(TAG, "USB MSC handling stop");
vTaskDelete(NULL);
}
static msc_device_t *find_msc_device(usb_device_handle_t device_handle)
{
msc_device_t *iter;
msc_device_t *device_found = NULL;
MSC_ENTER_CRITICAL();
STAILQ_FOREACH(iter, &s_msc_driver->devices_tailq, tailq_entry) {
if (device_handle == iter->handle) {
device_found = iter;
break;
}
}
MSC_EXIT_CRITICAL();
return device_found;
}
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(&s_msc_driver->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(&s_msc_driver->devices_tailq), ESP_ERR_INVALID_STATE );
s_msc_driver->end_client_event_handling = true;
MSC_EXIT_CRITICAL();
if (s_msc_driver->event_handling_started) {
ESP_ERROR_CHECK( usb_host_client_unblock(s_msc_driver->client_handle) );
// In case the event handling started, we must wait until it finishes
xSemaphoreTake(s_msc_driver->all_events_handled, portMAX_DELAY);
}
vSemaphoreDelete(s_msc_driver->all_events_handled);
ESP_ERROR_CHECK( usb_host_client_deregister(s_msc_driver->client_handle) );
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;
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(&s_msc_driver->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(DEFAULT_XFER_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( 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) );
msc_device->disk.block_size = block_size;
msc_device->disk.block_count = block_count;
*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);
}
static void copy_string_desc(wchar_t *dest, const usb_str_desc_t *src)
{
if (dest == NULL) {
return;
}
if (src != NULL) {
size_t len = MIN((src->bLength - USB_STANDARD_DESC_SIZE) / 2, MSC_STR_DESC_SIZE - 1);
for (int i = 0; i < len; i++) {
dest[i] = (wchar_t)src->wData[i];
}
if (dest != NULL) { // This should be always true, we just check to avoid LoadProhibited exception
dest[len] = 0;
}
} else {
dest[0] = 0;
}
}
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;
usb_device_info_t dev_info;
MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &desc) );
MSC_RETURN_ON_ERROR( usb_host_device_info(dev->handle, &dev_info) );
info->idProduct = desc->idProduct;
info->idVendor = desc->idVendor;
info->sector_size = dev->disk.block_size;
info->sector_count = dev->disk.block_count;
copy_string_desc(info->iManufacturer, dev_info.str_desc_manufacturer);
copy_string_desc(info->iProduct, dev_info.str_desc_product);
copy_string_desc(info->iSerialNumber, dev_info.str_desc_serial_num);
return ESP_OK;
}
esp_err_t msc_host_print_descriptors(msc_host_device_handle_t device)
{
msc_device_t *dev = (msc_device_t *)device;
const usb_device_desc_t *device_desc;
const usb_config_desc_t *config_desc;
MSC_RETURN_ON_ERROR( usb_host_get_device_descriptor(dev->handle, &device_desc) );
MSC_RETURN_ON_ERROR( usb_host_get_active_config_descriptor(dev->handle, &config_desc) );
usb_print_device_descriptor(device_desc);
usb_print_config_descriptor(config_desc, NULL);
return ESP_OK;
}
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);
}
xSemaphoreGive(device->transfer_done);
}
static usb_transfer_status_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));
usb_transfer_status_t status = xfer->status;
if (received != pdTRUE) {
usb_host_endpoint_halt(xfer->device_handle, xfer->bEndpointAddress);
usb_host_endpoint_flush(xfer->device_handle, xfer->bEndpointAddress);
usb_host_endpoint_clear(xfer->device_handle, xfer->bEndpointAddress);
xSemaphoreTake(device->transfer_done, portMAX_DELAY); // Since we flushed the EP, this should return immediately
status = USB_TRANSFER_STATUS_TIMED_OUT;
}
return status;
}
esp_err_t msc_bulk_transfer(msc_device_t *device, uint8_t *data, size_t size, msc_endpoint_t ep)
{
esp_err_t ret = ESP_OK;
usb_transfer_t *xfer = device->xfer;
size_t transfer_size = (ep == MSC_EP_IN) ? usb_round_up_to_mps(size, device->config.bulk_in_mps) : size;
if (xfer->data_buffer_size < transfer_size) {
// The allocated buffer is not large enough -> realloc
MSC_RETURN_ON_ERROR( usb_host_transfer_free(xfer) );
MSC_RETURN_ON_ERROR( usb_host_transfer_alloc(transfer_size, 0, &device->xfer) );
xfer = device->xfer;
}
if (ep == MSC_EP_IN) {
xfer->bEndpointAddress = device->config.bulk_in_ep;
} else {
xfer->bEndpointAddress = device->config.bulk_out_ep;
memcpy(xfer->data_buffer, data, size);
}
xfer->num_bytes = transfer_size;
xfer->device_handle = device->handle;
xfer->callback = transfer_callback;
xfer->timeout_ms = 5000;
xfer->context = device;
MSC_RETURN_ON_ERROR( usb_host_transfer_submit(xfer) );
const usb_transfer_status_t status = wait_for_transfer_done(xfer);
switch (status) {
case USB_TRANSFER_STATUS_COMPLETED:
if (ep == MSC_EP_IN) {
memcpy(data, xfer->data_buffer, xfer->actual_num_bytes);
}
ret = ESP_OK;
break;
case USB_TRANSFER_STATUS_STALL:
ret = ESP_ERR_MSC_STALL; break;
default:
ret = ESP_ERR_MSC_INTERNAL; break;
}
return ret;
}
esp_err_t msc_control_transfer(msc_device_t *device, size_t len)
{
usb_transfer_t *xfer = device->xfer;
xfer->device_handle = device->handle;
xfer->bEndpointAddress = 0;
xfer->callback = transfer_callback;
xfer->timeout_ms = 5000;
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) == USB_TRANSFER_STATUS_COMPLETED ? ESP_OK : ESP_ERR_MSC_INTERNAL;
}
esp_err_t msc_host_reset_recovery(msc_host_device_handle_t device)
{
// USB Mass Storage Class Bulk Only Transport Revision 1.0
// 5.3.4 Reset Recovery
// For Reset Recovery the host shall issue in the following order: :
// (a) a Bulk-Only Mass Storage Reset
// (b) a Clear Feature HALT to the Bulk-In endpoint
// (c) a Clear Feature HALT to the Bulk-Out endpoint
ESP_RETURN_ON_ERROR( msc_mass_reset(device), TAG, "Mass reset failed" );
// Clear feature will fail if there is not STALL on the endpoint, so we don't check the errors here
clear_feature(device, device->config.bulk_in_ep);
clear_feature(device, device->config.bulk_out_ep);
MSC_RETURN_ON_ERROR( msc_wait_for_ready_state(device, WAIT_FOR_READY_TIMEOUT_MS) );
return ESP_OK;
}

View file

@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2015-2023 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 "usb/msc_host_vfs.h"
#include "diskio_impl.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_idf_version.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);
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
FRESULT err = f_mkfs(drv, FM_ANY | FM_SFD, cluster_size, workbuf, workbuf_size);
#else
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, cluster_size};
FRESULT err = f_mkfs(drv, &opt, workbuf, workbuf_size);
#endif
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;
}

View file

@ -0,0 +1,489 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "esp_log.h"
#include "inttypes.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"
#include "usb/msc_host.h"
static const char *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 CSW_SIGNATURE 0x53425355
#define CBW_SIZE 31
#define CWB_FLAG_DIRECTION_IN (1<<7) // device -> host
/**
* @brief LUT with error codes and descriptions
*
* @see USB Mass Storage Class UFI Command Specification, Revision 1.0
* Table 51 - Sense Keys, ASC/ASCQ Listing for All Commands (sorted by Key)
*
*/
typedef struct {
uint8_t sense_key;
uint8_t asc;
uint8_t ascq;
const char *description;
} sense_errors_t;
const sense_errors_t sense_errors_lut[] = {
{0x00, 0x00, 0x00, "NO SENSE"},
{0x07, 0x27, 0x00, "WRITE PROTECTED MEDIA"},
// add more items as needed
};
#define SENSE_ERROR_COUNT (sizeof(sense_errors_lut) / sizeof(sense_errors_t))
/**
* @brief Command Block Wrapper structure
*
* @see USB Mass Storage Class Bulk Only Transport, Table 5.1
*/
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
*
* @see USB Mass Storage Class Bulk Only Transport, Table 5.2
*/
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)
{
const bool csw_ok = csw->signature == CSW_SIGNATURE && csw->tag == tag &&
csw->dataResidue == 0 && csw->status == 0;
if (!csw_ok) {
ESP_LOGV(TAG, "CSW failed: dCSWSignature = 0x%02"PRIx32", dCSWTag = 0x%02"PRIx32", dCSWDataResidue = 0x%02"PRIx32"",
csw->signature, csw->tag, csw->dataResidue);
ESP_LOGD(TAG, "CSW failed: bCSWStatus 0x%02"PRIx8"", csw->status);
}
return csw_ok ? ESP_OK : ESP_FAIL;
}
/**
* @brief Execute BOT command
*
* There are multiple stages in BOT command:
* 1. Command transport
* 2. Data transport (optional)
* 3. Status transport
* 3.1. Error recovery (in case of error)
*
* This function is not 'static' so it could be called from unit test
*
* @see USB Mass Storage Class Bulk Only Transport, Chapter 5.3
*
* @param[in] device MSC device handle
* @param[in] cbw Command Block Wrapper
* @param[in] data Data (optional)
* @param[in] size Size of data in bytes
* @return esp_err_t
*/
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;
// 1. Command transport
MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)cbw, CBW_SIZE, MSC_EP_OUT) );
// 2. Optional data transport
if (data) {
MSC_RETURN_ON_ERROR( msc_bulk_transfer(device, (uint8_t *)data, size, ep) );
}
// 3. Status transport
esp_err_t err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
// 3.1 Error recovery
if (err == ESP_ERR_MSC_STALL) {
// In case of the status transport failure, we can try reading the status again after clearing feature
ESP_RETURN_ON_ERROR( clear_feature(device, device->config.bulk_in_ep), TAG, "Clear feature failed" );
err = msc_bulk_transfer(device, (uint8_t *)&csw, sizeof(msc_csw_t), MSC_EP_IN);
if (ESP_OK != err) {
// In case the repeated status transport failed we do reset recovery
// We don't check the error code here, the command has already failed.
msc_host_reset_recovery(device);
}
}
MSC_RETURN_ON_ERROR(err);
return check_csw(&csw, cbw->tag);
}
static const char *decode_sense_keys(cbw_sense_response_t *sense_response)
{
// Only decode WRITE_PROTECTED_MEDIA sense key, other keys are not implemented
for (int i = 0; i < SENSE_ERROR_COUNT; i++) {
if (sense_errors_lut[i].sense_key == sense_response->sense_key &&
sense_errors_lut[i].asc == sense_response->sense_code &&
sense_errors_lut[i].ascq == sense_response->sense_code_qualifier) {
return sense_errors_lut[i].description;
}
}
return "not found, refer to USB Mass Storage Class UFI Command Specification (Table 51)";
}
esp_err_t scsi_cmd_read10(msc_host_device_handle_t dev,
uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size)
{
msc_device_t *device = (msc_device_t *)dev;
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),
};
esp_err_t ret = bot_execute_command(device, &cbw.base, data, num_sectors * sector_size);
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}
esp_err_t scsi_cmd_write10(msc_host_device_handle_t dev,
const uint8_t *data,
uint32_t sector_address,
uint32_t num_sectors,
uint32_t sector_size)
{
msc_device_t *device = (msc_device_t *)dev;
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),
};
esp_err_t ret = bot_execute_command(device, &cbw.base, (void *)data, num_sectors * sector_size);
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}
esp_err_t scsi_cmd_read_capacity(msc_host_device_handle_t dev, uint32_t *block_size, uint32_t *block_count)
{
msc_device_t *device = (msc_device_t *)dev;
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,
};
esp_err_t ret = bot_execute_command(device, &cbw.base, &response, sizeof(response));
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
*block_count = __builtin_bswap32(response.block_count);
*block_size = __builtin_bswap32(response.block_size);
return ret;
}
esp_err_t scsi_cmd_unit_ready(msc_host_device_handle_t dev)
{
msc_device_t *device = (msc_device_t *)dev;
cbw_unit_ready_t cbw = {
CBW_BASE_INIT(IN_DIR, CBW_CMD_SIZE(cbw_unit_ready_t), 0),
.opcode = SCSI_CMD_TEST_UNIT_READY,
};
esp_err_t ret = bot_execute_command(device, &cbw.base, NULL, 0);
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}
esp_err_t scsi_cmd_sense(msc_host_device_handle_t dev, scsi_sense_data_t *sense)
{
msc_device_t *device = (msc_device_t *)dev;
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 == NULL) {
ESP_LOGE(TAG, "Sense error codes: Sense Key 0x%02"PRIx8", ASC: 0x%02"PRIx8", ASCQ: 0x%02"PRIx8"",
response.sense_key, response.sense_code, response.sense_code_qualifier);
const char *error_description = decode_sense_keys(&response);
ESP_LOGE(TAG, "Sense error description: %s", error_description);
return ESP_OK;
}
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_host_device_handle_t dev)
{
msc_device_t *device = (msc_device_t *)dev;
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),
};
esp_err_t ret = bot_execute_command(device, &cbw.base, &response, sizeof(response) );
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}
esp_err_t scsi_cmd_mode_sense(msc_host_device_handle_t dev)
{
msc_device_t *device = (msc_device_t *)dev;
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),
};
esp_err_t ret = bot_execute_command(device, &cbw.base, &response, sizeof(response) );
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}
esp_err_t scsi_cmd_prevent_removal(msc_host_device_handle_t dev, bool prevent)
{
msc_device_t *device = (msc_device_t *)dev;
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 = (uint8_t) prevent,
};
esp_err_t ret = bot_execute_command(device, &cbw.base, NULL, 0);
// In case of an error, get an error code
if (unlikely(ret != ESP_OK)) {
MSC_RETURN_ON_ERROR( scsi_cmd_sense(device, NULL));
}
return ret;
}

View file

@ -0,0 +1,14 @@
# 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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
../../usb_host_msc
../../../../../device/esp_tinyusb
)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_usb_host_msc)

View file

@ -0,0 +1,14 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB: CDC Class test application
## MSC driver
Basic functionality such as MSC device install/uninstall, file operations,
raw access to MSC device and sudden disconnect is tested.
### Hardware Required
This test requires two ESP32-S2/S3 boards with a interconnected USB peripherals,
one acting as host running MSC host driver and another MSC device driver (tinyusb).

View file

@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity usb usb_host_msc esp_tinyusb
WHOLE_ARCHIVE)

View file

@ -0,0 +1,461 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "tinyusb.h"
#include "esp_idf_version.h"
#include "soc/soc_caps.h"
#include "test_common.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_check.h"
#include "driver/gpio.h"
#include "tusb_msc_storage.h"
#endif /* ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED
#include "diskio_impl.h"
#include "diskio_sdmmc.h"
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED */
#if SOC_USB_OTG_SUPPORTED
/* sd-card configuration to be done by user */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED
#define SDMMC_BUS_WIDTH 4 /* Select the bus width of SD or MMC interface (4 or 1).
Note that even if 1 line mode is used, D3 pin of the SD card must
have a pull-up resistor connected. Otherwise the card may enter
SPI mode, the only way to recover from which is to cycle power to the card. */
#define PIN_CMD 35 /* CMD GPIO number */
#define PIN_CLK 36 /* CLK GPIO number */
#define PIN_D0 37 /* D0 GPIO number */
#define PIN_D1 38 /* D1 GPIO number (applicable when width SDMMC_BUS_WIDTH is 4) */
#define PIN_D2 33 /* D2 GPIO number (applicable when width SDMMC_BUS_WIDTH is 4) */
#define PIN_D3 34 /* D3 GPIO number (applicable when width SDMMC_BUS_WIDTH is 4) */
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED */
static const char *TAG = "msc_example";
/* TinyUSB descriptors
********************************************************************* */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
enum {
ITF_NUM_MSC = 0,
ITF_NUM_TOTAL
};
enum {
EDPT_MSC_OUT = 0x01,
EDPT_MSC_IN = 0x81,
};
static uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, EP Out & EP In address, EP size
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, TUD_OPT_HIGH_SPEED ? 512 : 64),
};
static tusb_desc_device_t descriptor_config = {
.bLength = sizeof(descriptor_config),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
static char const *string_desc_arr[] = {
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
// We intentionally do not implement Serial String descriptor to make sure that the driver can handle it
//"123456", // 3: Serials
//"Test MSC", // 4. MSC
};
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) */
/*********************************************************************** TinyUSB descriptors*/
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define VBUS_MONITORING_GPIO_NUM GPIO_NUM_4
static void configure_vbus_monitoring(void)
{
// Configure GPIO Pin for vbus monitoring
const gpio_config_t vbus_gpio_config = {
.pin_bit_mask = BIT64(VBUS_MONITORING_GPIO_NUM),
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = true,
.pull_down_en = false,
};
ESP_ERROR_CHECK(gpio_config(&vbus_gpio_config));
}
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) */
static void storage_init(void)
{
ESP_LOGI(TAG, "USB MSC initialization");
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
.device_descriptor = &descriptor_config,
.configuration_descriptor = desc_configuration,
.string_descriptor = string_desc_arr,
.string_descriptor_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),
.self_powered = true,
.vbus_monitor_io = VBUS_MONITORING_GPIO_NUM
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) */
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle)
{
ESP_LOGI(TAG, "Initializing wear levelling");
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
if (data_partition == NULL) {
ESP_LOGE(TAG, "Failed to find FATFS partition. Check the partition table.");
return ESP_ERR_NOT_FOUND;
}
return wl_mount(data_partition, wl_handle);
}
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) */
void device_app(void)
{
ESP_LOGI(TAG, "Initializing storage...");
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
configure_vbus_monitoring();
static wl_handle_t wl_handle = WL_INVALID_HANDLE;
ESP_ERROR_CHECK(storage_init_spiflash(&wl_handle));
tinyusb_msc_spiflash_config_t config_spi;
config_spi.wl_handle = wl_handle;
ESP_ERROR_CHECK(tinyusb_msc_storage_init_spiflash(&config_spi));
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) */
storage_init();
while (1) {
vTaskDelay(100);
}
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED
static esp_err_t storage_init_sdmmc(sdmmc_card_t **card)
{
esp_err_t ret = ESP_OK;
bool host_init = false;
sdmmc_card_t *sd_card;
ESP_LOGI(TAG, "Initializing SDCard");
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 40MHz for SDMMC)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
if (SDMMC_BUS_WIDTH == 4) {
slot_config.width = 4;
} else {
slot_config.width = 1;
}
// On chips where the GPIOs used for SD card can be configured, set the user defined values
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
slot_config.clk = PIN_CLK;
slot_config.cmd = PIN_CMD;
slot_config.d0 = PIN_D0;
if (SDMMC_BUS_WIDTH == 4) {
slot_config.d1 = PIN_D1;
slot_config.d2 = PIN_D2;
slot_config.d3 = PIN_D3;
}
#endif // CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
// Enable internal pullups on enabled pins. The internal pullups
// are insufficient however, please make sure 10k external pullups are
// connected on the bus. This is for debug / example purpose only.
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
// not using ff_memalloc here, as allocation in internal RAM is preferred
sd_card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));
ESP_GOTO_ON_FALSE(sd_card, ESP_ERR_NO_MEM, clean, TAG, "could not allocate new sdmmc_card_t");
ESP_GOTO_ON_ERROR((*host.init)(), clean, TAG, "Host Config Init fail");
host_init = true;
ESP_GOTO_ON_ERROR(sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *) &slot_config),
clean, TAG, "Host init slot fail");
ESP_GOTO_ON_ERROR(sdmmc_card_init(&host, sd_card),
clean, TAG, "The detection pin of the slot is disconnected");
*card = sd_card;
return ESP_OK;
clean:
if (host_init) {
if (host.flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
host.deinit_p(host.slot);
} else {
(*host.deinit)();
}
}
if (sd_card) {
free(sd_card);
sd_card = NULL;
}
return ret;
}
void device_app_sdmmc(void)
{
ESP_LOGI(TAG, "Initializing storage...");
configure_vbus_monitoring();
static sdmmc_card_t *card = NULL;
ESP_ERROR_CHECK(storage_init_sdmmc(&card));
tinyusb_msc_sdmmc_config_t config_sdmmc;
config_sdmmc.card = card;
ESP_ERROR_CHECK(tinyusb_msc_storage_init_sdmmc(&config_sdmmc));
storage_init();
while (1) {
vTaskDelay(100);
}
}
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED */
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
// 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, 'F', 'A', 'T', '3', '2', ' ', ' ', ' ', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 /* ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) */
#endif /* SOC_USB_OTG_SUPPORTED */

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "esp_heap_caps.h"
static size_t before_free_8bit;
static size_t before_free_32bit;
#define TEST_MEMORY_LEAK_THRESHOLD (-530)
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void app_main(void)
{
// ____ ___ ___________________ __ __
// | | \/ _____/\______ \ _/ |_ ____ _______/ |_
// | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\.
// | | / / \ | | \ | | \ ___/ \___ \ | |
// |______/ /_______ / |______ / |__| \___ >____ > |__|
// \/ \/ \/ \/
printf(" ____ ___ ___________________ __ __ \r\n");
printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n");
printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n");
printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n");
printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n");
printf(" \\/ \\/ \\/ \\/ \r\n");
UNITY_BEGIN();
unity_run_menu();
UNITY_END();
}
/* setUp runs before every test */
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
/* tearDown runs after every test */
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_idf_version.h"
enum {
// FatFS only allows to format disks with number of blocks greater than 128
DISK_BLOCK_NUM = 128 + 1,
DISK_BLOCK_SIZE = 512
};
void device_app(void);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED
void device_app_sdmmc(void);
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED */
#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"

View file

@ -0,0 +1,549 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include "esp_private/usb_phy.h"
#include "esp_private/msc_scsi_bot.h"
#include "usb/usb_host.h"
#include "usb/msc_host_vfs.h"
#include "test_common.h"
#include "esp_idf_version.h"
#include "../private_include/msc_common.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 = true,
.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 usb_phy_handle_t phy_hdl = NULL;
static void force_conn_state(bool connected, TickType_t delay_ticks)
{
TEST_ASSERT(phy_hdl);
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
}
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_EQUAL(MSC_DEVICE_DISCONNECTED, event->event);
}
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);
fprintf(f, TEST_STRING);
fclose(f);
ESP_LOGI(TAG, "Reading file");
TEST_ASSERT(fopen(file_path, "r"));
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);
}
/**
* @brief MSC driver handling task
*
* This task is only used if the MSC driver was installed with no background task
*
* @param[in] args Not used
*/
static void msc_task(void *args)
{
ESP_LOGI(TAG, "USB MSC handling start");
while (msc_host_handle_events(portMAX_DELAY) == ESP_OK) {
}
ESP_LOGI(TAG, "USB MSC handling stop");
vTaskDelete(NULL);
}
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
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_NOT_NULL_MESSAGE(file, "Could not open file");
char content[200];
size_t read_cnt = fread(content, 1, sizeof(content), file);
TEST_ASSERT_EQUAL_MESSAGE(strlen(expected), read_cnt, "Error in reading file");
TEST_ASSERT_EQUAL_STRING(content, expected);
fclose(file);
}
#endif /* ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) */
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);
ESP_LOGI(TAG, "Write data");
TEST_ASSERT_EQUAL(DATA_SIZE, fwrite(data, 1, DATA_SIZE, file));
TEST_ASSERT_EQUAL(DATA_SIZE, fwrite(data, 1, DATA_SIZE, file));
TEST_ASSERT_EQUAL(0, fflush(file));
ESP_LOGI(TAG, "Trigger a disconnect");
//Trigger a disconnect
waiting_for_sudden_disconnect = true;
force_conn_state(false, 0);
// Make sure flag was leared in callback
vTaskDelay( pdMS_TO_TICKS(100) );
TEST_ASSERT_FALSE(waiting_for_sudden_disconnect);
ESP_LOGI(TAG, "Write data after disconnect");
TEST_ASSERT_NOT_EQUAL( DATA_SIZE, fwrite(data, 1, DATA_SIZE, file));
fclose(file);
}
static void msc_test_init(void)
{
BaseType_t task_created;
ready_to_deinit_usb = xSemaphoreCreateBinary();
TEST_ASSERT( app_queue = xQueueCreate(5, sizeof(msc_host_event_t)) );
//Initialize the internal USB PHY to connect to the USB OTG peripheral. We manually install the USB PHY for testing
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
};
ESP_OK_ASSERT(usb_new_phy(&phy_config, &phy_hdl));
const usb_host_config_t host_config = {
.skip_phy_setup = true,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_OK_ASSERT( usb_host_install(&host_config) );
task_created = xTaskCreatePinnedToCore(handle_usb_events, "usb_events", 2 * 2048, NULL, 2, NULL, 0);
TEST_ASSERT(task_created);
}
static void msc_test_wait_and_install_device(void)
{
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_EQUAL(MSC_DEVICE_CONNECTED, app_event.event);
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_setup(void)
{
msc_test_init();
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) );
msc_test_wait_and_install_device();
}
static void msc_test_uninstall_device(void)
{
ESP_OK_ASSERT( msc_host_vfs_unregister(vfs_handle) );
ESP_OK_ASSERT( msc_host_uninstall_device(device) );
}
static void msc_test_deinit(void)
{
ESP_OK_ASSERT( msc_host_uninstall() );
xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY);
vSemaphoreDelete(ready_to_deinit_usb);
vTaskDelay(10); // Wait to finish any ongoing USB operations
ESP_OK_ASSERT( usb_host_uninstall() );
//Tear down USB PHY
ESP_OK_ASSERT(usb_del_phy(phy_hdl));
phy_hdl = NULL;
vQueueDelete(app_queue);
vTaskDelay(10); // Wait for FreeRTOS to clean up deleted tasks
}
static void msc_teardown(void)
{
msc_test_uninstall_device();
msc_test_deinit();
}
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);
scsi_cmd_write10(device, write_data, 10, 1, DISK_BLOCK_SIZE);
scsi_cmd_read10(device, read_data, 10, 1, 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++) {
scsi_cmd_write10(device, data, block, 1, DISK_BLOCK_SIZE);
}
}
TEST_CASE("write_and_read_file", "[usb_msc]")
{
msc_setup();
write_read_file(FILE_NAME);
msc_teardown();
}
TEST_CASE("sudden_disconnect", "[usb_msc]")
{
msc_setup();
check_sudden_disconnect();
msc_teardown();
}
TEST_CASE("sectors_can_be_written_and_read", "[usb_msc]")
{
msc_setup();
write_read_sectors();
msc_teardown();
}
/**
* @brief Check README content
*
* This test strictly requires our implementation of USB MSC Mock device.
* This test will fail for usualW flash drives, as they don't have README.TXT file on them.
*/
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
TEST_CASE("check_README_content", "[usb_msc]")
{
msc_setup();
check_file_content("/usb/README.TXT", README_CONTENTS);
msc_teardown();
}
#endif /* ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) */
esp_err_t bot_execute_command(msc_device_t *device, uint8_t *cbw, void *data, size_t size);
/**
* @brief Error recovery testcase
*
* Various error cases:
* - Accessing non-existent memory
* - Invalid SCSI command
* - USB transfer STALL
*/
TEST_CASE("error_recovery_1", "[usb_msc][ignore]")
{
msc_setup();
uint8_t data[DISK_BLOCK_SIZE];
esp_err_t err;
// Some flash disks will respond with stall, some with error in CSW, some with timeout
printf("invalid bot command\n");
uint32_t dummy_cbw[8] = {0x5342555, 2, 0, 0x55555555};
err = bot_execute_command(device, (uint8_t *)dummy_cbw, data, 31);
TEST_ASSERT_NOT_EQUAL(ESP_OK, err);
err = msc_host_reset_recovery(device);
TEST_ASSERT_EQUAL(ESP_OK, err);
// Make sure we can read/write after the error was cleared
printf("read write after reset\n");
write_read_file(FILE_NAME);
msc_teardown();
}
TEST_CASE("error_recovery_2", "[usb_msc][ignore]")
{
msc_setup();
uint8_t data[DISK_BLOCK_SIZE];
esp_err_t err;
// Write to and read from invalid sector
// Some flash disks will respond with stall, some with error in CSW, some with timeout
printf("read 10\n");
err = scsi_cmd_read10(device, data, UINT32_MAX, 1, DISK_BLOCK_SIZE);
TEST_ASSERT_NOT_EQUAL(ESP_OK, err);
err = msc_host_reset_recovery(device);
TEST_ASSERT_EQUAL(ESP_OK, err);
printf("read write after reset\n");
write_read_file(FILE_NAME);
msc_teardown();
}
#define MAX_BUFFER_SIZE (1024 + 1) // Maximum buffer size for this test
#define SETVBUF_TEST(_size) do { \
printf("setvbuf %d\n", _size); \
int err = setvbuf(file, NULL, _IOFBF, _size); \
TEST_ASSERT_EQUAL_MESSAGE(0, err, "setvbuf failed"); \
err = fseek(file, SEEK_SET, 0); \
TEST_ASSERT_EQUAL_MESSAGE(0, err, "fseek failed"); \
size_t write_cnt = fwrite(write_buf, 1, _size, file); \
TEST_ASSERT_EQUAL(_size, write_cnt); \
err = fseek(file, SEEK_SET, 0); \
TEST_ASSERT_EQUAL_MESSAGE(0, err, "fseek failed"); \
memset(read_buf, 0, MAX_BUFFER_SIZE + 1); \
size_t read_cnt = fread(read_buf, 1, _size, file); \
TEST_ASSERT_EQUAL_MESSAGE(_size, read_cnt, "Error in reading file"); \
TEST_ASSERT_EQUAL_HEX8_ARRAY(write_buf, read_buf, _size); \
TEST_ASSERT_EQUAL_MESSAGE(0, read_buf[_size], "Read buffer accessed outside of its boundaries"); \
} while(0); \
/**
* @brief setvbuf testcase
*
* From v1.1.0 the MSC driver reuses buffer from VFS for USB transfers.
* Check that this feature works correctly with various buffer lengths.
*/
TEST_CASE("setvbuf", "[usb_msc]")
{
msc_setup();
FILE *file = fopen("/usb/test", "w+");
TEST_ASSERT_NOT_NULL_MESSAGE(file, "Could not open file for writing");
char *write_buf = malloc(MAX_BUFFER_SIZE);
char *read_buf = malloc(MAX_BUFFER_SIZE + 1); // 1 canary byte
TEST_ASSERT_NOT_NULL(write_buf);
TEST_ASSERT_NOT_NULL(read_buf);
// Initialize write buffer with some data
for (int i = 0; i < MAX_BUFFER_SIZE; i++) {
write_buf[i] = i & 0xFF;
}
SETVBUF_TEST(64);
SETVBUF_TEST(128);
SETVBUF_TEST(500);
SETVBUF_TEST(1000);
SETVBUF_TEST(1023);
SETVBUF_TEST(1024);
SETVBUF_TEST(MAX_BUFFER_SIZE);
fclose(file);
free(write_buf);
free(read_buf);
msc_teardown();
}
/**
* @brief USB MSC format testcase
* @attention This testcase deletes all content on the USB MSC device.
* The device must be reset in order to contain the FILE_NAME again.
*/
TEST_CASE("can_be_formated", "[usb_msc]")
{
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_FALSE(file_exists(FILE_NAME));
msc_teardown();
mount_config.format_if_mount_failed = false;
}
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: %"PRIu32"\n", info->sector_size);
printf("\t Sector count: %"PRIu32"\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);
}
/**
* @brief USB MSC device_info testcase
*
* Simple testcase that only runs msc_host_get_device_info()
* To make sure that we correctly handle missing string descriptors
*/
TEST_CASE("device_info", "[usb_msc]")
{
msc_setup();
msc_host_device_info_t info;
esp_err_t err = msc_host_get_device_info(device, &info);
msc_teardown();
TEST_ASSERT_EQUAL(ESP_OK, err);
print_device_info(&info);
}
/**
* @brief USB MSC driver with no background task
*
* Install the driver without background task
* and make sure that everything works
*/
TEST_CASE("no_background_task", "[usb_msc]")
{
msc_test_init();
const msc_host_driver_config_t msc_config = {
.create_backround_task = false,
.callback = msc_event_cb,
.stack_size = 4096,
.task_priority = 5,
};
ESP_OK_ASSERT( msc_host_install(&msc_config) );
BaseType_t task_created = xTaskCreatePinnedToCore(msc_task, "msc_events", 2 * 2048, NULL, 2, NULL, 0);
TEST_ASSERT(task_created);
msc_test_wait_and_install_device();
write_read_sectors(); // Do some dummy operations
msc_teardown();
}
/**
* @brief USB MSC driver with no background task
*
* Install and uninstall the driver without background task
* without ever calling usb_host_client_handle_events()
*/
TEST_CASE("no_background_task_2", "[usb_msc]")
{
msc_test_init();
const msc_host_driver_config_t msc_config = {
.create_backround_task = false,
.callback = msc_event_cb,
};
ESP_OK_ASSERT( msc_host_install(&msc_config) );
vTaskDelay(100); // Give USB Host Library some time for device enumeration
msc_test_deinit();
}
/**
* @brief USB MSC Device Mock
*
* We use this 'testcase' to provide MSC mock device for our DUT
*/
TEST_CASE("mock_device_app", "[usb_msc_device][ignore]")
{
device_app();
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED
TEST_CASE("mock_device_app", "[usb_msc_device_sdmmc][ignore]")
{
device_app_sdmmc();
}
#endif /* ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && SOC_SDMMC_HOST_SUPPORTED */
#endif /* SOC_USB_OTG_SUPPORTED */

View file

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

View file

@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from typing import Tuple
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.usb_host
@pytest.mark.parametrize('count', [
2,
], indirect=True)
def test_usb_host_msc(dut: Tuple[IdfDut, IdfDut]) -> None:
device = dut[0]
host = dut[1]
# 2.1 Prepare USB device for MSC test
device.expect_exact('Press ENTER to see the list of tests.')
device.write('[usb_msc_device]')
device.expect_exact('USB initialization DONE')
# 2.2 Run MSC test
host.run_all_single_board_cases(group='usb_msc')

View file

@ -0,0 +1,27 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_MSC_ENABLED=y
CONFIG_TINYUSB_CDC_ENABLED=n
CONFIG_TINYUSB_CDC_COUNT=0
CONFIG_TINYUSB_HID_COUNT=0
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_MODE_PERF=y
CONFIG_FATFS_LFN_HEAP=y