From 22b46580f8a1ed63ae04574520882851611e062f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Wed, 13 Apr 2022 14:11:58 +0200 Subject: [PATCH] fatfs: add esp_vfs_fat_info() + unit tests --- components/fatfs/test/test_fatfs_common.c | 27 +++ components/fatfs/test/test_fatfs_common.h | 2 + components/fatfs/test/test_fatfs_sdmmc.c | 172 +++++++++++++------- components/fatfs/test/test_fatfs_spiflash.c | 7 + components/fatfs/vfs/esp_vfs_fat.h | 13 ++ components/fatfs/vfs/vfs_fat.c | 32 ++++ 6 files changed, 195 insertions(+), 58 deletions(-) diff --git a/components/fatfs/test/test_fatfs_common.c b/components/fatfs/test/test_fatfs_common.c index 6b44bf03ce..4033d00541 100644 --- a/components/fatfs/test/test_fatfs_common.c +++ b/components/fatfs/test/test_fatfs_common.c @@ -892,3 +892,30 @@ void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_ (is_write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3, file_size / (1024.0f * 1024.0f * t_s)); } + +void test_fatfs_info(const char* base_path, const char* filepath) +{ + // Empty FS + uint64_t total_bytes = 0; + uint64_t free_bytes = 0; + TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes)); + ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes=%llu", total_bytes, free_bytes); + TEST_ASSERT_NOT_EQUAL(0, total_bytes); + + // FS with a file + FILE* f = fopen(filepath, "wb"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_TRUE(fputs(fatfs_test_hello_str, f) != EOF); + TEST_ASSERT_EQUAL(0, fclose(f)); + + uint64_t free_bytes_new = 0; + TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes_new)); + ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes_new=%llu", total_bytes, free_bytes_new); + TEST_ASSERT_NOT_EQUAL(free_bytes, free_bytes_new); + + // File removed + TEST_ASSERT_EQUAL(0, remove(filepath)); + TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_fat_info(base_path, &total_bytes, &free_bytes_new)); + ESP_LOGD("fatfs info", "total_bytes=%llu, free_bytes_after_delete=%llu", total_bytes, free_bytes_new); + TEST_ASSERT_EQUAL(free_bytes, free_bytes_new); +} diff --git a/components/fatfs/test/test_fatfs_common.h b/components/fatfs/test/test_fatfs_common.h index b66078b1b2..e1e612dbc7 100644 --- a/components/fatfs/test/test_fatfs_common.h +++ b/components/fatfs/test/test_fatfs_common.h @@ -64,3 +64,5 @@ void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix); void test_fatfs_opendir_readdir_rewinddir_utf_8(const char* dir_prefix); void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write); + +void test_fatfs_info(const char* base_path, const char* filepath); diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c index bd8356ea7c..a9504d0ace 100644 --- a/components/fatfs/test/test_fatfs_sdmmc.c +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -53,7 +53,7 @@ #include "driver/sdmmc_host.h" -static void test_setup(void) +static void test_setup_sdmmc(void) { sdmmc_host_t host = SDMMC_HOST_DEFAULT(); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); @@ -65,7 +65,7 @@ static void test_setup(void) TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); } -static void test_teardown(void) +static void test_teardown_sdmmc(void) { TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); } @@ -94,116 +94,116 @@ TEST_CASE("Mount fails cleanly without card inserted", "[fatfs][sd][ignore]") TEST_CASE("(SD) can create and write file", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can read file", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); test_fatfs_read_file(test_filename); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can read file with pread()", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); test_fatfs_pread_file(test_filename); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) pwrite() works well", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_pwrite_file(test_filename); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) overwrite and append file", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_overwrite_append(test_filename); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can lseek", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_lseek("/sdcard/seek.txt"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can truncate", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_truncate_file("/sdcard/truncate.txt"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can ftruncate", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_ftruncate_file("/sdcard/ftrunc.txt"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) stat returns correct values", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_stat("/sdcard/stat.txt", "/sdcard"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) utime sets modification time", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_utime("/sdcard/utime.txt", "/sdcard"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) unlink removes a file", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_unlink("/sdcard/unlink.txt"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) link copies a file, rename moves a file", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_link_rename("/sdcard/link"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can create and remove directories", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_mkdir_rmdir("/sdcard/dir"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) can opendir root directory of FS", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_can_opendir("/sdcard"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_opendir_readdir_rewinddir("/sdcard/dir"); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) multiple tasks can use same volume", "[fatfs][test_env=UT_T1_SDMODE][timeout=60]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_concurrent("/sdcard/f"); - test_teardown(); + test_teardown_sdmmc(); } static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool write); @@ -267,17 +267,17 @@ TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fat /* Mount FATFS in SD can WL at the same time. Create a file on each FS */ wl_handle_t wl_handle = WL_INVALID_HANDLE; - test_setup(); + test_setup_sdmmc(); 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_sd); test_fatfs_create_file_with_text(filename_wl, str_wl); TEST_ESP_OK(esp_vfs_fat_spiflash_unmount_rw_wl("/spiflash", wl_handle)); - test_teardown(); + test_teardown_sdmmc(); /* Check that the file "sd.txt" was created on FS in SD, and has the right data */ - test_setup(); + test_setup_sdmmc(); TEST_ASSERT_NULL(fopen(filename_wl, "r")); FILE* f = fopen(filename_sd, "r"); TEST_ASSERT_NOT_NULL(f); @@ -285,7 +285,7 @@ TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fat TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf) - 1, f)); TEST_ASSERT_EQUAL(0, strcmp(buf, str_sd)); fclose(f); - test_teardown(); + test_teardown_sdmmc(); /* Check that the file "wl.txt" was created on FS in WL, and has the right data */ TEST_ESP_OK(esp_vfs_fat_spiflash_mount_rw_wl("/spiflash", NULL, &mount_config, &wl_handle)); @@ -309,37 +309,48 @@ static const char* test_filename_utf_8 = "/sdcard/测试文件.txt"; TEST_CASE("(SD) can read file using UTF-8 encoded strings", "[fatfs][sd][test_env=UT_T1_SDMODE]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_create_file_with_text(test_filename_utf_8, fatfs_test_hello_str_utf); test_fatfs_read_file_utf_8(test_filename_utf_8); - test_teardown(); + test_teardown_sdmmc(); } TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected using UTF-8 encoded strings", "[fatfs][ignore]") { - test_setup(); + test_setup_sdmmc(); test_fatfs_opendir_readdir_rewinddir_utf_8("/sdcard/目录"); - test_teardown(); + test_teardown_sdmmc(); } #endif // CONFIG_FATFS_API_ENCODING_UTF_8 && CONFIG_FATFS_CODEPAGE == 936 +TEST_CASE("(SD) can get partition info", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]") +{ + test_setup_sdmmc(); + test_fatfs_info("/sdcard", "/sdcard/test.txt"); + test_teardown_sdmmc(); +} + #endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3) #endif //SDMMC HOST SUPPORTED #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3, ESP32C2) //no runners + +typedef struct sdspi_mem { + size_t heap_size; + uint32_t* buf; +} sdspi_mem_t; + static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write); -TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sd][test_env=UT_T1_SPIMODE][timeout=60]") +static void test_setup_sdspi(sdspi_mem_t* mem) { - size_t heap_size; - HEAP_SIZE_CAPTURE(heap_size); + HEAP_SIZE_CAPTURE(mem->heap_size); const size_t buf_size = 16 * 1024; - uint32_t* buf = (uint32_t*) calloc(1, buf_size); - esp_fill_random(buf, buf_size); - const size_t file_size = 1 * 1024 * 1024; + mem->buf = (uint32_t*) calloc(1, buf_size); + esp_fill_random(mem->buf, buf_size); spi_bus_config_t bus_cfg = { .mosi_io_num = SDSPI_MOSI_PIN, @@ -351,19 +362,31 @@ TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sd][test_env=UT_T1_SPIMODE][ }; esp_err_t err = spi_bus_initialize(SDSPI_HOST_ID, &bus_cfg, SPI_DMA_CHAN); TEST_ESP_OK(err); +} - sdspi_speed_test(buf, 4 * 1024, file_size, true); - sdspi_speed_test(buf, 8 * 1024, file_size, true); - sdspi_speed_test(buf, 16 * 1024, file_size, true); - - sdspi_speed_test(buf, 4 * 1024, file_size, false); - sdspi_speed_test(buf, 8 * 1024, file_size, false); - sdspi_speed_test(buf, 16 * 1024, file_size, false); - - free(buf); +static void test_teardown_sdspi(sdspi_mem_t* mem) +{ + free(mem->buf); spi_bus_free(SDSPI_HOST_ID); + HEAP_SIZE_CHECK(mem->heap_size, 0); +} - HEAP_SIZE_CHECK(heap_size, 0); +TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sd][test_env=UT_T1_SPIMODE][timeout=60]") +{ + sdspi_mem_t mem; + size_t file_size = 1 * 1024 * 1024; + + test_setup_sdspi(&mem); + + sdspi_speed_test(mem.buf, 4 * 1024, file_size, true); + sdspi_speed_test(mem.buf, 8 * 1024, file_size, true); + sdspi_speed_test(mem.buf, 16 * 1024, file_size, true); + + sdspi_speed_test(mem.buf, 4 * 1024, file_size, false); + sdspi_speed_test(mem.buf, 8 * 1024, file_size, false); + sdspi_speed_test(mem.buf, 16 * 1024, file_size, false); + + test_teardown_sdspi(&mem); } static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write) @@ -393,4 +416,37 @@ static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card)); } +TEST_CASE("(SDSPI) can get partition info", "[fatfs][sd][test_env=UT_T1_SPIMODE][timeout=60]") +{ + 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)); + + test_fatfs_info("/sdcard", "/sdcard/test.txt"); + + TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card)); + + test_teardown_sdspi(&mem); +} + #endif //TEMPORARY_DISABLED_FOR_TARGETS(ESP32S3) diff --git a/components/fatfs/test/test_fatfs_spiflash.c b/components/fatfs/test/test_fatfs_spiflash.c index bed03a19f0..e34007b9eb 100644 --- a/components/fatfs/test/test_fatfs_spiflash.c +++ b/components/fatfs/test/test_fatfs_spiflash.c @@ -194,6 +194,13 @@ TEST_CASE("(WL) write/read speed test", "[fatfs][wear_levelling][timeout=60]") test_teardown(); } +TEST_CASE("(WL) can get partition info", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_info("/spiflash", "/spiflash/test.txt"); + test_teardown(); +} + /* * In FatFs menuconfig, set CONFIG_FATFS_API_ENCODING to UTF-8 and set the * Codepage to CP936 (Simplified Chinese) in order to run the following tests. diff --git a/components/fatfs/vfs/esp_vfs_fat.h b/components/fatfs/vfs/esp_vfs_fat.h index 60b985d5c4..4925e758fc 100644 --- a/components/fatfs/vfs/esp_vfs_fat.h +++ b/components/fatfs/vfs/esp_vfs_fat.h @@ -294,6 +294,19 @@ esp_err_t esp_vfs_fat_rawflash_mount(const char* base_path, esp_err_t esp_vfs_fat_rawflash_unmount(const char* base_path, const char* partition_label) __attribute__((deprecated("esp_vfs_fat_rawflash_unmount is deprecated, please use esp_vfs_fat_spiflash_unmount_ro instead"))); +/** + * @brief Get information for FATFS partition + * + * @param base_path Path where partition should be registered (e.g. "/spiflash") + * @param[out] out_total_bytes Size of the file system + * @param[out] out_free_bytes Current used bytes in the file system + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if partition not found + * - ESP_FAIL if another FRESULT error (saved in errno) + */ +esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes); + #ifdef __cplusplus } #endif diff --git a/components/fatfs/vfs/vfs_fat.c b/components/fatfs/vfs/vfs_fat.c index d87d3ccff2..680bab9879 100644 --- a/components/fatfs/vfs/vfs_fat.c +++ b/components/fatfs/vfs/vfs_fat.c @@ -84,6 +84,7 @@ static int vfs_fat_truncate(void* ctx, const char *path, off_t length); static int vfs_fat_ftruncate(void* ctx, int fd, off_t length); static int vfs_fat_utime(void* ctx, const char *path, const struct utimbuf *times); #endif // CONFIG_VFS_SUPPORT_DIR +static int fresult_to_errno(FRESULT fr); static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL }; //backwards-compatibility with esp_vfs_fat_unregister() @@ -203,6 +204,37 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path) return ESP_OK; } +esp_err_t esp_vfs_fat_info(const char* base_path, + uint64_t* out_total_bytes, + uint64_t* out_free_bytes) +{ + size_t ctx = find_context_index_by_path(base_path); + if (ctx == FF_VOLUMES) { + return ESP_ERR_INVALID_STATE; + } + char* path = s_fat_ctxs[ctx]->fat_drive; + + FATFS* fs; + DWORD free_clusters; + int res = f_getfree(path, &free_clusters, &fs); + if (res != FR_OK) { + ESP_LOGE(TAG, "Failed to get number of free clusters (%d)", res); + errno = fresult_to_errno(res); + return ESP_FAIL; + } + uint64_t total_sectors = (fs->n_fatent - 2) * fs->csize; + uint64_t free_sectors = free_clusters * fs->csize; + + // Assuming the total size is < 4GiB, should be true for SPI Flash + if (out_total_bytes != NULL) { + *out_total_bytes = total_sectors * fs->ssize; + } + if (out_free_bytes != NULL) { + *out_free_bytes = free_sectors * fs->ssize; + } + return ESP_OK; +} + static int get_next_fd(vfs_fat_ctx_t* fat_ctx) { for (size_t i = 0; i < fat_ctx->max_files; ++i) {