diff options
| author | jacqueline <me@jacqueline.id.au> | 2025-07-25 13:33:07 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2025-07-25 13:33:07 +1000 |
| commit | c8e79a926620e48830778714cfe4b2ea2453fcaf (patch) | |
| tree | 8c756e08e01b8e147cf72bec128026f46bd854c5 /lib/fatfs/vfs | |
| parent | 237136f3e93cb6b5be24670d7520adb17cc0fa36 (diff) | |
| download | tangara-fw-c8e79a926620e48830778714cfe4b2ea2453fcaf.tar.gz | |
Update forked idf components
Diffstat (limited to 'lib/fatfs/vfs')
| -rw-r--r-- | lib/fatfs/vfs/esp_vfs_fat.h | 118 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat.c | 625 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_internal.h | 47 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_sdmmc.c | 85 | ||||
| -rw-r--r-- | lib/fatfs/vfs/vfs_fat_spiflash.c | 168 |
5 files changed, 819 insertions, 224 deletions
diff --git a/lib/fatfs/vfs/esp_vfs_fat.h b/lib/fatfs/vfs/esp_vfs_fat.h index 3e7b165b..7b87818f 100644 --- a/lib/fatfs/vfs/esp_vfs_fat.h +++ b/lib/fatfs/vfs/esp_vfs_fat.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,8 +7,7 @@ #pragma once #include <stddef.h> #include "esp_err.h" -#include "driver/gpio.h" -#include "driver/sdmmc_types.h" +#include "sd_protocol_types.h" #include "driver/sdspi_host.h" #include "ff.h" #include "wear_levelling.h" @@ -18,9 +17,19 @@ extern "C" { #endif /** + * @brief Configuration structure for esp_vfs_fat_register + */ +typedef struct { + const char* base_path; /*!< Path prefix where FATFS should be registered, */ + const char* fat_drive; /*!< FATFS drive specification; if only one drive is used, can be an empty string. */ + size_t max_files; /*!< Maximum number of files which can be open at the same time. */ +} esp_vfs_fat_conf_t; + +/** * @brief Register FATFS with VFS component * * This function registers given FAT drive in VFS, at the specified base path. + * Input arguments are held in esp_vfs_fat_conf_t structure. * 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 @@ -30,17 +39,14 @@ extern "C" { * 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 conf pointer to esp_vfs_fat_conf_t configuration structure * @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); +esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_fs); /** * @brief Un-register FATFS from VFS @@ -96,8 +102,26 @@ typedef struct { * Doesn't do anything for other memory storage media. */ bool disk_status_check_enable; + /** + * Use 1 FAT (File Allocation Tables) instead of 2. + * This decreases reliability, but makes more space available + * (usually only one sector). + * Note that this option has effect only when the filesystem is formatted. + * When mounting an already-formatted partition, the actual number of FATs + * may be different. + */ + bool use_one_fat; } esp_vfs_fat_mount_config_t; +#define VFS_FAT_MOUNT_DEFAULT_CONFIG() \ + { \ + .format_if_mount_failed = false, \ + .max_files = 5, \ + .allocation_unit_size = 0, \ + .disk_status_check_enable = false, \ + .use_one_fat = false, \ + } + // Compatibility definition typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t; @@ -208,6 +232,25 @@ esp_err_t esp_vfs_fat_sdmmc_unmount(void) __attribute__((deprecated("Please use esp_err_t esp_vfs_fat_sdcard_unmount(const char* base_path, sdmmc_card_t *card); /** + * @brief Format FAT filesystem with given configuration + * + * @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 + * @param cfg Pointer to structure with extra parameters for formatting FATFS (only relevant fields are used). + * If NULL, the previous configuration will be used. + * + * @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_cfg(const char *base_path, sdmmc_card_t *card, esp_vfs_fat_mount_config_t *cfg); + +/** * @brief Format FAT filesystem * * @note @@ -268,6 +311,27 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path, esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t wl_handle); /** + * @brief Format FAT filesystem with given configuration + * + * @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 + * @param cfg Pointer to structure with extra parameters for formatting FATFS (only relevant fields are used). + * If NULL and mounted the previous configuration will be used. + * If NULL and unmounted the default configuration will 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_cfg_rw_wl(const char* base_path, const char* partition_label, esp_vfs_fat_mount_config_t *cfg); + +/** * @brief Format FAT filesystem * * @note @@ -337,8 +401,46 @@ esp_err_t esp_vfs_fat_spiflash_unmount_ro(const char* base_path, const char* par */ esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes); +/** + * @brief Create a file with contiguous space at given path + * + * @note The file cannot exist before calling this function (or the file size has to be 0) + * For more information see documentation for `f_expand` from FATFS library + * + * @param base_path Base path of the partition examined (e.g. "/spiflash") + * @param full_path Full path of the file (e.g. "/spiflash/ABC.TXT") + * @param size File size expanded to, number of bytes in size to prepare or allocate for the file + * @param alloc_now True == allocate space now, false == prepare to allocate -- see `f_expand` from FATFS + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if invalid arguments (e.g. any of arguments are NULL or size lower or equal to 0) + * - ESP_ERR_INVALID_STATE if partition not found + * - ESP_FAIL if another FRESULT error (saved in errno) + */ +esp_err_t esp_vfs_fat_create_contiguous_file(const char* base_path, const char* full_path, uint64_t size, bool alloc_now); + +/** + * @brief Test if a file is contiguous in the FAT filesystem + * + * @param base_path Base path of the partition examined (e.g. "/spiflash") + * @param full_path Full path of the file (e.g. "/spiflash/ABC.TXT") + * @param[out] is_contiguous True == allocate space now, false == prepare to allocate -- see `f_expand` from FATFS + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if invalid arguments (e.g. any of arguments are NULL) + * - ESP_ERR_INVALID_STATE if partition not found + * - ESP_FAIL if another FRESULT error (saved in errno) + */ +esp_err_t esp_vfs_fat_test_contiguous_file(const char* base_path, const char* full_path, bool* is_contiguous); + /** @cond */ /** + * @deprecated Please use `esp_vfs_fat_register_cfg` instead + */ +esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, + size_t max_files, FATFS** out_fs); + +/** * @deprecated Please use `esp_vfs_fat_spiflash_mount_rw_wl` instead */ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path, diff --git a/lib/fatfs/vfs/vfs_fat.c b/lib/fatfs/vfs/vfs_fat.c index 4ae04c3d..b0b32fa9 100644 --- a/lib/fatfs/vfs/vfs_fat.c +++ b/lib/fatfs/vfs/vfs_fat.c @@ -1,21 +1,40 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include <string.h> #include <stdlib.h> +#include <limits.h> #include <unistd.h> #include <dirent.h> #include <sys/errno.h> #include <sys/fcntl.h> #include <sys/lock.h> +#include "esp_vfs_fat.h" #include "esp_vfs.h" #include "esp_log.h" #include "ff.h" #include "diskio_impl.h" +#define F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT 512 + +#ifdef CONFIG_VFS_SUPPORT_DIR +struct cached_data{ +#if FF_USE_LFN + char file_path[FILENAME_MAX+1+FF_LFN_BUF+1]; //FILENAME_MAX+1: for dir_path, FF_LFN_BUF+1: for file name +#else + char file_path[FILENAME_MAX+1+FF_SFN_BUF+1]; //FILENAME_MAX+1: for dir_path, FF_LFN_BUF+1: for file name +#endif + FILINFO fileinfo; +}; +#endif // CONFIG_VFS_SUPPORT_DIR + +#if !defined(FILENAME_MAX) +#define FILENAME_MAX 255 +#endif + typedef struct { char fat_drive[8]; /* FAT drive name */ char base_path[ESP_VFS_PATH_MAX]; /* base path in VFS where partition is registered */ @@ -24,8 +43,12 @@ typedef struct { 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 */ + uint32_t *flags; /* file descriptor flags, array of max_files size */ +#ifdef CONFIG_VFS_SUPPORT_DIR + char dir_path[FILENAME_MAX]; /* variable to store path of opened directory*/ + struct cached_data cached_fileinfo; +#endif + FIL files[]; /* array with max_files entries; must be the final member of the structure */ } vfs_fat_ctx_t; typedef struct { @@ -66,6 +89,7 @@ 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); +static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg); #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); @@ -90,6 +114,7 @@ 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++) { @@ -112,7 +137,54 @@ static size_t find_unused_context_index(void) 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); + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = fat_drive, + .max_files = max_files, + }; + return esp_vfs_fat_register_cfg(&conf, out_fs); +} + +#ifdef CONFIG_VFS_SUPPORT_DIR +static const esp_vfs_dir_ops_t s_vfs_fat_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 + +static const esp_vfs_fs_ops_t s_vfs_fat = { + .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, + .fcntl_p = &vfs_fat_fcntl, + .fsync_p = &vfs_fat_fsync, +#ifdef CONFIG_VFS_SUPPORT_DIR + .dir = &s_vfs_fat_dir, +#endif // CONFIG_VFS_SUPPORT_DIR +}; + +esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_fs) +{ + size_t ctx = find_context_index_by_path(conf->base_path); if (ctx < FF_VOLUMES) { return ESP_ERR_INVALID_STATE; } @@ -122,55 +194,30 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz 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 max_files = conf->max_files; + if (max_files < 1) { + max_files = 1; // ff_memalloc(max_files * sizeof(bool)) below will fail if max_files == 0 + } + 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) { + fat_ctx->flags = ff_memalloc(max_files * sizeof(*fat_ctx->flags)); + if (fat_ctx->flags == NULL) { free(fat_ctx); return ESP_ERR_NO_MEM; } - memset(fat_ctx->o_append, 0, max_files * sizeof(bool)); + memset(fat_ctx->flags, 0, max_files * sizeof(*fat_ctx->flags)); 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); + strlcpy(fat_ctx->fat_drive, conf->fat_drive, sizeof(fat_ctx->fat_drive) - 1); + strlcpy(fat_ctx->base_path, conf->base_path, sizeof(fat_ctx->base_path) - 1); - esp_err_t err = esp_vfs_register(base_path, &vfs, fat_ctx); + esp_err_t err = esp_vfs_register_fs(conf->base_path, &s_vfs_fat, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, fat_ctx); if (err != ESP_OK) { - free(fat_ctx->o_append); + free(fat_ctx->flags); free(fat_ctx); return err; } @@ -198,7 +245,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path) return err; } _lock_close(&fat_ctx->lock); - free(fat_ctx->o_append); + free(fat_ctx->flags); free(fat_ctx); s_fat_ctxs[ctx] = NULL; return ESP_OK; @@ -386,7 +433,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode) // 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; + fat_ctx->flags[fd] = (flags & (O_APPEND | O_ACCMODE)); _lock_release(&fat_ctx->lock); return fd; } @@ -396,10 +443,12 @@ 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]) { + _lock_acquire(&fat_ctx->lock); + if (fat_ctx->flags[fd] & O_APPEND) { if ((res = f_lseek(file, f_size(file))) != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); + _lock_release(&fat_ctx->lock); return -1; } } @@ -407,18 +456,19 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size) res = f_write(file, data, size, &written); if (((written == 0) && (size != 0)) && (res == 0)) { errno = ENOSPC; + _lock_release(&fat_ctx->lock); return -1; } if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); if (written == 0) { + _lock_release(&fat_ctx->lock); return -1; } } #if CONFIG_FATFS_IMMEDIATE_FSYNC - _lock_acquire(&fat_ctx->lock); if (written > 0) { res = f_sync(file); if (res != FR_OK) { @@ -428,9 +478,8 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size) return -1; } } - _lock_release(&fat_ctx->lock); #endif - + _lock_release(&fat_ctx->lock); return written; } @@ -549,10 +598,8 @@ pwrite_release: 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); @@ -631,6 +678,26 @@ static int vfs_fat_fstat(void* ctx, int fd, struct stat * st) return 0; } +static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg) +{ + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + switch (cmd) { + case F_GETFL: + return fat_ctx->flags[fd]; + case F_SETFL: + fat_ctx->flags[fd] = arg; + return 0; + // no-ops: + case F_SETLK: + case F_SETLKW: + case F_GETLK: + return 0; + default: + errno = EINVAL; + return -1; + } +} + #ifdef CONFIG_VFS_SUPPORT_DIR static inline mode_t get_stat_mode(bool is_dir) @@ -639,6 +706,32 @@ static inline mode_t get_stat_mode(bool is_dir) ((is_dir) ? S_IFDIR : S_IFREG); } +static void update_stat_struct(struct stat *st, FILINFO *info) +{ + 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; +} + static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) { if (strcmp(path, "/") == 0) { @@ -651,6 +744,16 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) } vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + + //If fileinfo is already cached by readdir for requested filename, + //then return the same info else obtain fileinfo with f_stat function + if (strcmp(path, fat_ctx->cached_fileinfo.file_path) == 0) { + update_stat_struct(st, &fat_ctx->cached_fileinfo.fileinfo); + memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(FILINFO)); + return 0; + } + + memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(fat_ctx->cached_fileinfo)); _lock_acquire(&fat_ctx->lock); prepend_drive_to_path(fat_ctx, &path, NULL); FILINFO info; @@ -662,28 +765,7 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) 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; + update_stat_struct(st, &info); return 0; } @@ -707,74 +789,85 @@ 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; + + FRESULT res = FR_OK; + int ret = 0; + FIL* pf1 = (FIL*) ff_memalloc(sizeof(FIL)); FIL* pf2 = (FIL*) ff_memalloc(sizeof(FIL)); + + const size_t copy_buf_size = fat_ctx->fs.csize; 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); + _lock_release(&fat_ctx->lock); errno = ENOMEM; - return -1; + ret = -1; + goto cleanup; } + 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; + goto cleanup; } + res = f_open(pf2, n2, FA_WRITE | FA_CREATE_NEW); + +#if !CONFIG_FATFS_LINK_LOCK _lock_release(&fat_ctx->lock); +#endif + if (res != FR_OK) { - goto fail2; + goto close_old; } + 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; + goto close_both; } else if (read != will_copy) { res = FR_DISK_ERR; - goto fail3; + goto close_both; } size_t written; res = f_write(pf2, buf, will_copy, &written); if (res != FR_OK) { - goto fail3; + goto close_both; } else if (written != will_copy) { res = FR_DISK_ERR; - goto fail3; + goto close_both; } size_left -= will_copy; } -#if CONFIG_FATFS_IMMEDIATE_FSYNC - _lock_acquire(&fat_ctx->lock); - res = f_sync(pf2); +close_both: + f_close(pf2); + +close_old: + f_close(pf1); + +#if CONFIG_FATFS_LINK_LOCK _lock_release(&fat_ctx->lock); #endif -fail3: - f_close(pf2); -fail2: - f_close(pf1); -fail1: +cleanup: free(buf); free(pf2); free(pf1); - if (res != FR_OK) { + if (ret == 0 && res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } - return 0; + + return ret; } static int vfs_fat_rename(void* ctx, const char *src, const char *dst) @@ -795,6 +888,7 @@ static int vfs_fat_rename(void* ctx, const char *src, const char *dst) static DIR* vfs_fat_opendir(void* ctx, const char* name) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + strlcpy(fat_ctx->dir_path, name, sizeof(fat_ctx->dir_path)); _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)); @@ -832,6 +926,9 @@ static int vfs_fat_closedir(void* ctx, DIR* pdir) static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir) { + assert(ctx); + assert(pdir); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; 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); @@ -839,6 +936,25 @@ static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir) errno = err; return NULL; } + + //Store the FILEINFO in the cached_fileinfo. If the stat function is invoked immediately afterward, + //the cached_fileinfo will provide the FILEINFO directly, as it was already obtained during the readdir operation. + //During directory size calculation, this optimization can reduce the computation time. + memset(&fat_ctx->cached_fileinfo, 0 ,sizeof(fat_ctx->cached_fileinfo)); + if (strcmp(fat_ctx->dir_path, "/") == 0) { + snprintf(fat_ctx->cached_fileinfo.file_path, sizeof(fat_ctx->cached_fileinfo.file_path), + "/%s", fat_dir->filinfo.fname); + } else { + char *temp_file_path = (char*) ff_memalloc(sizeof(fat_ctx->cached_fileinfo.file_path)); + if (temp_file_path == NULL) { + return out_dirent; + } + snprintf(temp_file_path, sizeof(fat_ctx->cached_fileinfo.file_path), + "%s/%s", fat_ctx->dir_path, fat_dir->filinfo.fname); + memcpy(fat_ctx->cached_fileinfo.file_path, temp_file_path, sizeof(fat_ctx->cached_fileinfo.file_path)); + ff_memfree(temp_file_path); + } + fat_ctx->cached_fileinfo.fileinfo = fat_dir->filinfo; return out_dirent; } @@ -881,8 +997,10 @@ static long vfs_fat_telldir(void* ctx, DIR* pdir) static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) { assert(pdir); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; FRESULT res; + _lock_acquire(&fat_ctx->lock); if (offset < fat_dir->offset) { res = f_rewinddir(&fat_dir->ffdir); if (res != FR_OK) { @@ -901,6 +1019,7 @@ static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) } fat_dir->offset++; } + _lock_release(&fat_ctx->lock); } static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) @@ -962,6 +1081,49 @@ static int vfs_fat_access(void* ctx, const char *path, int amode) return ret; } +static FRESULT f_write_zero_mem(FIL* fp, FSIZE_t data_size, FSIZE_t buf_size, UINT* bytes_written) +{ + if (fp == NULL || data_size <= 0 || buf_size <= 0) { + return FR_INVALID_PARAMETER; + } + + void* buf = ff_memalloc(buf_size); + if (buf == NULL) { + return FR_DISK_ERR; + } + memset(buf, 0, buf_size); + + FRESULT res = FR_OK; + UINT bw = 0; + FSIZE_t i = 0; + if (bytes_written != NULL) { + *bytes_written = 0; + } + + if (data_size > buf_size) { // prevent unsigned underflow + for (; i < (data_size - buf_size); i += buf_size) { // write chunks of buf_size + res = f_write(fp, buf, (UINT) buf_size, &bw); + if (res != FR_OK) { + goto out; + } + if (bytes_written != NULL) { + *bytes_written += bw; + } + } + } + + if (i < data_size) { // write the remaining data + res = f_write(fp, buf, (UINT) (data_size - i), &bw); + if (res == FR_OK && bytes_written != NULL) { + *bytes_written += bw; + } + } + +out: + ff_memfree(buf); + return res; +} + static int vfs_fat_truncate(void* ctx, const char *path, off_t length) { FRESULT res; @@ -1000,31 +1162,55 @@ static int vfs_fat_truncate(void* ctx, const char *path, off_t length) 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; - } + FSIZE_t seek_ptr_pos = (FSIZE_t) f_tell(file); // current seek pointer position + FSIZE_t sz = (FSIZE_t) f_size(file); // current file size (end of file position) 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; + if (res != FR_OK || f_tell(file) != length) { + goto lseek_or_write_fail; } - res = f_truncate(file); + if (sz < length) { + res = f_lseek(file, sz); // go to the previous end of file + if (res != FR_OK) { + goto lseek_or_write_fail; + } - if (res != FR_OK) { - ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); - errno = fresult_to_errno(res); - ret = -1; - goto close; + FSIZE_t new_free_space = ((FSIZE_t) length) - sz; + UINT written; + + if (new_free_space > UINT32_MAX) { + _lock_release(&fat_ctx->lock); + ESP_LOGE(TAG, "%s: Cannot extend the file more than 4GB at once", __func__); + ret = -1; + goto close; + } + + FSIZE_t buf_size_limit = F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT; + FSIZE_t buf_size = new_free_space < buf_size_limit ? new_free_space : buf_size_limit; + res = f_write_zero_mem(file, new_free_space, buf_size, &written); + + if (res != FR_OK) { + goto lseek_or_write_fail; + } else if (written != (UINT) new_free_space) { + res = FR_DISK_ERR; + goto lseek_or_write_fail; + } + + res = f_lseek(file, seek_ptr_pos); // return to the original position + if (res != FR_OK) { + goto lseek_or_write_fail; + } + } else { + res = f_truncate(file); + + 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; + } } #if CONFIG_FATFS_IMMEDIATE_FSYNC @@ -1052,6 +1238,13 @@ close: out: free(file); return ret; + +lseek_or_write_fail: + _lock_release(&fat_ctx->lock); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + ret = -1; + goto close; } static int vfs_fat_ftruncate(void* ctx, int fd, off_t length) @@ -1078,29 +1271,50 @@ static int vfs_fat_ftruncate(void* ctx, int fd, off_t length) 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; - } + FSIZE_t seek_ptr_pos = (FSIZE_t) f_tell(file); // current seek pointer position + FSIZE_t sz = (FSIZE_t) f_size(file); // current file size (end of file position) 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; + if (res != FR_OK || f_tell(file) != length) { + goto fail; } - res = f_truncate(file); + if (sz < length) { + res = f_lseek(file, sz); // go to the previous end of file + if (res != FR_OK) { + goto fail; + } - if (res != FR_OK) { - ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); - errno = fresult_to_errno(res); - ret = -1; - goto out; + FSIZE_t new_free_space = ((FSIZE_t) length) - sz; + UINT written; + + if (new_free_space > UINT32_MAX) { + ESP_LOGE(TAG, "%s: Cannot extend the file more than 4GB at once", __func__); + ret = -1; + goto out; + } + + FSIZE_t buf_size_limit = F_WRITE_MALLOC_ZEROING_BUF_SIZE_LIMIT; + FSIZE_t buf_size = new_free_space < buf_size_limit ? new_free_space : buf_size_limit; + res = f_write_zero_mem(file, new_free_space, buf_size, &written); + + if (res != FR_OK) { + goto fail; + } else if (written != (UINT) new_free_space) { + res = FR_DISK_ERR; + goto fail; + } + + res = f_lseek(file, seek_ptr_pos); // return to the original position + if (res != FR_OK) { + goto fail; + } + } else { + res = f_truncate(file); + + if (res != FR_OK) { + goto fail; + } } #if CONFIG_FATFS_IMMEDIATE_FSYNC @@ -1115,6 +1329,12 @@ static int vfs_fat_ftruncate(void* ctx, int fd, off_t length) out: _lock_release(&fat_ctx->lock); return ret; + +fail: + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + ret = -1; + goto out; } static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *times) @@ -1142,7 +1362,7 @@ static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *time fat_date_t fdate; fat_time_t ftime; - // this time transformation is esentially the reverse of the one in vfs_fat_stat() + // this time transformation is essentially 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 @@ -1170,3 +1390,142 @@ static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *time } #endif // CONFIG_VFS_SUPPORT_DIR + +esp_err_t esp_vfs_fat_create_contiguous_file(const char* base_path, const char* full_path, uint64_t size, bool alloc_now) +{ + if (base_path == NULL || full_path == NULL || size <= 0) { + return ESP_ERR_INVALID_ARG; + } + + 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]; + + _lock_acquire(&fat_ctx->lock); + const char* file_path = full_path + strlen(base_path); // shift the pointer and omit the base_path + prepend_drive_to_path(fat_ctx, &file_path, NULL); + + FIL* file = (FIL*) ff_memalloc(sizeof(FIL)); + if (file == NULL) { + _lock_release(&fat_ctx->lock); + ESP_LOGD(TAG, "esp_vfs_fat_create_contiguous_file alloc failed"); + errno = ENOMEM; + return -1; + } + memset(file, 0, sizeof(*file)); + + FRESULT res = f_open(file, file_path, FA_WRITE | FA_OPEN_ALWAYS); + if (res != FR_OK) { + goto fail; + } + + res = f_expand(file, size, alloc_now ? 1 : 0); + if (res != FR_OK) { + f_close(file); + goto fail; + } + + res = f_close(file); + if (res != FR_OK) { + goto fail; + } + + _lock_release(&fat_ctx->lock); + free(file); + + return 0; +fail: + _lock_release(&fat_ctx->lock); + free(file); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; +} + +static FRESULT test_contiguous_file( // From FATFS examples + FIL* fp, /* [IN] Open file object to be checked */ + int* cont /* [OUT] 1:Contiguous, 0:Fragmented or zero-length */ +) { + DWORD clst, clsz, step; + FSIZE_t fsz; + FRESULT fr; + + *cont = 0; + fr = f_rewind(fp); /* Validates and prepares the file */ + if (fr != FR_OK) return fr; + +#if FF_MAX_SS == FF_MIN_SS + clsz = (DWORD)fp->obj.fs->csize * FF_MAX_SS; /* Cluster size */ +#else + clsz = (DWORD)fp->obj.fs->csize * fp->obj.fs->ssize; +#endif + fsz = f_size(fp); + if (fsz > 0) { + clst = fp->obj.sclust - 1; /* A cluster leading the first cluster for first test */ + while (fsz) { + step = (fsz >= clsz) ? clsz : (DWORD)fsz; + fr = f_lseek(fp, f_tell(fp) + step); /* Advances file pointer a cluster */ + if (fr != FR_OK) return fr; + if (clst + 1 != fp->clust) break; /* Is not the cluster next to previous one? */ + clst = fp->clust; fsz -= step; /* Get current cluster for next test */ + } + if (fsz == 0) *cont = 1; /* All done without fail? */ + } + + return FR_OK; +} + +esp_err_t esp_vfs_fat_test_contiguous_file(const char* base_path, const char* full_path, bool* is_contiguous) +{ + if (base_path == NULL || full_path == NULL || is_contiguous == NULL) { + return ESP_ERR_INVALID_ARG; + } + + 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]; + + _lock_acquire(&fat_ctx->lock); + const char* file_path = full_path + strlen(base_path); // shift the pointer and omit the base_path + prepend_drive_to_path(fat_ctx, &file_path, NULL); + + FIL* file = (FIL*) ff_memalloc(sizeof(FIL)); + if (file == NULL) { + _lock_release(&fat_ctx->lock); + ESP_LOGD(TAG, "esp_vfs_fat_test_contiguous_file alloc failed"); + errno = ENOMEM; + return -1; + } + memset(file, 0, sizeof(*file)); + + FRESULT res = f_open(file, file_path, FA_WRITE | FA_OPEN_ALWAYS); + if (res != FR_OK) { + goto fail; + } + + res = test_contiguous_file(file, (int*) is_contiguous); + if (res != FR_OK) { + f_close(file); + goto fail; + } + + res = f_close(file); + if (res != FR_OK) { + goto fail; + } + + _lock_release(&fat_ctx->lock); + free(file); + + return 0; +fail: + _lock_release(&fat_ctx->lock); + free(file); + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; +} diff --git a/lib/fatfs/vfs/vfs_fat_internal.h b/lib/fatfs/vfs/vfs_fat_internal.h index dc3bae27..5b894604 100644 --- a/lib/fatfs/vfs/vfs_fat_internal.h +++ b/lib/fatfs/vfs/vfs_fat_internal.h @@ -1,23 +1,41 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once #include "esp_vfs_fat.h" +#include "diskio_impl.h" +#include "esp_partition.h" +#include "sdmmc_cmd.h" #include <sys/param.h> #include <stddef.h> +typedef enum { + FORMATTED_DURING_LAST_MOUNT = 1 << 0, // The FATFS partition was formatted during the last mount +} vfs_fat_x_ctx_flags_t; + +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 label 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_x_ctx_flags_t flags; //Flags +} vfs_fat_spiflash_ctx_t; + +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_x_ctx_flags_t flags; //Flags +} vfs_fat_sd_ctx_t; + static inline size_t esp_vfs_fat_get_allocation_unit_size( size_t sector_size, size_t requested_size) { @@ -28,3 +46,6 @@ static inline size_t esp_vfs_fat_get_allocation_unit_size( alloc_unit_size = MIN(alloc_unit_size, max_size); return alloc_unit_size; } + +vfs_fat_spiflash_ctx_t* get_vfs_fat_spiflash_ctx(wl_handle_t wlhandle); +vfs_fat_sd_ctx_t* get_vfs_fat_get_sd_ctx(const sdmmc_card_t *card); diff --git a/lib/fatfs/vfs/vfs_fat_sdmmc.c b/lib/fatfs/vfs/vfs_fat_sdmmc.c index f62d6e7c..cd012d4d 100644 --- a/lib/fatfs/vfs/vfs_fat_sdmmc.c +++ b/lib/fatfs/vfs/vfs_fat_sdmmc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,7 +16,7 @@ #include "diskio_impl.h" #include "diskio_sdmmc.h" #include "soc/soc_caps.h" -#include "driver/sdmmc_defs.h" +#include "sd_protocol_defs.h" #if SOC_SDMMC_HOST_SUPPORTED #include "driver/sdmmc_host.h" @@ -31,14 +31,6 @@ static const char* TAG = "vfs_fat_sdmmc"; } \ } 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. @@ -75,6 +67,15 @@ static uint32_t s_get_unused_context_id(void) return FF_VOLUMES; } +vfs_fat_sd_ctx_t* get_vfs_fat_get_sd_ctx(const sdmmc_card_t *card) +{ + uint32_t id = FF_VOLUMES; + if (s_get_context_id_by_card(card, &id)) { + return s_ctx[id]; + } + return NULL; +} + static esp_err_t mount_prepare_mem(const char *base_path, BYTE *out_pdrv, char **out_dup_path, @@ -116,7 +117,7 @@ cleanup: 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) +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, vfs_fat_x_ctx_flags_t *out_flags) { esp_err_t err = ESP_OK; FRESULT res = f_mount(fs, drv, 1); @@ -134,20 +135,28 @@ static esp_err_t s_f_mount(sdmmc_card_t *card, FATFS *fs, const char *drv, uint8 return err; } + if (out_flags) { + *out_flags |= FORMATTED_DURING_LAST_MOUNT; // set flag + } + ESP_LOGW(TAG, "mounting again"); - res = f_mount(fs, drv, 0); + res = f_mount(fs, drv, 1); if (res != FR_OK) { err = ESP_FAIL; ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res); return err; } + } else { + if (out_flags) { + *out_flags &= ~FORMATTED_DURING_LAST_MOUNT; // reset flag + } } 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) + const char *base_path, FATFS **out_fs, vfs_fat_x_ctx_flags_t *out_flags) { FATFS *fs = NULL; esp_err_t err; @@ -157,17 +166,22 @@ static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config char drv[3] = {(char)('0' + pdrv), ':', 0}; // connect FATFS to VFS - err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs); + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = drv, + .max_files = mount_config->max_files, + }; + err = esp_vfs_fat_register_cfg(&conf, &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); + ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", err); goto fail; } // Try to mount partition - err = s_f_mount(card, fs, drv, pdrv, mount_config); + err = s_f_mount(card, fs, drv, pdrv, mount_config, out_flags); if (err != ESP_OK) { goto fail; } @@ -207,7 +221,7 @@ static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config, 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}; + const MKFS_PARM opt = {(BYTE)FM_ANY, (mount_config->use_one_fat ? 1 : 2), 0, 0, alloc_unit_size}; res = f_mkfs(drv, &opt, workbuf, workbuf_size); if (res != FR_OK) { err = ESP_FAIL; @@ -265,7 +279,9 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, 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); + vfs_fat_x_ctx_flags_t flags = 0; + + err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs, &flags); CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed"); if (out_card != NULL) { @@ -276,7 +292,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, s_saved_ctx_id = 0; } - ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1); + ctx = calloc(1, sizeof(vfs_fat_sd_ctx_t)); if (!ctx) { CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem"); } @@ -285,6 +301,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, ctx->card = card; ctx->base_path = dup_path; ctx->fs = fs; + ctx->flags = flags; ctx_id = s_get_unused_context_id(); assert(ctx_id != FF_VOLUMES); s_ctx[ctx_id] = ctx; @@ -360,7 +377,9 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path, 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); + vfs_fat_x_ctx_flags_t flags = 0; + + err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path, &fs, &flags); CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed"); if (out_card != NULL) { @@ -371,7 +390,7 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path, s_saved_ctx_id = 0; } - ctx = calloc(sizeof(vfs_fat_sd_ctx_t), 1); + ctx = calloc(1, sizeof(vfs_fat_sd_ctx_t)); if (!ctx) { CHECK_EXECUTE_RESULT(ESP_ERR_NO_MEM, "no mem"); } @@ -380,6 +399,7 @@ esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path, ctx->card = card; ctx->base_path = dup_path; ctx->fs = fs; + ctx->flags = flags; ctx_id = s_get_unused_context_id(); assert(ctx_id != FF_VOLUMES); s_ctx[ctx_id] = ctx; @@ -450,7 +470,7 @@ esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card) return err; } -esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) +esp_err_t esp_vfs_fat_sdcard_format_cfg(const char *base_path, sdmmc_card_t *card, esp_vfs_fat_mount_config_t *cfg) { esp_err_t ret = ESP_OK; if (!card) { @@ -480,13 +500,22 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) //format uint32_t id = FF_VOLUMES; - bool found = s_get_context_id_by_card(card, &id); - assert(found); + + { + const bool found = s_get_context_id_by_card(card, &id); + (void)found; + assert(found); + } + + if (cfg) { + s_ctx[id]->mount_config = *cfg; + } + 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}; + const MKFS_PARM opt = {(BYTE)FM_ANY, (s_ctx[id]->mount_config.use_one_fat ? 1 : 2), 0, 0, alloc_unit_size}; res = f_mkfs(drv, &opt, workbuf, workbuf_size); free(workbuf); if (res != FR_OK) { @@ -495,7 +524,7 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) } //mount back - esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config); + esp_err_t err = s_f_mount(card, s_ctx[id]->fs, drv, pdrv, &s_ctx[id]->mount_config, NULL); if (err != ESP_OK) { unmount_card_core(base_path, card); ESP_LOGE(TAG, "failed to format, resources recycled, please mount again"); @@ -503,3 +532,7 @@ esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) return ret; } + +esp_err_t esp_vfs_fat_sdcard_format(const char *base_path, sdmmc_card_t *card) { + return esp_vfs_fat_sdcard_format_cfg(base_path, card, NULL); +} diff --git a/lib/fatfs/vfs/vfs_fat_spiflash.c b/lib/fatfs/vfs/vfs_fat_spiflash.c index 98e4160f..0ee00e25 100644 --- a/lib/fatfs/vfs/vfs_fat_spiflash.c +++ b/lib/fatfs/vfs/vfs_fat_spiflash.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,16 +15,11 @@ #include "wear_levelling.h" #include "diskio_wl.h" -static const char* TAG = "vfs_fat_spiflash"; +// If the available sectors based on partition size are less than 128, +// the root directory sector should be set to 1. +#define MIN_REQ_SEC 128 -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 const char* TAG = "vfs_fat_spiflash"; static vfs_fat_spiflash_ctx_t *s_ctx[FF_VOLUMES] = {}; @@ -74,35 +69,78 @@ static uint32_t s_get_unused_context_id(void) 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) +vfs_fat_spiflash_ctx_t* get_vfs_fat_spiflash_ctx(wl_handle_t wlhandle) { - FRESULT fresult = f_mount(fs, drv, 1); - if (fresult != FR_OK) { - ESP_LOGW(TAG, "f_mount failed (%d)", fresult); + uint32_t id = FF_VOLUMES; + if (s_get_context_id_by_wl_handle(wlhandle, &id)) { + return s_ctx[id]; + } + return NULL; +} - 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; +static esp_err_t s_f_mount_rw(FATFS *fs, const char *drv, const esp_vfs_fat_mount_config_t *mount_config, vfs_fat_x_ctx_flags_t *out_flags, size_t sec_num) +{ + FRESULT fresult = f_mount(fs, drv, 1); + if (fresult == FR_OK) { + if (out_flags) { + *out_flags &= ~FORMATTED_DURING_LAST_MOUNT; // reset flag } + return ESP_OK; + } - const size_t workbuf_size = 4096; - void *workbuf = ff_memalloc(workbuf_size); - if (workbuf == NULL) { - return ESP_ERR_NO_MEM; - } + const char *msg = "Unknown"; + const char *note = ""; + bool recoverable = false; + + switch (fresult) { + case FR_NO_FILESYSTEM: + msg = "No filesystem detected"; + note = "(This may indicate corrupt FS, or attempt to mount read-only fatfsgen image for write)"; + recoverable = true; + break; + case FR_INT_ERR: + msg = "Assertion failed"; + recoverable = true; + break; + default: + break; + } + + if (!recoverable || !mount_config->format_if_mount_failed) { + ESP_LOGE(TAG, "f_mount failed with error: \"%s\" [%d]. %s", msg, fresult, note); + return ESP_FAIL; + } + + ESP_LOGW(TAG, "FatFS mount (f_mount) failed with error: \"%s\" [%d]. Retrying after format...", msg, fresult); - 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); + 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); + UINT root_dir_entries; + if (CONFIG_WL_SECTOR_SIZE == 512) { + root_dir_entries = 16; + } else { + root_dir_entries = 128; + } + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (mount_config->use_one_fat ? 1 : 2), 0, (sec_num <= MIN_REQ_SEC ? root_dir_entries : 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); + + if (out_flags) { + *out_flags |= FORMATTED_DURING_LAST_MOUNT; // set flag + } + + ESP_LOGI(TAG, "Mounting again"); + fresult = f_mount(fs, drv, 1); + ESP_RETURN_ON_FALSE(fresult == FR_OK, ESP_FAIL, TAG, "f_mount failed after formatting (%d)", fresult); + return ESP_OK; } @@ -134,27 +172,36 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path, 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); + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = drv, + .max_files = mount_config->max_files, + }; + ret = esp_vfs_fat_register_cfg(&conf, &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); + ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", ret); goto fail; } + vfs_fat_x_ctx_flags_t flags = 0; + + size_t sec_num = wl_size(*wl_handle) / wl_sector_size(*wl_handle); // Try to mount partition - ret = s_f_mount_rw(fs, drv, mount_config); + ret = s_f_mount_rw(fs, drv, mount_config, &flags, sec_num); if (ret != ESP_OK) { goto fail; } - ctx = calloc(sizeof(vfs_fat_spiflash_ctx_t), 1); + ctx = calloc(1, sizeof(vfs_fat_spiflash_ctx_t)); 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; + ctx->flags = flags; 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 @@ -201,25 +248,43 @@ esp_err_t esp_vfs_fat_spiflash_unmount_rw_wl(const char* base_path, wl_handle_t return err; } -esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label) +esp_err_t esp_vfs_fat_spiflash_format_cfg_rw_wl(const char* base_path, const char* partition_label, esp_vfs_fat_mount_config_t *cfg) { esp_err_t ret = ESP_OK; bool partition_was_mounted = false; wl_handle_t temp_handle = WL_INVALID_HANDLE; uint32_t id = FF_VOLUMES; + size_t sec_num = 0; bool found = s_get_context_id_by_label(partition_label, &id); if (!found) { - const esp_vfs_fat_mount_config_t mount_config = { + esp_vfs_fat_mount_config_t default_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"); + esp_vfs_fat_mount_config_t *mount_cfg = NULL; + if (cfg) { + mount_cfg = cfg; + } else { + mount_cfg = &default_mount_config; + } + ESP_RETURN_ON_ERROR(esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition_label, mount_cfg, &temp_handle), TAG, "Failed to mount"); found = s_get_context_id_by_label(partition_label, &id); + sec_num = wl_size(temp_handle) / wl_sector_size(temp_handle); assert(found); + if (s_ctx[id]->flags & FORMATTED_DURING_LAST_MOUNT) { + ESP_LOGD(TAG, "partition was formatted during mounting, skipping another format"); + ret = ESP_OK; + goto mount_back; + } } else { partition_was_mounted = true; + if (cfg) { + s_ctx[id]->mount_config = *cfg; + } + temp_handle = s_ctx[id]->wlhandle; + sec_num = wl_size(temp_handle) / wl_sector_size(temp_handle); } //unmount @@ -236,7 +301,13 @@ esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* p } 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}; + UINT root_dir_entries; + if (CONFIG_WL_SECTOR_SIZE == 512) { + root_dir_entries = 16; + } else { + root_dir_entries = 128; + } + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), (s_ctx[id]->mount_config.use_one_fat ? 1 : 2), 0, (sec_num <= MIN_REQ_SEC ? root_dir_entries : 0), alloc_unit_size}; fresult = f_mkfs(drv, &opt, workbuf, workbuf_size); free(workbuf); workbuf = NULL; @@ -244,7 +315,7 @@ esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* p mount_back: if (partition_was_mounted) { - esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config); + esp_err_t err = s_f_mount_rw(s_ctx[id]->fs, drv, &s_ctx[id]->mount_config, NULL, sec_num); if (err != ESP_OK) { ESP_LOGE(TAG, "failed to mount back, go to recycle"); goto recycle; @@ -265,6 +336,10 @@ recycle: return ret; } +esp_err_t esp_vfs_fat_spiflash_format_rw_wl(const char* base_path, const char* partition_label) +{ + return esp_vfs_fat_spiflash_format_cfg_rw_wl(base_path, partition_label, NULL); +} esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path, const char* partition_label, @@ -287,11 +362,16 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path, 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); + esp_vfs_fat_conf_t conf = { + .base_path = base_path, + .fat_drive = drv, + .max_files = mount_config->max_files, + }; + ret = esp_vfs_fat_register_cfg(&conf, &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); + ESP_LOGD(TAG, "esp_vfs_fat_register_cfg failed 0x(%x)", ret); goto fail; } |
