From c8e79a926620e48830778714cfe4b2ea2453fcaf Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 25 Jul 2025 13:33:07 +1000 Subject: Update forked idf components --- lib/fatfs/.build-test-rules.yml | 7 - lib/fatfs/Kconfig | 111 +++- lib/fatfs/diskio/diskio.c | 6 +- lib/fatfs/diskio/diskio_rawflash.c | 16 +- lib/fatfs/diskio/diskio_sdmmc.c | 18 +- lib/fatfs/diskio/diskio_sdmmc.h | 2 +- lib/fatfs/diskio/diskio_wl.c | 24 +- lib/fatfs/fatfs_utils/boot_sector.py | 22 +- lib/fatfs/fatfs_utils/fatfs_state.py | 3 +- lib/fatfs/fatfs_utils/utils.py | 34 +- lib/fatfs/fatfsgen.py | 67 ++- lib/fatfs/fatfsparse.py | 11 +- lib/fatfs/host_test/.build-test-rules.yml | 7 + lib/fatfs/host_test/CMakeLists.txt | 4 +- lib/fatfs/host_test/main/CMakeLists.txt | 8 +- lib/fatfs/host_test/main/idf_component.yml | 2 + lib/fatfs/host_test/main/main.cpp | 7 - lib/fatfs/host_test/main/test_fatfs.cpp | 22 +- lib/fatfs/host_test/partition_table.csv | 4 +- lib/fatfs/host_test/pytest_fatfs_linux.py | 5 +- lib/fatfs/host_test/sdkconfig.defaults | 1 - lib/fatfs/port/freertos/ffsystem.c | 2 +- lib/fatfs/project_include.cmake | 57 +- lib/fatfs/src/ff.c | 59 +- lib/fatfs/src/ff.h | 12 +- lib/fatfs/src/ffconf.h | 43 +- lib/fatfs/test_apps/.build-test-rules.yml | 48 +- lib/fatfs/test_apps/README.md | 4 + lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt | 7 + lib/fatfs/test_apps/dyn_buffers/README.md | 8 + .../test_apps/dyn_buffers/main/CMakeLists.txt | 3 + .../dyn_buffers/main/test_fatfs_dyn_buffers.c | 101 ++++ lib/fatfs/test_apps/dyn_buffers/partitions.csv | 5 + .../dyn_buffers/pytest_fatfs_dyn_buffers.py | 22 + .../test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers | 1 + .../dyn_buffers/sdkconfig.ci.no_dyn_buffers | 1 + lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults | 14 + lib/fatfs/test_apps/flash_ro/README.md | 4 +- .../test_apps/flash_ro/main/test_fatfs_flash_ro.c | 3 +- .../test_apps/flash_ro/pytest_fatfs_flash_ro.py | 12 +- lib/fatfs/test_apps/flash_wl/README.md | 4 +- lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt | 2 +- .../test_apps/flash_wl/main/Kconfig.projbuild | 10 + .../test_apps/flash_wl/main/test_fatfs_flash_wl.c | 92 ++- .../flash_wl/main/test_fatfs_small_partition.c | 76 +++ lib/fatfs/test_apps/flash_wl/partitions.csv | 1 + .../test_apps/flash_wl/pytest_fatfs_flash_wl.py | 27 +- .../test_apps/flash_wl/sdkconfig.ci.auto_fsync | 2 + .../test_apps/flash_wl/sdkconfig.ci.dyn_buffers | 1 + lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram | 3 - .../test_apps/flash_wl/sdkconfig.ci.psram.esp32 | 4 + lib/fatfs/test_apps/sdcard/README.md | 4 +- lib/fatfs/test_apps/sdcard/main/CMakeLists.txt | 2 +- lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c | 275 ++++++++- lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c | 61 +- lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py | 48 +- lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram | 3 - .../test_apps/sdcard/sdkconfig.ci.psram.esp32 | 4 + .../test_apps/test_fatfs_common/CMakeLists.txt | 2 +- .../test_fatfs_common/test_fatfs_common.c | 245 ++++++-- .../test_fatfs_common/test_fatfs_common.h | 10 +- lib/fatfs/test_fatfs_host/Makefile | 106 ---- lib/fatfs/test_fatfs_host/Makefile.files | 44 -- lib/fatfs/test_fatfs_host/component.mk | 17 - lib/fatfs/test_fatfs_host/main.cpp | 2 - lib/fatfs/test_fatfs_host/partition_table.csv | 6 - lib/fatfs/test_fatfs_host/test_fatfs.cpp | 94 ---- lib/fatfs/test_fatfsgen/test_fatfsgen.py | 441 +++++++++++++-- lib/fatfs/test_fatfsgen/test_fatfsparse.py | 50 +- lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py | 187 +++++- lib/fatfs/vfs/esp_vfs_fat.h | 118 +++- lib/fatfs/vfs/vfs_fat.c | 625 ++++++++++++++++----- lib/fatfs/vfs/vfs_fat_internal.h | 47 +- lib/fatfs/vfs/vfs_fat_sdmmc.c | 85 ++- lib/fatfs/vfs/vfs_fat_spiflash.c | 168 ++++-- lib/fatfs/wl_fatfsgen.py | 33 +- 76 files changed, 2827 insertions(+), 859 deletions(-) delete mode 100644 lib/fatfs/.build-test-rules.yml create mode 100644 lib/fatfs/host_test/.build-test-rules.yml create mode 100644 lib/fatfs/host_test/main/idf_component.yml delete mode 100644 lib/fatfs/host_test/main/main.cpp create mode 100644 lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt create mode 100644 lib/fatfs/test_apps/dyn_buffers/README.md create mode 100644 lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt create mode 100644 lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c create mode 100644 lib/fatfs/test_apps/dyn_buffers/partitions.csv create mode 100644 lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py create mode 100644 lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers create mode 100644 lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers create mode 100644 lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults create mode 100644 lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild create mode 100644 lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c create mode 100644 lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers delete mode 100644 lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram create mode 100644 lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32 delete mode 100644 lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram create mode 100644 lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32 delete mode 100644 lib/fatfs/test_fatfs_host/Makefile delete mode 100644 lib/fatfs/test_fatfs_host/Makefile.files delete mode 100644 lib/fatfs/test_fatfs_host/component.mk delete mode 100644 lib/fatfs/test_fatfs_host/main.cpp delete mode 100644 lib/fatfs/test_fatfs_host/partition_table.csv delete mode 100644 lib/fatfs/test_fatfs_host/test_fatfs.cpp (limited to 'lib/fatfs') diff --git a/lib/fatfs/.build-test-rules.yml b/lib/fatfs/.build-test-rules.yml deleted file mode 100644 index 737e82b7..00000000 --- a/lib/fatfs/.build-test-rules.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps - -components/fatfs/test_apps/sdcard: - disable_test: - - if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c6", "esp32h2"] - temporary: true - reason: No sdspi runners for these targets diff --git a/lib/fatfs/Kconfig b/lib/fatfs/Kconfig index f2c737a6..1a35dc4e 100644 --- a/lib/fatfs/Kconfig +++ b/lib/fatfs/Kconfig @@ -64,7 +64,7 @@ menu "FAT Filesystem support" config FATFS_CODEPAGE_857 bool "Turkish (CP857)" config FATFS_CODEPAGE_860 - bool "Portugese (CP860)" + bool "Portuguese (CP860)" config FATFS_CODEPAGE_861 bool "Icelandic (CP861)" config FATFS_CODEPAGE_862 @@ -161,7 +161,7 @@ menu "FAT Filesystem support" help This option sets FATFS configuration value _FS_TIMEOUT, scaled to milliseconds. Sets the number of milliseconds FATFS will wait to acquire a mutex when - operating on an open file. For example, if one task is performing a lenghty + operating on an open file. For example, if one task is performing a lengthy operation, another task will wait for the first task to release the lock, and time out after amount of time set by this option. @@ -184,7 +184,7 @@ menu "FAT Filesystem support" config FATFS_ALLOC_PREFER_EXTRAM - bool "Perfer external RAM when allocating FATFS buffers" + bool "Prefer external RAM when allocating FATFS buffers" default y depends on SPIRAM_USE_CAPS_ALLOC || SPIRAM_USE_MALLOC help @@ -205,6 +205,63 @@ menu "FAT Filesystem support" file is opened in write-mode, the seek mechanism will automatically fallback to the default implementation. + choice FATFS_USE_STRFUNC_CHOICE + prompt "Enable string functions, f_gets(), f_putc(), f_puts() and f_printf()" + default FATFS_USE_STRFUNC_NONE + help + These are specialized alternatives to stdio functions for working + directly with FATFS without VFS. Legacy code may need functions, + but for new development, it is advised to use stdio under VFS. + + 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect. + 1: Enable without LF-CRLF conversion. + 2: Enable with LF-CRLF conversion. + + config FATFS_USE_STRFUNC_NONE + bool "0:Disable" + + config FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV + bool "1:Enable without LF-CRLF conversion" + + config FATFS_USE_STRFUNC_WITH_CRLF_CONV + bool "2:Enable with LF-CRLF conversion" + endchoice + + config FATFS_PRINT_LLI + depends on !FATFS_USE_STRFUNC_NONE + bool "Make fatfs f_printf() support long long argument" + default 0 + + config FATFS_PRINT_FLOAT + depends on !FATFS_USE_STRFUNC_NONE + bool "Make fatfs f_printf() support floating point argument" + default 0 + + choice FATFS_STRF_ENCODE_CHOICE + prompt "FatFS string functions: convert character encoding" + depends on !FATFS_LFN_NONE && !FATFS_USE_STRFUNC_NONE + default FATFS_STRF_ENCODE_UTF8 + help + When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character + encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE + to be read/written via those functions. + 0: ANSI/OEM in current CP + 1: Unicode in UTF-16LE + 2: Unicode in UTF-16BE + 3: Unicode in UTF-8 + + config FATFS_STRF_ENCODE_ANSI + bool "0:ANSI/OEM in current CP" + + config FATFS_STRF_ENCODE_UTF16LE + bool "1:Unicode in UTF-16LE" + + config FATFS_STRF_ENCODE_UTF16BE + bool "2:Unicode in UTF-16BE" + + config FATFS_STRF_ENCODE_UTF8 + bool "3:Unicode in UTF-8" + endchoice config FATFS_FAST_SEEK_BUFFER_SIZE int "Fast seek CLMT buffer size" @@ -239,4 +296,52 @@ menu "FAT Filesystem support" vfs_fat_pwrite(), vfs_fat_link(), vfs_fat_truncate() and vfs_fat_ftruncate() functions. This feature improves file-consistency and size reporting accuracy for the FatFS, at a price on decreased performance due to frequent disk operations + + config FATFS_USE_LABEL + bool "Use FATFS volume label" + default n + help + Allows FATFS volume label to be specified using f_setlabel + + config FATFS_LINK_LOCK + bool "Perform the whole link operation under lock" + default y + help + If enabled, the whole link operation (including file copying) is performed under lock. + This ensures that the link operation is atomic, but may cause performance for large files. + It may create less fragmented file copy. + + config FATFS_USE_DYN_BUFFERS + bool "Use dynamic buffers" + default n + help + If enabled, the buffers used by FATFS will be allocated separately from the rest of the structure. + This option is useful when using multiple FATFS instances with different + sector sizes, as the buffers will be allocated according to the sector size. + If disabled, the greatest sector size will be used for all FATFS instances. + (In most cases, this would be the sector size of Wear Levelling library) + This might cause more memory to be used than necessary. + + menu "File system free space calculation behavior" + help + Controls if the file system does or does not trust cached data like free cluster count and allocated + cluster number. Setting these to do not trust the data may result of more accurate output from + `f_getfree()` function but increased overhead (forces a full FAT scan, etc.). + + config FATFS_DONT_TRUST_FREE_CLUSTER_CNT + int "Don't trust free cluster count" + default 0 + range 0 1 + help + If 1, the file system will not trust the free cluster count in the FSINFO (in FATFS struct). + This may result in more accurate output from `f_getfree()` function but increased overhead. + + config FATFS_DONT_TRUST_LAST_ALLOC + int "Don't trust allocated cluster number" + default 0 + range 0 1 + help + If 1, the file system will not trust the last allocated cluster number in the FSINFO (in FATFS struct). + This may result in more accurate output from `f_getfree()` function but increased overhead. + endmenu endmenu diff --git a/lib/fatfs/diskio/diskio.c b/lib/fatfs/diskio/diskio.c index 4bafa7ea..7d39835f 100644 --- a/lib/fatfs/diskio/diskio.c +++ b/lib/fatfs/diskio/diskio.c @@ -1,10 +1,10 @@ /*-----------------------------------------------------------------------*/ /* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */ -/* ESP-IDF port Copyright 2016 Espressif Systems (Shanghai) PTE LTD */ +/* ESP-IDF port Copyright 2016-2025 Espressif Systems (Shanghai) PTE LTD */ /*-----------------------------------------------------------------------*/ /* If a working storage control module is available, it should be */ /* attached to the FatFs via a glue function rather than modifying it. */ -/* This is an example of glue functions to attach various exsisting */ +/* This is an example of glue functions to attach various existing */ /* storage control modules to the FatFs module with a defined API. */ /*-----------------------------------------------------------------------*/ @@ -19,7 +19,7 @@ static ff_diskio_impl_t * s_impls[FF_VOLUMES] = { NULL }; #if FF_MULTI_PARTITION /* Multiple partition configuration */ -const PARTITION VolToPart[FF_VOLUMES] = { +PARTITION VolToPart[FF_VOLUMES] = { {0, 0}, /* Logical drive 0 ==> Physical drive 0, auto detection */ #if FF_VOLUMES > 1 {1, 0}, /* Logical drive 1 ==> Physical drive 1, auto detection */ diff --git a/lib/fatfs/diskio/diskio_rawflash.c b/lib/fatfs/diskio/diskio_rawflash.c index 313aacec..4bc3eb8e 100644 --- a/lib/fatfs/diskio/diskio_rawflash.c +++ b/lib/fatfs/diskio/diskio_rawflash.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -25,7 +25,7 @@ static uint8_t s_initialized[FF_VOLUMES]; #define BPB_TotSec32 32 -DSTATUS ff_raw_initialize (BYTE pdrv) +static DSTATUS ff_raw_initialize (BYTE pdrv) { uint16_t sector_size_tmp; @@ -61,7 +61,7 @@ DSTATUS ff_raw_initialize (BYTE pdrv) return STA_PROTECT; } -DSTATUS ff_raw_status (BYTE pdrv) +static DSTATUS ff_raw_status (BYTE pdrv) { DSTATUS status = STA_PROTECT; if (!s_initialized[pdrv]) { @@ -70,7 +70,7 @@ DSTATUS ff_raw_status (BYTE pdrv) return status; } -DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) +static DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { ESP_LOGV(TAG, "ff_raw_read - pdrv=%i, sector=%i, count=%in", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count); const esp_partition_t* part = s_ff_raw_handles[pdrv]; @@ -84,16 +84,16 @@ DRESULT ff_raw_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) } -DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) +static DRESULT ff_raw_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { return RES_WRPRT; } -DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff) +static DRESULT ff_raw_ioctl (BYTE pdrv, BYTE cmd, void *buff) { - const esp_partition_t* part = s_ff_raw_handles[pdrv]; ESP_LOGV(TAG, "ff_raw_ioctl: cmd=%in", cmd); - assert(part); + assert(s_ff_raw_handles[pdrv]); + switch (cmd) { case CTRL_SYNC: return RES_OK; diff --git a/lib/fatfs/diskio/diskio_sdmmc.c b/lib/fatfs/diskio/diskio_sdmmc.c index 3e61eb00..689a07bc 100644 --- a/lib/fatfs/diskio/diskio_sdmmc.c +++ b/lib/fatfs/diskio/diskio_sdmmc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,12 +34,12 @@ static DSTATUS ff_sdmmc_card_available(BYTE pdrv) * fails. This error value is checked throughout the FATFS code. * Both functions return 0 on success. */ -DSTATUS ff_sdmmc_initialize (BYTE pdrv) +static DSTATUS ff_sdmmc_initialize (BYTE pdrv) { return ff_sdmmc_card_available(pdrv); } -DSTATUS ff_sdmmc_status(BYTE pdrv) +static DSTATUS ff_sdmmc_status(BYTE pdrv) { if (s_disk_status_check_en[pdrv]) { return ff_sdmmc_card_available(pdrv); @@ -47,32 +47,32 @@ DSTATUS ff_sdmmc_status(BYTE pdrv) return 0; } -DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) +static DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); esp_err_t err = sdmmc_read_sectors(card, buff, sector, count); if (unlikely(err != ESP_OK)) { - ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); + ESP_LOGE(TAG, "sdmmc_read_blocks failed (0x%x)", err); return RES_ERROR; } return RES_OK; } -DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) +static DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); esp_err_t err = sdmmc_write_sectors(card, buff, sector, count); if (unlikely(err != ESP_OK)) { - ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); + ESP_LOGE(TAG, "sdmmc_write_blocks failed (0x%x)", err); return RES_ERROR; } return RES_OK; } #if FF_USE_TRIM -DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count) +static DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); @@ -88,7 +88,7 @@ DRESULT ff_sdmmc_trim (BYTE pdrv, DWORD start_sector, DWORD sector_count) } #endif //FF_USE_TRIM -DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff) +static DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff) { sdmmc_card_t* card = s_cards[pdrv]; assert(card); diff --git a/lib/fatfs/diskio/diskio_sdmmc.h b/lib/fatfs/diskio/diskio_sdmmc.h index 2ba4c2d9..d6c881ae 100644 --- a/lib/fatfs/diskio/diskio_sdmmc.h +++ b/lib/fatfs/diskio/diskio_sdmmc.h @@ -7,7 +7,7 @@ #pragma once #include "sdmmc_cmd.h" -#include "driver/sdmmc_defs.h" +#include "sd_protocol_defs.h" #ifdef __cplusplus extern "C" { diff --git a/lib/fatfs/diskio/diskio_wl.c b/lib/fatfs/diskio/diskio_wl.c index 8e20290e..4852ca7a 100644 --- a/lib/fatfs/diskio/diskio_wl.c +++ b/lib/fatfs/diskio/diskio_wl.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 */ @@ -19,52 +19,52 @@ wl_handle_t ff_wl_handles[FF_VOLUMES] = { [0 ... FF_VOLUMES - 1] = WL_INVALID_HANDLE }; -DSTATUS ff_wl_initialize (BYTE pdrv) +static DSTATUS ff_wl_initialize (BYTE pdrv) { return 0; } -DSTATUS ff_wl_status (BYTE pdrv) +static DSTATUS ff_wl_status (BYTE pdrv) { return 0; } -DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) +static DRESULT ff_wl_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { ESP_LOGV(TAG, "ff_wl_read - pdrv=%i, sector=%i, count=%i", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count); wl_handle_t wl_handle = ff_wl_handles[pdrv]; - assert(wl_handle + 1); + assert(wl_handle != WL_INVALID_HANDLE); esp_err_t err = wl_read(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle)); if (unlikely(err != ESP_OK)) { - ESP_LOGE(TAG, "wl_read failed (%d)", err); + ESP_LOGE(TAG, "wl_read failed (0x%x)", err); return RES_ERROR; } return RES_OK; } -DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) +static DRESULT ff_wl_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { ESP_LOGV(TAG, "ff_wl_write - pdrv=%i, sector=%i, count=%i", (unsigned int)pdrv, (unsigned int)sector, (unsigned int)count); wl_handle_t wl_handle = ff_wl_handles[pdrv]; - assert(wl_handle + 1); + assert(wl_handle != WL_INVALID_HANDLE); esp_err_t err = wl_erase_range(wl_handle, sector * wl_sector_size(wl_handle), count * wl_sector_size(wl_handle)); if (unlikely(err != ESP_OK)) { - ESP_LOGE(TAG, "wl_erase_range failed (%d)", err); + ESP_LOGE(TAG, "wl_erase_range failed (0x%x)", err); return RES_ERROR; } err = wl_write(wl_handle, sector * wl_sector_size(wl_handle), buff, count * wl_sector_size(wl_handle)); if (unlikely(err != ESP_OK)) { - ESP_LOGE(TAG, "wl_write failed (%d)", err); + ESP_LOGE(TAG, "wl_write failed (0x%x)", err); return RES_ERROR; } return RES_OK; } -DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff) +static DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff) { wl_handle_t wl_handle = ff_wl_handles[pdrv]; ESP_LOGV(TAG, "ff_wl_ioctl: cmd=%i", cmd); - assert(wl_handle + 1); + assert(wl_handle != WL_INVALID_HANDLE); switch (cmd) { case CTRL_SYNC: return RES_OK; diff --git a/lib/fatfs/fatfs_utils/boot_sector.py b/lib/fatfs/fatfs_utils/boot_sector.py index 615dd065..2809c033 100644 --- a/lib/fatfs/fatfs_utils/boot_sector.py +++ b/lib/fatfs/fatfs_utils/boot_sector.py @@ -1,9 +1,9 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 from inspect import getmembers, isroutine from typing import Optional -from construct import Const, Int8ul, Int16ul, Int32ul, PaddedString, Struct, core +from construct import Bytes, Const, Int8ul, Int16ul, Int32ul, PaddedString, Padding, Struct, core from .exceptions import InconsistentFATAttributes, NotInitialized from .fatfs_state import BootSectorState @@ -29,8 +29,7 @@ class BootSector: BOOT_HEADER_SIZE = 512 BOOT_SECTOR_HEADER = Struct( - # this value reflects BS_jmpBoot used for ESP32 boot sector (any other accepted) - 'BS_jmpBoot' / Const(b'\xeb\xfe\x90'), + 'BS_jmpBoot' / Bytes(3), 'BS_OEMName' / PaddedString(MAX_OEM_NAME_SIZE, SHORT_NAMES_ENCODING), 'BPB_BytsPerSec' / Int16ul, 'BPB_SecPerClus' / Int8ul, @@ -45,12 +44,12 @@ class BootSector: 'BPB_HiddSec' / Int32ul, 'BPB_TotSec32' / Int32ul, # zero if the FAT type is 12/16, otherwise number of sectors 'BS_DrvNum' / Const(b'\x80'), - 'BS_Reserved1' / Const(EMPTY_BYTE), + 'BS_Reserved1' / Padding(1), 'BS_BootSig' / Const(b'\x29'), 'BS_VolID' / Int32ul, 'BS_VolLab' / PaddedString(MAX_VOL_LAB_SIZE, SHORT_NAMES_ENCODING), 'BS_FilSysType' / PaddedString(MAX_FS_TYPE_SIZE, SHORT_NAMES_ENCODING), - 'BS_EMPTY' / Const(448 * EMPTY_BYTE), + 'BS_EMPTY' / Padding(448), 'Signature_word' / Const(FATDefaults.SIGNATURE_WORD) ) assert BOOT_SECTOR_HEADER.sizeof() == BOOT_HEADER_SIZE @@ -65,15 +64,17 @@ class BootSector: raise NotInitialized('The BootSectorState instance is not initialized!') volume_uuid = generate_4bytes_random() pad_header: bytes = (boot_sector_state.sector_size - BootSector.BOOT_HEADER_SIZE) * EMPTY_BYTE - data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE - root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE fat_tables_content: bytes = (boot_sector_state.sectors_per_fat_cnt * boot_sector_state.fat_tables_cnt * boot_sector_state.sector_size * EMPTY_BYTE) + root_dir_content: bytes = boot_sector_state.root_dir_sectors_cnt * boot_sector_state.sector_size * EMPTY_BYTE + data_content: bytes = boot_sector_state.data_sectors * boot_sector_state.sector_size * FULL_BYTE + self.boot_sector_state.binary_image = ( BootSector.BOOT_SECTOR_HEADER.build( - dict(BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE), + dict(BS_jmpBoot=(b'\xeb\xfe\x90'), + BS_OEMName=pad_string(boot_sector_state.oem_name, size=BootSector.MAX_OEM_NAME_SIZE), BPB_BytsPerSec=boot_sector_state.sector_size, BPB_SecPerClus=boot_sector_state.sectors_per_cluster, BPB_RsvdSecCnt=boot_sector_state.reserved_sectors_cnt, @@ -91,8 +92,7 @@ class BootSector: BS_VolLab=pad_string(boot_sector_state.volume_label, size=BootSector.MAX_VOL_LAB_SIZE), BS_FilSysType=pad_string(boot_sector_state.file_sys_type, - size=BootSector.MAX_FS_TYPE_SIZE) - ) + size=BootSector.MAX_FS_TYPE_SIZE)) ) + pad_header + fat_tables_content + root_dir_content + data_content ) diff --git a/lib/fatfs/fatfs_utils/fatfs_state.py b/lib/fatfs/fatfs_utils/fatfs_state.py index 22af7bfb..4ba57132 100644 --- a/lib/fatfs/fatfs_utils/fatfs_state.py +++ b/lib/fatfs/fatfs_utils/fatfs_state.py @@ -152,6 +152,7 @@ class BootSectorState: def non_data_sectors(self) -> int: non_data_sectors_: int = get_non_data_sectors_cnt(self.reserved_sectors_cnt, self.sectors_per_fat_cnt, + self.fat_tables_cnt, self.root_dir_sectors_cnt) return non_data_sectors_ @@ -166,5 +167,5 @@ class BootSectorState: @property def root_directory_start(self) -> int: - root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt) * self.sector_size + root_dir_start: int = (self.reserved_sectors_cnt + self.sectors_per_fat_cnt * self.fat_tables_cnt) * self.sector_size return root_dir_start diff --git a/lib/fatfs/fatfs_utils/utils.py b/lib/fatfs/fatfs_utils/utils.py index d2180c76..662586a5 100644 --- a/lib/fatfs/fatfs_utils/utils.py +++ b/lib/fatfs/fatfs_utils/utils.py @@ -1,15 +1,18 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - import argparse import binascii import os import re import uuid from datetime import datetime -from typing import List, Optional, Tuple +from typing import List +from typing import Optional +from typing import Tuple -from construct import BitsInteger, BitStruct, Int16ul +from construct import BitsInteger +from construct import BitStruct +from construct import Int16ul # the regex pattern defines symbols that are allowed by long file names but not by short file names INVALID_SFN_CHARS_PATTERN = re.compile(r'[.+,;=\[\]]') @@ -68,8 +71,8 @@ def number_of_clusters(number_of_sectors: int, sectors_per_cluster: int) -> int: return number_of_sectors // sectors_per_cluster -def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, root_dir_sectors_cnt: int) -> int: - return reserved_sectors_cnt + sectors_per_fat_cnt + root_dir_sectors_cnt +def get_non_data_sectors_cnt(reserved_sectors_cnt: int, sectors_per_fat_cnt: int, fat_tables_cnt: int, root_dir_sectors_cnt: int) -> int: + return reserved_sectors_cnt + sectors_per_fat_cnt * fat_tables_cnt + root_dir_sectors_cnt def get_fatfs_type(clusters_count: int) -> int: @@ -203,9 +206,19 @@ def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace: type=int, choices=[FAT12, FAT16, 0], help=""" - Type of fat. Select 12 for fat12, 16 for fat16. Don't set, or set to 0 for automatic - calculation using cluster size and partition size. + Type of the FAT file-system. Select '12' for FAT12, '16' for FAT16. + Leave unset or select 0 for automatic file-system type detection. """) + parser.add_argument('--fat_count', + default=FATDefaults.FAT_TABLES_COUNT, + type=int, + choices=[1, 2], + help='Number of file allocation tables (FATs) in the filesystem.') + parser.add_argument('--wl_mode', + default=None, + type=str, + choices=['safe', 'perf'], + help='Wear levelling mode to use. Safe or performance. Only for sector size of 512') args = parser.parse_args() if args.fat_type == 0: @@ -215,6 +228,9 @@ def get_args_for_partition_generator(desc: str, wl: bool) -> argparse.Namespace: args.partition_size = int(str(args.partition_size), 0) if not os.path.isdir(args.input_directory): raise NotADirectoryError(f'The target directory `{args.input_directory}` does not exist!') + if args.wl_mode is not None: + if args.sector_size != 512: + raise ValueError('Wear levelling mode can be set only for sector size 512') return args @@ -276,7 +292,7 @@ class FATDefaults: # FATFS defaults SIZE: int = 1024 * 1024 RESERVED_SECTORS_COUNT: int = 1 - FAT_TABLES_COUNT: int = 1 + FAT_TABLES_COUNT: int = 2 SECTORS_PER_CLUSTER: int = 1 SECTOR_SIZE: int = 0x1000 HIDDEN_SECTORS: int = 0 diff --git a/lib/fatfs/fatfsgen.py b/lib/fatfs/fatfsgen.py index 199916ef..30d274f5 100755 --- a/lib/fatfs/fatfsgen.py +++ b/lib/fatfs/fatfsgen.py @@ -1,10 +1,11 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - import os from datetime import datetime -from typing import Any, List, Optional +from typing import Any +from typing import List +from typing import Optional from fatfs_utils.boot_sector import BootSector from fatfs_utils.exceptions import NoFreeClusterException @@ -12,10 +13,24 @@ from fatfs_utils.fat import FAT from fatfs_utils.fatfs_state import FATFSState from fatfs_utils.fs_object import Directory from fatfs_utils.long_filename_utils import get_required_lfn_entries_count -from fatfs_utils.utils import (BYTES_PER_DIRECTORY_ENTRY, FATFS_INCEPTION, FATFS_MIN_ALLOC_UNIT, - RESERVED_CLUSTERS_COUNT, FATDefaults, get_args_for_partition_generator, - get_fat_sectors_count, get_non_data_sectors_cnt, read_filesystem, - required_clusters_count) +from fatfs_utils.utils import BYTES_PER_DIRECTORY_ENTRY +from fatfs_utils.utils import FATDefaults +from fatfs_utils.utils import FATFS_INCEPTION +from fatfs_utils.utils import FATFS_MIN_ALLOC_UNIT +from fatfs_utils.utils import get_args_for_partition_generator +from fatfs_utils.utils import get_fat_sectors_count +from fatfs_utils.utils import get_non_data_sectors_cnt +from fatfs_utils.utils import read_filesystem +from fatfs_utils.utils import required_clusters_count +from fatfs_utils.utils import RESERVED_CLUSTERS_COUNT + + +def duplicate_fat_decorator(func): # type: ignore + def wrapper(self, *args, **kwargs) -> None: # type: ignore + func(self, *args, **kwargs) + if isinstance(self, FATFS): + self.duplicate_fat() + return wrapper class FATFS: @@ -40,14 +55,15 @@ class FATFS: volume_label: str = FATDefaults.VOLUME_LABEL, file_sys_type: str = FATDefaults.FILE_SYS_TYPE, root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT, - explicit_fat_type: int = None, + explicit_fat_type: Optional[int] = None, media_type: int = FATDefaults.MEDIA_TYPE) -> None: # root directory bytes should be aligned by sector size - assert (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0 + assert (int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) % sector_size == 0 # number of bytes in the root dir must be even multiple of BPB_BytsPerSec - assert ((root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0 + if (int(root_entry_count) > 128): + assert ((int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) // sector_size) % 2 == 0 - root_dir_sectors_cnt: int = (root_entry_count * BYTES_PER_DIRECTORY_ENTRY) // sector_size + root_dir_sectors_cnt: int = (int(root_entry_count) * BYTES_PER_DIRECTORY_ENTRY) // sector_size self.state: FATFSState = FATFSState(sector_size=sector_size, explicit_fat_type=explicit_fat_type, @@ -79,6 +95,7 @@ class FATFS: fatfs_state=self.state) self.root_directory.init_directory() + @duplicate_fat_decorator def create_file(self, name: str, extension: str = '', path_from_root: Optional[List[str]] = None, @@ -102,6 +119,7 @@ class FATFS: object_timestamp_=object_timestamp_, is_empty=is_empty) + @duplicate_fat_decorator def create_directory(self, name: str, path_from_root: Optional[List[str]] = None, object_timestamp_: datetime = FATFS_INCEPTION) -> None: @@ -126,6 +144,7 @@ class FATFS: path_from_root=path_from_root, object_timestamp_=object_timestamp_) + @duplicate_fat_decorator def write_content(self, path_from_root: List[str], content: bytes) -> None: """ fat fs invokes root directory to recursively find the required file and writes the content @@ -137,10 +156,24 @@ class FATFS: boot_sector_.generate_boot_sector() return boot_sector_.binary_image + def duplicate_fat(self) -> None: + """ + Duplicate FAT table if 2 FAT tables are required + """ + boot_sec_st = self.state.boot_sector_state + if boot_sec_st.fat_tables_cnt == 2: + fat_start = boot_sec_st.reserved_sectors_cnt * boot_sec_st.sector_size + fat_end = fat_start + boot_sec_st.sectors_per_fat_cnt * boot_sec_st.sector_size + second_fat_shift = boot_sec_st.sectors_per_fat_cnt * boot_sec_st.sector_size + self.state.binary_image[fat_start + second_fat_shift: fat_end + second_fat_shift] = ( + self.state.binary_image[fat_start: fat_end] + ) + def write_filesystem(self, output_path: str) -> None: with open(output_path, 'wb') as output: output.write(bytearray(self.state.binary_image)) + @duplicate_fat_decorator def _generate_partition_from_folder(self, folder_relative_path: str, folder_path: str = '', @@ -225,17 +258,19 @@ def main() -> None: args.partition_size = max(FATFS_MIN_ALLOC_UNIT * args.sector_size, (clusters + fats + get_non_data_sectors_cnt(RESERVED_CLUSTERS_COUNT, fats, + args.fat_count, root_dir_sectors) ) * args.sector_size ) - fatfs = FATFS(sector_size=args.sector_size, + fatfs = FATFS(size=args.partition_size, + fat_tables_cnt=args.fat_count, sectors_per_cluster=args.sectors_per_cluster, - size=args.partition_size, - root_entry_count=args.root_entry_count, - explicit_fat_type=args.fat_type, + sector_size=args.sector_size, long_names_enabled=args.long_name_support, - use_default_datetime=args.use_default_datetime) + use_default_datetime=args.use_default_datetime, + root_entry_count=args.root_entry_count, + explicit_fat_type=args.fat_type) fatfs.generate(args.input_directory) fatfs.write_filesystem(args.output_file) diff --git a/lib/fatfs/fatfsparse.py b/lib/fatfs/fatfsparse.py index de50f2fe..13c9a056 100755 --- a/lib/fatfs/fatfsparse.py +++ b/lib/fatfs/fatfsparse.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import argparse import os @@ -100,7 +100,7 @@ def remove_wear_levelling_if_exists(fs_: bytes) -> bytes: boot_sector__.parse_boot_sector(fs_) if boot_sector__.boot_sector_state.size == len(fs_): return fs_ - except construct.core.ConstError: + except (UnicodeDecodeError, construct.core.StringError): pass plain_fs: bytes = remove_wl(fs_) return plain_fs @@ -124,6 +124,9 @@ if __name__ == '__main__': default=None, help="If detection doesn't work correctly, " 'you can force analyzer to or not to assume WL.') + argument_parser.add_argument('--verbose', + action='store_true', + help='Prints details about FAT image.') args = argument_parser.parse_args() @@ -157,6 +160,10 @@ if __name__ == '__main__': boot_sector_ = BootSector() boot_sector_.parse_boot_sector(fs) + + if args.verbose: + print(str(boot_sector_)) + fat = FAT(boot_sector_.boot_sector_state, init_=False) boot_dir_start_ = boot_sector_.boot_sector_state.root_directory_start diff --git a/lib/fatfs/host_test/.build-test-rules.yml b/lib/fatfs/host_test/.build-test-rules.yml new file mode 100644 index 00000000..17714e9c --- /dev/null +++ b/lib/fatfs/host_test/.build-test-rules.yml @@ -0,0 +1,7 @@ +components/fatfs/host_test: + enable: + - if: IDF_TARGET == "linux" + disable_test: + - if: IDF_TARGET == "esp32p4" + temporary: true + reason: test not pass, should be re-enable # TODO: IDF-8980 diff --git a/lib/fatfs/host_test/CMakeLists.txt b/lib/fatfs/host_test/CMakeLists.txt index 48267da8..7bf861b1 100644 --- a/lib/fatfs/host_test/CMakeLists.txt +++ b/lib/fatfs/host_test/CMakeLists.txt @@ -2,9 +2,7 @@ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) set(COMPONENTS main) -# Freertos is included via common components. However, CATCH isn't compatible with the FreeRTOS component yet, hence -# using the FreeRTOS mock component. -# target. +# This test doesn't require FreeRTOS, uses a mock instead list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") project(fatfs_host_test) diff --git a/lib/fatfs/host_test/main/CMakeLists.txt b/lib/fatfs/host_test/main/CMakeLists.txt index ef8aeb43..046433c1 100644 --- a/lib/fatfs/host_test/main/CMakeLists.txt +++ b/lib/fatfs/host_test/main/CMakeLists.txt @@ -1,6 +1,8 @@ -idf_component_register(SRCS "main.cpp" - "test_fatfs.cpp" - INCLUDE_DIRS "$ENV{IDF_PATH}/tools/catch" +idf_component_register(SRCS "test_fatfs.cpp" REQUIRES fatfs WHOLE_ARCHIVE ) + +# Currently 'main' for IDF_TARGET=linux is defined in freertos component. +# Since we are using a freertos mock here, need to let Catch2 provide 'main'. +target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) diff --git a/lib/fatfs/host_test/main/idf_component.yml b/lib/fatfs/host_test/main/idf_component.yml new file mode 100644 index 00000000..f7982136 --- /dev/null +++ b/lib/fatfs/host_test/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/catch2: "^3.4.0" diff --git a/lib/fatfs/host_test/main/main.cpp b/lib/fatfs/host_test/main/main.cpp deleted file mode 100644 index cd66dc30..00000000 --- a/lib/fatfs/host_test/main/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#define CATCH_CONFIG_MAIN -#include "catch.hpp" diff --git a/lib/fatfs/host_test/main/test_fatfs.cpp b/lib/fatfs/host_test/main/test_fatfs.cpp index 77af2e70..88aa16e1 100644 --- a/lib/fatfs/host_test/main/test_fatfs.cpp +++ b/lib/fatfs/host_test/main/test_fatfs.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,7 +12,7 @@ #include "diskio_impl.h" #include "diskio_wl.h" -#include "catch.hpp" +#include TEST_CASE("Create volume, open file, write and read back data", "[fatfs]") { @@ -44,7 +44,10 @@ TEST_CASE("Create volume, open file, write and read back data", "[fatfs]") fr_result = f_fdisk(pdrv, part_list, work_area); REQUIRE(fr_result == FR_OK); - const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0}; + + // For host tests, include FM_SFD flag when formatting partitions smaller than 128KB. + // if n_root field of MKFS_PARM is set to 128 => 1 root directory sec and if set to 0(default 512) => 4 root directory sectors. + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, 0}; fr_result = f_mkfs("", &opt, work_area, sizeof(work_area)); // Use default volume // Mount the volume @@ -56,7 +59,7 @@ TEST_CASE("Create volume, open file, write and read back data", "[fatfs]") REQUIRE(fr_result == FR_OK); // Generate data - uint32_t data_size = 100000; + uint32_t data_size = 1000; char *data = (char*) malloc(data_size); char *read = (char*) malloc(data_size); @@ -130,7 +133,7 @@ static void prepare_fatfs(const char* partition_label, const esp_partition_t** p fr_result = f_fdisk(_pdrv, part_list, work_area); REQUIRE(fr_result == FR_OK); - const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0}; + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, 0}; fr_result = f_mkfs(drv, &opt, work_area, sizeof(work_area)); // Use default volume REQUIRE(fr_result == FR_OK); } @@ -141,7 +144,7 @@ static void prepare_fatfs(const char* partition_label, const esp_partition_t** p * at the time of writing this - therefore there also is a device test_apps test in * `components/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c` which tests our VFS FATFS SPIFLASH API. */ -TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, reading data", "[fatfs]") +TEST_CASE("Test mounting 2 volumes, writing data and formatting the 2nd one, reading data", "[fatfs]") { FRESULT fr_result; esp_err_t esp_result; @@ -158,7 +161,6 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read FATFS fs1; wl_handle_t wl_handle1 = WL_INVALID_HANDLE; - size_t allocation_unit_size = CONFIG_WL_SECTOR_SIZE; size_t data_size = 10; @@ -223,7 +225,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read const size_t workbuf_size = 4096; void *workbuf = ff_memalloc(workbuf_size); REQUIRE(workbuf != NULL); - const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, CONFIG_WL_SECTOR_SIZE}; + const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 128, CONFIG_WL_SECTOR_SIZE}; fr_result = f_mkfs(drv1, &opt, workbuf, workbuf_size); free(workbuf); workbuf = NULL; @@ -241,7 +243,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read fr_result = f_read(&file1, read1, data_size, &bw1); REQUIRE(fr_result == FR_OK); REQUIRE(bw1 != data_size); - // Comapre data + // Compare data printf("data1=%s, read1=%s\n", data1, read1); REQUIRE(strncmp(data1, read1, data_size-1) != 0); // 987654321 should be ersead due to formatting // Close file from file1 @@ -259,7 +261,7 @@ TEST_CASE("Test mounting 2 volumes, writing data and formating the 2nd one, read fr_result = f_read(&file0, read0, data_size, &bw0); REQUIRE(fr_result == FR_OK); REQUIRE(bw0 == data_size); - // Comapre data + // Compare data printf("data0=%s, read0=%s\n", data0, read0); REQUIRE(strncmp(data0, read0, data_size-1) == 0); // should match since the partition was not formatted // Close file from file0 diff --git a/lib/fatfs/host_test/partition_table.csv b/lib/fatfs/host_test/partition_table.csv index 30d2d909..777a3e67 100644 --- a/lib/fatfs/host_test/partition_table.csv +++ b/lib/fatfs/host_test/partition_table.csv @@ -3,5 +3,5 @@ nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, -storage, data, fat, , 1M, -storage2, data, fat, , 1M, +storage, data, fat, , 32k, +storage2, data, fat, , 32k, diff --git a/lib/fatfs/host_test/pytest_fatfs_linux.py b/lib/fatfs/host_test/pytest_fatfs_linux.py index 7b12c361..e14e927a 100644 --- a/lib/fatfs/host_test/pytest_fatfs_linux.py +++ b/lib/fatfs/host_test/pytest_fatfs_linux.py @@ -1,10 +1,11 @@ -# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 import pytest from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.linux @pytest.mark.host_test +@idf_parametrize('target', ['linux'], indirect=['target']) def test_fatfs_linux(dut: Dut) -> None: dut.expect_exact('All tests passed', timeout=120) diff --git a/lib/fatfs/host_test/sdkconfig.defaults b/lib/fatfs/host_test/sdkconfig.defaults index e0d9a692..53e7687b 100644 --- a/lib/fatfs/host_test/sdkconfig.defaults +++ b/lib/fatfs/host_test/sdkconfig.defaults @@ -6,7 +6,6 @@ CONFIG_LOG_DEFAULT_LEVEL=3 CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv" -CONFIG_ESPTOOLPY_FLASHSIZE="8MB" CONFIG_MMU_PAGE_SIZE=0X10000 CONFIG_ESP_PARTITION_ENABLE_STATS=y CONFIG_FATFS_VOLUME_COUNT=3 diff --git a/lib/fatfs/port/freertos/ffsystem.c b/lib/fatfs/port/freertos/ffsystem.c index 7ca7ecc5..37c31de3 100644 --- a/lib/fatfs/port/freertos/ffsystem.c +++ b/lib/fatfs/port/freertos/ffsystem.c @@ -58,7 +58,7 @@ static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */ #elif OS_TYPE == 2 /* uc/OS-II */ #include "includes.h" -static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */ +static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pointers */ #elif OS_TYPE == 3 /* FreeRTOS */ #include "freertos/FreeRTOS.h" diff --git a/lib/fatfs/project_include.cmake b/lib/fatfs/project_include.cmake index d945c0e9..aa0edf2a 100644 --- a/lib/fatfs/project_include.cmake +++ b/lib/fatfs/project_include.cmake @@ -3,7 +3,7 @@ # Create a fatfs image of the specified directory on the host during build and optionally # have the created image flashed using `idf.py flash` function(fatfs_create_partition_image partition base_dir) - set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME) + set(options FLASH_IN_PROJECT WL_INIT PRESERVE_TIME ONE_FAT) cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") @@ -22,6 +22,12 @@ function(fatfs_create_partition_image partition base_dir) set(default_datetime_option --use_default_datetime) endif() + if(arg_ONE_FAT) + set(fatfsgen_fat_count --fat_count=1) + else() + set(fatfsgen_fat_count) + endif() + if("${CONFIG_FATFS_SECTOR_512}") set(fatfs_sector_size 512) elseif("${CONFIG_FATFS_SECTOR_1024}") @@ -52,6 +58,7 @@ function(fatfs_create_partition_image partition base_dir) COMMAND ${fatfsgen_py} ${base_dir_full_path} ${fatfs_long_names_option} ${default_datetime_option} + ${fatfsgen_fat_count} --partition_size ${size} --output_file ${image_file} --sector_size "${fatfs_sector_size}" @@ -81,39 +88,39 @@ endfunction() function(fatfs_create_rawflash_image partition base_dir) - set(options FLASH_IN_PROJECT PRESERVE_TIME) + set(options FLASH_IN_PROJECT PRESERVE_TIME ONE_FAT) cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") + set(argument_list) + if(arg_FLASH_IN_PROJECT) - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT) - endif() - else() - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir}) - endif() + list(APPEND argument_list FLASH_IN_PROJECT) + endif() + if(arg_PRESERVE_TIME) + list(APPEND argument_list PRESERVE_TIME) + endif() + if(arg_ONE_FAT) + list(APPEND argument_list ONE_FAT) endif() + + fatfs_create_partition_image(${partition} ${base_dir} ${argument_list}) endfunction() function(fatfs_create_spiflash_image partition base_dir) - set(options FLASH_IN_PROJECT PRESERVE_TIME) + set(options FLASH_IN_PROJECT PRESERVE_TIME ONE_FAT) cmake_parse_arguments(arg "${options}" "" "${multi}" "${ARGN}") + set(argument_list WL_INIT) + if(arg_FLASH_IN_PROJECT) - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} FLASH_IN_PROJECT WL_INIT) - endif() - else() - if(arg_PRESERVE_TIME) - fatfs_create_partition_image(${partition} ${base_dir} WL_INIT PRESERVE_TIME) - else() - fatfs_create_partition_image(${partition} ${base_dir} WL_INIT) - endif() + list(APPEND argument_list FLASH_IN_PROJECT) endif() + if(arg_PRESERVE_TIME) + list(APPEND argument_list PRESERVE_TIME) + endif() + if(arg_ONE_FAT) + list(APPEND argument_list ONE_FAT) + endif() + + fatfs_create_partition_image(${partition} ${base_dir} ${argument_list}) endfunction() diff --git a/lib/fatfs/src/ff.c b/lib/fatfs/src/ff.c index 52adc2ab..3c3f1ace 100644 --- a/lib/fatfs/src/ff.c +++ b/lib/fatfs/src/ff.c @@ -34,7 +34,6 @@ #error Wrong include file (ff.h). #endif - /* Limits and boundaries */ #define MAX_DIR 0x200000 /* Max size of FAT directory */ #define MAX_DIR_EX 0x10000000 /* Max size of exFAT directory */ @@ -43,6 +42,10 @@ #define MAX_FAT32 0x0FFFFFF5 /* Max FAT32 clusters (not specified, practical limit) */ #define MAX_EXFAT 0x7FFFFFFD /* Max exFAT clusters (differs from specs, implementation limit) */ +#define MIN_FAT12_SEC_VOL 4 /* Min size of the FAT sector volume + 1 FAT, 1 root dir, 1 reserved, 1 data sector */ +#define MIN_FAT12_DATA_SEC 1 /* Min FAT data sectors */ + /* Character code support macros */ #define IsUpper(c) ((c) >= 'A' && (c) <= 'Z') @@ -1118,7 +1121,7 @@ static FRESULT sync_fs ( /* Returns FR_OK or FR_DISK_ERR */ if (res == FR_OK) { if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { /* FAT32: Update FSInfo sector if needed */ /* Create FSInfo structure */ - memset(fs->win, 0, sizeof fs->win); + memset(fs->win, 0, SS(fs)); st_word(fs->win + BS_55AA, 0xAA55); /* Boot signature */ st_dword(fs->win + FSI_LeadSig, 0x41615252); /* Leading signature */ st_dword(fs->win + FSI_StrucSig, 0x61417272); /* Structure signature */ @@ -1670,7 +1673,7 @@ static FRESULT dir_clear ( /* Returns FR_OK or FR_DISK_ERR */ if (sync_window(fs) != FR_OK) return FR_DISK_ERR; /* Flush disk access window */ sect = clst2sect(fs, clst); /* Top of the cluster */ fs->winsect = sect; /* Set window to top of the cluster */ - memset(fs->win, 0, sizeof fs->win); /* Clear window buffer */ + memset(fs->win, 0, SS(fs)); /* Clear window buffer */ #if FF_USE_LFN == 3 /* Quick table clear by using multi-secter write */ /* Allocate a temporary buffer */ for (szb = ((DWORD)fs->csize * SS(fs) >= MAX_MALLOC) ? MAX_MALLOC : fs->csize * SS(fs), ibuf = 0; szb > SS(fs) && (ibuf = ff_memalloc(szb)) == 0; szb /= 2) ; @@ -3318,7 +3321,7 @@ static UINT check_fs ( /* 0:FAT/FAT32 VBR, 1:exFAT VBR, 2:Not FAT and valid BS, && ld_word(fs->win + BPB_RsvdSecCnt) != 0 /* Properness of reserved sectors (MNBZ) */ && (UINT)fs->win[BPB_NumFATs] - 1 <= 1 /* Properness of FATs (1 or 2) */ && ld_word(fs->win + BPB_RootEntCnt) != 0 /* Properness of root dir entries (MNBZ) */ - && (ld_word(fs->win + BPB_TotSec16) >= 128 || ld_dword(fs->win + BPB_TotSec32) >= 0x10000) /* Properness of volume sectors (>=128) */ + && (ld_word(fs->win + BPB_TotSec16) >= MIN_FAT12_SEC_VOL || ld_dword(fs->win + BPB_TotSec32) >= 0x10000) /* Properness of volume sectors (>=MIN_FAT12_SEC_VOL) */ && ld_word(fs->win + BPB_FATSz16) != 0) { /* Properness of FAT size (MNBZ) */ return 0; /* It can be presumed an FAT VBR */ } @@ -3438,6 +3441,10 @@ static FRESULT mount_volume ( /* FR_OK(0): successful, !=0: an error occurred */ if (disk_ioctl(fs->pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK) return FR_DISK_ERR; if (SS(fs) > FF_MAX_SS || SS(fs) < FF_MIN_SS || (SS(fs) & (SS(fs) - 1))) return FR_DISK_ERR; #endif +#if FF_USE_DYN_BUFFER + fs->win = ff_memalloc(SS(fs)); /* Allocate memory for sector buffer */ + if (!fs->win) return FR_NOT_ENOUGH_CORE; +#endif /* Find an FAT volume on the hosting drive */ fmt = find_volume(fs, LD2PT(vol)); @@ -3680,6 +3687,10 @@ FRESULT f_mount ( #endif #if FF_FS_REENTRANT /* Discard mutex of the current volume */ ff_mutex_delete(vol); +#endif +#if FF_USE_DYN_BUFFER + if (cfs->fs_type) /* Check if the buffer was ever allocated */ + ff_memfree(cfs->win); /* Deallocate buffer allocated for the filesystem object */ #endif cfs->fs_type = 0; /* Invalidate the filesystem object to be unregistered */ } @@ -3868,7 +3879,19 @@ FRESULT f_open ( fp->fptr = 0; /* Set file pointer top of the file */ #if !FF_FS_READONLY #if !FF_FS_TINY - memset(fp->buf, 0, sizeof fp->buf); /* Clear sector buffer */ +#if FF_USE_DYN_BUFFER + fp->buf = NULL; + if (res == FR_OK) { + fp->buf = ff_memalloc(SS(fs)); + if (!fp->buf) { + res = FR_NOT_ENOUGH_CORE; /* Not enough memory */ + goto fail; + } + memset(fp->buf, 0, SS(fs)); /* Clear sector buffer */ + } +#else + memset(fp->buf, 0, SS(fs)); /* Clear sector buffer */ +#endif #endif if ((mode & FA_SEEKEND) && fp->obj.objsize > 0) { /* Seek to end of file if FA_OPEN_APPEND is specified */ fp->fptr = fp->obj.objsize; /* Offset to seek */ @@ -3901,7 +3924,19 @@ FRESULT f_open ( FREE_NAMBUF(); } - if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */ + if (res != FR_OK) { + fp->obj.fs = 0; /* Invalidate file object on error */ +#if !FF_FS_TINY && FF_USE_DYN_BUFFER + if (fp->buf) { + ff_memfree(fp->buf); + fp->buf = NULL; + } +#endif + } + +#if FF_USE_DYN_BUFFER +fail: +#endif LEAVE_FF(fs, res); } @@ -4235,6 +4270,10 @@ FRESULT f_close ( #else fp->obj.fs = 0; /* Invalidate file object */ #endif +#if !FF_FS_TINY && FF_USE_DYN_BUFFER + ff_memfree(fp->buf); + fp->buf = NULL; +#endif #if FF_FS_REENTRANT unlock_volume(fs, FR_OK); /* Unlock volume */ #endif @@ -5998,7 +6037,11 @@ FRESULT f_mkfs ( } } } - if (sz_vol < 128) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >=128s */ + if (n_fat == 1) { + if (sz_vol < MIN_FAT12_SEC_VOL) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >= MIN_FAT12_SEC_VOLs */ + } else { + if (sz_vol < (MIN_FAT12_SEC_VOL + 1)) LEAVE_MKFS(FR_MKFS_ABORTED); /* Check if volume size is >= (MIN_FAT12_SEC_VOL+1)s */ + } /* Now start to create an FAT volume at b_vol and sz_vol */ @@ -6229,7 +6272,7 @@ FRESULT f_mkfs ( } /* Determine number of clusters and final check of validity of the FAT sub-type */ - if (sz_vol < b_data + pau * 16 - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ + if (sz_vol < b_data + pau * MIN_FAT12_DATA_SEC - b_vol) LEAVE_MKFS(FR_MKFS_ABORTED); /* Too small volume? */ n_clst = ((DWORD)sz_vol - sz_rsv - sz_fat * n_fat - sz_dir) / pau; if (fsty == FS_FAT32) { if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32? */ diff --git a/lib/fatfs/src/ff.h b/lib/fatfs/src/ff.h index 19854705..f4493e03 100644 --- a/lib/fatfs/src/ff.h +++ b/lib/fatfs/src/ff.h @@ -116,7 +116,7 @@ typedef struct { BYTE pd; /* Physical drive number */ BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ } PARTITION; -extern const PARTITION VolToPart[]; /* Volume - Partition mapping table */ +extern PARTITION VolToPart[]; /* Volume - Partition mapping table */ #endif #if FF_STR_VOLUME_ID @@ -170,7 +170,11 @@ typedef struct { LBA_t bitbase; /* Allocation bitmap base sector */ #endif LBA_t winsect; /* Current sector appearing in the win[] */ +#if FF_USE_DYN_BUFFER + BYTE* win; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +#else BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +#endif } FATFS; @@ -215,8 +219,12 @@ typedef struct { DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ #endif #if !FF_FS_TINY +#if FF_USE_DYN_BUFFER + BYTE* buf; /* File private data read/write window */ +#else BYTE buf[FF_MAX_SS]; /* File private data read/write window */ #endif +#endif } FIL; @@ -289,7 +297,7 @@ typedef enum { FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ - FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_NOT_ENOUGH_CORE, /* (17) Buffer could not be allocated */ FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ } FRESULT; diff --git a/lib/fatfs/src/ffconf.h b/lib/fatfs/src/ffconf.h index a5a4ae17..d378ee6d 100644 --- a/lib/fatfs/src/ffconf.h +++ b/lib/fatfs/src/ffconf.h @@ -1,3 +1,6 @@ +#ifndef _FFCONF_DEFINED +#define _FFCONF_DEFINED + #include "sdkconfig.h" /*---------------------------------------------------------------------------/ @@ -40,7 +43,7 @@ /* This option switches fast seek function. (0:Disable or 1:Enable) */ -#define FF_USE_EXPAND 0 +#define FF_USE_EXPAND 1 /* This option switches f_expand function. (0:Disable or 1:Enable) */ @@ -57,11 +60,36 @@ #define FF_USE_FORWARD 1 /* This option switches f_forward() function. (0:Disable or 1:Enable) */ +#if defined(CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV) +#define FF_USE_STRFUNC 1 +#elif defined(CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV) +#define FF_USE_STRFUNC 2 +#else /* CONFIG_FATFS_USE_STRFUNC_NONE */ +#define FF_USE_STRFUNC 0 +#endif -#define FF_USE_STRFUNC 1 +#ifdef CONFIG_FATFS_PRINT_LLI +#define FF_PRINT_LLI 1 +#else #define FF_PRINT_LLI 0 +#endif + +#ifdef CONFIG_FATFS_PRINT_FLOAT +#define FF_PRINT_FLOAT 1 +#else #define FF_PRINT_FLOAT 0 +#endif + +#if defined(CONFIG_FATFS_STRF_ENCODE_ANSI) +#define FF_STRF_ENCODE 0 +#elif defined(CONFIG_FATFS_STRF_ENCODE_UTF16LE) +#define FF_STRF_ENCODE 1 +#elif defined(CONFIG_FATFS_STRF_ENCODE_UTF16BE) +#define FF_STRF_ENCODE 2 +#else /* CONFIG_FATFS_STRF_ENCODE_UTF8 */ #define FF_STRF_ENCODE 3 +#endif + /* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and / f_printf(). / @@ -271,7 +299,7 @@ / These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ -#define FF_FS_NOFSINFO 0 +#define FF_FS_NOFSINFO (CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT << 0 | CONFIG_FATFS_DONT_TRUST_LAST_ALLOC << 1) /* If you need to know correct free space on the FAT32 volume, set bit 0 of this / option, and f_getfree() function at the first time after volume mount will force / a full FAT scan. Bit 1 controls the use of last allocated cluster number. @@ -311,6 +339,13 @@ / The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick. */ +#define FF_USE_DYN_BUFFER CONFIG_FATFS_USE_DYN_BUFFERS +/* The option FF_USE_DYN_BUFFER controls source of size used for buffers in the FS and FIL objects. +/ +/ 0: Disable dynamic buffer size and use static size buffers defined by FF_MAX_SS. +/ 1: Enable dynamic buffer size and use ff_memmalloc() to allocate buffers. +*/ + #include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -330,3 +365,5 @@ void ff_memfree(void*); #define disk_read ff_disk_read #define disk_write ff_disk_write #define disk_ioctl ff_disk_ioctl + +#endif /* _FFCONF_DEFINED */ diff --git a/lib/fatfs/test_apps/.build-test-rules.yml b/lib/fatfs/test_apps/.build-test-rules.yml index d8277804..86cfac0d 100644 --- a/lib/fatfs/test_apps/.build-test-rules.yml +++ b/lib/fatfs/test_apps/.build-test-rules.yml @@ -1,11 +1,47 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps -components/fatfs/test_apps/sdcard: +components/fatfs/test_apps/dyn_buffers: disable_test: - - if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c6", "esp32h2"] - temporary: true - reason: No sdspi runners for these targets + - if: IDF_TARGET != "esp32" + reason: only one target required + + depends_components: + - fatfs + +components/fatfs/test_apps/flash_ro: + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + reason: only one target per arch needed + + depends_components: + - esp_partition + - spi_flash + - fatfs + - vfs + +components/fatfs/test_apps/flash_wl: + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3", "linux"] + reason: only one target per arch needed + depends_components: + - esp_partition + - spi_flash + - fatfs + - vfs + - wear_leveling + +components/fatfs/test_apps/sdcard: disable: - - if: IDF_TARGET == "esp32p4" + - if: IDF_TARGET in ["esp32h21", "esp32h4"] + temporary: true + reason: not supported yet # TODO: [esp32h21] IDF-11593 [ESP32H4] IDF-12372 + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] temporary: true - reason: target esp32p4 is not supported yet # TODO: IDF-7501 + reason: lack of runners + depends_components: + - esp_driver_sdmmc + - esp_driver_spi + - sdmmc + - fatfs + - vfs diff --git a/lib/fatfs/test_apps/README.md b/lib/fatfs/test_apps/README.md index 2140e8c4..78a9dcd9 100644 --- a/lib/fatfs/test_apps/README.md +++ b/lib/fatfs/test_apps/README.md @@ -1,3 +1,6 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | + # fatfs component target tests This directory contains tests for `fatfs` component which are run on chip targets. @@ -9,6 +12,7 @@ Fatfs tests can be executed with different `diskio` backends: `diskio_sdmmc` (SD - [sdcard](sdcard/) — runs fatfs tests with an SD card over SDMMC or SDSPI interface - [flash_wl](flash_wl/) - runs fatfs test in a wear_levelling partition in SPI flash - [flash_ro](flash_ro/) - runs fatfs test in a read-only (no wear levelling) partition in SPI flash +- [dyn_buffers](dyn_buffers/) - check if enabling dynamic buffers in FATFS has an effect These test apps define: - test functions diff --git a/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt b/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt new file mode 100644 index 00000000..4e6f01d2 --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(dyn_buffers) diff --git a/lib/fatfs/test_apps/dyn_buffers/README.md b/lib/fatfs/test_apps/dyn_buffers/README.md new file mode 100644 index 00000000..261aeee2 --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/README.md @@ -0,0 +1,8 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | + +This test app checks if `CONFIG_FATFS_USE_DYN_BUFFERS` has any effect. + +These tests should be possible to run on any ESP development board, not extra hardware is necessary. + +See [../README.md](../README.md) for more information about FATFS test apps. diff --git a/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt b/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt new file mode 100644 index 00000000..c3cd1aa7 --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_fatfs_dyn_buffers.c" + INCLUDE_DIRS "." + REQUIRES wear_levelling fatfs vfs) diff --git a/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c b/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c new file mode 100644 index 00000000..c945d97b --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/main/test_fatfs_dyn_buffers.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include +#include "wear_levelling.h" +#include "esp_partition.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "esp_heap_caps.h" +#include "sdkconfig.h" +#include "ff.h" +#include "esp_debug_helpers.h" + +static const char* TAG = "Test dynamic buffers"; + +static volatile bool g_alloc_count_enable = false; +static volatile int g_buffer_alloc_count = 0; + +static esp_vfs_fat_mount_config_t g_mount_config = { + .format_if_mount_failed = true, + .max_files = 5, +}; + +void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps) +{ + (void) ptr; + (void) caps; + + if (!g_alloc_count_enable) { + return; + } + + // This will work only on SPI flash + // Different flash types might break this check + if (size == FF_MAX_SS) { + g_buffer_alloc_count++; + } +} + +void app_main(void) +{ + esp_err_t err = ESP_OK; + + wl_handle_t wl_handle; + + err = esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &g_mount_config); + + ESP_LOGI(TAG, "Mounting FATFS"); + + g_mount_config.format_if_mount_failed = false, + + g_alloc_count_enable = true; + + err = esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &g_mount_config, &wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "FATFS mount failed with error: %d", err); + return; + } + + ESP_LOGI(TAG, "Mounted"); + + int fd = open("/spiflash/test.txt", O_RDWR|O_CREAT); + if (fd < 0) { + ESP_LOGE(TAG, "Failed opening file"); + } + + close(fd); + + g_alloc_count_enable = false; + + ESP_LOGI(TAG, "Unmounting FATFS"); + + esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle); + + ESP_LOGI(TAG, "Unmounted"); + + ESP_LOGI(TAG, "Allocs called:\n\tBuffer: %d" + , g_buffer_alloc_count); + +#if CONFIG_FATFS_USE_DYN_BUFFERS + + if (g_buffer_alloc_count != 2) { + ESP_LOGE(TAG, "FATFS buffer should have been allocated once for each context (file and fatfs)"); + return; + } +#else + + if (g_buffer_alloc_count != 0) { + ESP_LOGE(TAG, "FATFS buffer should not have been allocated"); + return; + } + +#endif + + ESP_LOGI(TAG, "Done"); +} diff --git a/lib/fatfs/test_apps/dyn_buffers/partitions.csv b/lib/fatfs/test_apps/dyn_buffers/partitions.csv new file mode 100644 index 00000000..d68a9de0 --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +factory, app, factory, 0x10000, 768k, +storage, data, fat, , 528k, +storage2, data, fat, , 528k, +storage1, data, fat, , 32k, diff --git a/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py b/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py new file mode 100644 index 00000000..a60ac87e --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/pytest_fatfs_dyn_buffers.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'dyn_buffers', + 'no_dyn_buffers', + ], +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_fatfs_flash_dyn_buffers(config: str, dut: Dut) -> None: + dut.expect('Mounting FATFS') + dut.expect('Mounted') + dut.expect('Unmounting FATFS') + dut.expect('Unmounted') + dut.expect('Done') diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers new file mode 100644 index 00000000..380dafbf --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.dyn_buffers @@ -0,0 +1 @@ +CONFIG_FATFS_USE_DYN_BUFFERS=y diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers new file mode 100644 index 00000000..bc9b68f3 --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.ci.no_dyn_buffers @@ -0,0 +1 @@ +CONFIG_FATFS_USE_DYN_BUFFERS=n diff --git a/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults new file mode 100644 index 00000000..8b57aacd --- /dev/null +++ b/lib/fatfs/test_apps/dyn_buffers/sdkconfig.defaults @@ -0,0 +1,14 @@ +# General options for additional checks +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +# use custom partition table +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + +# to measure allocations +CONFIG_HEAP_USE_HOOKS=y diff --git a/lib/fatfs/test_apps/flash_ro/README.md b/lib/fatfs/test_apps/flash_ro/README.md index 4d341fbc..90297186 100644 --- a/lib/fatfs/test_apps/flash_ro/README.md +++ b/lib/fatfs/test_apps/flash_ro/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | This test app runs a few FATFS test cases in a read-only FAT partition. diff --git a/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c b/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c index 61786bfe..cca3dc0d 100644 --- a/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c +++ b/lib/fatfs/test_apps/flash_ro/main/test_fatfs_flash_ro.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include #include #include @@ -263,7 +264,7 @@ TEST_CASE("(raw) multiple tasks can use same volume", "[fatfs]") read_test_arg_t args4 = READ_TEST_ARG_INIT(names[3], 0x34343434); const int cpuid_0 = 0; - const int cpuid_1 = portNUM_PROCESSORS - 1; + const int cpuid_1 = CONFIG_FREERTOS_NUMBER_OF_CORES - 1; const int stack_size = 4096; printf("reading files 1.txt 2.txt 3.txt 4.txt \n"); diff --git a/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py b/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py index eda296ef..0339f8b6 100644 --- a/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py +++ b/lib/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py @@ -1,15 +1,11 @@ -# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 - import pytest from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.supported_targets @pytest.mark.generic +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_flash_ro(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('*') - dut.expect_unity_test_output() + dut.run_all_single_board_cases() diff --git a/lib/fatfs/test_apps/flash_wl/README.md b/lib/fatfs/test_apps/flash_wl/README.md index 9112db39..a90777a3 100644 --- a/lib/fatfs/test_apps/flash_wl/README.md +++ b/lib/fatfs/test_apps/flash_wl/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | This test app runs a few FATFS test cases in a wear levelling FAT partition. diff --git a/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt b/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt index 20b600fc..f6501bda 100644 --- a/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt +++ b/lib/fatfs/test_apps/flash_wl/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "test_fatfs_flash_wl.c" +idf_component_register(SRCS "test_fatfs_flash_wl.c" "test_fatfs_small_partition.c" INCLUDE_DIRS "." PRIV_REQUIRES unity spi_flash fatfs vfs test_fatfs_common WHOLE_ARCHIVE) diff --git a/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild b/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild new file mode 100644 index 00000000..c7292513 --- /dev/null +++ b/lib/fatfs/test_apps/flash_wl/main/Kconfig.projbuild @@ -0,0 +1,10 @@ +menu "Test configuration" + config SPI_WL_TEST_ERASE_PARTITION + bool "Erase partition" + default y if IDF_TARGET_LINUX + help + Erase the partition before each format operation. + This will destroy the flash fairly quickly in CI, but is necessary to + ensure that the test is not affected by previous test runs. + Run with caution. +endmenu diff --git a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c index 1af50fe6..fa383803 100644 --- a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.c +++ b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_flash_wl.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 */ @@ -23,6 +23,7 @@ #include "wear_levelling.h" #include "esp_partition.h" #include "esp_memory_utils.h" +#include "vfs_fat_internal.h" void app_main(void) { @@ -32,7 +33,7 @@ void app_main(void) static wl_handle_t s_test_wl_handle; static void test_setup(void) { - esp_vfs_fat_sdmmc_mount_config_t mount_config = { + esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = true, .max_files = 5, }; @@ -45,14 +46,47 @@ static void test_teardown(void) TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle)); } -TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling][timeout=180]") +#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION +static void corrupt_wl_data(void) { + const esp_partition_t* part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + TEST_ASSERT_NOT_NULL(part); + TEST_ESP_OK(esp_partition_erase_range(part, 0, part->size)); +} +#endif + +TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling][timeout=120]") +{ +#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION + corrupt_wl_data(); +#endif TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL)); test_setup(); + vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle); + TEST_ASSERT_NOT_NULL(ctx); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 2); // 2 FATs are created by default + test_teardown(); +} + +TEST_CASE("(WL) can format partition with config", "[fatfs][wear_levelling][timeout=120]") +{ +#ifdef CONFIG_SPI_WL_TEST_ERASE_PARTITION + corrupt_wl_data(); +#endif + esp_vfs_fat_mount_config_t format_config = { + .format_if_mount_failed = true, + .max_files = 5, + .use_one_fat = true, + }; + TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &format_config)); + test_setup(); + vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle); + TEST_ASSERT_NOT_NULL(ctx); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 1); test_teardown(); } -TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_levelling][timeout=180]") +TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_levelling][timeout=120]") { test_setup(); TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL)); @@ -61,9 +95,28 @@ TEST_CASE("(WL) can format when the FAT is mounted already", "[fatfs][wear_level test_teardown(); } -TEST_CASE("(WL) can format specified FAT when more are mounted", "[fatfs][wear_levelling][timeout=180]") +TEST_CASE("(WL) can format when the FAT is mounted already with config", "[fatfs][wear_levelling][timeout=120]") +{ + TEST_ESP_OK(esp_vfs_fat_spiflash_format_rw_wl("/spiflash", NULL)); // To reset the FAT number to 2 + test_setup(); + vfs_fat_spiflash_ctx_t* ctx = get_vfs_fat_spiflash_ctx(s_test_wl_handle); + TEST_ASSERT_NOT_NULL(ctx); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 2); + esp_vfs_fat_mount_config_t format_config = { + .format_if_mount_failed = true, + .max_files = 5, + .use_one_fat = true, + }; + TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", NULL, &format_config)); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 1); + test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str); + test_fatfs_pread_file("/spiflash/hello.txt"); + test_teardown(); +} + +TEST_CASE("(WL) can format specified FAT when more are mounted", "[fatfs][wear_levelling][timeout=120]") { - esp_vfs_fat_sdmmc_mount_config_t mount_config = { + esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = true, .max_files = 5, }; @@ -126,7 +179,7 @@ TEST_CASE("(WL) pwrite() works well", "[fatfs][wear_levelling]") TEST_CASE("(WL) can open maximum number of files", "[fatfs][wear_levelling]") { size_t max_files = FOPEN_MAX - 3; /* account for stdin, stdout, stderr */ - esp_vfs_fat_sdmmc_mount_config_t mount_config = { + esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = true, .max_files = max_files }; @@ -152,9 +205,25 @@ TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]") TEST_CASE("(WL) can truncate", "[fatfs][wear_levelling]") { test_setup(); - test_fatfs_truncate_file("/spiflash/truncate.txt"); + test_fatfs_truncate_file("/spiflash/truncate.txt", true); + test_teardown(); +} + +TEST_CASE("(WL) can ftruncate", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_ftruncate_file("/spiflash/ftrunc.txt", true); + test_teardown(); +} + +#if FF_USE_EXPAND +TEST_CASE("(WL) can esp_vfs_fat_create_contiguous_file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_create_contiguous_file("/spiflash", "/spiflash/expand.txt"); test_teardown(); } +#endif TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]") { @@ -205,6 +274,13 @@ TEST_CASE("(WL) can opendir root directory of FS", "[fatfs][wear_levelling]") test_teardown(); } +TEST_CASE("(WL) readdir, stat work as expected", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_readdir_stat("/spiflash/dir"); + test_teardown(); +} + TEST_CASE("(WL) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][wear_levelling]") { test_setup(); diff --git a/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c new file mode 100644 index 00000000..fd65a4db --- /dev/null +++ b/lib/fatfs/test_apps/flash_wl/main/test_fatfs_small_partition.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "unity.h" +#include "esp_vfs_fat.h" + +static wl_handle_t s_test_wl_handle; +static void test_setup(void) +{ + // With this configuration, for 32k partition size, + // 4 sectors will be used for WL and 4 sectors for FATFS + // (1 FAT, 1 root directory, 1 reserved and 1 data sector) + esp_vfs_fat_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5, + .use_one_fat = true, + }; + + TEST_ESP_OK(esp_vfs_fat_spiflash_format_cfg_rw_wl("/spiflash", "storage1", &mount_config)); + TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", "storage1", &mount_config, &s_test_wl_handle)); +} + +static void test_teardown(void) +{ + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", s_test_wl_handle)); +} + +static void test_write_data_sec(int num_data_sec) +{ + int fd = open("/spiflash/test.txt", O_CREAT | O_WRONLY); + TEST_ASSERT_NOT_EQUAL(-1, fd); + + // Generate data + uint32_t data_size = 4096*num_data_sec; + + char *data = (char*) malloc(data_size); + char *read_data = (char*) malloc(data_size); + + for(uint32_t i = 0; i < (data_size); i += sizeof(i)) + { + *((uint32_t*)(data + i)) = i; + } + ssize_t wr = write(fd, data, data_size); + if (num_data_sec == 1) { + TEST_ASSERT_EQUAL(data_size, wr); + } else { + TEST_ASSERT_NOT_EQUAL(data_size, wr); + } + TEST_ASSERT_EQUAL(0, close(fd)); + + fd = open("/spiflash/test.txt", O_RDONLY); + int r = read(fd, read_data, data_size); + if (num_data_sec == 1) { + TEST_ASSERT_EQUAL(data_size, r); + } else { + TEST_ASSERT_NOT_EQUAL(data_size, r); + } + TEST_ASSERT_EQUAL(0, strcmp(data, read_data)); + TEST_ASSERT_EQUAL(0, close(fd)); +} + +TEST_CASE("(WL) can format small partition and read-write data", "[fatfs][wear_levelling][timeout=120]") +{ + test_setup(); + test_write_data_sec(1); //for 1 data sectors, write and read func should work + test_write_data_sec(2); //for 2 data sectors, write and read func should fail + test_teardown(); +} diff --git a/lib/fatfs/test_apps/flash_wl/partitions.csv b/lib/fatfs/test_apps/flash_wl/partitions.csv index d1dcbae6..d68a9de0 100644 --- a/lib/fatfs/test_apps/flash_wl/partitions.csv +++ b/lib/fatfs/test_apps/flash_wl/partitions.csv @@ -2,3 +2,4 @@ factory, app, factory, 0x10000, 768k, storage, data, fat, , 528k, storage2, data, fat, , 528k, +storage1, data, fat, , 32k, diff --git a/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py b/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py index af8c5db6..a9863635 100644 --- a/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py +++ b/lib/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py @@ -1,11 +1,10 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 - import pytest from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.supported_targets @pytest.mark.generic @pytest.mark.parametrize( 'config', @@ -13,27 +12,23 @@ from pytest_embedded import Dut 'default', 'release', 'fastseek', - ] + 'auto_fsync', + 'dyn_buffers', + ], ) +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_flash_wl_generic(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('*') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(timeout=240) -@pytest.mark.supported_targets +@pytest.mark.generic @pytest.mark.psram @pytest.mark.parametrize( 'config', [ 'psram', - ] + ], ) +@idf_parametrize('target', ['esp32'], indirect=['target']) def test_fatfs_flash_wl_psram(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('*') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(timeout=180) diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync index b74d5124..9ab32baa 100644 --- a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync +++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.auto_fsync @@ -1 +1,3 @@ CONFIG_FATFS_IMMEDIATE_FSYNC=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=2048 diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers new file mode 100644 index 00000000..380dafbf --- /dev/null +++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.dyn_buffers @@ -0,0 +1 @@ +CONFIG_FATFS_USE_DYN_BUFFERS=y diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram deleted file mode 100644 index b3b45db9..00000000 --- a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 -CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y diff --git a/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32 b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32 new file mode 100644 index 00000000..5becfb94 --- /dev/null +++ b/lib/fatfs/test_apps/flash_wl/sdkconfig.ci.psram.esp32 @@ -0,0 +1,4 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y diff --git a/lib/fatfs/test_apps/sdcard/README.md b/lib/fatfs/test_apps/sdcard/README.md index 639f1d38..381b38a9 100644 --- a/lib/fatfs/test_apps/sdcard/README.md +++ b/lib/fatfs/test_apps/sdcard/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | This test app runs a few FATFS test cases in a FAT-formatted SD card. diff --git a/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt b/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt index be2a969a..e4a519cb 100644 --- a/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt +++ b/lib/fatfs/test_apps/sdcard/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register(SRCS "test_fatfs_sdcard_main.c" "test_fatfs_sdspi.c" INCLUDE_DIRS "." - PRIV_REQUIRES unity fatfs vfs sdmmc driver test_fatfs_common + PRIV_REQUIRES unity fatfs vfs sdmmc driver test_fatfs_common esp_timer WHOLE_ARCHIVE) if(CONFIG_SOC_SDMMC_HOST_SUPPORTED) diff --git a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c index 4504a5d6..e6478ad9 100644 --- a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c +++ b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdmmc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,7 @@ #include "unity.h" #include "esp_log.h" #include "esp_random.h" +#include "esp_timer.h" #include "esp_vfs.h" #include "esp_vfs_fat.h" #include "freertos/FreeRTOS.h" @@ -22,6 +23,7 @@ #include "ff.h" #include "test_fatfs_common.h" #include "soc/soc_caps.h" +#include "vfs_fat_internal.h" #if CONFIG_IDF_TARGET_ESP32 #define SDSPI_MISO_PIN 2 @@ -50,6 +52,7 @@ #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3) // No runner #include "driver/sdmmc_host.h" +const char* base_path = "/sdcard"; static void test_setup_sdmmc(sdmmc_card_t **out_card) { @@ -61,7 +64,7 @@ static void test_setup_sdmmc(sdmmc_card_t **out_card) .max_files = 5, .allocation_unit_size = 16 * 1024 }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card)); + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount(base_path, &host, &slot_config, &mount_config, &card)); *out_card = card; } @@ -102,6 +105,35 @@ TEST_CASE("(SD) can format partition", "[fatfs][sdmmc][timeout=180]") test_teardown_sdmmc(card); } +TEST_CASE("(SD) can format partition with config", "[fatfs][sdmmc][timeout=180]") +{ + sdmmc_card_t *card = NULL; + test_setup_sdmmc(&card); + vfs_fat_sd_ctx_t* ctx = get_vfs_fat_get_sd_ctx(card); + TEST_ASSERT_NOT_NULL(ctx); + + esp_vfs_fat_mount_config_t format_config = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 16 * 1024, + .use_one_fat = true, + }; + TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config)); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 1); + + test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); + test_fatfs_read_file(test_filename); + + format_config.use_one_fat = false; + TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config)); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 2); + + test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); + test_fatfs_read_file(test_filename); + + test_teardown_sdmmc(card); +} + TEST_CASE("(SD) can create and write file", "[fatfs][sdmmc]") { sdmmc_card_t *card = NULL; @@ -156,7 +188,7 @@ TEST_CASE("(SD) can truncate", "[fatfs][sdmmc]") { sdmmc_card_t *card = NULL; test_setup_sdmmc(&card); - test_fatfs_truncate_file("/sdcard/truncate.txt"); + test_fatfs_truncate_file("/sdcard/truncate.txt", true); test_teardown_sdmmc(card); } @@ -164,9 +196,19 @@ TEST_CASE("(SD) can ftruncate", "[fatfs][sdmmc]") { sdmmc_card_t *card = NULL; test_setup_sdmmc(&card); - test_fatfs_ftruncate_file("/sdcard/ftrunc.txt"); + test_fatfs_ftruncate_file("/sdcard/ftrunc.txt", true); + test_teardown_sdmmc(card); +} + +#if FF_USE_EXPAND +TEST_CASE("(SD) can esp_vfs_fat_create_contiguous_file", "[fatfs][sdmmc]") +{ + sdmmc_card_t *card = NULL; + test_setup_sdmmc(&card); + test_fatfs_create_contiguous_file("/sdcard", "/sdcard/expand.txt"); test_teardown_sdmmc(card); } +#endif TEST_CASE("(SD) stat returns correct values", "[fatfs][sdmmc]") { @@ -275,6 +317,231 @@ static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool TEST_ESP_OK(esp_vfs_fat_sdcard_unmount("/sdcard", card)); } +TEST_CASE("(SD) mount FAT partitions and readdir to get stat structure", "[fatfs][sdmmc]") +{ + char name_dir_file[64]; + char name_dir_stat[64] = {0}; + const char* dir_prefix = "/sdcard"; + int dir_prefix_len = strlen(dir_prefix); + int file_num = 300; + + /* Mount FATFS in SD can WL at the same time. Create a file on each FS */ + sdmmc_card_t* card = NULL; + test_setup_sdmmc(&card); + TEST_ESP_OK(esp_vfs_fat_sdcard_format("/sdcard", card)); + + //Create multiple files with text on sdcard. Each file size is 14 bytes + //Total files created are file_num (300 in this case) + //So directory size will be 300*14 bytes + for(int i=0;id_name)+1, "%s/%s", dir_prefix, de->d_name); + TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st)); + dir_size += st.st_size; + } + TEST_ASSERT_EQUAL(0, closedir(dir)); + int64_t end = esp_timer_get_time(); + int64_t total_time_readdir = end-start; + printf("Time in us for calculating directory size by calling readdir first and then stat func: %lld \n",total_time_readdir); + printf("Size of the directory %s is %"PRIu32"Kb\n", dir_prefix, (dir_size/1000)); + TEST_ASSERT_EQUAL(file_num*strlen(fatfs_test_hello_str), dir_size); //each file size is 14 bytes + + // Call stat function directly without calling readdir and record the time needed to calculate the directory size + dir_size = 0; + start = esp_timer_get_time(); + for(int i=0;idir); + TEST_ASSERT_NOT_NULL(dir); + while(1) { + de = readdir(dir); + if (!de) { + break; + } + //Intentionally introduced a delay to ensure that the second task is triggered simultaneously. + vTaskDelay(10 / portTICK_PERIOD_MS); + snprintf(name_dir_stat, sizeof(test_task_param->dir)+sizeof(de->d_name), "%s/%s", test_task_param->dir, de->d_name); + TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st)); + if (strcasecmp(de->d_name, test_task_param->filename) == 0) { + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + TEST_ASSERT_EQUAL(strlen(test_task_param->str), st.st_size); + } else { + TEST_FAIL_MESSAGE("unexpected directory entry"); + } + } + + if (test_task_param->sem) { + xSemaphoreGive(test_task_param->sem); + } + vTaskDelete(NULL); +} + +TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time and readdir to get stat structure", "[fatfs][sdmmc]") +{ + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + + const char *dir_prefix[FF_VOLUMES] = {"/sdcard", "/spiflash"}; + const char *dir_filename[FF_VOLUMES] = {"sd.txt", "wl.txt"}; + const char* str[FF_VOLUMES] = {"this is sd\n", "this is spiflash\n"}; + const char* filename_sd = "/sdcard/sd.txt"; + const char* filename_wl = "/spiflash/wl.txt"; + + /* Erase flash before the first use */ + const esp_partition_t *test_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + TEST_ASSERT_NOT_NULL(test_partition); + esp_partition_erase_range(test_partition, 0, test_partition->size); + + /* Mount FATFS in SD can WL at the same time. Create a file on each FS */ + wl_handle_t wl_handle = WL_INVALID_HANDLE; + sdmmc_card_t *card = NULL; + test_setup_sdmmc(&card); + TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &wl_handle)); + unlink(filename_sd); + unlink(filename_wl); + test_fatfs_create_file_with_text(filename_sd, str[0]); + test_fatfs_create_file_with_text(filename_wl, str[1]); + + test_task_param_t test_task_param_sd = { + .dir = dir_prefix[0], + .filename = dir_filename[0], + .str = str[0], + .sem = xSemaphoreCreateBinary(), + }; + + test_task_param_t test_task_param_spiflash = { + .dir = dir_prefix[1], + .filename = dir_filename[1], + .str = str[1], + .sem = xSemaphoreCreateBinary(), + }; + + //Create two tasks with same priority to check file size on two different FAT partitions at the same time + xTaskCreate(test_task, "test_task_1", 8*1024, (void *) &test_task_param_sd, 5, NULL); + xTaskCreate(test_task, "test_task_2", 8*1024, (void *) &test_task_param_spiflash, 5, NULL); + + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_task_param_sd.sem, 1000 / portTICK_PERIOD_MS)); + vSemaphoreDelete(test_task_param_sd.sem); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(test_task_param_spiflash.sem, 1000 / portTICK_PERIOD_MS)); + vSemaphoreDelete(test_task_param_spiflash.sem); + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle)); + test_teardown_sdmmc(card); +} + +TEST_CASE("(SD) read two directories and get stat structure for respective file at the same time", "[fatfs][sdmmc]") +{ + char name_dir_file[64]; + char name_dir1_stat[64] = {0}; + char name_dir2_stat[64] = {0}; + const char* dir1_prefix = "/sdcard/dir1"; + const char* dir2_prefix = "/sdcard/dir2"; + int dir1_prefix_len = strlen(dir1_prefix); + int dir2_prefix_len = strlen(dir2_prefix); + const char* test_str1 = "Hello, World!\n"; + const char* test_str2 = "Hello, ESP Community\n"; + + /* Mount FATFS in SD can WL at the same time. Create a file on each FS */ + sdmmc_card_t* card = NULL; + test_setup_sdmmc(&card); + + TEST_ASSERT_EQUAL(0, mkdir(dir1_prefix, 0755)); + TEST_ASSERT_EQUAL(0, mkdir(dir2_prefix, 0755)); + + snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir1_prefix); + test_fatfs_create_file_with_text(name_dir_file, test_str1); + snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir2_prefix); + test_fatfs_create_file_with_text(name_dir_file, test_str2); + + DIR* dir1 = opendir(dir1_prefix); + TEST_ASSERT_NOT_NULL(dir1); + DIR* dir2 = opendir(dir2_prefix); + TEST_ASSERT_NOT_NULL(dir2); + struct dirent* de1; + struct dirent* de2; + struct stat st1; + struct stat st2; + + while(1) { + de1 = readdir(dir1); + if (!de1) { + break; + } + de2 = readdir(dir2); + if (!de2) { + break; + } + snprintf(name_dir1_stat, dir1_prefix_len+sizeof(de1->d_name)+1, "%s/%s", dir1_prefix, de1->d_name); + snprintf(name_dir2_stat, dir2_prefix_len+sizeof(de2->d_name)+1, "%s/%s", dir2_prefix, de2->d_name); + TEST_ASSERT_EQUAL(0, stat(name_dir1_stat, &st1)); + TEST_ASSERT_EQUAL(0, stat(name_dir2_stat, &st2)); + TEST_ASSERT_EQUAL(strlen(test_str1), st1.st_size); //size of dir1/boo_1.bin is 14 + TEST_ASSERT_EQUAL(strlen(test_str2), st2.st_size); //size of dir2/boo_1.bin is 21 + } + TEST_ASSERT_EQUAL(0, closedir(dir1)); + TEST_ASSERT_EQUAL(0, closedir(dir2)); + + snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir1_prefix); + unlink(name_dir_file); + snprintf(name_dir_file, sizeof(name_dir_file), "%s/boo_1.bin", dir2_prefix); + unlink(name_dir_file); + rmdir(dir1_prefix); + rmdir(dir2_prefix); + + test_teardown_sdmmc(card); +} + TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fatfs][sdmmc]") { esp_vfs_fat_sdmmc_mount_config_t mount_config = { diff --git a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c index c1d567c0..a3247bd2 100644 --- a/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c +++ b/lib/fatfs/test_apps/sdcard/main/test_fatfs_sdspi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +22,7 @@ #include "ff.h" #include "test_fatfs_common.h" #include "soc/soc_caps.h" +#include "vfs_fat_internal.h" #if CONFIG_IDF_TARGET_ESP32 #define SDSPI_MISO_PIN 2 @@ -34,13 +35,13 @@ #define SDSPI_MOSI_PIN 35 #define SDSPI_CLK_PIN 36 #define SDSPI_CS_PIN 34 -#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 #define SDSPI_MISO_PIN 6 #define SDSPI_MOSI_PIN 4 #define SDSPI_CLK_PIN 5 #define SDSPI_CS_PIN 1 #define SPI_DMA_CHAN SPI_DMA_CH_AUTO -#elif CONFIG_IDF_TARGET_ESP32H2 +#elif CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32P4 #define SDSPI_MISO_PIN 0 #define SDSPI_MOSI_PIN 5 #define SDSPI_CLK_PIN 4 @@ -89,7 +90,7 @@ static void test_teardown_sdspi(sdspi_mem_t* mem) HEAP_SIZE_CHECK(mem->heap_size, 0); } -TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sdspi]") +TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sdspi][timeout=120]") { sdspi_mem_t mem; size_t file_size = 1 * 1024 * 1024; @@ -197,3 +198,55 @@ TEST_CASE("(SDSPI) can format card", "[fatfs][sdspi][timeout=180]") TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card)); test_teardown_sdspi(&mem); } + + +TEST_CASE("(SDSPI) can format card with config", "[fatfs][sdspi][timeout=180]") +{ + sdspi_mem_t mem; + test_setup_sdspi(&mem); + + const char path[] = "/sdcard"; + sdmmc_card_t *card; + card = NULL; + sdspi_device_config_t device_cfg = { + .gpio_cs = SDSPI_CS_PIN, + .host_id = SDSPI_HOST_ID, + .gpio_cd = SDSPI_SLOT_NO_CD, + .gpio_wp = SDSPI_SLOT_NO_WP, + .gpio_int = SDSPI_SLOT_NO_INT, + }; + + sdmmc_host_t host = SDSPI_HOST_DEFAULT(); + host.slot = SDSPI_HOST_ID; + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 64 * 1024, + }; + TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card)); + + vfs_fat_sd_ctx_t* ctx = get_vfs_fat_get_sd_ctx(card); + TEST_ASSERT_NOT_NULL(ctx); + + esp_vfs_fat_mount_config_t format_config = { + .format_if_mount_failed = true, + .max_files = 5, + .allocation_unit_size = 64 * 1024, + .use_one_fat = true, + }; + TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config)); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 1); + + test_fatfs_create_file_with_text(s_test_filename, fatfs_test_hello_str); + test_fatfs_read_file(s_test_filename); + + format_config.use_one_fat = false; + TEST_ESP_OK(esp_vfs_fat_sdcard_format_cfg("/sdcard", card, &format_config)); + TEST_ASSERT_TRUE(ctx->fs->n_fats == 2); + + test_fatfs_create_file_with_text(s_test_filename, fatfs_test_hello_str); + test_fatfs_read_file(s_test_filename); + + TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card)); + test_teardown_sdspi(&mem); +} diff --git a/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py b/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py index fe720a02..b0657186 100644 --- a/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py +++ b/lib/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py @@ -1,75 +1,59 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 - import pytest from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.esp32 @pytest.mark.sdcard_sdmode @pytest.mark.parametrize( 'config', [ 'default', 'release', - ] + ], ) +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_sdcard_generic_sdmmc(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('[sdmmc]') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(group='sdmmc', timeout=180) -@pytest.mark.esp32 -@pytest.mark.esp32s2 -@pytest.mark.esp32c3 +@pytest.mark.temp_skip_ci(targets=['esp32'], reason='IDFCI-2058, temporary lack runner') @pytest.mark.sdcard_spimode @pytest.mark.parametrize( 'config', [ 'default', 'release', - ] + ], ) +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_sdcard_generic_sdspi(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('[sdspi]') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(group='sdspi', timeout=180) -@pytest.mark.esp32 @pytest.mark.sdcard_sdmode @pytest.mark.psram @pytest.mark.parametrize( 'config', [ 'psram', - ] + ], ) +@idf_parametrize('target', ['esp32'], indirect=['target']) def test_fatfs_sdcard_psram_sdmmc(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('[sdmmc]') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(group='sdmmc', timeout=180) -@pytest.mark.esp32 +@pytest.mark.temp_skip_ci(targets=['esp32'], reason='IDFCI-2058, temporary lack runner') @pytest.mark.sdcard_spimode @pytest.mark.psram @pytest.mark.parametrize( 'config', [ 'psram', - ] + ], ) +@idf_parametrize('target', ['esp32'], indirect=['target']) def test_fatfs_sdcard_psram_sdspi(dut: Dut) -> None: - dut.expect_exact('Press ENTER to see the list of tests') - dut.write('') - dut.expect_exact('Enter test for running.') - dut.write('[sdspi]') - dut.expect_unity_test_output(timeout=180) + dut.run_all_single_board_cases(group='sdspi', timeout=180) diff --git a/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram b/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram deleted file mode 100644 index b3b45db9..00000000 --- a/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 -CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y diff --git a/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32 b/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32 new file mode 100644 index 00000000..5becfb94 --- /dev/null +++ b/lib/fatfs/test_apps/sdcard/sdkconfig.ci.psram.esp32 @@ -0,0 +1,4 @@ +CONFIG_IDF_TARGET="esp32" +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y diff --git a/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt b/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt index 8f06878d..74ec85e7 100644 --- a/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt +++ b/lib/fatfs/test_apps/test_fatfs_common/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "test_fatfs_common.c" INCLUDE_DIRS "." - PRIV_REQUIRES unity fatfs vfs unity) + PRIV_REQUIRES unity fatfs vfs unity esp_timer) diff --git a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c index 77372fbb..887123cb 100644 --- a/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c +++ b/lib/fatfs/test_apps/test_fatfs_common/test_fatfs_common.c @@ -1,9 +1,10 @@ /* - * 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 "sdkconfig.h" #include #include #include @@ -16,10 +17,12 @@ #include #include "unity.h" #include "esp_vfs.h" +#include "esp_timer.h" #include "esp_vfs_fat.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "test_fatfs_common.h" +#include "ff.h" const char* fatfs_test_hello_str = "Hello, World!\n"; const char* fatfs_test_hello_str_utf = "世界,你好!\n"; @@ -252,7 +255,7 @@ void test_fatfs_lseek(const char* filename) } -void test_fatfs_truncate_file(const char* filename) +void test_fatfs_truncate_file(const char* filename, bool allow_expanding_files) { int read = 0; int truncated_len = 0; @@ -267,14 +270,44 @@ void test_fatfs_truncate_file(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f)); + struct stat st; + size_t size; - // Extending file beyond size is not supported - TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1)); - TEST_ASSERT_EQUAL(errno, EPERM); + stat(filename, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(strlen(input), size); - TEST_ASSERT_EQUAL(-1, truncate(filename, -1)); - TEST_ASSERT_EQUAL(errno, EINVAL); + if (allow_expanding_files) { + size_t trunc_add = 2; + size_t new_size = strlen(input) + trunc_add; + TEST_ASSERT_EQUAL(0, truncate(filename, new_size)); + + stat(filename, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(new_size, size); + + f = fopen(filename, "rb"); + TEST_ASSERT_NOT_NULL(f); + + char expanded_output[sizeof(input) + trunc_add]; + memset(expanded_output, 42, sizeof(expanded_output)); // set to something else than 0 (42) + + read = fread(expanded_output, 1, sizeof(input) + trunc_add, f); + TEST_ASSERT_EQUAL(new_size, read); + + TEST_ASSERT_EQUAL('Z', expanded_output[strlen(input) - 1]); // 'Z' character + TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 3]); // zeroed expanded space + TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 2]); // zeroed expanded space + TEST_ASSERT_EQUAL(42, expanded_output[sizeof(input) + trunc_add - 1]); // 42 set with memset, end of the array + TEST_ASSERT_EQUAL(0, fclose(f)); + } else { + TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1)); + TEST_ASSERT_EQUAL(errno, EPERM); + + TEST_ASSERT_EQUAL(-1, truncate(filename, -1)); + TEST_ASSERT_EQUAL(errno, EINVAL); + } // Truncating should succeed const char truncated_1[] = "ABCDEFGHIJ"; @@ -282,6 +315,10 @@ void test_fatfs_truncate_file(const char* filename) TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len)); + stat(filename, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(strlen(truncated_1), size); + f = fopen(filename, "rb"); TEST_ASSERT_NOT_NULL(f); @@ -293,28 +330,34 @@ void test_fatfs_truncate_file(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f)); + if (allow_expanding_files) { + TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len + 1)); + } else { + // Once truncated, the new file size should be the basis + // whether truncation should succeed or not when `allow_expanding_files == false` + TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1)); + TEST_ASSERT_EQUAL(EPERM, errno); - // Once truncated, the new file size should be the basis - // whether truncation should succeed or not - TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1)); - TEST_ASSERT_EQUAL(EPERM, errno); - - TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input))); - TEST_ASSERT_EQUAL(EPERM, errno); + TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input))); + TEST_ASSERT_EQUAL(EPERM, errno); - TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1)); - TEST_ASSERT_EQUAL(EPERM, errno); + TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1)); + TEST_ASSERT_EQUAL(EPERM, errno); + } TEST_ASSERT_EQUAL(-1, truncate(filename, -1)); TEST_ASSERT_EQUAL(EINVAL, errno); - // Truncating a truncated file should succeed const char truncated_2[] = "ABCDE"; truncated_len = strlen(truncated_2); TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len)); + stat(filename, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(strlen(truncated_2), size); + f = fopen(filename, "rb"); TEST_ASSERT_NOT_NULL(f); @@ -327,29 +370,63 @@ void test_fatfs_truncate_file(const char* filename) TEST_ASSERT_EQUAL(0, fclose(f)); } -void test_fatfs_ftruncate_file(const char* filename) +void test_fatfs_ftruncate_file(const char* filename, bool allow_expanding_files) { int truncated_len = 0; const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char output[sizeof(input)]; - int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC); + int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC); TEST_ASSERT_NOT_EQUAL(-1, fd); TEST_ASSERT_EQUAL(strlen(input), write(fd, input, strlen(input))); - // Extending file beyond size is not supported - TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1)); - TEST_ASSERT_EQUAL(errno, EPERM); - - TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1)); - TEST_ASSERT_EQUAL(errno, EINVAL); + struct stat st; + size_t size; + + fstat(fd, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(strlen(input), size); + + if (allow_expanding_files) { + size_t trunc_add = 2; + size_t new_size = strlen(input) + trunc_add; + TEST_ASSERT_EQUAL(0, ftruncate(fd, new_size)); + + fstat(fd, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(new_size, size); + + char expanded_output[sizeof(input) + trunc_add]; + memset(expanded_output, 42, sizeof(expanded_output)); // set to something else than 0 (42) + + lseek(fd, 0, SEEK_SET); + int r = read(fd, expanded_output, sizeof(input) + trunc_add); + TEST_ASSERT_NOT_EQUAL(-1, r); + TEST_ASSERT_EQUAL(new_size, r); + + TEST_ASSERT_EQUAL('Z', expanded_output[strlen(input) - 1]); // 'Z' character + TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 3]); // zeroed expanded space + TEST_ASSERT_EQUAL('\0', expanded_output[sizeof(input) + trunc_add - 2]); // zeroed expanded space + TEST_ASSERT_EQUAL(42, expanded_output[sizeof(input) + trunc_add - 1]); // 42 set with memset, end of the array + } else { + TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1)); + TEST_ASSERT_EQUAL(errno, EPERM); + + TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1)); + TEST_ASSERT_EQUAL(errno, EINVAL); + } // Truncating should succeed const char truncated_1[] = "ABCDEFGHIJ"; truncated_len = strlen(truncated_1); TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len)); + + fstat(fd, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(truncated_len, size); + TEST_ASSERT_EQUAL(0, close(fd)); // open file for reading and validate the content @@ -367,25 +444,35 @@ void test_fatfs_ftruncate_file(const char* filename) // further truncate the file fd = open(filename, O_WRONLY); TEST_ASSERT_NOT_EQUAL(-1, fd); - // Once truncated, the new file size should be the basis - // whether truncation should succeed or not - TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1)); - TEST_ASSERT_EQUAL(EPERM, errno); - TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input))); - TEST_ASSERT_EQUAL(EPERM, errno); + if (allow_expanding_files) { + TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len + 1)); + } else { + // Once truncated, the new file size should be the basis + // whether truncation should succeed or not when `allow_expanding_files == false` + TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1)); + TEST_ASSERT_EQUAL(EPERM, errno); - TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1)); - TEST_ASSERT_EQUAL(EPERM, errno); + TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input))); + TEST_ASSERT_EQUAL(EPERM, errno); - TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1)); - TEST_ASSERT_EQUAL(EINVAL, errno); + TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1)); + TEST_ASSERT_EQUAL(EPERM, errno); + + TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1)); + TEST_ASSERT_EQUAL(EINVAL, errno); + } // Truncating a truncated file should succeed const char truncated_2[] = "ABCDE"; truncated_len = strlen(truncated_2); TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len)); + + fstat(fd, &st); + size = st.st_size; + TEST_ASSERT_EQUAL(truncated_len, size); + TEST_ASSERT_EQUAL(0, close(fd)); // open file for reading and validate the content @@ -556,7 +643,7 @@ void test_fatfs_utime(const char* filename, const char* root_dir) TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat)); TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime); - //WARNING: it has the Unix Millenium bug (Y2K38) + //WARNING: it has the Unix Millennium bug (Y2K38) // 00:00:00. January 1st, 1970 - FATFS cannot handle years before 1980 desired_tm.tm_mon = 1 - 1; @@ -670,6 +757,65 @@ void test_fatfs_can_opendir(const char* path) unlink(name_dir_file); } +void test_fatfs_readdir_stat(const char* dir_prefix) +{ + char name_dir_file[64]; + char name_dir_stat[64]; + int file_num = 25; + + rmdir(dir_prefix); + TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755)); + + for(int i=0;id_name), "%s/%s", dir_prefix, de->d_name); + TEST_ASSERT_EQUAL(0, stat(name_dir_stat, &st)); + dir_size += st.st_size; + } + TEST_ASSERT_EQUAL(0, closedir(dir)); + int64_t end = esp_timer_get_time(); + int64_t total_time_readdir = end-start; + printf("Time in us for calculating directory size by calling readdir first and then stat func: %lld \n",total_time_readdir); + printf("Size of the directory %s is %"PRIu32"bytes\n", dir_prefix, dir_size); + TEST_ASSERT_EQUAL(file_num*14, dir_size); //each file size is 14 bytes + + // Call stat function directly and record the time needed to calculate the directory size + dir_size = 0; + start = esp_timer_get_time(); + for(int i=0;i -#include - -#include "ff.h" -#include "esp_partition.h" -#include "wear_levelling.h" -#include "diskio_impl.h" -#include "diskio_wl.h" - -#include "catch.hpp" - -extern "C" void _spi_flash_init(const char* chip_size, size_t block_size, size_t sector_size, size_t page_size, const char* partition_bin); - -TEST_CASE("create volume, open file, write and read back data", "[fatfs]") -{ - _spi_flash_init(CONFIG_ESPTOOLPY_FLASHSIZE, CONFIG_WL_SECTOR_SIZE * 16, CONFIG_WL_SECTOR_SIZE, CONFIG_WL_SECTOR_SIZE, "partition_table.bin"); - - FRESULT fr_result; - BYTE pdrv; - FATFS fs; - FIL file; - UINT bw; - - esp_err_t esp_result; - - const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "storage"); - - // Mount wear-levelled partition - wl_handle_t wl_handle; - esp_result = wl_mount(partition, &wl_handle); - REQUIRE(esp_result == ESP_OK); - - // Get a physical drive - esp_result = ff_diskio_get_drive(&pdrv); - REQUIRE(esp_result == ESP_OK); - - // Register physical drive as wear-levelled partition - esp_result = ff_diskio_register_wl_partition(pdrv, wl_handle); - - // Create FAT volume on the entire disk - LBA_t part_list[] = {100, 0, 0, 0}; - BYTE work_area[FF_MAX_SS]; - - fr_result = f_fdisk(pdrv, part_list, work_area); - REQUIRE(fr_result == FR_OK); - const MKFS_PARM opt = {(BYTE)FM_ANY, 0, 0, 0, 0}; - fr_result = f_mkfs("", &opt, work_area, sizeof(work_area)); // Use default volume - - // Mount the volume - fr_result = f_mount(&fs, "", 0); - REQUIRE(fr_result == FR_OK); - - // Open, write and read data - fr_result = f_open(&file, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE); - REQUIRE(fr_result == FR_OK); - - // Generate data - uint32_t data_size = 100000; - - char *data = (char*) malloc(data_size); - char *read = (char*) malloc(data_size); - - for(uint32_t i = 0; i < data_size; i += sizeof(i)) - { - *((uint32_t*)(data + i)) = i; - } - - // Write generated data - fr_result = f_write(&file, data, data_size, &bw); - REQUIRE(fr_result == FR_OK); - REQUIRE(bw == data_size); - - // Move to beginning of file - fr_result = f_lseek(&file, 0); - REQUIRE(fr_result == FR_OK); - - // Read written data - fr_result = f_read(&file, read, data_size, &bw); - REQUIRE(fr_result == FR_OK); - REQUIRE(bw == data_size); - - REQUIRE(memcmp(data, read, data_size) == 0); - - // Close file - fr_result = f_close(&file); - REQUIRE(fr_result == FR_OK); - - // Unmount default volume - fr_result = f_mount(0, "", 0); - REQUIRE(fr_result == FR_OK); - - free(read); - free(data); -} diff --git a/lib/fatfs/test_fatfsgen/test_fatfsgen.py b/lib/fatfs/test_fatfsgen/test_fatfsgen.py index 5816991c..0e73ec29 100755 --- a/lib/fatfs/test_fatfsgen/test_fatfsgen.py +++ b/lib/fatfs/test_fatfsgen/test_fatfsgen.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import os @@ -39,7 +39,17 @@ class FatFSGen(unittest.TestCase): os.remove('fatfs_image.img') def test_empty_file_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_file('TESTFILE') + + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000:0x300c], b'TESTFILE \x20') # check entry name and type + self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + + def test_empty_file_sn_fat12_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_file('TESTFILE') fatfs.write_filesystem(CFG['output_file']) @@ -49,7 +59,18 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat def test_directory_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_directory('TESTFOLD') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') # check entry name and type + self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + self.assertEqual(file_system[0x7000:0x700c], b'. \x10') # reference to itself + self.assertEqual(file_system[0x7020:0x702c], b'.. \x10') # reference to parent + + def test_directory_sn_fat12_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_directory('TESTFOLD') fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) @@ -60,7 +81,15 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6020:0x602c], b'.. \x10') # reference to parent def test_empty_file_with_extension_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_file('TESTF', extension='TXT') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000:0x300c], b'TESTF TXT\x20') # check entry name and type + self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + + def test_empty_file_with_extension_sn_fat12_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_file('TESTF', extension='TXT') fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) @@ -68,7 +97,19 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat def test_write_to_file_with_extension_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_file('WRITEF', extension='TXT') + fatfs.write_content(path_from_root=['WRITEF.TXT'], content=b'testcontent') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000:0x300c], b'WRITEF TXT\x20') # check entry name and type + self.assertEqual(file_system[0x301a:0x3020], b'\x02\x00\x0b\x00\x00\x00') # check size and cluster ref + self.assertEqual(file_system[0x1000:0x1006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + self.assertEqual(file_system[0x7000:0x700f], b'testcontent\x00\x00\x00\x00') # check file content + + def test_write_to_file_with_extension_sn_fat12_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=b'testcontent') fatfs.write_filesystem(CFG['output_file']) @@ -80,7 +121,23 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6000:0x600f], b'testcontent\x00\x00\x00\x00') # check file content def test_write_to_file_in_folder_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_directory('TESTFOLD') + fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD']) + fatfs.write_content(path_from_root=['TESTFOLD', 'WRITEF.TXT'], content=b'testcontent') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') + self.assertEqual( + file_system[0x1000:0x1010], + b'\xf8\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040:0x7050], b'WRITEF TXT\x20\x00\x00\x00\x00') + self.assertEqual(file_system[0x705a:0x7060], b'\x03\x00\x0b\x00\x00\x00') + self.assertEqual(file_system[0x8000:0x800b], b'testcontent') # check file content + + def test_write_to_file_in_folder_sn_fat12_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_directory('TESTFOLD') fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD']) fatfs.write_content(path_from_root=['TESTFOLD', 'WRITEF.TXT'], content=b'testcontent') @@ -96,7 +153,7 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x7000:0x700b], b'testcontent') # check file content def test_cluster_setting_values(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) fatfs.create_file('TESTFIL1') fatfs.create_file('TESTFIL2') fatfs.create_file('TESTFIL3') @@ -112,7 +169,16 @@ class FatFSGen(unittest.TestCase): b'\xf8\xff\xff\xe8\x43\x00\x05\xf0\xff\xff\x0f\x00\x00\x00\x00\x00') def test_full_sector_file(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_file('WRITEF', extension='TXT') + fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7000: 0x8000], CFG['sector_size'] * b'a') + + def test_full_sector_file_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a') fatfs.write_filesystem(CFG['output_file']) @@ -121,7 +187,16 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6000: 0x7000], CFG['sector_size'] * b'a') def test_file_chaining(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_file('WRITEF', extension='TXT') + fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\x03\xf0\xff\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x8000: 0x9000], b'a' + (CFG['sector_size'] - 1) * b'\x00') + + def test_file_chaining_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') fatfs.write_filesystem(CFG['output_file']) @@ -130,7 +205,22 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x7000: 0x8000], b'a' + (CFG['sector_size'] - 1) * b'\x00') def test_full_sector_folder(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_directory('TESTFOLD') + + fill_sector(fatfs) + fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first') + fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x1000: 0x10d0], + b'\xf8\xff\xff\x82\xf0\xff' + 192 * b'\xff' + 10 * b'\x00') + self.assertEqual(file_system[0x86000:0x86005], b'later') + self.assertEqual(file_system[0x87000:0x87010], b'A126 \x00\x00\x00\x00') + self.assertEqual(file_system[0x87020:0x87030], b'A127 \x00\x00\x00\x00') + + def test_full_sector_folder_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_directory('TESTFOLD') fill_sector(fatfs) @@ -145,21 +235,21 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x86020:0x86030], b'A127 \x00\x00\x00\x00') def test_write_to_folder_in_folder_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) fatfs.create_directory('TESTFOLD') fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD']) self.assertRaises(WriteDirectoryException, fatfs.write_content, path_from_root=['TESTFOLD', 'TESTFOLL'], content=b'testcontent') def test_write_non_existing_file_in_folder_sn_fat12(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) fatfs.create_directory('TESTFOLD') self.assertRaises(FileNotFoundError, fatfs.write_content, path_from_root=['TESTFOLD', 'AHOJ'], content=b'testcontent') @staticmethod def create_too_many_files() -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) fatfs.create_directory('TESTFOLD') for i in range(2 * CFG['sector_size'] // CFG['entry_size']): fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD']) @@ -168,7 +258,20 @@ class FatFSGen(unittest.TestCase): self.assertRaises(NoFreeClusterException, self.create_too_many_files) def test_full_two_sectors_folder(self) -> None: - fatfs = fatfsgen.FATFS(size=2 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=2 * 1024 * 1024) + fatfs.create_directory('TESTFOLD') + + for i in range(2 * CFG['sector_size'] // CFG['entry_size']): + fatfs.create_file(f'A{str(i).upper()}', path_from_root=['TESTFOLD']) + fatfs.write_content(path_from_root=['TESTFOLD', 'A253'], content=b'later') + fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x106000:0x106010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x109000:0x109010], b'last\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_full_two_sectors_folder_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=2 * 1024 * 1024) fatfs.create_directory('TESTFOLD') for i in range(2 * CFG['sector_size'] // CFG['entry_size']): @@ -181,26 +284,38 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x108000:0x108010], b'last\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_lower_case_dir_short_names(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertRaises(LowerCaseException, fatfs.create_directory, 'testfold') def test_lower_case_file_short_names(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertRaises(LowerCaseException, fatfs.create_file, 'newfile') def test_too_long_name_dir_short_names(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertRaises(TooLongNameException, fatfs.create_directory, 'TOOLONGNAME') def test_fatfs16_detection(self) -> None: - fatfs = fatfsgen.FATFS(size=16 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=16 * 1024 * 1024) self.assertEqual(fatfs.state.boot_sector_state.fatfs_type, 16) def test_fatfs32_detection(self) -> None: self.assertRaises(NotImplementedError, fatfsgen.FATFS, size=256 * 1024 * 1024) def test_deep_structure(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_directory('TESTFOLD') + fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD']) + fatfs.create_directory('TESTFOLO', path_from_root=['TESTFOLD', 'TESTFOLL']) + fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO']) + fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLL', 'TESTFOLO', 'WRITEF.TXT'], content=b'later') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0xa000:0xa010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_deep_structure_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_directory('TESTFOLD') fatfs.create_directory('TESTFOLL', path_from_root=['TESTFOLD']) fatfs.create_directory('TESTFOLO', path_from_root=['TESTFOLD', 'TESTFOLL']) @@ -212,7 +327,26 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_same_name_deep_structure(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.create_directory('TESTFOLD') + fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD']) + fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD', 'TESTFOLD']) + fatfs.create_file('WRITEF', extension='TXT', path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD']) + fatfs.write_content(path_from_root=['TESTFOLD', 'TESTFOLD', 'TESTFOLD', 'WRITEF.TXT'], content=b'later') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000:0x3010], b'TESTFOLD \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x3010:0x3020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040:0x7050], b'TESTFOLD \x10\x00\x00\x00\x00') + + self.assertEqual(file_system[0x8040:0x8050], b'TESTFOLD \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x9040:0x9050], b'WRITEF TXT \x00\x00\x00\x00') + self.assertEqual(file_system[0xa000:0xa010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_same_name_deep_structure_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.create_directory('TESTFOLD') fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD']) fatfs.create_directory('TESTFOLD', path_from_root=['TESTFOLD', 'TESTFOLD']) @@ -231,7 +365,19 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x9000:0x9010], b'later\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_e2e_deep_folder_into_image(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.generate(CFG['test_dir']) + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00') + self.assertEqual(file_system[0x7070:0x7080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00') + self.assertEqual(file_system[0x8040:0x8050], b'LASTFILE \x00\x00\x00\x00') + self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_e2e_deep_folder_into_image_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.generate(CFG['test_dir']) fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) @@ -243,7 +389,22 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0xa000:0xa010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') def test_e2e_deep_folder_into_image_ext(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + fatfs.generate(CFG['test_dir2']) + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3020:0x3030], b'TESTFILE \x00\x00\x00\x00') + self.assertEqual(file_system[0x7060:0x7070], b'TESTFIL2 \x00\x00\x00\x00') + self.assertEqual(file_system[0x8000:0x8010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x8040:0x8050], b'LASTFILETXT \x00\x00\x00\x00') + self.assertEqual(file_system[0x9000:0x9010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xc000:0xc009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff') + + def test_e2e_deep_folder_into_image_ext_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) fatfs.generate(CFG['test_dir2']) fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) @@ -258,20 +419,29 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0xb000:0xb009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff') def test_empty_fat16(self) -> None: - fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\x00\x00\x00') def test_simple_fat16(self) -> None: - fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) fatfs.create_directory('TESTFOLD') fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) self.assertEqual(file_system[0x1000:0x1007], b'\xf8\xff\xff\xff\xff\xff\x00') def test_chaining_fat16(self) -> None: - fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) + fatfs.create_file('WRITEF', extension='TXT') + fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x1000: 0x100e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xc000: 0xd000], b'a' + (CFG['sector_size'] - 1) * b'\x00') + + def test_chaining_fat16_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=17 * 1024 * 1024) fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') fatfs.write_filesystem(CFG['output_file']) @@ -280,7 +450,22 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x9000: 0xa000], b'a' + (CFG['sector_size'] - 1) * b'\x00') def test_full_sector_folder_fat16(self) -> None: - fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) + fatfs.create_directory('TESTFOLD') + + fill_sector(fatfs) + fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first') + fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x1000: 0x1110], + b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00') + self.assertEqual(file_system[0x8a000:0x8a005], b'later') + self.assertEqual(file_system[0x8b000:0x8b010], b'A126 \x00\x00\x00\x00') + self.assertEqual(file_system[0x8b020:0x8b030], b'A127 \x00\x00\x00\x00') + + def test_full_sector_folder_fat16_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, size=17 * 1024 * 1024) fatfs.create_directory('TESTFOLD') fill_sector(fatfs) @@ -295,14 +480,31 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x88020:0x88030], b'A127 \x00\x00\x00\x00') def test_empty_lfn_short_name(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_file('HELLO', extension='TXT') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3019], b'HELLO TXT \x18\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!') + + def test_empty_lfn_short_name_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_file('HELLO', extension='TXT') fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) self.assertEqual(file_system[0x2000: 0x2019], b'HELLO TXT \x18\x00\x00\x00!\x00!\x00\x00\x00\x00\x00!') def test_lfn_short_name(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_file('HELLO', extension='TXT') + fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'HELLO TXT \x18\x00\x00\x00') + self.assertEqual(file_system[0x3010: 0x3020], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00') + self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00') + + def test_lfn_short_name_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_file('HELLO', extension='TXT') fatfs.write_content(path_from_root=['HELLO.TXT'], content=b'this is a test') fatfs.write_filesystem(CFG['output_file']) @@ -312,7 +514,19 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00') def test_lfn_empty_name(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_file('HELLOHELLOHELLO', extension='TXT') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01TXT \x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + + def test_lfn_empty_name_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_file('HELLOHELLOHELLO', extension='TXT') fatfs.write_filesystem(CFG['output_file']) file_system = read_filesystem(CFG['output_file']) @@ -324,7 +538,21 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x2050: 0x2060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') def test_lfn_plain_name(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_file('HELLOHELLOHELLO', extension='TXT') + fatfs.write_content(path_from_root=['HELLOHELLOHELLO.TXT'], content=b'this is a test') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xadt\x00') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01TXT \x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00') + self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00') + + def test_lfn_plain_name_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_file('HELLOHELLOHELLO', extension='TXT') fatfs.write_content(path_from_root=['HELLOHELLOHELLO.TXT'], content=b'this is a test') fatfs.write_filesystem(CFG['output_file']) @@ -338,7 +566,21 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00') def test_lfn_plain_name_no_ext(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_file('HELLOHELLOHELLO') + fatfs.write_content(path_from_root=['HELLOHELLOHELLO'], content=b'this is a test') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bl\x00o\x00\x00\x00\xff\xff\xff\xff\x0f\x00P\xff\xff') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00Ph\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'HELLOH~\x01 \x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x0e\x00\x00\x00') + self.assertEqual(file_system[0x7000: 0x7010], b'this is a test\x00\x00') + + def test_lfn_plain_name_no_ext_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_file('HELLOHELLOHELLO') fatfs.write_content(path_from_root=['HELLOHELLOHELLO'], content=b'this is a test') fatfs.write_filesystem(CFG['output_file']) @@ -352,11 +594,31 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6000: 0x6010], b'this is a test\x00\x00') def test_lfn_short_entry_exception(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) self.assertRaises(LowerCaseException, fatfs.create_file, 'hello', extension='txt') def test_lfn_nested_empty(self) -> None: - fatfs = fatfsgen.FATFS(long_names_enabled=True) + fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_directory('VERYLONGTESTFOLD') + fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + + self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040: 0x7050], b'HELLO TXT \x18\x00\x00\x00') + self.assertEqual(file_system[0x7050: 0x7060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00') + + def test_lfn_nested_empty_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_directory('VERYLONGTESTFOLD') fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) fatfs.write_filesystem(CFG['output_file']) @@ -376,7 +638,29 @@ class FatFSGen(unittest.TestCase): self.assertEqual(file_system[0x6050: 0x6060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x00\x00\x00\x00') def test_lfn_nested_long_empty(self) -> None: - fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True) + fatfs: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_directory('verylongtestfold') + fatfs.create_file('hellohellohello', extension='txt', path_from_root=['verylongtestfold']) + fatfs.write_filesystem(CFG['output_file']) + + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\n\xff\xff') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\no\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'verylo~\x01 \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + + self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040: 0x7050], b'Bl\x00o\x00.\x00t\x00x\x00\x0f\x00\xcft\x00') + self.assertEqual(file_system[0x7050: 0x7060], + b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + + def test_lfn_nested_long_empty_one_fat(self) -> None: + fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_directory('verylongtestfold') fatfs.create_file('hellohellohello', extension='txt', path_from_root=['verylongtestfold']) fatfs.write_filesystem(CFG['output_file']) @@ -398,7 +682,30 @@ class FatFSGen(unittest.TestCase): b'\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') def test_lfn_nested_text(self) -> None: - fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True) + fatfs: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_directory('VERYLONGTESTFOLD') + fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) + fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLO.TXT'], content=b'this is a test') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + self.assertEqual(file_system[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff') + self.assertEqual(file_system[0x3012: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + + self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7012: 0x7020], b'!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040: 0x7050], b'HELLO TXT \x18\x00\x00\x00') + self.assertEqual(file_system[0x7050: 0x7060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x0e\x00\x00\x00') + + self.assertEqual(file_system[0x8000: 0x8010], b'this is a test\x00\x00') + + def test_lfn_nested_text_one_fat(self) -> None: + fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_directory('VERYLONGTESTFOLD') fatfs.create_file('HELLO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLO.TXT'], content=b'this is a test') @@ -441,7 +748,45 @@ class FatFSGen(unittest.TestCase): self.assertRaises(InconsistentFATAttributes, fatfsgen.FATFS, size=20480000, explicit_fat_type=FAT12) def test_lfn_increasing(self) -> None: - fatfs: fatfsgen.FATFS = fatfsgen.FATFS(long_names_enabled=True) + fatfs: fatfsgen.fatfs = fatfsgen.FATFS(fat_tables_cnt=2, long_names_enabled=True) + fatfs.create_directory('VERYLONGTESTFOLD') + fatfs.create_file('HELLOHELLOHELLOOOOOOO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) + fatfs.create_file('HELLOHELLOHELLOOOOOOB', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) + fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOO.TXT'], + content=b'this is a test A') + fatfs.write_content(path_from_root=['VERYLONGTESTFOLD', 'HELLOHELLOHELLOOOOOOB.TXT'], + content=b'this is a test B') + fatfs.write_filesystem(CFG['output_file']) + file_system = read_filesystem(CFG['output_file']) + + self.assertEqual(file_system[0x3000: 0x3010], b'Bo\x00l\x00d\x00\x00\x00\xff\xff\x0f\x00\xa0\xff\xff') + self.assertEqual(file_system[0x3011: 0x3020], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff') + self.assertEqual(file_system[0x3020: 0x3030], b'\x01v\x00e\x00r\x00y\x00l\x00\x0f\x00\xa0o\x00') + self.assertEqual(file_system[0x3030: 0x3040], b'n\x00g\x00t\x00e\x00s\x00\x00\x00t\x00f\x00') + self.assertEqual(file_system[0x3040: 0x3050], b'VERYLO~\x01 \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x3050: 0x3060], b'!\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + + self.assertEqual(file_system[0x7000: 0x7010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7011: 0x7020], b'\x00!\x00\x00\x00\x00\x00!\x00\x02\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7020: 0x7030], b'.. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x7030: 0x7040], b'!\x00!\x00\x00\x00\x00\x00!\x00\x01\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x7040: 0x7050], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\xado\x00') + self.assertEqual(file_system[0x7050: 0x7060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00') + + self.assertEqual(file_system[0x7050: 0x7060], b'o\x00o\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00') + self.assertEqual(file_system[0x7060: 0x7070], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\xadh\x00') + self.assertEqual(file_system[0x7070: 0x7080], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00') + self.assertEqual(file_system[0x7080: 0x7090], b'HELLOH~\x01TXT \x00\x00\x00\x00') + self.assertEqual(file_system[0x7090: 0x70a0], b'!\x00!\x00\x00\x00\x00\x00!\x00\x03\x00\x10\x00\x00\x00') + self.assertEqual(file_system[0x70a0: 0x70b0], b'Bl\x00o\x00o\x00o\x00o\x00\x0f\x00\x8do\x00') + + self.assertEqual(file_system[0x70b0: 0x70c0], b'o\x00b\x00.\x00t\x00x\x00\x00\x00t\x00\x00\x00') + self.assertEqual(file_system[0x70c0: 0x70d0], b'\x01h\x00e\x00l\x00l\x00o\x00\x0f\x00\x8dh\x00') + self.assertEqual(file_system[0x70d0: 0x70e0], b'e\x00l\x00l\x00o\x00h\x00\x00\x00e\x00l\x00') + self.assertEqual(file_system[0x70e0: 0x70f0], b'HELLOH~\x02TXT \x00\x00\x00\x00') + + def test_lfn_increasing_one_fat(self) -> None: + fatfs: fatfsgen.FATFS = fatfsgen.FATFS(fat_tables_cnt=1, long_names_enabled=True) fatfs.create_directory('VERYLONGTESTFOLD') fatfs.create_file('HELLOHELLOHELLOOOOOOO', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) fatfs.create_file('HELLOHELLOHELLOOOOOOB', extension='TXT', path_from_root=['VERYLONGTESTFOLD']) @@ -484,7 +829,21 @@ class FatFSGen(unittest.TestCase): self.assertRaises(NotInitialized, lambda: BootSector().binary_image) # encapsulate property to callable def test_bs_str(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) + bs = BootSector(fatfs.state.boot_sector_state) + bs.generate_boot_sector() + bs.parse_boot_sector(bs.binary_image) + x = 'FATFS properties:,clusters: 251,data_region_start: 28672,data_sectors: ' \ + '249,entries_root_count: 512,fat_table_start_address: 4096,fat_tables_cnt: 2,' \ + 'fatfs_type: 12,file_sys_type: FAT ,hidden_sectors: 0,media_type: 248,' \ + 'non_data_sectors: 7,num_heads: 255,oem_name: MSDOS5.0,reserved_sectors_cnt: 1,' \ + 'root_dir_sectors_cnt: 4,root_directory_start: 12288,sec_per_track: 63,sector_size: 4096,' \ + 'sectors_count: 256,sectors_per_cluster: 1,sectors_per_fat_cnt: 1,size: 1048576,' \ + 'volume_label: Espressif ,volume_uuid: 1144419653,' + self.assertEqual(x.split(',')[:-2], str(bs).split('\n')[:-2]) # except for volume id + + def test_bs_str_one_fat(self) -> None: + fatfs = fatfsgen.FATFS(fat_tables_cnt=1) bs = BootSector(fatfs.state.boot_sector_state) bs.generate_boot_sector() bs.parse_boot_sector(bs.binary_image) @@ -508,19 +867,19 @@ class FatFSGen(unittest.TestCase): 2) def test_get_cluster_value_from_fat(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertEqual(fatfs.fat.get_cluster_value(1), 0xFFF) def test_is_cluster_last(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertEqual(fatfs.fat.is_cluster_last(2), False) def test_chain_in_fat(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) self.assertEqual(fatfs.fat.get_chained_content(1), b'\x00' * 0x1000) def test_retrieve_file_chaining(self) -> None: - fatfs = fatfsgen.FATFS() + fatfs = fatfsgen.FATFS(fat_tables_cnt=2) fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') fatfs.write_filesystem(CFG['output_file']) diff --git a/lib/fatfs/test_fatfsgen/test_fatfsparse.py b/lib/fatfs/test_fatfsgen/test_fatfsparse.py index 15a7f3d8..43d62eb5 100755 --- a/lib/fatfs/test_fatfsgen/test_fatfsparse.py +++ b/lib/fatfs/test_fatfsgen/test_fatfsparse.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import os @@ -32,13 +32,13 @@ class FatFSGen(unittest.TestCase): @staticmethod def test_gen_parse() -> None: run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'output_data/tst_str' ], stderr=STDOUT) - run(['python', '../fatfsgen.py', 'output_data/tst_str'], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsgen.py', 'output_data/tst_str'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert set(os.listdir('Espressif')) == {'TEST', 'TESTFILE'} with open('Espressif/TESTFILE', 'rb') as in_: @@ -59,7 +59,7 @@ class FatFSGen(unittest.TestCase): fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a') fatfs.write_filesystem('fatfs_image.img') - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) with open('Espressif/WRITEF.TXT', 'rb') as in_: assert in_.read() == 4097 * b'a' @@ -74,7 +74,7 @@ class FatFSGen(unittest.TestCase): fatfs.write_content(path_from_root=['TESTFOLD', 'A255'], content=b'last') fatfs.write_filesystem('fatfs_image.img') - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert set(os.listdir('Espressif')) == {'TESTFOLD'} assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(256)} @@ -88,7 +88,7 @@ class FatFSGen(unittest.TestCase): def test_empty_fat16() -> None: fatfs = fatfsgen.FATFS(size=17 * 1024 * 1024) fatfs.write_filesystem('fatfs_image.img') - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) @staticmethod def test_chaining_fat16() -> None: @@ -96,7 +96,7 @@ class FatFSGen(unittest.TestCase): fatfs.create_file('WRITEF', extension='TXT') fatfs.write_content(path_from_root=['WRITEF.TXT'], content=4096 * b'a' + b'a') fatfs.write_filesystem('fatfs_image.img') - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) with open('Espressif/WRITEF.TXT', 'rb') as in_: assert in_.read() == 4097 * b'a' @@ -109,7 +109,7 @@ class FatFSGen(unittest.TestCase): fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first') fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later') fatfs.write_filesystem('fatfs_image.img') - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert set(os.listdir('Espressif')) == {'TESTFOLD'} assert set(os.listdir('Espressif/TESTFOLD')) == {f'A{str(i).upper()}' for i in range(128)} with open('Espressif/TESTFOLD/A0', 'rb') as in_: @@ -134,20 +134,20 @@ class FatFSGen(unittest.TestCase): } generate_local_folder_structure(struct_, path_='.') run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') shutil.rmtree('Espressif', ignore_errors=True) run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') def test_e2e_deeper(self) -> None: @@ -173,20 +173,20 @@ class FatFSGen(unittest.TestCase): generate_local_folder_structure(struct_, path_='.') run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') shutil.rmtree('Espressif', ignore_errors=True) run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') def test_e2e_deeper_large(self) -> None: @@ -229,20 +229,20 @@ class FatFSGen(unittest.TestCase): } generate_local_folder_structure(struct_, path_='.') run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') shutil.rmtree('Espressif', ignore_errors=True) run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "wl_fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') def test_e2e_very_deep(self) -> None: @@ -287,11 +287,11 @@ class FatFSGen(unittest.TestCase): } generate_local_folder_structure(struct_, path_='.') run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'testf' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') def test_e2e_very_deep_long(self) -> None: @@ -317,11 +317,11 @@ class FatFSGen(unittest.TestCase): } generate_local_folder_structure(struct_, path_='.') run([ - 'python', + sys.executable, f'{os.path.join(os.path.dirname(__file__), "..", "fatfsgen.py")}', 'testf', '--long_name_support' ], stderr=STDOUT) - run(['python', '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) + run([sys.executable, '../fatfsparse.py', 'fatfs_image.img'], stderr=STDOUT) assert compare_folders('testf', 'Espressif') def test_parse_long_name(self) -> None: diff --git a/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py b/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py index 2b3e28c7..3179441d 100755 --- a/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py +++ b/lib/fatfs/test_fatfsgen/test_wl_fatfsgen.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import os @@ -7,7 +7,7 @@ import shutil import sys import unittest -from test_utils import CFG, generate_test_dir_1, generate_test_dir_2 +from test_utils import CFG, fill_sector, generate_test_dir_1, generate_test_dir_2 sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import wl_fatfsgen # noqa E402 # pylint: disable=C0413 @@ -24,7 +24,18 @@ class WLFatFSGen(unittest.TestCase): shutil.rmtree('output_data') def test_empty_file_sn_fat12(self) -> None: - fatfs = wl_fatfsgen.WLFATFS() + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2) + fatfs.plain_fatfs.create_file('TESTFILE') + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = fs_file.read() + + self.assertEqual(file_system[0x4000:0x400c], b'TESTFILE \x20') # check entry name and type + self.assertEqual(file_system[0x3000:0x3006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + + def test_empty_file_sn_fat12_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1) fatfs.plain_fatfs.create_file('TESTFILE') fatfs.init_wl() fatfs.wl_write_filesystem(CFG['output_file']) @@ -35,7 +46,44 @@ class WLFatFSGen(unittest.TestCase): self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat def test_directory_sn_fat12(self) -> None: - fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905) + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, device_id=3750448905) + fatfs.plain_fatfs.create_directory('TESTFOLD') + fatfs.init_wl() + + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = fs_file.read() + + # boot sector + self.assertEqual(file_system[0x1000:0x1010], b'\xeb\xfe\x90MSDOS5.0\x00\x10\x01\x01\x00') + self.assertEqual(file_system[0x1010:0x1020], b'\x02\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00') # two fats b'\x02...' + self.assertEqual(file_system[0x102b:0x1034], b'Espressif') + + self.assertEqual(file_system[0x4000:0x400c], b'TESTFOLD \x10') # check entry name and type + self.assertEqual(file_system[0x2000:0x2006], b'\xf8\xff\xff\xff\x0f\x00') # check fat + self.assertEqual(file_system[0x8000:0x800c], b'. \x10') # reference to itself + self.assertEqual(file_system[0x8020:0x802c], b'.. \x10') # reference to parent + + # check state1 + self.assertEqual(file_system[0xfb000:0xfb00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xfb010:0xfb020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf') + self.assertEqual(file_system[0xfb020:0xfb02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xfb031:0xfb040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i') + + # check state2 + self.assertEqual(file_system[0xfd000:0xfd00f], b'\x00\x00\x00\x00\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xfd010:0xfd020], b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf') + self.assertEqual(file_system[0xfd020:0xfd02f], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xfd031:0xfd040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd4\xa1\x94i') + + # check config + self.assertEqual(file_system[0xff001:0xff010], b'\x00\x00\x00\x00\x00\x10\x00\x00\x10\x00\x00\x00\x10\x00\x00') + self.assertEqual(file_system[0xff010:0xff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00') + self.assertEqual(file_system[0xff020:0xff030], b'\xe0b\xb5O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xff030:0xff03f], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') + + def test_directory_sn_fat12_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, device_id=3750448905) fatfs.plain_fatfs.create_directory('TESTFOLD') fatfs.init_wl() @@ -45,7 +93,7 @@ class WLFatFSGen(unittest.TestCase): # boot sector self.assertEqual(file_system[0x1000:0x1010], b'\xeb\xfe\x90MSDOS5.0\x00\x10\x01\x01\x00') - self.assertEqual(file_system[0x1010:0x1020], b'\x01\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1010:0x1020], b'\x01\x00\x02\xfa\x00\xf8\x01\x00?\x00\xff\x00\x00\x00\x00\x00') # one fat b'\x01...' self.assertEqual(file_system[0x102b:0x1034], b'Espressif') self.assertEqual(file_system[0x3000:0x300c], b'TESTFOLD \x10') # check entry name and type @@ -72,7 +120,36 @@ class WLFatFSGen(unittest.TestCase): self.assertEqual(file_system[0xff030:0xff03f], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') def test_directory_sn_fat122mb(self) -> None: - fatfs = wl_fatfsgen.WLFATFS(device_id=3750448905, size=2 * 1024 * 1024) + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, device_id=3750448905, size=2 * 1024 * 1024) + fatfs.plain_fatfs.create_directory('TESTFOLD') + fatfs.init_wl() + + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = fs_file.read() + + # check state1 + self.assertEqual(file_system[0x1f9000:0x1f900e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1f9010:0x1f9020], + b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf') + self.assertEqual(file_system[0x1f9020:0x1f902e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1f9030:0x1f9040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp') + + # check state2 + self.assertEqual(file_system[0x1fc000:0x1fc00e], b'\x00\x00\x00\x00\xf9\x01\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1fc010:0x1fc020], + b'\x10\x00\x00\x00\x00\x10\x00\x00\x02\x00\x00\x00\tO\x8b\xdf') + self.assertEqual(file_system[0x1fc020:0x1fc02e], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1fc030:0x1fc040], b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00j5\xbdp') + + # check config + self.assertEqual(file_system[0x1ff000:0x1ff00f], b'\x00\x00\x00\x00\x00\x00 \x00\x00\x10\x00\x00\x00\x10\x00') + self.assertEqual(file_system[0x1ff010:0x1ff01f], b'\x10\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x00 \x00\x00') + self.assertEqual(file_system[0x1ff020:0x1ff030], b')\x892j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0x1ff030:0x1ff03e], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') + + def test_directory_sn_fat122mb_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, device_id=3750448905, size=2 * 1024 * 1024) fatfs.plain_fatfs.create_directory('TESTFOLD') fatfs.init_wl() @@ -101,12 +178,29 @@ class WLFatFSGen(unittest.TestCase): self.assertEqual(file_system[0x1ff030:0x1ff03e], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff') def test_write_not_initialized_wlfatfs(self) -> None: - fatfs = wl_fatfsgen.WLFATFS() + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2) fatfs.plain_fatfs.create_directory('TESTFOLD') self.assertRaises(WLNotInitialized, fatfs.wl_write_filesystem, CFG['output_file']) def test_e2e_deep_folder_into_image_ext(self) -> None: - fatfs = wl_fatfsgen.WLFATFS() + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2) + fatfs.plain_fatfs.generate(CFG['test_dir2']) + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x4020:0x4030], b'TESTFILE \x00\x00\x00\x00') + self.assertEqual(file_system[0x8060:0x8070], b'TESTFIL2 \x00\x00\x00\x00') + self.assertEqual(file_system[0x9000:0x9010], b'. \x10\x00\x00\x00\x00') + self.assertEqual(file_system[0x9040:0x9050], b'LASTFILETXT \x00\x00\x00\x00') + self.assertEqual(file_system[0xa000:0xa010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xb000:0xb010], b'thisistest\n\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xc000:0xc010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xd000:0xd009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff') + + def test_e2e_deep_folder_into_image_ext_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1) fatfs.plain_fatfs.generate(CFG['test_dir2']) fatfs.init_wl() fatfs.wl_write_filesystem(CFG['output_file']) @@ -123,7 +217,22 @@ class WLFatFSGen(unittest.TestCase): self.assertEqual(file_system[0xc000:0xc009], b'\xff\xff\xff\xff\xff\xff\xff\xff\xff') def test_e2e_deep_folder_into_image(self) -> None: - fatfs = wl_fatfsgen.WLFATFS() + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2) + fatfs.plain_fatfs.generate(CFG['test_dir']) + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x8060:0x8070], b'TESTFIL2 \x00\x00\x00\x00') + self.assertEqual(file_system[0x8070:0x8080], b'!\x00!\x00\x00\x00\x00\x00!\x00\x05\x00\x0b\x00\x00\x00') + self.assertEqual(file_system[0x9040:0x9050], b'LASTFILE \x00\x00\x00\x00') + self.assertEqual(file_system[0xa000:0xa010], b'deeptest\n\x00\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xb000:0xb010], b'thisistest\n\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xc000:0xc010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + + def test_e2e_deep_folder_into_image_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1) fatfs.plain_fatfs.generate(CFG['test_dir']) fatfs.init_wl() fatfs.wl_write_filesystem(CFG['output_file']) @@ -137,6 +246,66 @@ class WLFatFSGen(unittest.TestCase): self.assertEqual(file_system[0xa000:0xa010], b'thisistest\n\x00\x00\x00\x00\x00') self.assertEqual(file_system[0xb000:0xb010], b'ahoj\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + def test_chaining_wl_fat16(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) + fatfs.plain_fatfs.create_file('WRITEF', extension='TXT') + fatfs.plain_fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x2000: 0x200e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xd000: 0xe000], b'a' + (CFG['sector_size'] - 1) * b'\x00') + + def test_chaining_wl_fat16_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, size=17 * 1024 * 1024) + fatfs.plain_fatfs.create_file('WRITEF', extension='TXT') + fatfs.plain_fatfs.write_content(path_from_root=['WRITEF.TXT'], content=CFG['sector_size'] * b'a' + b'a') + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x2000: 0x200e], b'\xf8\xff\xff\xff\x03\x00\xff\xff\x00\x00\x00\x00\x00\x00') + self.assertEqual(file_system[0xa000: 0xb000], b'a' + (CFG['sector_size'] - 1) * b'\x00') + + def test_full_sector_folder_wl_fat16(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=2, size=17 * 1024 * 1024) + fatfs.plain_fatfs.create_directory('TESTFOLD') + + fill_sector(fatfs.plain_fatfs) + fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first') + fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later') + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x2000: 0x2110], + b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00') + self.assertEqual(file_system[0x8b000:0x8b005], b'later') + self.assertEqual(file_system[0x8c000:0x8c010], b'A126 \x00\x00\x00\x00') + self.assertEqual(file_system[0x8c020:0x8c030], b'A127 \x00\x00\x00\x00') + + def test_full_sector_folder_wl_fat16_one_fat(self) -> None: + fatfs = wl_fatfsgen.WLFATFS(fat_tables_cnt=1, size=17 * 1024 * 1024) + fatfs.plain_fatfs.create_directory('TESTFOLD') + + fill_sector(fatfs.plain_fatfs) + fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A0'], content=b'first') + fatfs.plain_fatfs.write_content(path_from_root=['TESTFOLD', 'A126'], content=b'later') + fatfs.init_wl() + fatfs.wl_write_filesystem(CFG['output_file']) + with open(CFG['output_file'], 'rb') as fs_file: + file_system = bytearray(fs_file.read()) + + self.assertEqual(file_system[0x2000: 0x2110], + b'\xf8\xff\xff\xff\x82\x00' + 258 * b'\xff' + 8 * b'\x00') + self.assertEqual(file_system[0x88000:0x88005], b'later') + self.assertEqual(file_system[0x89000:0x89010], b'A126 \x00\x00\x00\x00') + self.assertEqual(file_system[0x89020:0x89030], b'A127 \x00\x00\x00\x00') + if __name__ == '__main__': unittest.main() 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 #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" @@ -17,10 +16,20 @@ 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; @@ -207,6 +231,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 * @@ -267,6 +310,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 * @@ -337,7 +401,45 @@ 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 */ 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 #include +#include #include #include #include #include #include +#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; ibase_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 #include +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; } diff --git a/lib/fatfs/wl_fatfsgen.py b/lib/fatfs/wl_fatfsgen.py index 4a685434..13ef5168 100755 --- a/lib/fatfs/wl_fatfsgen.py +++ b/lib/fatfs/wl_fatfsgen.py @@ -1,11 +1,18 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 +from typing import Optional -from construct import Const, Int32ul, Struct +from construct import Const +from construct import Int32ul +from construct import Struct from fatfs_utils.exceptions import WLNotInitialized -from fatfs_utils.utils import (FULL_BYTE, UINT32_MAX, FATDefaults, crc32, generate_4bytes_random, - get_args_for_partition_generator) +from fatfs_utils.utils import crc32 +from fatfs_utils.utils import FATDefaults +from fatfs_utils.utils import FULL_BYTE +from fatfs_utils.utils import generate_4bytes_random +from fatfs_utils.utils import get_args_for_partition_generator +from fatfs_utils.utils import UINT32_MAX from fatfsgen import FATFS @@ -53,6 +60,7 @@ class WLFATFS: WL_STATE_HEADER_SIZE = 64 WL_STATE_COPY_COUNT = 2 # always 2 copies for power failure safety WL_SECTOR_SIZE = 0x1000 + WL_SAFE_MODE_DUMP_SECTORS = 2 WL_STATE_T_DATA = Struct( 'pos' / Int32ul, @@ -97,7 +105,8 @@ class WLFATFS: temp_buff_size: int = FATDefaults.TEMP_BUFFER_SIZE, device_id: int = None, root_entry_count: int = FATDefaults.ROOT_ENTRIES_COUNT, - media_type: int = FATDefaults.MEDIA_TYPE) -> None: + media_type: int = FATDefaults.MEDIA_TYPE, + wl_mode: Optional[str] = None) -> None: self._initialized = False self._version = version self._temp_buff_size = temp_buff_size @@ -105,6 +114,7 @@ class WLFATFS: self.partition_size = size self.total_sectors = self.partition_size // FATDefaults.WL_SECTOR_SIZE self.wl_state_size = WLFATFS.WL_STATE_HEADER_SIZE + WLFATFS.WL_STATE_RECORD_SIZE * self.total_sectors + self.wl_mode = wl_mode # determine the number of required sectors (roundup to sector size) self.wl_state_sectors = (self.wl_state_size + FATDefaults.WL_SECTOR_SIZE - 1) // FATDefaults.WL_SECTOR_SIZE @@ -114,6 +124,9 @@ class WLFATFS: wl_sectors = (WLFATFS.WL_DUMMY_SECTORS_COUNT + WLFATFS.WL_CFG_SECTORS_COUNT + self.wl_state_sectors * WLFATFS.WL_STATE_COPY_COUNT) + if self.wl_mode is not None and self.wl_mode == 'safe': + wl_sectors += WLFATFS.WL_SAFE_MODE_DUMP_SECTORS + self.plain_fat_sectors = self.total_sectors - wl_sectors self.plain_fatfs = FATFS( explicit_fat_type=explicit_fat_type, @@ -202,13 +215,15 @@ class WLFATFS: if __name__ == '__main__': desc = 'Create a FAT filesystem with support for wear levelling and populate it with directory content' args = get_args_for_partition_generator(desc, wl=True) - wl_fatfs = WLFATFS(sectors_per_cluster=args.sectors_per_cluster, - size=args.partition_size, + wl_fatfs = WLFATFS(size=args.partition_size, sector_size=args.sector_size, - root_entry_count=args.root_entry_count, + fat_tables_cnt=args.fat_count, + sectors_per_cluster=args.sectors_per_cluster, explicit_fat_type=args.fat_type, long_names_enabled=args.long_name_support, - use_default_datetime=args.use_default_datetime) + use_default_datetime=args.use_default_datetime, + root_entry_count=args.root_entry_count, + wl_mode=args.wl_mode) wl_fatfs.plain_fatfs.generate(args.input_directory) wl_fatfs.init_wl() -- cgit v1.2.3