diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-07-25 17:42:00 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-07-25 17:42:00 +1000 |
| commit | 7b72e5479ee6d11f76c49f7463ba0e7f4e5165c5 (patch) | |
| tree | 2965c66cf3973583b7751b771f9bc06232d98222 /lib/fatfs/vfs | |
| parent | 9287c4eb8c60cc89251a1d2bdfe9c576d81d6715 (diff) | |
| download | tangara-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.h | 356 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat.c | 1117 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_internal.h | 30 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_sdmmc.c | 498 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_spiflash.c | 335 |
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(×->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"))); |
