Skip to content

Commit

Permalink
fix(tusb_msc): Cache sector metrics, enable buffering on ESP32S3
Browse files Browse the repository at this point in the history
Cached `get_sector_size` and `get_sector_count` to improve performance and avoid redundant calls.
Storage buffering mechanism is enabled only for ESP32S3 targets since it
negatively impacts SD card write speed on other platforms.

Closes espressif/esp-idf#11110
  • Loading branch information
igi540 committed Feb 7, 2025
1 parent 9c4d6a2 commit 83da0d2
Showing 1 changed file with 126 additions and 32 deletions.
158 changes: 126 additions & 32 deletions device/esp_tinyusb/tusb_msc_storage.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -15,6 +15,7 @@
#include "esp_partition.h"
#include "vfs_fat_internal.h"
#include "tinyusb.h"
#include "device/usbd_pvt.h"
#include "class/msc/msc_device.h"
#include "tusb_msc_storage.h"
#include "esp_vfs_fat.h"
Expand All @@ -24,29 +25,56 @@

static const char *TAG = "tinyusb_msc_storage";

/**
* @brief Handle for TinyUSB MSC storage interface.
*
* This structure holds metadata and function pointers required to
* manage the underlying storage medium (SPI flash, SDMMC).
*/
typedef struct {
bool is_fat_mounted;
const char *base_path;
bool is_fat_mounted; /*!< Indicates if the FAT filesystem is currently mounted. */
const char *base_path; /*!< Base path where the filesystem is mounted. */
union {
wl_handle_t wl_handle;
wl_handle_t wl_handle; /*!< Handle for wear leveling on SPI flash. */
#if SOC_SDMMC_HOST_SUPPORTED
sdmmc_card_t *card;
sdmmc_card_t *card; /*!< Handle for SDMMC card. */
#endif
};
esp_err_t (*mount)(BYTE pdrv);
esp_err_t (*unmount)(void);
uint32_t (*sector_count)(void);
uint32_t (*sector_size)(void);
esp_err_t (*read)(size_t sector_size, uint32_t lba, uint32_t offset, size_t size, void *dest);
esp_err_t (*write)(size_t sector_size, size_t addr, uint32_t lba, uint32_t offset, size_t size, const void *src);
tusb_msc_callback_t callback_mount_changed;
tusb_msc_callback_t callback_premount_changed;
int max_files;
} tinyusb_msc_storage_handle_s; /*!< MSC object */
esp_err_t (*mount)(BYTE pdrv); /*!< Pointer to the mount function. */
esp_err_t (*unmount)(void); /*!< Pointer to the unmount function. */
uint32_t sector_count; /*!< Total number of sectors in the storage medium. */
uint32_t sector_size; /*!< Size of a single sector in bytes. */
esp_err_t (*read)(size_t sector_size, /*!< Function pointer for reading data. */
uint32_t lba, uint32_t offset, size_t size, void *dest);
esp_err_t (*write)(size_t sector_size, /*!< Function pointer for writing data. */
size_t addr, uint32_t lba, uint32_t offset, size_t size, const void *src);
tusb_msc_callback_t callback_mount_changed; /*!< Callback for mount state change. */
tusb_msc_callback_t callback_premount_changed; /*!< Callback for pre-mount state change. */
int max_files; /*!< Maximum number of files that can be open simultaneously. */
} tinyusb_msc_storage_handle_s;

/* handle of tinyusb driver connected to application */
static tinyusb_msc_storage_handle_s *s_storage_handle;

#define MSC_STORAGE_BUFFER_SIZE CONFIG_TINYUSB_MSC_BUFSIZE /*!< Size of the buffer, configured via menuconfig (MSC FIFO size) */
#if ((MSC_STORAGE_BUFFER_SIZE) % 4 != 0)
#error "CONFIG_TINYUSB_MSC_BUFSIZE must be divisible by 4. Adjust your configuration (MSC FIFO size) in menuconfig."
#endif

#if CONFIG_IDF_TARGET_ESP32S3
/**
* @brief Structure representing a single write buffer for MSC operations.
*/
typedef struct {
uint8_t data_buffer[MSC_STORAGE_BUFFER_SIZE]; /*!< Buffer to store write data. The size is defined by MSC_STORAGE_BUFFER_SIZE. */
uint32_t lba; /*!< Logical Block Address for the current WRITE10 operation. */
uint32_t offset; /*!< Offset within the specified LBA for the current write operation. */
uint32_t bufsize; /*!< Number of bytes to be written in this operation. */
} msc_storage_buffer_t;

static msc_storage_buffer_t *storage_buffer;
#endif

static esp_err_t _mount_spiflash(BYTE pdrv)
{
return ff_diskio_register_wl_partition(pdrv, s_storage_handle->wl_handle);
Expand Down Expand Up @@ -170,7 +198,7 @@ static esp_err_t _write_sector_sdmmc(size_t sector_size,
}
#endif

static esp_err_t msc_storage_read_sector(uint32_t lba,
static esp_err_t _msc_storage_read_sector(uint32_t lba,
uint32_t offset,
size_t size,
void *dest)
Expand All @@ -180,7 +208,7 @@ static esp_err_t msc_storage_read_sector(uint32_t lba,
return (s_storage_handle->read)(sector_size, lba, offset, size, dest);
}

static esp_err_t msc_storage_write_sector(uint32_t lba,
static esp_err_t _msc_storage_write_sector(uint32_t lba,
uint32_t offset,
size_t size,
const void *src)
Expand Down Expand Up @@ -249,6 +277,47 @@ static esp_err_t _mount(char *drv, FATFS *fs)
return ret;
}

#if CONFIG_IDF_TARGET_ESP32S3

static void _write_task(void *param)
{
// Process the data in storage_buffer
esp_err_t err = _msc_storage_write_sector(
storage_buffer->lba,
storage_buffer->offset,
storage_buffer->bufsize,
(const void *)storage_buffer->data_buffer
);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Write operation failed: 0x%x", err);
tud_msc_set_sense(0, SCSI_SENSE_MEDIUM_ERROR, 0x03, 0x11);// Set SCSI sense data to report a medium error to the USB host.
}
}

static esp_err_t _init_storage_buffer(void)
{
assert(!storage_buffer);
storage_buffer = heap_caps_malloc(sizeof(msc_storage_buffer_t), MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(storage_buffer, ESP_ERR_NO_MEM, TAG, "could not allocate write buffer");

memset(storage_buffer, 0, sizeof(msc_storage_buffer_t));

if (!esp_ptr_dma_capable((const void *)storage_buffer->data_buffer)) {
ESP_LOGW(TAG, "storage buffer is not DMA capable");
}

return ESP_OK;
}

static void _free_storage_buffer(void)
{
if (storage_buffer) {
free(storage_buffer);
storage_buffer = NULL;
}
}
#endif

esp_err_t tinyusb_msc_storage_mount(const char *base_path)
{
esp_err_t ret = ESP_OK;
Expand Down Expand Up @@ -364,13 +433,13 @@ esp_err_t tinyusb_msc_storage_unmount(void)
uint32_t tinyusb_msc_storage_get_sector_count(void)
{
assert(s_storage_handle);
return (s_storage_handle->sector_count)();
return (s_storage_handle->sector_count);
}

uint32_t tinyusb_msc_storage_get_sector_size(void)
{
assert(s_storage_handle);
return (s_storage_handle->sector_size)();
return (s_storage_handle->sector_size);
}

esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t *config)
Expand All @@ -380,13 +449,13 @@ esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t
ESP_RETURN_ON_FALSE(s_storage_handle, ESP_ERR_NO_MEM, TAG, "could not allocate new handle for storage");
s_storage_handle->mount = &_mount_spiflash;
s_storage_handle->unmount = &_unmount_spiflash;
s_storage_handle->sector_count = &_get_sector_count_spiflash;
s_storage_handle->sector_size = &_get_sector_size_spiflash;
s_storage_handle->wl_handle = config->wl_handle;
s_storage_handle->sector_count = _get_sector_count_spiflash();
s_storage_handle->sector_size = _get_sector_size_spiflash();
s_storage_handle->read = &_read_sector_spiflash;
s_storage_handle->write = &_write_sector_spiflash;
s_storage_handle->is_fat_mounted = false;
s_storage_handle->base_path = NULL;
s_storage_handle->wl_handle = config->wl_handle;
// In case the user does not set mount_config.max_files
// and for backward compatibility with versions <1.4.2
// max_files is set to 2
Expand All @@ -405,7 +474,11 @@ esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED);
}

#if CONFIG_IDF_TARGET_ESP32S3
return _init_storage_buffer();
#else
return ESP_OK;
#endif
}

#if SOC_SDMMC_HOST_SUPPORTED
Expand All @@ -416,13 +489,13 @@ esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *confi
ESP_RETURN_ON_FALSE(s_storage_handle, ESP_ERR_NO_MEM, TAG, "could not allocate new handle for storage");
s_storage_handle->mount = &_mount_sdmmc;
s_storage_handle->unmount = &_unmount_sdmmc;
s_storage_handle->sector_count = &_get_sector_count_sdmmc;
s_storage_handle->sector_size = &_get_sector_size_sdmmc;
s_storage_handle->card = config->card;
s_storage_handle->sector_count = _get_sector_count_sdmmc();
s_storage_handle->sector_size = _get_sector_size_sdmmc();
s_storage_handle->read = &_read_sector_sdmmc;
s_storage_handle->write = &_write_sector_sdmmc;
s_storage_handle->is_fat_mounted = false;
s_storage_handle->base_path = NULL;
s_storage_handle->card = config->card;
// In case the user does not set mount_config.max_files
// and for backward compatibility with versions <1.4.2
// max_files is set to 2
Expand All @@ -441,15 +514,23 @@ esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *confi
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED);
}

#if CONFIG_IDF_TARGET_ESP32S3
return _init_storage_buffer();
#else
return ESP_OK;
#endif
}
#endif

void tinyusb_msc_storage_deinit(void)
{
assert(s_storage_handle);
free(s_storage_handle);
s_storage_handle = NULL;
#if CONFIG_IDF_TARGET_ESP32S3
_free_storage_buffer();
#endif
if (s_storage_handle) {
free(s_storage_handle);
s_storage_handle = NULL;
}
}

esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type,
Expand Down Expand Up @@ -508,7 +589,7 @@ void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16
(void) lun;
const char vid[] = "TinyUSB";
const char pid[] = "Flash Storage";
const char rev[] = "0.1";
const char rev[] = "0.2";

memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, pid, strlen(pid));
Expand Down Expand Up @@ -567,7 +648,7 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo
// - Application fill the buffer (up to bufsize) with address contents and return number of read byte.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
{
esp_err_t err = msc_storage_read_sector(lba, offset, bufsize, buffer);
esp_err_t err = _msc_storage_read_sector(lba, offset, bufsize, buffer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "msc_storage_read_sector failed: 0x%x", err);
return 0;
Expand All @@ -580,11 +661,24 @@ int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buff
// - Application write data from buffer to address contents (up to bufsize) and return number of written byte.
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
esp_err_t err = msc_storage_write_sector(lba, offset, bufsize, buffer);
#if CONFIG_IDF_TARGET_ESP32S3
assert(bufsize <= MSC_STORAGE_BUFFER_SIZE);
// Copy data to the buffer
memcpy((void *)storage_buffer->data_buffer, buffer, bufsize);
storage_buffer->lba = lba;
storage_buffer->offset = offset;
storage_buffer->bufsize = bufsize;

// Defer execution of the write to the TinyUSB task
usbd_defer_func(_write_task, NULL, false);
#else
esp_err_t err = _msc_storage_write_sector(lba, offset, bufsize, buffer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "msc_storage_write_sector failed: 0x%x", err);
return 0;
return -1;
}
#endif
// Return the number of bytes accepted
return bufsize;
}

Expand Down

0 comments on commit 83da0d2

Please sign in to comment.