Merge pull request #73 from espressif/feature/usb_host_msc

usb host msc class
This commit is contained in:
Tomas Rezucha 2022-08-10 09:26:11 +02:00 committed by GitHub
commit 98a30f24c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2512 additions and 3 deletions

View File

@ -15,6 +15,6 @@ jobs:
- name: Upload components to component service
uses: espressif/upload-components-ci-action@v1
with:
directories: "cbor;jsmn;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat;esp_encrypted_img;coap;pcap;json_generator;json_parser;usb/usb_host_cdc_acm"
directories: "cbor;jsmn;libsodium;pid_ctrl;qrcode;nghttp;sh2lib;expat;esp_encrypted_img;coap;pcap;json_generator;json_parser;usb/usb_host_cdc_acm;usb/usb_host_msc"
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

View File

@ -3,9 +3,9 @@
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../usb_host_cdc_acm
$ENV{IDF_PATH}/examples/peripherals/usb/host/msc/components/)
../usb_host_msc)
# Set the components to include the tests for.
set(TEST_COMPONENTS "usb_host_cdc_acm" "msc" CACHE STRING "List of components to test")
set(TEST_COMPONENTS "usb_host_cdc_acm" "usb_host_msc" CACHE STRING "List of components to test")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(usb_test_app)

View File

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

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

View File

@ -0,0 +1,10 @@
version: "1.0.0"
description: USB Host MSC driver
url: https://github.com/espressif/idf-extra-components/tree/master/usb/usb_host_msc
dependencies:
idf: ">=4.4"
targets:
- esp32s2
- esp32s3

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,302 @@
/*
* SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org)
*
* SPDX-License-Identifier: MIT
*
* SPDX-FileContributor: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
*/
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include <stdint.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "test_common.h"
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#define MASS_STORAGE_CLASS 0x08
#define SCSI_COMMAND_SET 0x06
#define BULK_ONLY_TRANSFER 0x50
static const char *TAG = "msc_example";
/**** Kconfig driven Descriptor ****/
static const tusb_desc_device_t device_descriptor = {
.bLength = sizeof(device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = MASS_STORAGE_CLASS,
.bDeviceSubClass = SCSI_COMMAND_SET,
.bDeviceProtocol = BULK_ONLY_TRANSFER,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_ESPRESSIF_VID,
.idProduct = 0x1234,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
const uint16_t msc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_MSC * TUD_MSC_DESC_LEN;
static const uint8_t msc_desc_configuration[] = {
TUD_CONFIG_DESCRIPTOR(1, 4, 0, msc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_MSC_DESCRIPTOR(0, 5, 1, 0x80 | 1, 64),
};
void device_app(void)
{
ESP_LOGI(TAG, "USB initialization");
tinyusb_config_t tusb_cfg = {
.device_descriptor = &device_descriptor,
.configuration_descriptor = msc_desc_configuration
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
while (1) {
vTaskDelay(100);
}
}
// whether host does safe-eject
static bool ejected = false;
// Some MCU doesn't have enough 8KB SRAM to store the whole disk
// We will use Flash as read-only disk with board that has
// CFG_EXAMPLE_MSC_READONLY defined
uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE] = {
//------------- Block0: Boot Sector -------------//
// byte_per_sector = DISK_BLOCK_SIZE; fat12_sector_num_16 = DISK_BLOCK_NUM;
// sector_per_cluster = 1; reserved_sectors = 1;
// fat_num = 1; fat12_root_entry_num = 16;
// sector_per_fat = 1; sector_per_track = 1; head_num = 1; hidden_sectors = 0;
// drive_number = 0x80; media_type = 0xf8; extended_boot_signature = 0x29;
// filesystem_type = "FAT12 "; volume_serial_number = 0x1234; volume_label = "TinyUSB MSC";
// FAT magic code at offset 510-511
{
0xEB, 0x3C, 0x90, 0x4D, 0x53, 0x44, 0x4F, 0x53, 0x35, 0x2E, 0x30, 0x00, 0x02, 0x01, 0x01, 0x00,
0x01, 0x10, 0x00, 0x10, 0x00, 0xF8, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x29, 0x34, 0x12, 0x00, 0x00, 'T', 'i', 'n', 'y', 'U',
'S', 'B', ' ', 'M', 'S', 'C', 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00,
// Zero up to 2 last bytes of FAT magic code
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, '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 /* SOC_USB_OTG_SUPPORTED */

View File

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
enum {
// FatFS only allows to format disks with number of blocks greater than 128
DISK_BLOCK_NUM = 128 + 1,
DISK_BLOCK_SIZE = 512
};
#define README_CONTENTS \
"This is tinyusb's MassStorage Class demo.\r\n\r\n\
If you find any bugs or get any questions, feel free to file an\r\n\
issue at github.com/hathach/tinyusb"
void device_app(void);

View File

@ -0,0 +1,324 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_private/usb_phy.h"
#include "usb/usb_host.h"
#include "msc_host.h"
#include "msc_host_vfs.h"
#include "ffconf.h"
#include "ff.h"
#include "test_common.h"
#include "soc/usb_wrap_struct.h"
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
static const char *TAG = "APP";
#define ESP_OK_ASSERT(exp) TEST_ASSERT_EQUAL(ESP_OK, exp)
static esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 1024,
};
static QueueHandle_t app_queue;
static SemaphoreHandle_t ready_to_deinit_usb;
static msc_host_device_handle_t device;
static msc_host_vfs_handle_t vfs_handle;
static volatile bool waiting_for_sudden_disconnect;
static 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);
}
static void check_file_content(const char *file_path, const char *expected)
{
ESP_LOGI(TAG, "Reading %s:", file_path);
FILE *file = fopen(file_path, "r");
TEST_ASSERT(file)
char content[200];
fread(content, 1, sizeof(content), file);
TEST_ASSERT_EQUAL_STRING(content, expected);
fclose(file);
}
static void check_sudden_disconnect(void)
{
uint8_t data[512];
const size_t DATA_SIZE = sizeof(data);
ESP_LOGI(TAG, "Creating test.tx");
FILE *file = fopen("/usb/test.txt", "w");
TEST_ASSERT(file);
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_setup(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
.gpio_conf = NULL,
};
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", 2048, NULL, 2, NULL, 0);
TEST_ASSERT(task_created);
const msc_host_driver_config_t msc_config = {
.create_backround_task = true,
.callback = msc_event_cb,
.stack_size = 4096,
.task_priority = 5,
};
ESP_OK_ASSERT( msc_host_install(&msc_config) );
ESP_LOGI(TAG, "Waiting for USB stick to be connected");
msc_host_event_t app_event;
xQueueReceive(app_queue, &app_event, portMAX_DELAY);
TEST_ASSERT_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_teardown(void)
{
// Wait to finish any ongoing USB operations
vTaskDelay(100);
ESP_OK_ASSERT( msc_host_vfs_unregister(vfs_handle) );
ESP_OK_ASSERT( msc_host_uninstall_device(device) );
ESP_OK_ASSERT( msc_host_uninstall() );
xSemaphoreTake(ready_to_deinit_usb, portMAX_DELAY);
vSemaphoreDelete(ready_to_deinit_usb);
ESP_OK_ASSERT( usb_host_uninstall() );
//Tear down USB PHY
ESP_OK_ASSERT(usb_del_phy(phy_hdl));
phy_hdl = NULL;
vQueueDelete(app_queue);
}
static void write_read_sectors(void)
{
uint8_t write_data[DISK_BLOCK_SIZE];
uint8_t read_data[DISK_BLOCK_SIZE];
memset(write_data, 0x55, DISK_BLOCK_SIZE);
memset(read_data, 0, DISK_BLOCK_SIZE);
msc_host_write_sector(device, 10, write_data, DISK_BLOCK_SIZE);
msc_host_read_sector(device, 10, read_data, DISK_BLOCK_SIZE);
TEST_ASSERT_EQUAL_MEMORY(write_data, read_data, DISK_BLOCK_SIZE);
}
static void erase_storage(void)
{
uint8_t data[DISK_BLOCK_SIZE];
memset(data, 0xFF, DISK_BLOCK_SIZE);
for (int block = 0; block < DISK_BLOCK_NUM; block++) {
msc_host_write_sector(device, block, data, DISK_BLOCK_SIZE);
}
}
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();
}
TEST_CASE("check_README_content", "[usb_msc]")
{
msc_setup();
check_file_content("/usb/README.TXT", README_CONTENTS);
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;
}
TEST_CASE("mock_device_app", "[usb_msc_device][ignore]")
{
device_app();
}
#endif