summaryrefslogtreecommitdiff
path: root/lib/fatfs/vfs
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-07-25 17:42:00 +1000
committerjacqueline <me@jacqueline.id.au>2023-07-25 17:42:00 +1000
commit7b72e5479ee6d11f76c49f7463ba0e7f4e5165c5 (patch)
tree2965c66cf3973583b7751b771f9bc06232d98222 /lib/fatfs/vfs
parent9287c4eb8c60cc89251a1d2bdfe9c576d81d6715 (diff)
downloadtangara-fw-7b72e5479ee6d11f76c49f7463ba0e7f4e5165c5.tar.gz
fork the esp-idf fatfs for f_forward and exfat support
Diffstat (limited to 'lib/fatfs/vfs')
-rw-r--r--lib/fatfs/vfs/esp_vfs_fat.h356
-rw-r--r--lib/fatfs/vfs/vfs_fat.c1117
-rw-r--r--lib/fatfs/vfs/vfs_fat_internal.h30
-rw-r--r--lib/fatfs/vfs/vfs_fat_sdmmc.c498
-rw-r--r--lib/fatfs/vfs/vfs_fat_spiflash.c335
5 files changed, 2336 insertions, 0 deletions
diff --git a/lib/fatfs/vfs/esp_vfs_fat.h b/lib/fatfs/vfs/esp_vfs_fat.h
new file mode 100644
index 00000000..c84a2055
--- /dev/null
+++ b/lib/fatfs/vfs/esp_vfs_fat.h
@@ -0,0 +1,356 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+#include <stddef.h>
+#include "esp_err.h"
+#include "driver/gpio.h"
+#include "driver/sdmmc_types.h"
+#include "driver/sdspi_host.h"
+#include "ff.h"
+#include "wear_levelling.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Register FATFS with VFS component
+ *
+ * This function registers given FAT drive in VFS, at the specified base path.
+ * If only one drive is used, fat_drive argument can be an empty string.
+ * Refer to FATFS library documentation on how to specify FAT drive.
+ * This function also allocates FATFS structure which should be used for f_mount
+ * call.
+ *
+ * @note This function doesn't mount the drive into FATFS, it just connects
+ * POSIX and C standard library IO function with FATFS. You need to mount
+ * desired drive into FATFS separately.
+ *
+ * @param base_path path prefix where FATFS should be registered
+ * @param fat_drive FATFS drive specification; if only one drive is used, can be an empty string
+ * @param max_files maximum number of files which can be open at the same time
+ * @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument.
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called
+ * - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered
+ */
+esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive,
+ size_t max_files, FATFS** out_fs);
+
+/**
+ * @brief Un-register FATFS from VFS
+ *
+ * @note FATFS structure returned by esp_vfs_fat_register is destroyed after
+ * this call. Make sure to call f_mount function to unmount it before
+ * calling esp_vfs_fat_unregister_ctx.
+ * Difference between this function and the one above is that this one
+ * will release the correct drive, while the one above will release
+ * the last registered one
+ *
+ * @param base_path path prefix where FATFS is registered. This is the same
+ * used when esp_vfs_fat_register was called
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
+ */
+esp_err_t esp_vfs_fat_unregister_path(const char* base_path);
+
+
+/**
+ * @brief Configuration arguments for esp_vfs_fat_sdmmc_mount and esp_vfs_fat_spiflash_mount_rw_wl functions
+ */
+typedef struct {
+ /**
+ * If FAT partition can not be mounted, and this parameter is true,
+ * create partition table and format the filesystem.
+ */
+ bool format_if_mount_failed;
+ int max_files; ///< Max number of open files
+ /**
+ * If format_if_mount_failed is set, and mount fails, format the card
+ * with given allocation unit size. Must be a power of 2, between sector
+ * size and 128 * sector size.
+ * For SD cards, sector size is always 512 bytes. For wear_levelling,
+ * sector size is determined by CONFIG_WL_SECTOR_SIZE option.
+ *
+ * Using larger allocation unit size will result in higher read/write
+ * performance and higher overhead when storing small files.
+ *
+ * Setting this field to 0 will result in allocation unit set to the
+ * sector size.
+ */
+ size_t allocation_unit_size;
+ /**
+ * Enables real ff_disk_status function implementation for SD cards
+ * (ff_sdmmc_status). Possibly slows down IO performance.
+ *
+ * Try to enable if you need to handle situations when SD cards
+ * are not unmounted properly before physical removal
+ * or you are experiencing issues with SD cards.
+ *
+ * Doesn't do anything for other memory storage media.
+ */
+ bool disk_status_check_enable;
+} esp_vfs_fat_mount_config_t;
+
+// Compatibility definition
+typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
+
+/**
+ * @brief Convenience function to get FAT filesystem on SD card registered in VFS
+ *
+ * This is an all-in-one function which does the following:
+ * - initializes SDMMC driver or SPI driver with configuration in host_config
+ * - initializes SD card with configuration in slot_config
+ * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * This function is intended to make example code more compact.
+ * For real world applications, developers should implement the logic of
+ * probing SD card, locating and mounting partition, and registering FATFS in VFS,
+ * with proper error checking and handling of exceptional conditions.
+ *
+ * @note Use this API to mount a card through SDSPI is deprecated. Please call
+ * `esp_vfs_fat_sdspi_mount()` instead for that case.
+ *
+ * @param base_path path where partition should be registered (e.g. "/sdcard")
+ * @param host_config Pointer to structure describing SDMMC host. When using
+ * SDMMC peripheral, this structure can be initialized using
+ * SDMMC_HOST_DEFAULT() macro. When using SPI peripheral,
+ * this structure can be initialized using SDSPI_HOST_DEFAULT()
+ * macro.
+ * @param slot_config Pointer to structure with slot configuration.
+ * For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
+ * structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
+ * @param mount_config pointer to structure with extra parameters for mounting FATFS
+ * @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
+ * - ESP_ERR_NO_MEM if memory can not be allocated
+ * - ESP_FAIL if partition can not be mounted
+ * - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
+ const sdmmc_host_t* host_config,
+ const void* slot_config,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ sdmmc_card_t** out_card);
+
+/**
+ * @brief Convenience function to get FAT filesystem on SD card registered in VFS
+ *
+ * This is an all-in-one function which does the following:
+ * - initializes an SPI Master device based on the SPI Master driver with configuration in
+ * slot_config, and attach it to an initialized SPI bus.
+ * - initializes SD card with configuration in host_config_input
+ * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * This function is intended to make example code more compact.
+ * For real world applications, developers should implement the logic of
+ * probing SD card, locating and mounting partition, and registering FATFS in VFS,
+ * with proper error checking and handling of exceptional conditions.
+ *
+ * @note This function try to attach the new SD SPI device to the bus specified in host_config.
+ * Make sure the SPI bus specified in `host_config->slot` have been initialized by
+ * `spi_bus_initialize()` before.
+ *
+ * @param base_path path where partition should be registered (e.g. "/sdcard")
+ * @param host_config_input Pointer to structure describing SDMMC host. This structure can be
+ * initialized using SDSPI_HOST_DEFAULT() macro.
+ * @param slot_config Pointer to structure with slot configuration.
+ * For SPI peripheral, pass a pointer to sdspi_device_config_t
+ * structure initialized using SDSPI_DEVICE_CONFIG_DEFAULT().
+ * @param mount_config pointer to structure with extra parameters for mounting FATFS
+ * @param[out] out_card If not NULL, pointer to the card information structure will be returned via
+ * this argument. It is suggested to hold this handle and use it to unmount the card later if
+ * needed. Otherwise it's not suggested to use more than one card at the same time and unmount one
+ * of them in your application.
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
+ * - ESP_ERR_NO_MEM if memory can not be allocated
+ * - ESP_FAIL if partition can not be mounted
+ * - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
+ const sdmmc_host_t* host_config_input,
+ const sdspi_device_config_t* slot_config,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ sdmmc_card_t** out_card);
+
+/**
+ * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount
+ *
+ * @deprecated Use `esp_vfs_fat_sdcard_unmount()` instead.
+ *
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
+ */
+esp_err_t esp_vfs_fat_sdmmc_unmount(void) __attribute__((deprecated("Please use esp_vfs_fat_sdcard_unmount instead")));
+
+/**
+ * @brief Unmount an SD card from the FAT filesystem and release resources acquired using
+ * `esp_vfs_fat_sdmmc_mount()` or `esp_vfs_fat_sdspi_mount()`
+ *
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_ARG if the card argument is unregistered
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
+ */
+esp_err_t esp_vfs_fat_sdcard_unmount(const char* base_path, sdmmc_card_t *card);
+
+/**
+ * @brief Format FAT filesystem
+ *
+ * @note
+ * This API should be only called when the FAT is already mounted.
+ *
+ * @param base_path Path where partition should be registered (e.g. "/sdcard")
+ * @param card Pointer to the card handle, which should be initialised by calling `esp_vfs_fat_sdspi_mount` first
+ *
+ * @return
+ * - ESP_OK
+ * - ESP_ERR_INVALID_STATE: FAT partition isn't mounted, call esp_vfs_fat_sdmmc_mount or esp_vfs_fat_sdspi_mount first
+ * - ESP_ERR_NO_MEM: if memory can not be allocated
+ * - ESP_FAIL: fail to format it, or fail to mount back
+ */
+esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card);
+
+/**
+ * @brief Convenience function to initialize FAT filesystem in SPI flash and register it in VFS
+ *
+ * This is an all-in-one function which does the following:
+ *
+ * - finds the partition with defined partition_label. Partition label should be
+ * configured in the partition table.
+ * - initializes flash wear levelling library on top of the given partition
+ * - mounts FAT partition using FATFS library on top of flash wear levelling
+ * library
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * This function is intended to make example code more compact.
+ *
+ * @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
+ * @param partition_label label of the partition which should be used
+ * @param mount_config pointer to structure with extra parameters for mounting FATFS
+ * @param[out] wl_handle wear levelling driver handle
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_rw_wl was already called
+ * - ESP_ERR_NO_MEM if memory can not be allocated
+ * - ESP_FAIL if partition can not be mounted
+ * - other error codes from wear levelling library, SPI flash driver, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ wl_handle_t* wl_handle);
+
+/**
+ * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_spiflash_mount_rw_wl
+ *
+ * @param base_path path where partition should be registered (e.g. "/spiflash")
+ * @param wl_handle wear levelling driver handle returned by esp_vfs_fat_spiflash_mount_rw_wl
+ *
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_rw_wl hasn't been called
+ */
+esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle);
+
+/**
+ * @brief Format FAT filesystem
+ *
+ * @note
+ * This API can be called when the FAT is mounted / not mounted.
+ * If this API is called when the FAT isn't mounted (by calling esp_vfs_fat_spiflash_mount_rw_wl),
+ * this API will first mount the FAT then format it, then restore back to the original state.
+ *
+ * @param base_path Path where partition should be registered (e.g. "/spiflash")
+ * @param partition_label Label of the partition which should be used
+ *
+ * @return
+ * - ESP_OK
+ * - ESP_ERR_NO_MEM: if memory can not be allocated
+ * - Other errors from esp_vfs_fat_spiflash_mount_rw_wl
+ */
+esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label);
+
+/**
+ * @brief Convenience function to initialize read-only FAT filesystem and register it in VFS
+ *
+ * This is an all-in-one function which does the following:
+ *
+ * - finds the partition with defined partition_label. Partition label should be
+ * configured in the partition table.
+ * - mounts FAT partition using FATFS library
+ * - registers FATFS library with VFS, with prefix given by base_prefix variable
+ *
+ * @note Wear levelling is not used when FAT is mounted in read-only mode using this function.
+ *
+ * @param base_path path where FATFS partition should be mounted (e.g. "/spiflash")
+ * @param partition_label label of the partition which should be used
+ * @param mount_config pointer to structure with extra parameters for mounting FATFS
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_NOT_FOUND if the partition table does not contain FATFS partition with given label
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_ro was already called for the same partition
+ * - ESP_ERR_NO_MEM if memory can not be allocated
+ * - ESP_FAIL if partition can not be mounted
+ * - other error codes from SPI flash driver, or FATFS drivers
+ */
+esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config);
+
+/**
+ * @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_spiflash_mount_ro
+ *
+ * @param base_path path where partition should be registered (e.g. "/spiflash")
+ * @param partition_label label of partition to be unmounted
+ *
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if esp_vfs_fat_spiflash_mount_ro hasn't been called
+ */
+esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* partition_label);
+
+esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ wl_handle_t* wl_handle)
+ __attribute__((deprecated("esp_vfs_fat_spiflash_mount is deprecated, please use esp_vfs_fat_spiflash_mount_rw_wl instead")));
+esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle)
+ __attribute__((deprecated("esp_vfs_fat_spiflash_unmount is deprecated, please use esp_vfs_fat_spiflash_unmount_rw_wl instead")));
+esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config)
+ __attribute__((deprecated("esp_vfs_fat_rawflash_mount is deprecated, please use esp_vfs_fat_spiflash_mount_ro instead")));
+esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label)
+ __attribute__((deprecated("esp_vfs_fat_rawflash_unmount is deprecated, please use esp_vfs_fat_spiflash_unmount_ro instead")));
+
+/**
+ * @brief Get information for FATFS partition
+ *
+ * @param base_path Path where partition should be registered (e.g. "/spiflash")
+ * @param[out] out_total_bytes Size of the file system
+ * @param[out] out_free_bytes Current used bytes in the file system
+ * @return
+ * - ESP_OK on success
+ * - ESP_ERR_INVALID_STATE if partition not found
+ * - ESP_FAIL if another FRESULT error (saved in errno)
+ */
+esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lib/fatfs/vfs/vfs_fat.c b/lib/fatfs/vfs/vfs_fat.c
new file mode 100644
index 00000000..eff73ec3
--- /dev/null
+++ b/lib/fatfs/vfs/vfs_fat.c
@@ -0,0 +1,1117 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/errno.h>
+#include <sys/fcntl.h>
+#include <sys/lock.h>
+#include "esp_vfs.h"
+#include "esp_log.h"
+#include "ff.h"
+#include "diskio_impl.h"
+
+typedef struct {
+ char fat_drive[8]; /* FAT drive name */
+ char base_path[ESP_VFS_PATH_MAX]; /* base path in VFS where partition is registered */
+ size_t max_files; /* max number of simultaneously open files; size of files[] array */
+ _lock_t lock; /* guard for access to this structure */
+ FATFS fs; /* fatfs library FS structure */
+ char tmp_path_buf[FILENAME_MAX+3]; /* temporary buffer used to prepend drive name to the path */
+ char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */
+ bool *o_append; /* O_APPEND is stored here for each max_files entries (because O_APPEND is not compatible with FA_OPEN_APPEND) */
+ FIL files[0]; /* array with max_files entries; must be the final member of the structure */
+} vfs_fat_ctx_t;
+
+typedef struct {
+ DIR dir;
+ long offset;
+ FF_DIR ffdir;
+ FILINFO filinfo;
+ struct dirent cur_dirent;
+} vfs_fat_dir_t;
+
+/* Date and time storage formats in FAT */
+typedef union {
+ struct {
+ uint16_t mday : 5; /* Day of month, 1 - 31 */
+ uint16_t mon : 4; /* Month, 1 - 12 */
+ uint16_t year : 7; /* Year, counting from 1980. E.g. 37 for 2017 */
+ };
+ uint16_t as_int;
+} fat_date_t;
+
+typedef union {
+ struct {
+ uint16_t sec : 5; /* Seconds divided by 2. E.g. 21 for 42 seconds */
+ uint16_t min : 6; /* Minutes, 0 - 59 */
+ uint16_t hour : 5; /* Hour, 0 - 23 */
+ };
+ uint16_t as_int;
+} fat_time_t;
+
+static const char* TAG = "vfs_fat";
+
+static ssize_t vfs_fat_write(void* p, int fd, const void * data, size_t size);
+static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode);
+static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size);
+static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset);
+static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset);
+static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
+static int vfs_fat_close(void* ctx, int fd);
+static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
+static int vfs_fat_fsync(void* ctx, int fd);
+#ifdef CONFIG_VFS_SUPPORT_DIR
+static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
+static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
+static int vfs_fat_unlink(void* ctx, const char *path);
+static int vfs_fat_rename(void* ctx, const char *src, const char *dst);
+static DIR* vfs_fat_opendir(void* ctx, const char* name);
+static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir);
+static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
+static long vfs_fat_telldir(void* ctx, DIR* pdir);
+static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset);
+static int vfs_fat_closedir(void* ctx, DIR* pdir);
+static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode);
+static int vfs_fat_rmdir(void* ctx, const char* name);
+static int vfs_fat_access(void* ctx, const char *path, int amode);
+static int vfs_fat_truncate(void* ctx, const char *path, off_t length);
+static int vfs_fat_ftruncate(void* ctx, int fd, off_t length);
+static int vfs_fat_utime(void* ctx, const char *path, const struct utimbuf *times);
+#endif // CONFIG_VFS_SUPPORT_DIR
+static int fresult_to_errno(FRESULT fr);
+
+static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL };
+//backwards-compatibility with esp_vfs_fat_unregister()
+static vfs_fat_ctx_t* s_fat_ctx = NULL;
+
+static size_t find_context_index_by_path(const char* base_path)
+{
+ for(size_t i=0; i<FF_VOLUMES; i++) {
+ if (s_fat_ctxs[i] && !strcmp(s_fat_ctxs[i]->base_path, base_path)) {
+ return i;
+ }
+ }
+ return FF_VOLUMES;
+}
+
+static size_t find_unused_context_index(void)
+{
+ for(size_t i=0; i<FF_VOLUMES; i++) {
+ if (!s_fat_ctxs[i]) {
+ return i;
+ }
+ }
+ return FF_VOLUMES;
+}
+
+esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs)
+{
+ size_t ctx = find_context_index_by_path(base_path);
+ if (ctx < FF_VOLUMES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ ctx = find_unused_context_index();
+ if (ctx == FF_VOLUMES) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ const esp_vfs_t vfs = {
+ .flags = ESP_VFS_FLAG_CONTEXT_PTR,
+ .write_p = &vfs_fat_write,
+ .lseek_p = &vfs_fat_lseek,
+ .read_p = &vfs_fat_read,
+ .pread_p = &vfs_fat_pread,
+ .pwrite_p = &vfs_fat_pwrite,
+ .open_p = &vfs_fat_open,
+ .close_p = &vfs_fat_close,
+ .fstat_p = &vfs_fat_fstat,
+ .fsync_p = &vfs_fat_fsync,
+#ifdef CONFIG_VFS_SUPPORT_DIR
+ .stat_p = &vfs_fat_stat,
+ .link_p = &vfs_fat_link,
+ .unlink_p = &vfs_fat_unlink,
+ .rename_p = &vfs_fat_rename,
+ .opendir_p = &vfs_fat_opendir,
+ .closedir_p = &vfs_fat_closedir,
+ .readdir_p = &vfs_fat_readdir,
+ .readdir_r_p = &vfs_fat_readdir_r,
+ .seekdir_p = &vfs_fat_seekdir,
+ .telldir_p = &vfs_fat_telldir,
+ .mkdir_p = &vfs_fat_mkdir,
+ .rmdir_p = &vfs_fat_rmdir,
+ .access_p = &vfs_fat_access,
+ .truncate_p = &vfs_fat_truncate,
+ .ftruncate_p = &vfs_fat_ftruncate,
+ .utime_p = &vfs_fat_utime,
+#endif // CONFIG_VFS_SUPPORT_DIR
+ };
+ size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL);
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ff_memalloc(ctx_size);
+ if (fat_ctx == NULL) {
+ return ESP_ERR_NO_MEM;
+ }
+ memset(fat_ctx, 0, ctx_size);
+ fat_ctx->o_append = ff_memalloc(max_files * sizeof(bool));
+ if (fat_ctx->o_append == NULL) {
+ free(fat_ctx);
+ return ESP_ERR_NO_MEM;
+ }
+ memset(fat_ctx->o_append, 0, max_files * sizeof(bool));
+ fat_ctx->max_files = max_files;
+ strlcpy(fat_ctx->fat_drive, fat_drive, sizeof(fat_ctx->fat_drive) - 1);
+ strlcpy(fat_ctx->base_path, base_path, sizeof(fat_ctx->base_path) - 1);
+
+ esp_err_t err = esp_vfs_register(base_path, &vfs, fat_ctx);
+ if (err != ESP_OK) {
+ free(fat_ctx->o_append);
+ free(fat_ctx);
+ return err;
+ }
+
+ _lock_init(&fat_ctx->lock);
+ s_fat_ctxs[ctx] = fat_ctx;
+
+ //compatibility
+ s_fat_ctx = fat_ctx;
+
+ *out_fs = &fat_ctx->fs;
+
+ return ESP_OK;
+}
+
+esp_err_t esp_vfs_fat_unregister_path(const char* base_path)
+{
+ size_t ctx = find_context_index_by_path(base_path);
+ if (ctx == FF_VOLUMES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx];
+ esp_err_t err = esp_vfs_unregister(fat_ctx->base_path);
+ if (err != ESP_OK) {
+ return err;
+ }
+ _lock_close(&fat_ctx->lock);
+ free(fat_ctx->o_append);
+ free(fat_ctx);
+ s_fat_ctxs[ctx] = NULL;
+ return ESP_OK;
+}
+
+esp_err_t esp_vfs_fat_info(const char* base_path,
+ uint64_t* out_total_bytes,
+ uint64_t* out_free_bytes)
+{
+ size_t ctx = find_context_index_by_path(base_path);
+ if (ctx == FF_VOLUMES) {
+ return ESP_ERR_INVALID_STATE;
+ }
+ char* path = s_fat_ctxs[ctx]->fat_drive;
+
+ FATFS* fs;
+ DWORD free_clusters;
+ int res = f_getfree(path, &free_clusters, &fs);
+ if (res != FR_OK) {
+ ESP_LOGE(TAG, "Failed to get number of free clusters (%d)", res);
+ errno = fresult_to_errno(res);
+ return ESP_FAIL;
+ }
+ uint64_t total_sectors = ((uint64_t)(fs->n_fatent - 2)) * fs->csize;
+ uint64_t free_sectors = ((uint64_t)free_clusters) * fs->csize;
+ WORD sector_size = FF_MIN_SS; // 512
+#if FF_MAX_SS != FF_MIN_SS
+ sector_size = fs->ssize;
+#endif
+
+ // Assuming the total size is < 4GiB, should be true for SPI Flash
+ if (out_total_bytes != NULL) {
+ *out_total_bytes = total_sectors * sector_size;
+ }
+ if (out_free_bytes != NULL) {
+ *out_free_bytes = free_sectors * sector_size;
+ }
+ return ESP_OK;
+}
+
+static int get_next_fd(vfs_fat_ctx_t* fat_ctx)
+{
+ for (size_t i = 0; i < fat_ctx->max_files; ++i) {
+ if (fat_ctx->files[i].obj.fs == NULL) {
+ return (int) i;
+ }
+ }
+ return -1;
+}
+
+static int fat_mode_conv(int m)
+{
+ int res = 0;
+ int acc_mode = m & O_ACCMODE;
+ if (acc_mode == O_RDONLY) {
+ res |= FA_READ;
+ } else if (acc_mode == O_WRONLY) {
+ res |= FA_WRITE;
+ } else if (acc_mode == O_RDWR) {
+ res |= FA_READ | FA_WRITE;
+ }
+ if ((m & O_CREAT) && (m & O_EXCL)) {
+ res |= FA_CREATE_NEW;
+ } else if ((m & O_CREAT) && (m & O_TRUNC)) {
+ res |= FA_CREATE_ALWAYS;
+ } else if ((m & O_APPEND) || (m & O_CREAT)) {
+ res |= FA_OPEN_ALWAYS;
+ } else {
+ res |= FA_OPEN_EXISTING;
+ }
+ return res;
+}
+
+static int fresult_to_errno(FRESULT fr)
+{
+ switch(fr) {
+ case FR_DISK_ERR: return EIO;
+ case FR_INT_ERR: return EIO;
+ case FR_NOT_READY: return ENODEV;
+ case FR_NO_FILE: return ENOENT;
+ case FR_NO_PATH: return ENOENT;
+ case FR_INVALID_NAME: return EINVAL;
+ case FR_DENIED: return EACCES;
+ case FR_EXIST: return EEXIST;
+ case FR_INVALID_OBJECT: return EBADF;
+ case FR_WRITE_PROTECTED: return EACCES;
+ case FR_INVALID_DRIVE: return ENXIO;
+ case FR_NOT_ENABLED: return ENODEV;
+ case FR_NO_FILESYSTEM: return ENODEV;
+ case FR_MKFS_ABORTED: return EINTR;
+ case FR_TIMEOUT: return ETIMEDOUT;
+ case FR_LOCKED: return EACCES;
+ case FR_NOT_ENOUGH_CORE: return ENOMEM;
+ case FR_TOO_MANY_OPEN_FILES: return ENFILE;
+ case FR_INVALID_PARAMETER: return EINVAL;
+ case FR_OK: return 0;
+ }
+ assert(0 && "unhandled FRESULT");
+ return ENOTSUP;
+}
+
+static void file_cleanup(vfs_fat_ctx_t* ctx, int fd)
+{
+ memset(&ctx->files[fd], 0, sizeof(FIL));
+}
+
+/**
+ * @brief Prepend drive letters to path names
+ * This function returns new path path pointers, pointing to a temporary buffer
+ * inside ctx.
+ * @note Call this function with ctx->lock acquired. Paths are valid while the
+ * lock is held.
+ * @param ctx vfs_fat_ctx_t context
+ * @param[inout] path as input, pointer to the path; as output, pointer to the new path
+ * @param[inout] path2 as input, pointer to the path; as output, pointer to the new path
+ */
+static void prepend_drive_to_path(vfs_fat_ctx_t * ctx, const char ** path, const char ** path2){
+ snprintf(ctx->tmp_path_buf, sizeof(ctx->tmp_path_buf), "%s%s", ctx->fat_drive, *path);
+ *path = ctx->tmp_path_buf;
+ if(path2){
+ snprintf(ctx->tmp_path_buf2, sizeof(ctx->tmp_path_buf2), "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, *path2);
+ *path2 = ctx->tmp_path_buf2;
+ }
+}
+
+static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
+{
+ ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode);
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+ int fd = get_next_fd(fat_ctx);
+ if (fd < 0) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGE(TAG, "open: no free file descriptors");
+ errno = ENFILE;
+ return -1;
+ }
+
+ FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags));
+ if (res != FR_OK) {
+ file_cleanup(fat_ctx, fd);
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+
+#ifdef CONFIG_FATFS_USE_FASTSEEK
+ FIL* file = &fat_ctx->files[fd];
+ //fast-seek is only allowed in read mode, since file cannot be expanded
+ //to use it.
+ if(!(fat_mode_conv(flags) & (FA_WRITE))) {
+ DWORD *clmt_mem = ff_memalloc(sizeof(DWORD) * CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE);
+ if (clmt_mem == NULL) {
+ f_close(file);
+ file_cleanup(fat_ctx, fd);
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGE(TAG, "open: Failed to pre-allocate CLMT buffer for fast-seek");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ file->cltbl = clmt_mem;
+ file->cltbl[0] = CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE;
+ res = f_lseek(file, CREATE_LINKMAP);
+ ESP_LOGD(TAG, "%s: fast-seek has: %s",
+ __func__,
+ (res == FR_OK) ? "activated" : "failed");
+ if(res != FR_OK) {
+ ESP_LOGW(TAG, "%s: fast-seek not activated reason code: %d",
+ __func__, res);
+ //If linkmap creation fails, fallback to the non fast seek.
+ ff_memfree(file->cltbl);
+ file->cltbl = NULL;
+ }
+ } else {
+ file->cltbl = NULL;
+ }
+#endif
+
+ // O_APPEND need to be stored because it is not compatible with FA_OPEN_APPEND:
+ // - FA_OPEN_APPEND means to jump to the end of file only after open()
+ // - O_APPEND means to jump to the end only before each write()
+ // Other VFS drivers handles O_APPEND well (to the best of my knowledge),
+ // therefore this flag is stored here (at this VFS level) in order to save
+ // memory.
+ fat_ctx->o_append[fd] = (flags & O_APPEND) == O_APPEND;
+ _lock_release(&fat_ctx->lock);
+ return fd;
+}
+
+static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ FIL* file = &fat_ctx->files[fd];
+ FRESULT res;
+ if (fat_ctx->o_append[fd]) {
+ if ((res = f_lseek(file, f_size(file))) != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ }
+ unsigned written = 0;
+ res = f_write(file, data, size, &written);
+ if (((written == 0) && (size != 0)) && (res == 0)) {
+ errno = ENOSPC;
+ return -1;
+ }
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ if (written == 0) {
+ return -1;
+ }
+ }
+ return written;
+}
+
+static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ FIL* file = &fat_ctx->files[fd];
+ unsigned read = 0;
+ FRESULT res = f_read(file, dst, size, &read);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ if (read == 0) {
+ return -1;
+ }
+ }
+ return read;
+}
+
+static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset)
+{
+ ssize_t ret = -1;
+ vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ FIL *file = &fat_ctx->files[fd];
+ const off_t prev_pos = f_tell(file);
+
+ FRESULT f_res = f_lseek(file, offset);
+
+ if (f_res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ errno = fresult_to_errno(f_res);
+ goto pread_release;
+ }
+
+ unsigned read = 0;
+ f_res = f_read(file, dst, size, &read);
+ if (f_res == FR_OK) {
+ ret = read;
+ } else {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ errno = fresult_to_errno(f_res);
+ // No return yet - need to restore previous position
+ }
+
+ f_res = f_lseek(file, prev_pos);
+ if (f_res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ if (ret >= 0) {
+ errno = fresult_to_errno(f_res);
+ } // else f_read failed so errno shouldn't be overwritten
+ ret = -1; // in case the read was successful but the seek wasn't
+ }
+
+pread_release:
+ _lock_release(&fat_ctx->lock);
+ return ret;
+}
+
+static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset)
+{
+ ssize_t ret = -1;
+ vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ FIL *file = &fat_ctx->files[fd];
+ const off_t prev_pos = f_tell(file);
+
+ FRESULT f_res = f_lseek(file, offset);
+
+ if (f_res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ errno = fresult_to_errno(f_res);
+ goto pwrite_release;
+ }
+
+ unsigned wr = 0;
+ f_res = f_write(file, src, size, &wr);
+ if (((wr == 0) && (size != 0)) && (f_res == 0)) {
+ errno = ENOSPC;
+ return -1;
+ }
+ if (f_res == FR_OK) {
+ ret = wr;
+ } else {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ errno = fresult_to_errno(f_res);
+ // No return yet - need to restore previous position
+ }
+
+ f_res = f_lseek(file, prev_pos);
+ if (f_res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
+ if (ret >= 0) {
+ errno = fresult_to_errno(f_res);
+ } // else f_write failed so errno shouldn't be overwritten
+ ret = -1; // in case the write was successful but the seek wasn't
+ }
+
+pwrite_release:
+ _lock_release(&fat_ctx->lock);
+ return ret;
+}
+
+static int vfs_fat_fsync(void* ctx, int fd)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ FIL* file = &fat_ctx->files[fd];
+ FRESULT res = f_sync(file);
+ _lock_release(&fat_ctx->lock);
+ int rc = 0;
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ rc = -1;
+ }
+ return rc;
+}
+
+static int vfs_fat_close(void* ctx, int fd)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ FIL* file = &fat_ctx->files[fd];
+
+#ifdef CONFIG_FATFS_USE_FASTSEEK
+ ff_memfree(file->cltbl);
+ file->cltbl = NULL;
+#endif
+
+ FRESULT res = f_close(file);
+ file_cleanup(fat_ctx, fd);
+ _lock_release(&fat_ctx->lock);
+ int rc = 0;
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ rc = -1;
+ }
+ return rc;
+}
+
+static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ FIL* file = &fat_ctx->files[fd];
+ off_t new_pos;
+ if (mode == SEEK_SET) {
+ new_pos = offset;
+ } else if (mode == SEEK_CUR) {
+ off_t cur_pos = f_tell(file);
+ new_pos = cur_pos + offset;
+ } else if (mode == SEEK_END) {
+ off_t size = f_size(file);
+ new_pos = size + offset;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+#if FF_FS_EXFAT
+ ESP_LOGD(TAG, "%s: offset=%ld, filesize:=%" PRIu64, __func__, new_pos, f_size(file));
+#else
+ ESP_LOGD(TAG, "%s: offset=%ld, filesize:=%" PRIu32, __func__, new_pos, f_size(file));
+#endif
+ FRESULT res = f_lseek(file, new_pos);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return new_pos;
+}
+
+static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ FIL* file = &fat_ctx->files[fd];
+ memset(st, 0, sizeof(*st));
+ st->st_size = f_size(file);
+ st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG;
+ st->st_mtime = 0;
+ st->st_atime = 0;
+ st->st_ctime = 0;
+ st->st_blksize = CONFIG_FATFS_VFS_FSTAT_BLKSIZE;
+ return 0;
+}
+
+#ifdef CONFIG_VFS_SUPPORT_DIR
+
+static inline mode_t get_stat_mode(bool is_dir)
+{
+ return S_IRWXU | S_IRWXG | S_IRWXO |
+ ((is_dir) ? S_IFDIR : S_IFREG);
+}
+
+static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
+{
+ if (strcmp(path, "/") == 0) {
+ /* FatFS f_stat function does not work for the drive root.
+ * Just pretend that this is a directory.
+ */
+ memset(st, 0, sizeof(*st));
+ st->st_mode = get_stat_mode(true);
+ return 0;
+ }
+
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+ FILINFO info;
+ FRESULT res = f_stat(path, &info);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+
+ memset(st, 0, sizeof(*st));
+ st->st_size = info.fsize;
+ st->st_mode = get_stat_mode((info.fattrib & AM_DIR) != 0);
+ fat_date_t fdate = { .as_int = info.fdate };
+ fat_time_t ftime = { .as_int = info.ftime };
+ struct tm tm = {
+ .tm_mday = fdate.mday,
+ .tm_mon = fdate.mon - 1, /* unlike tm_mday, tm_mon is zero-based */
+ .tm_year = fdate.year + 80,
+ .tm_sec = ftime.sec * 2,
+ .tm_min = ftime.min,
+ .tm_hour = ftime.hour,
+ /* FAT doesn't keep track if the time was DST or not, ask the C library
+ * to try to figure this out. Note that this may yield incorrect result
+ * in the hour before the DST comes in effect, when the local time can't
+ * be converted to UTC uniquely.
+ */
+ .tm_isdst = -1
+ };
+ st->st_mtime = mktime(&tm);
+ st->st_atime = 0;
+ st->st_ctime = 0;
+ return 0;
+}
+
+static int vfs_fat_unlink(void* ctx, const char *path)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+ FRESULT res = f_unlink(path);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static int vfs_fat_link(void* ctx, const char* n1, const char* n2)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &n1, &n2);
+ const size_t copy_buf_size = fat_ctx->fs.csize;
+ FRESULT res;
+ FIL* pf1 = (FIL*) ff_memalloc(sizeof(FIL));
+ FIL* pf2 = (FIL*) ff_memalloc(sizeof(FIL));
+ void* buf = ff_memalloc(copy_buf_size);
+ if (buf == NULL || pf1 == NULL || pf2 == NULL) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "alloc failed, pf1=%p, pf2=%p, buf=%p", pf1, pf2, buf);
+ free(pf1);
+ free(pf2);
+ free(buf);
+ errno = ENOMEM;
+ return -1;
+ }
+ memset(pf1, 0, sizeof(*pf1));
+ memset(pf2, 0, sizeof(*pf2));
+ res = f_open(pf1, n1, FA_READ | FA_OPEN_EXISTING);
+ if (res != FR_OK) {
+ _lock_release(&fat_ctx->lock);
+ goto fail1;
+ }
+ res = f_open(pf2, n2, FA_WRITE | FA_CREATE_NEW);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ goto fail2;
+ }
+ size_t size_left = f_size(pf1);
+ while (size_left > 0) {
+ size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size;
+ size_t read;
+ res = f_read(pf1, buf, will_copy, &read);
+ if (res != FR_OK) {
+ goto fail3;
+ } else if (read != will_copy) {
+ res = FR_DISK_ERR;
+ goto fail3;
+ }
+ size_t written;
+ res = f_write(pf2, buf, will_copy, &written);
+ if (res != FR_OK) {
+ goto fail3;
+ } else if (written != will_copy) {
+ res = FR_DISK_ERR;
+ goto fail3;
+ }
+ size_left -= will_copy;
+ }
+fail3:
+ f_close(pf2);
+fail2:
+ f_close(pf1);
+fail1:
+ free(buf);
+ free(pf2);
+ free(pf1);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static int vfs_fat_rename(void* ctx, const char *src, const char *dst)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &src, &dst);
+ FRESULT res = f_rename(src, dst);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static DIR* vfs_fat_opendir(void* ctx, const char* name)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &name, NULL);
+ vfs_fat_dir_t* fat_dir = ff_memalloc(sizeof(vfs_fat_dir_t));
+ if (!fat_dir) {
+ _lock_release(&fat_ctx->lock);
+ errno = ENOMEM;
+ return NULL;
+ }
+ memset(fat_dir, 0, sizeof(*fat_dir));
+
+ FRESULT res = f_opendir(&fat_dir->ffdir, name);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ free(fat_dir);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return NULL;
+ }
+ return (DIR*) fat_dir;
+}
+
+static int vfs_fat_closedir(void* ctx, DIR* pdir)
+{
+ assert(pdir);
+ vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
+ FRESULT res = f_closedir(&fat_dir->ffdir);
+ free(pdir);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir)
+{
+ vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
+ struct dirent* out_dirent;
+ int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent);
+ if (err != 0) {
+ errno = err;
+ return NULL;
+ }
+ return out_dirent;
+}
+
+static int vfs_fat_readdir_r(void* ctx, DIR* pdir,
+ struct dirent* entry, struct dirent** out_dirent)
+{
+ assert(pdir);
+ vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
+ FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
+ if (res != FR_OK) {
+ *out_dirent = NULL;
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ return fresult_to_errno(res);
+ }
+ if (fat_dir->filinfo.fname[0] == 0) {
+ // end of directory
+ *out_dirent = NULL;
+ return 0;
+ }
+ entry->d_ino = 0;
+ if (fat_dir->filinfo.fattrib & AM_DIR) {
+ entry->d_type = DT_DIR;
+ } else {
+ entry->d_type = DT_REG;
+ }
+ strlcpy(entry->d_name, fat_dir->filinfo.fname,
+ sizeof(entry->d_name));
+ fat_dir->offset++;
+ *out_dirent = entry;
+ return 0;
+}
+
+static long vfs_fat_telldir(void* ctx, DIR* pdir)
+{
+ assert(pdir);
+ vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
+ return fat_dir->offset;
+}
+
+static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset)
+{
+ assert(pdir);
+ vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
+ FRESULT res;
+ if (offset < fat_dir->offset) {
+ res = f_rewinddir(&fat_dir->ffdir);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return;
+ }
+ fat_dir->offset = 0;
+ }
+ while (fat_dir->offset < offset) {
+ res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return;
+ }
+ fat_dir->offset++;
+ }
+}
+
+static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode)
+{
+ (void) mode;
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &name, NULL);
+ FRESULT res = f_mkdir(name);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static int vfs_fat_rmdir(void* ctx, const char* name)
+{
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &name, NULL);
+ FRESULT res = f_unlink(name);
+ _lock_release(&fat_ctx->lock);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+ return 0;
+}
+
+static int vfs_fat_access(void* ctx, const char *path, int amode)
+{
+ FILINFO info;
+ int ret = 0;
+ FRESULT res;
+
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+ res = f_stat(path, &info);
+ _lock_release(&fat_ctx->lock);
+
+ if (res == FR_OK) {
+ if (((amode & W_OK) == W_OK) && ((info.fattrib & AM_RDO) == AM_RDO)) {
+ ret = -1;
+ errno = EACCES;
+ }
+ // There is no flag to test readable or executable: we assume that if
+ // it exists then it is readable and executable
+ } else {
+ ret = -1;
+ errno = fresult_to_errno(res);
+ }
+
+ return ret;
+}
+
+static int vfs_fat_truncate(void* ctx, const char *path, off_t length)
+{
+ FRESULT res;
+ FIL* file = NULL;
+
+ int ret = 0;
+
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+
+ if (length < 0) {
+ errno = EINVAL;
+ ret = -1;
+ goto out;
+ }
+
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+
+ file = (FIL*) ff_memalloc(sizeof(FIL));
+ if (file == NULL) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "truncate alloc failed");
+ errno = ENOMEM;
+ ret = -1;
+ goto out;
+ }
+ memset(file, 0, sizeof(*file));
+
+ res = f_open(file, path, FA_WRITE);
+
+ if (res != FR_OK) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto out;
+ }
+
+ long sz = f_size(file);
+ if (sz < length) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "truncate does not support extending size");
+ errno = EPERM;
+ ret = -1;
+ goto close;
+ }
+
+ res = f_lseek(file, length);
+ if (res != FR_OK) {
+ _lock_release(&fat_ctx->lock);
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto close;
+ }
+
+ res = f_truncate(file);
+ _lock_release(&fat_ctx->lock);
+
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ }
+
+close:
+ res = f_close(file);
+
+ if (res != FR_OK) {
+ ESP_LOGE(TAG, "closing file opened for truncate failed");
+ // Overwrite previous errors, since not being able to close
+ // an opened file is a more critical issue.
+ errno = fresult_to_errno(res);
+ ret = -1;
+ }
+
+out:
+ free(file);
+ return ret;
+}
+
+static int vfs_fat_ftruncate(void* ctx, int fd, off_t length)
+{
+ FRESULT res;
+ FIL* file = NULL;
+
+ int ret = 0;
+
+ vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
+
+ if (length < 0) {
+ errno = EINVAL;
+ ret = -1;
+ return ret;
+ }
+
+ _lock_acquire(&fat_ctx->lock);
+ file = &fat_ctx->files[fd];
+ if (file == NULL) {
+ ESP_LOGD(TAG, "ftruncate NULL file pointer");
+ errno = EINVAL;
+ ret = -1;
+ goto out;
+ }
+
+ long sz = f_size(file);
+ if (sz < length) {
+ ESP_LOGD(TAG, "ftruncate does not support extending size");
+ errno = EPERM;
+ ret = -1;
+ goto out;
+ }
+
+ res = f_lseek(file, length);
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ goto out;
+ }
+
+ res = f_truncate(file);
+
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ ret = -1;
+ }
+
+out:
+ _lock_release(&fat_ctx->lock);
+ return ret;
+}
+
+static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *times)
+{
+ FILINFO filinfo_time;
+
+ {
+ struct tm tm_time;
+
+ if (times) {
+ localtime_r(&times->modtime, &tm_time);
+ } else {
+ // use current time
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm_time);
+ }
+
+ if (tm_time.tm_year < 80) {
+ // FATFS cannot handle years before 1980
+ errno = EINVAL;
+ return -1;
+ }
+
+ fat_date_t fdate;
+ fat_time_t ftime;
+
+ // this time transformation is esentially the reverse of the one in vfs_fat_stat()
+ fdate.mday = tm_time.tm_mday;
+ fdate.mon = tm_time.tm_mon + 1; // January in fdate.mon is 1, and 0 in tm_time.tm_mon
+ fdate.year = tm_time.tm_year - 80; // tm_time.tm_year=0 is 1900, tm_time.tm_year=0 is 1980
+ ftime.sec = tm_time.tm_sec / 2, // ftime.sec counts seconds by 2
+ ftime.min = tm_time.tm_min;
+ ftime.hour = tm_time.tm_hour;
+
+ filinfo_time.fdate = fdate.as_int;
+ filinfo_time.ftime = ftime.as_int;
+ }
+
+ vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
+ _lock_acquire(&fat_ctx->lock);
+ prepend_drive_to_path(fat_ctx, &path, NULL);
+ FRESULT res = f_utime(path, &filinfo_time);
+ _lock_release(&fat_ctx->lock);
+
+ if (res != FR_OK) {
+ ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
+ errno = fresult_to_errno(res);
+ return -1;
+ }
+
+ return 0;
+}
+
+#endif // CONFIG_VFS_SUPPORT_DIR
diff --git a/lib/fatfs/vfs/vfs_fat_internal.h b/lib/fatfs/vfs/vfs_fat_internal.h
new file mode 100644
index 00000000..dc3bae27
--- /dev/null
+++ b/lib/fatfs/vfs/vfs_fat_internal.h
@@ -0,0 +1,30 @@
+// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+//
+// 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.
+
+#pragma once
+
+#include "esp_vfs_fat.h"
+#include <sys/param.h>
+#include <stddef.h>
+
+static inline size_t esp_vfs_fat_get_allocation_unit_size(
+ size_t sector_size, size_t requested_size)
+{
+ size_t alloc_unit_size = requested_size;
+ const size_t max_sectors_per_cylinder = 128;
+ const size_t max_size = sector_size * max_sectors_per_cylinder;
+ alloc_unit_size = MAX(alloc_unit_size, sector_size);
+ alloc_unit_size = MIN(alloc_unit_size, max_size);
+ return alloc_unit_size;
+}
diff --git a/lib/fatfs/vfs/vfs_fat_sdmmc.c b/lib/fatfs/vfs/vfs_fat_sdmmc.c
new file mode 100644
index 00000000..f3625a2f
--- /dev/null
+++ b/lib/fatfs/vfs/vfs_fat_sdmmc.c
@@ -0,0 +1,498 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "esp_vfs_fat.h"
+#include "vfs_fat_internal.h"
+#include "driver/sdspi_host.h"
+#include "sdmmc_cmd.h"
+#include "diskio_impl.h"
+#include "diskio_sdmmc.h"
+#include "soc/soc_caps.h"
+#include "driver/sdmmc_defs.h"
+
+#if SOC_SDMMC_HOST_SUPPORTED
+#include "driver/sdmmc_host.h"
+#endif
+
+static const char* TAG = "vfs_fat_sdmmc";
+
+#define CHECK_EXECUTE_RESULT(err, str) do { \
+ if ((err) !=ESP_OK) { \
+ ESP_LOGE(TAG, str" (0x%x).", err); \
+ goto cleanup; \
+ } \
+ } while(0)
+
+typedef struct vfs_fat_sd_ctx_t {
+ BYTE pdrv; //Drive number that is mounted
+ esp_vfs_fat_mount_config_t mount_config; //Mount configuration
+ FATFS *fs; //FAT structure pointer that is registered
+ sdmmc_card_t *card; //Card info
+ char *base_path; //Path where partition is registered
+} vfs_fat_sd_ctx_t;
+
+static vfs_fat_sd_ctx_t *s_ctx[FF_VOLUMES] = {};
+/**
+ * This `s_saved_ctx_id` is only used by `esp_vfs_fat_sdmmc_unmount`, which is deprecated.
+ * This variable together with `esp_vfs_fat_sdmmc_unmount` should be removed in next major version
+ */
+static uint32_t s_saved_ctx_id = FF_VOLUMES;
+
+static void call_host_deinit(const sdmmc_host_t *host_config);
+static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
+ const char *drv, sdmmc_card_t *card, BYTE pdrv);
+
+static bool s_get_context_id_by_card(const sdmmc_card_t *card, uint32_t *out_id)
+{
+ vfs_fat_sd_ctx_t *p_ctx = NULL;
+ for (int i = 0; i < FF_VOLUMES; i++) {
+ p_ctx = s_ctx[i];
+ if (p_ctx) {
+ if (p_ctx->card == card) {
+ *out_id = i;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static uint32_t s_get_unused_context_id(void)
+{
+ for (uint32_t i = 0; i < FF_VOLUMES; i++) {
+ if (!s_ctx[i]) {
+ return i;
+ }
+ }
+ return FF_VOLUMES;
+}
+
+static esp_err_t mount_prepare_mem(const char *base_path,
+ BYTE *out_pdrv,
+ char **out_dup_path,
+ sdmmc_card_t** out_card)
+{
+ esp_err_t err = ESP_OK;
+ char* dup_path = NULL;
+ sdmmc_card_t* card = NULL;
+
+ // connect SDMMC driver to FATFS
+ BYTE pdrv = FF_DRV_NOT_USED;
+ if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == FF_DRV_NOT_USED) {
+ ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
+ return ESP_ERR_NO_MEM;
+ }
+
+ // not using ff_memalloc here, as allocation in internal RAM is preferred
+ card = (sdmmc_card_t*)malloc(sizeof(sdmmc_card_t));
+ if (card == NULL) {
+ ESP_LOGD(TAG, "could not locate new sdmmc_card_t");
+ err = ESP_ERR_NO_MEM;
+ goto cleanup;
+ }
+
+ dup_path = strdup(base_path);
+ if(!dup_path){
+ ESP_LOGD(TAG, "could not copy base_path");
+ err = ESP_ERR_NO_MEM;
+ goto cleanup;
+ }
+
+ *out_card = card;
+ *out_pdrv = pdrv;
+ *out_dup_path = dup_path;
+ return ESP_OK;
+cleanup:
+ free(card);
+ free(dup_path);
+ return err;
+}
+
+static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8_t pdrv, const esp_vfs_fat_mount_config_t *mount_config)
+{
+ esp_err_t err = ESP_OK;
+ FRESULT res = f_mount(fs, drv, 1);
+ if (res != FR_OK) {
+ err = ESP_FAIL;
+ ESP_LOGW(TAG, "failed to mount card (%d)", res);
+
+ bool need_mount_again = (res == FR_NO_FILESYSTEM || res == FR_INT_ERR) && mount_config->format_if_mount_failed;
+ if (!need_mount_again) {
+ return ESP_FAIL;
+ }
+
+ err = partition_card(mount_config, drv, card, pdrv);
+ if (err != ESP_OK) {
+ return err;
+ }
+
+ ESP_LOGW(TAG, "mounting again");
+ res = f_mount(fs, drv, 0);
+ if (res != FR_OK) {
+ err = ESP_FAIL;
+ ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res);
+ return err;
+ }
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv,
+ const char *base_path, FATFS **out_fs)
+{
+ FATFS *fs = NULL;
+ esp_err_t err;
+ ff_diskio_register_sdmmc(pdrv, card);
+ ff_sdmmc_set_disk_status_check(pdrv, mount_config->disk_status_check_enable);
+ ESP_LOGD(TAG, "using pdrv=%i", pdrv);
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+
+ // connect FATFS to VFS
+ err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ *out_fs = fs;
+ if (err == ESP_ERR_INVALID_STATE) {
+ // it's okay, already registered with VFS
+ } else if (err != ESP_OK) {
+ ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
+ goto fail;
+ }
+
+ // Try to mount partition
+ err = s_f_mount(card, fs, drv, pdrv, mount_config);
+ if (err != ESP_OK) {
+ goto fail;
+ }
+ return ESP_OK;
+
+fail:
+ if (fs) {
+ f_mount(NULL, drv, 0);
+ }
+ esp_vfs_fat_unregister_path(base_path);
+ ff_diskio_unregister(pdrv);
+ return err;
+}
+
+static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
+ const char *drv, sdmmc_card_t *card, BYTE pdrv)
+{
+ FRESULT res = FR_OK;
+ esp_err_t err;
+ const size_t workbuf_size = 4096;
+ void* workbuf = NULL;
+ ESP_LOGW(TAG, "partitioning card");
+
+ workbuf = ff_memalloc(workbuf_size);
+ if (workbuf == NULL) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ LBA_t plist[] = {100, 0, 0, 0};
+ res = f_fdisk(pdrv, plist, workbuf);
+ if (res != FR_OK) {
+ err = ESP_FAIL;
+ ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
+ goto fail;
+ }
+ size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
+ card->csd.sector_size,
+ mount_config->allocation_unit_size);
+ ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
+ const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
+ res = f_mkfs(drv, &opt, workbuf, workbuf_size);
+ if (res != FR_OK) {
+ err = ESP_FAIL;
+ ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
+ goto fail;
+ }
+
+ free(workbuf);
+ return ESP_OK;
+fail:
+ free(workbuf);
+ return err;
+}
+
+#if SOC_SDMMC_HOST_SUPPORTED
+static esp_err_t init_sdmmc_host(int slot, const void *slot_config, int *out_slot)
+{
+ *out_slot = slot;
+ return sdmmc_host_init_slot(slot, (const sdmmc_slot_config_t*) slot_config);
+}
+
+
+esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
+ const sdmmc_host_t* host_config,
+ const void* slot_config,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ sdmmc_card_t** out_card)
+{
+ esp_err_t err;
+ vfs_fat_sd_ctx_t *ctx = NULL;
+ uint32_t ctx_id = FF_VOLUMES;
+ FATFS *fs = NULL;
+ int card_handle = -1; //uninitialized
+ sdmmc_card_t* card = NULL;
+ BYTE pdrv = FF_DRV_NOT_USED;
+ char* dup_path = NULL;
+ bool host_inited = false;
+
+ err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "mount_prepare failed");
+ return err;
+ }
+
+ err = (*host_config->init)();
+ CHECK_EXECUTE_RESULT(err, "host init failed");
+ //deinit() needs to be called to revert the init
+ host_inited = true;
+ //If this failed (indicated by card_handle != -1), slot deinit needs to called()
+ //leave card_handle as is to indicate that (though slot deinit not implemented yet.
+ err = init_sdmmc_host(host_config->slot, slot_config, &card_handle);
+ CHECK_EXECUTE_RESULT(err, "slot init failed");
+
+ // probe and initialize card
+ err = sdmmc_card_init(host_config, card);
+ CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
+
+ err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
+ CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
+
+ if (out_card != NULL) {
+ *out_card = card;
+ }
+ //For deprecation backward compatibility
+ if (s_saved_ctx_id == FF_VOLUMES) {
+ s_saved_ctx_id = 0;
+ }
+
+ ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+ if (!ctx) {
+ CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
+ }
+ ctx->pdrv = pdrv;
+ memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
+ ctx->card = card;
+ ctx->base_path = dup_path;
+ ctx->fs = fs;
+ ctx_id = s_get_unused_context_id();
+ assert(ctx_id != FF_VOLUMES);
+ s_ctx[ctx_id] = ctx;
+
+ return ESP_OK;
+cleanup:
+ if (host_inited) {
+ call_host_deinit(host_config);
+ }
+ free(card);
+ free(dup_path);
+ return err;
+}
+#endif
+
+static esp_err_t init_sdspi_host(int slot, const void *slot_config, int *out_slot)
+{
+ esp_err_t err = sdspi_host_init_device((const sdspi_device_config_t*)slot_config, out_slot);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG,
+"Failed to attach sdspi device onto an SPI bus (rc=0x%x), please initialize the \
+bus first and check the device parameters."
+ , err);
+ }
+ return err;
+}
+
+esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
+ const sdmmc_host_t* host_config_input,
+ const sdspi_device_config_t* slot_config,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ sdmmc_card_t** out_card)
+{
+ const sdmmc_host_t* host_config = host_config_input;
+ esp_err_t err;
+ vfs_fat_sd_ctx_t *ctx = NULL;
+ uint32_t ctx_id = FF_VOLUMES;
+ FATFS *fs = NULL;
+ int card_handle = -1; //uninitialized
+ bool host_inited = false;
+ BYTE pdrv = FF_DRV_NOT_USED;
+ sdmmc_card_t* card = NULL;
+ char* dup_path = NULL;
+
+ err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "mount_prepare failed");
+ return err;
+ }
+
+ //the init() function is usually empty, doesn't require any deinit to revert it
+ err = (*host_config->init)();
+ CHECK_EXECUTE_RESULT(err, "host init failed");
+
+ err = init_sdspi_host(host_config->slot, slot_config, &card_handle);
+ CHECK_EXECUTE_RESULT(err, "slot init failed");
+ //Set `host_inited` to true to indicate that host_config->deinit() needs
+ //to be called to revert `init_sdspi_host`
+ host_inited = true;
+
+ /*
+ * The `slot` argument inside host_config should be replaced by the SD SPI handled returned
+ * above. But the input pointer is const, so create a new variable.
+ */
+ sdmmc_host_t new_config;
+ if (card_handle != host_config->slot) {
+ new_config = *host_config_input;
+ host_config = &new_config;
+ new_config.slot = card_handle;
+ }
+
+ // probe and initialize card
+ err = sdmmc_card_init(host_config, card);
+ CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
+
+ err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs);
+ CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
+
+ if (out_card != NULL) {
+ *out_card = card;
+ }
+ //For deprecation backward compatibility
+ if (s_saved_ctx_id == FF_VOLUMES) {
+ s_saved_ctx_id = 0;
+ }
+
+ ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1);
+ if (!ctx) {
+ CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem");
+ }
+ ctx->pdrv = pdrv;
+ memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
+ ctx->card = card;
+ ctx->base_path = dup_path;
+ ctx->fs = fs;
+ ctx_id = s_get_unused_context_id();
+ assert(ctx_id != FF_VOLUMES);
+ s_ctx[ctx_id] = ctx;
+
+ return ESP_OK;
+
+cleanup:
+ if (host_inited) {
+ call_host_deinit(host_config);
+ }
+ free(card);
+ free(dup_path);
+ return err;
+}
+
+static void call_host_deinit(const sdmmc_host_t *host_config)
+{
+ if (host_config->flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
+ host_config->deinit_p(host_config->slot);
+ } else {
+ host_config->deinit();
+ }
+}
+
+static esp_err_t unmount_card_core(const char *base_path, sdmmc_card_t *card)
+{
+ BYTE pdrv = ff_diskio_get_pdrv_card(card);
+ if (pdrv == 0xff) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ // unmount
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ f_mount(0, drv, 0);
+ // release SD driver
+ ff_diskio_unregister(pdrv);
+
+ call_host_deinit(&card->host);
+ free(card);
+
+ esp_err_t err = esp_vfs_fat_unregister_path(base_path);
+ return err;
+}
+
+esp_err_t esp_vfs_fat_sdmmc_unmount(void)
+{
+ esp_err_t err = unmount_card_core(s_ctx[s_saved_ctx_id]->base_path, s_ctx[s_saved_ctx_id]->card);
+ free(s_ctx[s_saved_ctx_id]);
+ s_ctx[s_saved_ctx_id] = NULL;
+ s_saved_ctx_id = FF_VOLUMES;
+ return err;
+}
+
+esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card)
+{
+ uint32_t id = FF_VOLUMES;
+ bool found = s_get_context_id_by_card(card, &id);
+ if (!found) {
+ return ESP_ERR_INVALID_ARG;
+ }
+ free(s_ctx[id]);
+ s_ctx[id] = NULL;
+
+ esp_err_t err = unmount_card_core(base_path, card);
+
+ return err;
+}
+
+esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card)
+{
+ esp_err_t ret = ESP_OK;
+ if (!card) {
+ ESP_LOGE(TAG, "card not initialized");
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ BYTE pdrv = ff_diskio_get_pdrv_card(card);
+ if (pdrv == 0xff) {
+ ESP_LOGE(TAG, "card driver not registered");
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ const size_t workbuf_size = 4096;
+ void *workbuf = ff_memalloc(workbuf_size);
+ if (workbuf == NULL) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ //unmount
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ f_mount(0, drv, 0);
+
+ //format
+ uint32_t id = FF_VOLUMES;
+ bool found = s_get_context_id_by_card(card, &id);
+ assert(found);
+ size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
+ card->csd.sector_size,
+ s_ctx[id]->mount_config.allocation_unit_size);
+ ESP_LOGI(TAG, "Formatting card, allocation unit size=%d", alloc_unit_size);
+ const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, alloc_unit_size};
+ FRESULT res = f_mkfs(drv, &opt, workbuf, workbuf_size);
+ free(workbuf);
+ if (res != FR_OK) {
+ ret = ESP_FAIL;
+ ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
+ }
+
+ //mount back
+ esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config);
+ if (err != ESP_OK) {
+ unmount_card_core(base_path, card);
+ ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
+ }
+
+ return ret;
+}
diff --git a/lib/fatfs/vfs/vfs_fat_spiflash.c b/lib/fatfs/vfs/vfs_fat_spiflash.c
new file mode 100644
index 00000000..d227de82
--- /dev/null
+++ b/lib/fatfs/vfs/vfs_fat_spiflash.c
@@ -0,0 +1,335 @@
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "esp_check.h"
+#include "esp_log.h"
+#include "esp_vfs.h"
+#include "esp_vfs_fat.h"
+#include "vfs_fat_internal.h"
+#include "diskio_impl.h"
+#include "diskio_rawflash.h"
+#include "wear_levelling.h"
+#include "diskio_wl.h"
+
+static const char* TAG = "vfs_fat_spiflash";
+
+typedef struct vfs_fat_spiflash_ctx_t {
+ const esp_partition_t *partition; //The partition where the FAT is located
+ bool by_label; //If the partition is mounted by lable or not
+ BYTE pdrv; //Drive number that is mounted
+ FATFS *fs; //FAT structure pointer that is registered
+ wl_handle_t wlhandle; //WL handle
+ esp_vfs_fat_mount_config_t mount_config; //Mount configuration
+} vfs_fat_spiflash_ctx_t;
+
+static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {};
+
+static bool s_get_context_id_by_label(const char *label, uint32_t *out_id)
+{
+ vfs_fat_spiflash_ctx_t *p_ctx = NULL;
+ for (int i = 0; i < FF_VOLUMES; i++) {
+ p_ctx = s_ctx[i];
+ if (p_ctx) {
+ if (!label && !p_ctx->by_label) {
+ *out_id = i;
+ return true;
+ }
+ if (label && p_ctx->by_label && strncmp(label, p_ctx->partition->label, 20) == 0) {
+ *out_id = i;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool s_get_context_id_by_wl_handle(wl_handle_t wlhandle, uint32_t *out_id)
+{
+ vfs_fat_spiflash_ctx_t *p_ctx = NULL;
+ for (int i = 0; i < FF_VOLUMES; i++) {
+ p_ctx = s_ctx[i];
+ if (p_ctx) {
+ if (p_ctx->wlhandle == wlhandle) {
+ *out_id = i;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static uint32_t s_get_unused_context_id(void)
+{
+ for (uint32_t i = 0; i < FF_VOLUMES; i++) {
+ if (!s_ctx[i]) {
+ return i;
+ }
+ }
+ return FF_VOLUMES;
+}
+
+static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_mount_config_t *mount_config)
+{
+ FRESULT fresult = f_mount(fs, drv, 1);
+ if (fresult != FR_OK) {
+ ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
+
+ bool need_mount_again = (fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR) && mount_config->format_if_mount_failed;
+ if (!need_mount_again) {
+ return ESP_FAIL;
+ }
+
+ const size_t workbuf_size = 4096;
+ void *workbuf = ff_memalloc(workbuf_size);
+ if (workbuf == NULL) {
+ return ESP_ERR_NO_MEM;
+ }
+
+ size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, mount_config->allocation_unit_size);
+ ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
+ fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
+ free(workbuf);
+ workbuf = NULL;
+ ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mkfs failed (%d)", fresult);
+
+ ESP_LOGI(TAG, "Mounting again");
+ fresult = f_mount(fs, drv, 0);
+ ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult);
+ }
+ return ESP_OK;
+}
+
+esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ wl_handle_t* wl_handle)
+{
+ esp_err_t ret = ESP_OK;
+ vfs_fat_spiflash_ctx_t *ctx = NULL;
+ uint32_t ctx_id = FF_VOLUMES;
+
+ esp_partition_subtype_t subtype = partition_label ?
+ ESP_PARTITION_SUBTYPE_ANY : ESP_PARTITION_SUBTYPE_DATA_FAT;
+ const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
+ subtype, partition_label);
+ ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
+
+ ESP_RETURN_ON_ERROR(wl_mount(data_partition, wl_handle), TAG, "failed to mount wear levelling layer. ret = %i", ret);
+
+ // connect driver to FATFS
+ BYTE pdrv = 0xFF;
+ if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
+ ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
+ return ESP_ERR_NO_MEM;
+ }
+ ESP_LOGD(TAG, "using pdrv=%i", pdrv);
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ ESP_GOTO_ON_ERROR(ff_diskio_register_wl_partition(pdrv, *wl_handle), fail, TAG, "ff_diskio_register_wl_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
+
+ FATFS *fs;
+ ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ if (ret == ESP_ERR_INVALID_STATE) {
+ // it's okay, already registered with VFS
+ } else if (ret != ESP_OK) {
+ ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
+ goto fail;
+ }
+
+ // Try to mount partition
+ ret = s_f_mount_rw(fs, drv, mount_config);
+ if (ret != ESP_OK) {
+ goto fail;
+ }
+
+ ctx = calloc(sizeof(vfs_fat_spiflash_ctx_t), 1);
+ ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, fail, TAG, "no mem");
+ ctx->partition = data_partition;
+ ctx->by_label = (partition_label != NULL);
+ ctx->pdrv = pdrv;
+ ctx->fs = fs;
+ ctx->wlhandle = *wl_handle;
+ memcpy(&ctx->mount_config, mount_config, sizeof(esp_vfs_fat_mount_config_t));
+ ctx_id = s_get_unused_context_id();
+ //At this stage, we should always get a free context, otherwise program should return already
+ assert(ctx_id != FF_VOLUMES);
+ s_ctx[ctx_id] = ctx;
+
+ return ESP_OK;
+
+fail:
+ esp_vfs_fat_unregister_path(base_path);
+ ff_diskio_unregister(pdrv);
+ free(ctx);
+ return ret;
+}
+
+esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle)
+{
+ BYTE pdrv = ff_diskio_get_pdrv_wl(wl_handle);
+ ESP_RETURN_ON_FALSE(pdrv != 0xff, ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_rw_wl first");
+
+ uint32_t id = FF_VOLUMES;
+ ESP_RETURN_ON_FALSE(s_get_context_id_by_wl_handle(wl_handle, &id), ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_rw_wl first");
+ //At this stage, as the wl_handle is valid, we should always get its context id, otherwise program should return already
+ assert(id != FF_VOLUMES);
+
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ f_mount(0, drv, 0);
+ ff_diskio_unregister(pdrv);
+ ff_diskio_clear_pdrv_wl(wl_handle);
+ // release partition driver
+ esp_err_t err_drv = wl_unmount(wl_handle);
+ esp_err_t err = esp_vfs_fat_unregister_path(base_path);
+ if (err == ESP_OK) {
+ err = err_drv;
+ }
+
+ free(s_ctx[id]);
+ s_ctx[id] = NULL;
+
+ return err;
+}
+
+esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label)
+{
+ esp_err_t ret = ESP_OK;
+ bool partition_was_mounted = false;
+
+ wl_handle_t temp_handle = WL_INVALID_HANDLE;
+ uint32_t id = FF_VOLUMES;
+ char drv[3] = {0, ':', 0};
+
+ bool found = s_get_context_id_by_label(partition_label, &id);
+ if (!found) {
+ const esp_vfs_fat_mount_config_t mount_config = {
+ .max_files = 1,
+ .format_if_mount_failed = true,
+ };
+ ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, &mount_config, &temp_handle), TAG, "Failed to mount");
+ found = s_get_context_id_by_label(partition_label, &id);
+ assert(found);
+ } else {
+ partition_was_mounted = true;
+ }
+
+ //unmount
+ drv[1] = (char)('0' + s_ctx[id]->pdrv);
+ f_mount(0, drv, 0);
+
+ const size_t workbuf_size = 4096;
+ void *workbuf = ff_memalloc(workbuf_size);
+ if (workbuf == NULL) {
+ ret = ESP_ERR_NO_MEM;
+ goto mount_back;
+ }
+ size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(CONFIG_WL_SECTOR_SIZE, s_ctx[id]->mount_config.allocation_unit_size);
+ ESP_LOGI(TAG, "Formatting FATFS partition, allocation unit size=%d", alloc_unit_size);
+ const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, alloc_unit_size};
+ FRESULT fresult = f_mkfs(drv, &opt, workbuf, workbuf_size);
+ free(workbuf);
+ workbuf = NULL;
+ ESP_GOTO_ON_FALSE(fresult == FR_OK, ESP_FAIL, mount_back, TAG, "f_mkfs failed (%d)", fresult);
+
+mount_back:
+ if (partition_was_mounted) {
+ esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "failed to mount back, go to recycle");
+ goto recycle;
+ }
+ } else {
+ esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_ctx[id]->wlhandle);
+ }
+ return ret;
+
+recycle:
+ ff_diskio_unregister(s_ctx[id]->pdrv);
+ ff_diskio_clear_pdrv_wl(s_ctx[id]->wlhandle);
+ wl_unmount(s_ctx[id]->wlhandle);
+ esp_vfs_fat_unregister_path(base_path);
+ free(s_ctx[id]);
+ s_ctx[id] = NULL;
+ ESP_LOGE(TAG, "failed to format, resources recycled, please mount again");
+ return ret;
+}
+
+
+esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config)
+{
+ esp_err_t ret = ESP_OK;
+
+ const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
+ ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
+ ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
+
+ // connect driver to FATFS
+ BYTE pdrv = 0xFF;
+ if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
+ ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
+ return ESP_ERR_NO_MEM;
+ }
+ ESP_LOGD(TAG, "using pdrv=%i", pdrv);
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ ESP_GOTO_ON_ERROR(ff_diskio_register_raw_partition(pdrv, data_partition), fail, TAG, "ff_diskio_register_raw_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret);
+
+ FATFS *fs;
+ ret = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
+ if (ret == ESP_ERR_INVALID_STATE) {
+ // it's okay, already registered with VFS
+ } else if (ret != ESP_OK) {
+ ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", ret);
+ goto fail;
+ }
+
+ // Try to mount partition
+ FRESULT fresult = f_mount(fs, drv, 1);
+ if (fresult != FR_OK) {
+ ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
+ ret = ESP_FAIL;
+ goto fail;
+ }
+ return ESP_OK;
+
+fail:
+ esp_vfs_fat_unregister_path(base_path);
+ ff_diskio_unregister(pdrv);
+ return ret;
+}
+
+esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* partition_label)
+{
+ const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
+ ESP_PARTITION_SUBTYPE_DATA_FAT, partition_label);
+ ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "Failed to find FATFS partition (type='data', subtype='fat', partition_label='%s'). Check the partition table.", partition_label);
+
+ BYTE pdrv = ff_diskio_get_pdrv_raw(data_partition);
+ ESP_RETURN_ON_FALSE(pdrv != 0xff, ESP_ERR_INVALID_STATE, TAG, "partition isn't registered, call esp_vfs_fat_spiflash_mount_ro first");
+
+ char drv[3] = {(char)('0' + pdrv), ':', 0};
+ f_mount(0, drv, 0);
+ ff_diskio_unregister(pdrv);
+ esp_err_t err = esp_vfs_fat_unregister_path(base_path);
+ return err;
+}
+
+esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config,
+ wl_handle_t* wl_handle)
+ __attribute__((alias("esp_vfs_fat_spiflash_mount_rw_wl")));
+esp_err_t esp_vfs_fat_spiflash_unmount(const char* base_path, wl_handle_t wl_handle)
+ __attribute__((alias("esp_vfs_fat_spiflash_unmount_rw_wl")));
+esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path,
+ const char* partition_label,
+ const esp_vfs_fat_mount_config_t* mount_config)
+ __attribute__((alias("esp_vfs_fat_spiflash_mount_ro")));
+esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label)
+ __attribute__((alias("esp_vfs_fat_spiflash_unmount_ro")));