diff --git a/components/fatfs/src/diskio.c b/components/fatfs/src/diskio.c index c61702f51f..7ccc1379f1 100644 --- a/components/fatfs/src/diskio.c +++ b/components/fatfs/src/diskio.c @@ -9,23 +9,20 @@ /*-----------------------------------------------------------------------*/ #include +#include +#include #include "diskio.h" /* FatFs lower layer API */ #include "ffconf.h" #include "ff.h" -#include "sdmmc_cmd.h" -#include "esp_log.h" -#include -#include -static const char* TAG = "ff_diskio"; -static ff_diskio_impl_t * s_impls[_VOLUMES]; -static sdmmc_card_t* s_cards[_VOLUMES] = { NULL }; -static bool s_impls_initialized = false; +static ff_diskio_impl_t * s_impls[_VOLUMES] = { NULL }; +#if _MULTI_PARTITION /* Multiple partition configuration */ PARTITION VolToPart[] = { - {0, 1}, /* Logical drive 0 ==> Physical drive 0, 1st partition */ + {0, 0}, /* Logical drive 0 ==> Physical drive 0, auto detection */ {1, 0} /* Logical drive 1 ==> Physical drive 1, auto detection */ }; +#endif esp_err_t ff_diskio_get_drive(BYTE* out_pdrv) { @@ -43,11 +40,6 @@ void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl) { assert(pdrv < _VOLUMES); - if (!s_impls_initialized) { - s_impls_initialized = true; - memset(s_impls, 0, _VOLUMES * sizeof(ff_diskio_impl_t*)); - } - if (s_impls[pdrv]) { ff_diskio_impl_t* im = s_impls[pdrv]; s_impls[pdrv] = NULL; @@ -97,70 +89,3 @@ DWORD get_fattime(void) | (WORD)(tmr->tm_min << 5) | (WORD)(tmr->tm_sec >> 1); } - -DSTATUS ff_sdmmc_initialize (BYTE pdrv) -{ - return 0; -} - -DSTATUS ff_sdmmc_status (BYTE pdrv) -{ - return 0; -} - -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 (err != ESP_OK) { - ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); - return RES_ERROR; - } - return RES_OK; -} - -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 (err != ESP_OK) { - ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); - return RES_ERROR; - } - return RES_OK; -} - -DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff) -{ - sdmmc_card_t* card = s_cards[pdrv]; - assert(card); - switch(cmd) { - case CTRL_SYNC: - return RES_OK; - case GET_SECTOR_COUNT: - *((uint32_t*) buff) = card->csd.capacity; - return RES_OK; - case GET_SECTOR_SIZE: - *((uint32_t*) buff) = card->csd.sector_size; - return RES_OK; - case GET_BLOCK_SIZE: - return RES_ERROR; - } - return RES_ERROR; -} - -void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card) -{ - static const ff_diskio_impl_t sdmmc_impl = { - .init = &ff_sdmmc_initialize, - .status = &ff_sdmmc_status, - .read = &ff_sdmmc_read, - .write = &ff_sdmmc_write, - .ioctl = &ff_sdmmc_ioctl - }; - s_cards[pdrv] = card; - ff_diskio_register(pdrv, &sdmmc_impl); -} - diff --git a/components/fatfs/src/diskio_sdmmc.c b/components/fatfs/src/diskio_sdmmc.c new file mode 100644 index 0000000000..51f0587b0a --- /dev/null +++ b/components/fatfs/src/diskio_sdmmc.c @@ -0,0 +1,90 @@ +// Copyright 2015-2017 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. + +#include "diskio.h" +#include "ffconf.h" +#include "ff.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" + +static sdmmc_card_t* s_cards[_VOLUMES] = { NULL }; + +static const char* TAG = "diskio_sdmmc"; + +DSTATUS ff_sdmmc_initialize (BYTE pdrv) +{ + return 0; +} + +DSTATUS ff_sdmmc_status (BYTE pdrv) +{ + return 0; +} + +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 (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err); + return RES_ERROR; + } + return RES_OK; +} + +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 (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err); + return RES_ERROR; + } + return RES_OK; +} + +DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff) +{ + sdmmc_card_t* card = s_cards[pdrv]; + assert(card); + switch(cmd) { + case CTRL_SYNC: + return RES_OK; + case GET_SECTOR_COUNT: + *((uint32_t*) buff) = card->csd.capacity; + return RES_OK; + case GET_SECTOR_SIZE: + *((uint32_t*) buff) = card->csd.sector_size; + return RES_OK; + case GET_BLOCK_SIZE: + return RES_ERROR; + } + return RES_ERROR; +} + +void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card) +{ + static const ff_diskio_impl_t sdmmc_impl = { + .init = &ff_sdmmc_initialize, + .status = &ff_sdmmc_status, + .read = &ff_sdmmc_read, + .write = &ff_sdmmc_write, + .ioctl = &ff_sdmmc_ioctl + }; + s_cards[pdrv] = card; + ff_diskio_register(pdrv, &sdmmc_impl); +} + diff --git a/components/fatfs/src/diskio_spiflash.c b/components/fatfs/src/diskio_spiflash.c index eb518e0e8f..2fdf075dea 100644 --- a/components/fatfs/src/diskio_spiflash.c +++ b/components/fatfs/src/diskio_spiflash.c @@ -3,7 +3,7 @@ // 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 @@ -13,31 +13,18 @@ // limitations under the License. #include -#include "diskio.h" /* FatFs lower layer API */ +#include "diskio.h" #include "ffconf.h" #include "ff.h" -#include "sdmmc_cmd.h" #include "esp_log.h" -#include -#include - #include "diskio_spiflash.h" #include "wear_levelling.h" static const char* TAG = "ff_diskio_spiflash"; -#ifndef MAX_FF_WL_DRIVES -#define MAX_FF_WL_DRIVES 8 -#endif // MAX_FF_WL_DRIVES -wl_handle_t ff_wl_handles[MAX_FF_WL_DRIVES] = { +wl_handle_t ff_wl_handles[_VOLUMES] = { WL_INVALID_HANDLE, WL_INVALID_HANDLE, - WL_INVALID_HANDLE, - WL_INVALID_HANDLE, - WL_INVALID_HANDLE, - WL_INVALID_HANDLE, - WL_INVALID_HANDLE, - WL_INVALID_HANDLE }; DSTATUS ff_wl_initialize (BYTE pdrv) @@ -104,7 +91,9 @@ DRESULT ff_wl_ioctl (BYTE pdrv, BYTE cmd, void *buff) esp_err_t ff_diskio_register_wl_partition(BYTE pdrv, wl_handle_t flash_handle) { - if (pdrv >= MAX_FF_WL_DRIVES) return ESP_FAIL; + if (pdrv >= _VOLUMES) { + return ESP_ERR_INVALID_ARG; + } static const ff_diskio_impl_t wl_impl = { .init = &ff_wl_initialize, .status = &ff_wl_status, @@ -119,12 +108,10 @@ esp_err_t ff_diskio_register_wl_partition(BYTE pdrv, wl_handle_t flash_handle) BYTE ff_diskio_get_pdrv_wl(wl_handle_t flash_handle) { - for (int i=0 ; i< MAX_FF_WL_DRIVES ; i++) - { - if (flash_handle == ff_wl_handles[i]) - { + for (int i = 0; i < _VOLUMES; i++) { + if (flash_handle == ff_wl_handles[i]) { return i; } } - return -1; + return 0xff; } diff --git a/components/fatfs/src/vfs_fat.c b/components/fatfs/src/vfs_fat.c index b9f5e40aaf..df2eb4cadf 100644 --- a/components/fatfs/src/vfs_fat.c +++ b/components/fatfs/src/vfs_fat.c @@ -22,17 +22,17 @@ #include "esp_vfs.h" #include "esp_log.h" #include "ff.h" - #include "diskio.h" - typedef struct { - char fat_drive[8]; - char base_path[ESP_VFS_PATH_MAX]; - size_t max_files; - FATFS fs; - FIL files[0]; - _lock_t lock; + char fat_drive[8]; /* FAT drive name */ + char base_path[ESP_VFS_PATH_MAX]; /* base path in VFS where partition is registered */ + size_t max_files; /* max number of simultaneously open files; size of files[] array */ + _lock_t lock; /* guard for access to this structure */ + FATFS fs; /* fatfs library FS structure */ + char tmp_path_buf[FILENAME_MAX+3]; /* temporary buffer used to prepend drive name to the path */ + char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */ + FIL files[0]; /* array with max_files entries; must be the final member of the structure */ } vfs_fat_ctx_t; typedef struct { @@ -245,23 +245,31 @@ static void file_cleanup(vfs_fat_ctx_t* ctx, int fd) memset(&ctx->files[fd], 0, sizeof(FIL)); } -static void prepend_drive_to_path(void * ctx, const char * path, const char * path2){ - static char buf[FILENAME_MAX+3]; - static char buf2[FILENAME_MAX+3]; - sprintf(buf, "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, path); - path = (const char *)buf; +/** + * @brief Prepend drive letters to path names + * This function returns new path path pointers, pointing to a temporary buffer + * inside ctx. + * @note Call this function with ctx->lock acquired. Paths are valid while the + * lock is held. + * @param ctx vfs_fat_ctx_t context + * @param[inout] path as input, pointer to the path; as output, pointer to the new path + * @param[inout] path2 as input, pointer to the path; as output, pointer to the new path + */ +static void prepend_drive_to_path(vfs_fat_ctx_t * ctx, const char ** path, const char ** path2){ + snprintf(ctx->tmp_path_buf, sizeof(ctx->tmp_path_buf), "%s%s", ctx->fat_drive, *path); + *path = ctx->tmp_path_buf; if(path2){ - sprintf(buf2, "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, path2); - path2 = (const char *)buf; + snprintf(ctx->tmp_path_buf2, sizeof(ctx->tmp_path_buf2), "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, *path2); + *path2 = ctx->tmp_path_buf2; } } static int vfs_fat_open(void* ctx, const char * path, int flags, int mode) { - prepend_drive_to_path(ctx, path, NULL); ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode); vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &path, NULL); int fd = get_next_fd(fat_ctx); if (fd < 0) { ESP_LOGE(TAG, "open: no free file descriptors"); @@ -368,9 +376,12 @@ static int vfs_fat_fstat(void* ctx, int fd, struct stat * st) static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) { - prepend_drive_to_path(ctx, path, NULL); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &path, NULL); FILINFO info; FRESULT res = f_stat(path, &info); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); @@ -398,8 +409,11 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) static int vfs_fat_unlink(void* ctx, const char *path) { - prepend_drive_to_path(ctx, path, NULL); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &path, NULL); FRESULT res = f_unlink(path); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); @@ -410,28 +424,39 @@ static int vfs_fat_unlink(void* ctx, const char *path) static int vfs_fat_link(void* ctx, const char* n1, const char* n2) { - prepend_drive_to_path(ctx, n1, n2); - const size_t copy_buf_size = 4096; + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &n1, &n2); + const size_t copy_buf_size = fat_ctx->fs.csize; + FRESULT res; + FIL* pf1 = calloc(1, sizeof(FIL)); + FIL* pf2 = calloc(1, sizeof(FIL)); void* buf = malloc(copy_buf_size); - if (buf == NULL) { + if (buf == NULL || pf1 == NULL || pf2 == NULL) { + ESP_LOGD(TAG, "alloc failed, pf1=%p, pf2=%p, buf=%p", pf1, pf2, buf); + free(pf1); + free(pf2); + free(buf); errno = ENOMEM; + _lock_release(&fat_ctx->lock); return -1; } - FIL f1; - FRESULT res = f_open(&f1, n1, FA_READ | FA_OPEN_EXISTING); + res = f_open(pf1, n1, FA_READ | FA_OPEN_EXISTING); if (res != FR_OK) { + _lock_release(&fat_ctx->lock); goto fail1; } - FIL f2; - res = f_open(&f2, n2, FA_WRITE | FA_CREATE_NEW); + res = f_open(pf2, n2, FA_WRITE | FA_CREATE_NEW); if (res != FR_OK) { + _lock_release(&fat_ctx->lock); goto fail2; } - size_t size_left = f_size(&f1); + _lock_release(&fat_ctx->lock); + 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(&f1, buf, will_copy, &read); + res = f_read(pf1, buf, will_copy, &read); if (res != FR_OK) { goto fail3; } else if (read != will_copy) { @@ -439,7 +464,7 @@ static int vfs_fat_link(void* ctx, const char* n1, const char* n2) goto fail3; } size_t written; - res = f_write(&f2, buf, will_copy, &written); + res = f_write(pf2, buf, will_copy, &written); if (res != FR_OK) { goto fail3; } else if (written != will_copy) { @@ -448,11 +473,12 @@ static int vfs_fat_link(void* ctx, const char* n1, const char* n2) } size_left -= will_copy; } - fail3: - f_close(&f2); + f_close(pf2); + free(pf2); fail2: - f_close(&f1); + f_close(pf1); + free(pf1); fail1: free(buf); if (res != FR_OK) { @@ -465,8 +491,11 @@ fail1: static int vfs_fat_rename(void* ctx, const char *src, const char *dst) { - prepend_drive_to_path(ctx, src, dst); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &src, &dst); FRESULT res = f_rename(src, dst); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); @@ -477,13 +506,17 @@ static int vfs_fat_rename(void* ctx, const char *src, const char *dst) static DIR* vfs_fat_opendir(void* ctx, const char* name) { - prepend_drive_to_path(ctx, name, NULL); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &name, NULL); vfs_fat_dir_t* fat_dir = calloc(1, sizeof(vfs_fat_dir_t)); if (!fat_dir) { + _lock_release(&fat_ctx->lock); errno = ENOMEM; return NULL; } FRESULT res = f_opendir(&fat_dir->ffdir, name); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { free(fat_dir); ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); @@ -582,8 +615,11 @@ static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) { (void) mode; - prepend_drive_to_path(ctx, name, NULL); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &name, NULL); FRESULT res = f_mkdir(name); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); @@ -594,8 +630,11 @@ static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) static int vfs_fat_rmdir(void* ctx, const char* name) { - prepend_drive_to_path(ctx, name, NULL); + vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &name, NULL); FRESULT res = f_unlink(name); + _lock_release(&fat_ctx->lock); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); diff --git a/components/fatfs/src/vfs_fat_sdmmc.c b/components/fatfs/src/vfs_fat_sdmmc.c index edbc2c12ed..cb8324289f 100644 --- a/components/fatfs/src/vfs_fat_sdmmc.c +++ b/components/fatfs/src/vfs_fat_sdmmc.c @@ -80,6 +80,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, ff_diskio_register_sdmmc(pdrv, s_card); s_pdrv = pdrv; + ESP_LOGD(TAG, "using pdrv=%i", pdrv); char drv[3] = {(char)('0' + pdrv), ':', 0}; // connect FATFS to VFS @@ -109,7 +110,7 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, goto fail; } ESP_LOGW(TAG, "formatting card"); - res = f_mkfs("", FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size); + res = f_mkfs(drv, FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size); if (res != FR_OK) { err = ESP_FAIL; ESP_LOGD(TAG, "f_mkfs failed (%d)", res); diff --git a/components/fatfs/src/vfs_fat_spiflash.c b/components/fatfs/src/vfs_fat_spiflash.c index 10c4781f2d..03682e564e 100644 --- a/components/fatfs/src/vfs_fat_spiflash.c +++ b/components/fatfs/src/vfs_fat_spiflash.c @@ -45,12 +45,11 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path, } // connect driver to FATFS BYTE pdrv = 0xFF; - if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) { + if (ff_diskio_get_drive(&pdrv) != ESP_OK) { ESP_LOGD(TAG, "the maximum count of volumes is already mounted"); return ESP_ERR_NO_MEM; } - ESP_LOGD(TAG, "pdrv=%i\n", pdrv); - + ESP_LOGD(TAG, "using pdrv=%i", pdrv); char drv[3] = {(char)('0' + pdrv), ':', 0}; result = ff_diskio_register_wl_partition(pdrv, *wl_handle); @@ -77,7 +76,7 @@ esp_err_t esp_vfs_fat_spiflash_mount(const char* base_path, } workbuf = malloc(workbuf_size); ESP_LOGI(TAG, "Formatting FATFS partition"); - fresult = f_mkfs("", FM_ANY | FM_SFD, workbuf_size, workbuf, workbuf_size); + fresult = f_mkfs(drv, FM_ANY | FM_SFD, workbuf_size, workbuf, workbuf_size); if (fresult != FR_OK) { result = ESP_FAIL; ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult); @@ -103,11 +102,14 @@ fail: esp_err_t esp_vfs_fat_spiflash_unmount(const char *base_path, wl_handle_t wl_handle) { - BYTE s_pdrv = ff_diskio_get_pdrv_wl(wl_handle); - char drv[3] = {(char)('0' + s_pdrv), ':', 0}; + BYTE pdrv = ff_diskio_get_pdrv_wl(wl_handle); + if (pdrv == 0xff) { + return ESP_ERR_INVALID_STATE; + } + char drv[3] = {(char)('0' + pdrv), ':', 0}; f_mount(0, drv, 0); - ff_diskio_unregister(s_pdrv); + ff_diskio_unregister(pdrv); // release partition driver esp_err_t err_drv = wl_unmount(wl_handle); esp_err_t err = esp_vfs_fat_unregister_path(base_path); diff --git a/components/fatfs/test/test_fatfs.c b/components/fatfs/test/test_fatfs.c deleted file mode 100644 index d518652bc4..0000000000 --- a/components/fatfs/test/test_fatfs.c +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright 2015-2016 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. - -#include -#include -#include -#include -#include -#include -#include "unity.h" -#include "esp_log.h" -#include "esp_system.h" -#include "esp_vfs.h" -#include "esp_vfs_fat.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "driver/sdmmc_host.h" -#include "driver/sdmmc_defs.h" -#include "sdmmc_cmd.h" -#include "diskio.h" -#include "ff.h" - -static const char* hello_str = "Hello, World!\n"; - -#define HEAP_SIZE_CAPTURE() \ - size_t heap_size = esp_get_free_heap_size(); - -#define HEAP_SIZE_CHECK(tolerance) \ - do {\ - size_t final_heap_size = esp_get_free_heap_size(); \ - if (final_heap_size < heap_size - tolerance) { \ - printf("Initial heap size: %d, final: %d, diff=%d\n", heap_size, final_heap_size, heap_size - final_heap_size); \ - } \ - } while(0) - -static void create_file_with_text(const char* name, const char* text) -{ - FILE* f = fopen(name, "wb"); - TEST_ASSERT_NOT_NULL(f); - TEST_ASSERT_TRUE(fputs(text, f) != EOF); - TEST_ASSERT_EQUAL(0, fclose(f)); -} - -TEST_CASE("Mount fails cleanly without card inserted", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 5 - }; - - for (int i = 0; i < 3; ++i) { - printf("Initializing card, attempt %d ", i); - esp_err_t err = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL); - printf(" err=%d\n", err); - TEST_ESP_ERR(ESP_FAIL, err); - } - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("can create and write file on sd card", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - create_file_with_text("/sdcard/hello.txt", hello_str); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("overwrite and append file on sd card", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - /* Create new file with 'aaaa' */ - const char *NAME = "/sdcard/hello.txt"; - create_file_with_text(NAME, "aaaa"); - - /* Append 'bbbb' to file */ - FILE *f_a = fopen(NAME, "a"); - TEST_ASSERT_NOT_NULL(f_a); - TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a)); - TEST_ASSERT_EQUAL(0, fclose(f_a)); - - /* Read back 8 bytes from file, verify it's 'aaaabbbb' */ - char buf[10] = { 0 }; - FILE *f_r = fopen(NAME, "r"); - TEST_ASSERT_NOT_NULL(f_r); - TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r)); - TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8); - - /* Be sure we're at end of file */ - TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r)); - - TEST_ASSERT_EQUAL(0, fclose(f_r)); - - /* Overwrite file with 'cccc' */ - create_file_with_text(NAME, "cccc"); - - /* Verify file now only contains 'cccc' */ - f_r = fopen(NAME, "r"); - TEST_ASSERT_NOT_NULL(f_r); - bzero(buf, sizeof(buf)); - TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4 - TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4); - TEST_ASSERT_EQUAL(0, fclose(f_r)); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("can read file on sd card", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = false, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - FILE* f = fopen("/sdcard/hello.txt", "r"); - TEST_ASSERT_NOT_NULL(f); - char buf[32]; - int cb = fread(buf, 1, sizeof(buf), f); - TEST_ASSERT_EQUAL(strlen(hello_str), cb); - TEST_ASSERT_EQUAL(0, strcmp(hello_str, buf)); - TEST_ASSERT_EQUAL(0, fclose(f)); - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write) -{ - const size_t buf_count = file_size / buf_size; - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = write, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - FILE* f = fopen("/sdcard/4mb.bin", (write) ? "wb" : "rb"); - TEST_ASSERT_NOT_NULL(f); - - struct timeval tv_start; - gettimeofday(&tv_start, NULL); - for (size_t n = 0; n < buf_count; ++n) { - if (write) { - TEST_ASSERT_EQUAL(1, fwrite(buf, buf_size, 1, f)); - } else { - if (fread(buf, buf_size, 1, f) != 1) { - printf("reading at n=%d, eof=%d", n, feof(f)); - TEST_FAIL(); - } - } - } - - struct timeval tv_end; - gettimeofday(&tv_end, NULL); - - TEST_ASSERT_EQUAL(0, fclose(f)); - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - - float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec); - printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n", - (write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3, - (file_size / 1024 / 1024) / t_s); -} - - -TEST_CASE("read speed test", "[fatfs][ignore]") -{ - - HEAP_SIZE_CAPTURE(); - const size_t buf_size = 16 * 1024; - uint32_t* buf = (uint32_t*) calloc(1, buf_size); - const size_t file_size = 4 * 1024 * 1024; - speed_test(buf, 4 * 1024, file_size, false); - HEAP_SIZE_CHECK(0); - speed_test(buf, 8 * 1024, file_size, false); - HEAP_SIZE_CHECK(0); - speed_test(buf, 16 * 1024, file_size, false); - HEAP_SIZE_CHECK(0); - free(buf); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("write speed test", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - - const size_t buf_size = 16 * 1024; - uint32_t* buf = (uint32_t*) calloc(1, buf_size); - for (size_t i = 0; i < buf_size / 4; ++i) { - buf[i] = esp_random(); - } - const size_t file_size = 4 * 1024 * 1024; - - speed_test(buf, 4 * 1024, file_size, true); - speed_test(buf, 8 * 1024, file_size, true); - speed_test(buf, 16 * 1024, file_size, true); - - free(buf); - - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("can lseek", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - FILE* f = fopen("/sdcard/seek.txt", "wb+"); - TEST_ASSERT_NOT_NULL(f); - TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); - TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); - TEST_ASSERT_EQUAL('9', fgetc(f)); - TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); - TEST_ASSERT_EQUAL('3', fgetc(f)); - TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); - TEST_ASSERT_EQUAL('8', fgetc(f)); - TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END)); - TEST_ASSERT_EQUAL(14, ftell(f)); - TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); - TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); - TEST_ASSERT_EQUAL(18, ftell(f)); - TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); - char buf[20]; - TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f)); - const char ref_buf[] = "0123456789\n\0\0\0abc\n"; - TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); - - TEST_ASSERT_EQUAL(0, fclose(f)); - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("stat returns correct values", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - struct tm tm; - tm.tm_year = 2016 - 1900; - tm.tm_mon = 0; - tm.tm_mday = 10; - tm.tm_hour = 16; - tm.tm_min = 30; - tm.tm_sec = 0; - time_t t = mktime(&tm); - printf("Setting time: %s", asctime(&tm)); - struct timeval now = { .tv_sec = t }; - settimeofday(&now, NULL); - - create_file_with_text("/sdcard/stat.txt", "foo\n"); - - struct stat st; - TEST_ASSERT_EQUAL(0, stat("/sdcard/stat.txt", &st)); - time_t mtime = st.st_mtime; - struct tm mtm; - localtime_r(&mtime, &mtm); - printf("File time: %s", asctime(&mtm)); - TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision - - TEST_ASSERT(st.st_mode & S_IFREG); - TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("unlink removes a file", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - create_file_with_text("/sdcard/unlink.txt", "unlink\n"); - - TEST_ASSERT_EQUAL(0, unlink("/sdcard/unlink.txt")); - - TEST_ASSERT_NULL(fopen("/sdcard/unlink.txt", "r")); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("link copies a file, rename moves a file", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - unlink("/sdcard/linkcopy.txt"); - unlink("/sdcard/link_dst.txt"); - unlink("/sdcard/link_src.txt"); - - FILE* f = fopen("/sdcard/link_src.txt", "w+"); - TEST_ASSERT_NOT_NULL(f); - char* str = "0123456789"; - for (int i = 0; i < 4000; ++i) { - TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); - } - TEST_ASSERT_EQUAL(0, fclose(f)); - - TEST_ASSERT_EQUAL(0, link("/sdcard/link_src.txt", "/sdcard/linkcopy.txt")); - - FILE* fcopy = fopen("/sdcard/linkcopy.txt", "r"); - TEST_ASSERT_NOT_NULL(fcopy); - TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END)); - TEST_ASSERT_EQUAL(40000, ftell(fcopy)); - TEST_ASSERT_EQUAL(0, fclose(fcopy)); - - TEST_ASSERT_EQUAL(0, rename("/sdcard/linkcopy.txt", "/sdcard/link_dst.txt")); - TEST_ASSERT_NULL(fopen("/sdcard/linkcopy.txt", "r")); - FILE* fdst = fopen("/sdcard/link_dst.txt", "r"); - TEST_ASSERT_NOT_NULL(fdst); - TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); - TEST_ASSERT_EQUAL(40000, ftell(fdst)); - TEST_ASSERT_EQUAL(0, fclose(fdst)); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -typedef struct { - const char* filename; - bool write; - size_t word_count; - int seed; - SemaphoreHandle_t done; - int result; -} read_write_test_arg_t; - -#define READ_WRITE_TEST_ARG_INIT(name, seed_) \ - { \ - .filename = name, \ - .seed = seed_, \ - .word_count = 8192, \ - .write = true, \ - .done = xSemaphoreCreateBinary() \ - } - -static void read_write_task(void* param) -{ - read_write_test_arg_t* args = (read_write_test_arg_t*) param; - FILE* f = fopen(args->filename, args->write ? "wb" : "rb"); - if (f == NULL) { - args->result = ESP_ERR_NOT_FOUND; - goto done; - } - - srand(args->seed); - for (size_t i = 0; i < args->word_count; ++i) { - uint32_t val = rand(); - if (args->write) { - int cnt = fwrite(&val, sizeof(val), 1, f); - if (cnt != 1) { - args->result = ESP_FAIL; - goto close; - } - } else { - uint32_t rval; - int cnt = fread(&rval, sizeof(rval), 1, f); - if (cnt != 1 || rval != val) { - ets_printf("E: i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val); - args->result = ESP_FAIL; - goto close; - } - } - } - args->result = ESP_OK; - -close: - fclose(f); - -done: - xSemaphoreGive(args->done); - vTaskDelay(1); - vTaskDelete(NULL); -} - - -TEST_CASE("multiple tasks can use same volume", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT("/sdcard/f1", 1); - read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT("/sdcard/f2", 2); - - printf("writing f1 and f2\n"); - - xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); - xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); - - xSemaphoreTake(args1.done, portMAX_DELAY); - printf("f1 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args1.result); - xSemaphoreTake(args2.done, portMAX_DELAY); - printf("f2 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args2.result); - - args1.write = false; - args2.write = false; - read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT("/sdcard/f3", 3); - read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT("/sdcard/f4", 4); - - printf("reading f1 and f2, writing f3 and f4\n"); - - xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1); - xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0); - xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); - xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); - - xSemaphoreTake(args1.done, portMAX_DELAY); - printf("f1 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args1.result); - xSemaphoreTake(args2.done, portMAX_DELAY); - printf("f2 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args2.result); - xSemaphoreTake(args3.done, portMAX_DELAY); - printf("f3 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args3.result); - xSemaphoreTake(args4.done, portMAX_DELAY); - printf("f4 done\n"); - TEST_ASSERT_EQUAL(ESP_OK, args4.result); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - vSemaphoreDelete(args1.done); - vSemaphoreDelete(args2.done); - vSemaphoreDelete(args3.done); - vSemaphoreDelete(args4.done); - vTaskDelay(10); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("can create and remove directories", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir1", 0755)); - struct stat st; - TEST_ASSERT_EQUAL(0, stat("/sdcard/dir1", &st)); - TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); - TEST_ASSERT_FALSE(st.st_mode & S_IFREG); - TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir1")); - TEST_ASSERT_EQUAL(-1, stat("/sdcard/dir1", &st)); - - TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir2", 0755)); - create_file_with_text("/sdcard/dir2/1.txt", "foo\n"); - TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2", &st)); - TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); - TEST_ASSERT_FALSE(st.st_mode & S_IFREG); - TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2/1.txt", &st)); - TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); - TEST_ASSERT_TRUE(st.st_mode & S_IFREG); - TEST_ASSERT_EQUAL(-1, rmdir("/sdcard/dir2")); - TEST_ASSERT_EQUAL(0, unlink("/sdcard/dir2/1.txt")); - TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir2")); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} - -TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][ignore]") -{ - HEAP_SIZE_CAPTURE(); - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, - .max_files = 5 - }; - TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); - - unlink("/sdcard/dir/inner/3.txt"); - rmdir("/sdcard/dir/inner"); - unlink("/sdcard/dir/2.txt"); - unlink("/sdcard/dir/1.txt"); - unlink("/sdcard/dir/boo.bin"); - rmdir("/sdcard/dir"); - - TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir", 0755)); - create_file_with_text("/sdcard/dir/2.txt", "1\n"); - create_file_with_text("/sdcard/dir/1.txt", "1\n"); - create_file_with_text("/sdcard/dir/boo.bin", "\01\02\03"); - TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir/inner", 0755)); - create_file_with_text("/sdcard/dir/inner/3.txt", "3\n"); - - DIR* dir = opendir("/sdcard/dir"); - TEST_ASSERT_NOT_NULL(dir); - int count = 0; - const char* names[4]; - while(count < 4) { - struct dirent* de = readdir(dir); - if (!de) { - break; - } - printf("found '%s'\n", de->d_name); - if (strcasecmp(de->d_name, "1.txt") == 0) { - TEST_ASSERT_TRUE(de->d_type == DT_REG); - names[count] = "1.txt"; - ++count; - } else if (strcasecmp(de->d_name, "2.txt") == 0) { - TEST_ASSERT_TRUE(de->d_type == DT_REG); - names[count] = "2.txt"; - ++count; - } else if (strcasecmp(de->d_name, "inner") == 0) { - TEST_ASSERT_TRUE(de->d_type == DT_DIR); - names[count] = "inner"; - ++count; - } else if (strcasecmp(de->d_name, "boo.bin") == 0) { - TEST_ASSERT_TRUE(de->d_type == DT_REG); - names[count] = "boo.bin"; - ++count; - } else { - TEST_FAIL_MESSAGE("unexpected directory entry"); - } - } - TEST_ASSERT_EQUAL(count, 4); - - rewinddir(dir); - struct dirent* de = readdir(dir); - TEST_ASSERT_NOT_NULL(de); - TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0])); - seekdir(dir, 3); - de = readdir(dir); - TEST_ASSERT_NOT_NULL(de); - TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3])); - seekdir(dir, 1); - de = readdir(dir); - TEST_ASSERT_NOT_NULL(de); - TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1])); - seekdir(dir, 2); - de = readdir(dir); - TEST_ASSERT_NOT_NULL(de); - TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2])); - - TEST_ASSERT_EQUAL(0, closedir(dir)); - - TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); - HEAP_SIZE_CHECK(0); -} diff --git a/components/fatfs/test/test_fatfs_common.c b/components/fatfs/test/test_fatfs_common.c new file mode 100644 index 0000000000..47dca17e93 --- /dev/null +++ b/components/fatfs/test/test_fatfs_common.c @@ -0,0 +1,454 @@ +// Copyright 2015-2017 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. + +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "ff.h" +#include "test_fatfs_common.h" + +const char* fatfs_test_hello_str = "Hello, World!\n"; + +void test_fatfs_create_file_with_text(const char* name, const char* text) +{ + FILE* f = fopen(name, "wb"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_TRUE(fputs(text, f) != EOF); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_fatfs_overwrite_append(const char* filename) +{ + /* Create new file with 'aaaa' */ + test_fatfs_create_file_with_text(filename, "aaaa"); + + /* Append 'bbbb' to file */ + FILE *f_a = fopen(filename, "a"); + TEST_ASSERT_NOT_NULL(f_a); + TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a)); + TEST_ASSERT_EQUAL(0, fclose(f_a)); + + /* Read back 8 bytes from file, verify it's 'aaaabbbb' */ + char buf[10] = { 0 }; + FILE *f_r = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f_r); + TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r)); + TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8); + + /* Be sure we're at end of file */ + TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r)); + + TEST_ASSERT_EQUAL(0, fclose(f_r)); + + /* Overwrite file with 'cccc' */ + test_fatfs_create_file_with_text(filename, "cccc"); + + /* Verify file now only contains 'cccc' */ + f_r = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f_r); + bzero(buf, sizeof(buf)); + TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4 + TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4); + TEST_ASSERT_EQUAL(0, fclose(f_r)); +} + +void test_fatfs_read_file(const char* filename) +{ + FILE* f = fopen(filename, "r"); + TEST_ASSERT_NOT_NULL(f); + char buf[32] = { 0 }; + int cb = fread(buf, 1, sizeof(buf), f); + TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), cb); + TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf)); + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count) +{ + FILE** files = calloc(files_count, sizeof(FILE*)); + for (size_t i = 0; i < files_count; ++i) { + char name[32]; + snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i); + files[i] = fopen(name, "w"); + TEST_ASSERT_NOT_NULL(files[i]); + } + /* close everything and clean up */ + for (size_t i = 0; i < files_count; ++i) { + fclose(files[i]); + } + free(files); +} + +void test_fatfs_lseek(const char* filename) +{ + FILE* f = fopen(filename, "wb+"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n")); + TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR)); + TEST_ASSERT_EQUAL('9', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET)); + TEST_ASSERT_EQUAL('3', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END)); + TEST_ASSERT_EQUAL('8', fgetc(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END)); + TEST_ASSERT_EQUAL(14, ftell(f)); + TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n")); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END)); + TEST_ASSERT_EQUAL(18, ftell(f)); + TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET)); + char buf[20]; + TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f)); + const char ref_buf[] = "0123456789\n\0\0\0abc\n"; + TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1); + + TEST_ASSERT_EQUAL(0, fclose(f)); +} + +void test_fatfs_stat(const char* filename) +{ + struct tm tm; + tm.tm_year = 2016 - 1900; + tm.tm_mon = 0; + tm.tm_mday = 10; + tm.tm_hour = 16; + tm.tm_min = 30; + tm.tm_sec = 0; + time_t t = mktime(&tm); + printf("Setting time: %s", asctime(&tm)); + struct timeval now = { .tv_sec = t }; + settimeofday(&now, NULL); + + test_fatfs_create_file_with_text(filename, "foo\n"); + + struct stat st; + TEST_ASSERT_EQUAL(0, stat(filename, &st)); + time_t mtime = st.st_mtime; + struct tm mtm; + localtime_r(&mtime, &mtm); + printf("File time: %s", asctime(&mtm)); + TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision + + TEST_ASSERT(st.st_mode & S_IFREG); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); +} + +void test_fatfs_unlink(const char* filename) +{ + test_fatfs_create_file_with_text(filename, "unlink\n"); + + TEST_ASSERT_EQUAL(0, unlink(filename)); + + TEST_ASSERT_NULL(fopen(filename, "r")); +} + +void test_fatfs_link_rename(const char* filename_prefix) +{ + char name_copy[64]; + char name_dst[64]; + char name_src[64]; + snprintf(name_copy, sizeof(name_copy), "%s_cpy.txt", filename_prefix); + snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix); + snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix); + + unlink(name_copy); + unlink(name_dst); + unlink(name_src); + + FILE* f = fopen(name_src, "w+"); + TEST_ASSERT_NOT_NULL(f); + char* str = "0123456789"; + for (int i = 0; i < 4000; ++i) { + TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f)); + } + TEST_ASSERT_EQUAL(0, fclose(f)); + TEST_ASSERT_EQUAL(0, link(name_src, name_copy)); + FILE* fcopy = fopen(name_copy, "r"); + TEST_ASSERT_NOT_NULL(fcopy); + TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fcopy)); + TEST_ASSERT_EQUAL(0, fclose(fcopy)); + TEST_ASSERT_EQUAL(0, rename(name_copy, name_dst)); + TEST_ASSERT_NULL(fopen(name_copy, "r")); + FILE* fdst = fopen(name_dst, "r"); + TEST_ASSERT_NOT_NULL(fdst); + TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END)); + TEST_ASSERT_EQUAL(40000, ftell(fdst)); + TEST_ASSERT_EQUAL(0, fclose(fdst)); +} + +void test_fatfs_mkdir_rmdir(const char* filename_prefix) +{ + char name_dir1[64]; + char name_dir2[64]; + char name_dir2_file[64]; + snprintf(name_dir1, sizeof(name_dir1), "%s1", filename_prefix); + snprintf(name_dir2, sizeof(name_dir2), "%s2", filename_prefix); + snprintf(name_dir2_file, sizeof(name_dir2_file), "%s2/1.txt", filename_prefix); + + TEST_ASSERT_EQUAL(0, mkdir(name_dir1, 0755)); + struct stat st; + TEST_ASSERT_EQUAL(0, stat(name_dir1, &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, rmdir(name_dir1)); + TEST_ASSERT_EQUAL(-1, stat(name_dir1, &st)); + + TEST_ASSERT_EQUAL(0, mkdir(name_dir2, 0755)); + test_fatfs_create_file_with_text(name_dir2_file, "foo\n"); + TEST_ASSERT_EQUAL(0, stat(name_dir2, &st)); + TEST_ASSERT_TRUE(st.st_mode & S_IFDIR); + TEST_ASSERT_FALSE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(0, stat(name_dir2_file, &st)); + TEST_ASSERT_FALSE(st.st_mode & S_IFDIR); + TEST_ASSERT_TRUE(st.st_mode & S_IFREG); + TEST_ASSERT_EQUAL(-1, rmdir(name_dir2)); + TEST_ASSERT_EQUAL(0, unlink(name_dir2_file)); + TEST_ASSERT_EQUAL(0, rmdir(name_dir2)); +} + +void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix) +{ + char name_dir_inner_file[64]; + char name_dir_inner[64]; + char name_dir_file3[64]; + char name_dir_file2[64]; + char name_dir_file1[64]; + + snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix); + snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix); + snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix); + snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix); + snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix); + + unlink(name_dir_inner_file); + rmdir(name_dir_inner); + unlink(name_dir_file1); + unlink(name_dir_file2); + unlink(name_dir_file3); + rmdir(dir_prefix); + + TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755)); + test_fatfs_create_file_with_text(name_dir_file1, "1\n"); + test_fatfs_create_file_with_text(name_dir_file2, "2\n"); + test_fatfs_create_file_with_text(name_dir_file3, "\01\02\03"); + TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755)); + test_fatfs_create_file_with_text(name_dir_inner_file, "3\n"); + + DIR* dir = opendir(dir_prefix); + TEST_ASSERT_NOT_NULL(dir); + int count = 0; + const char* names[4]; + while(count < 4) { + struct dirent* de = readdir(dir); + if (!de) { + break; + } + printf("found '%s'\n", de->d_name); + if (strcasecmp(de->d_name, "1.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "1.txt"; + ++count; + } else if (strcasecmp(de->d_name, "2.txt") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "2.txt"; + ++count; + } else if (strcasecmp(de->d_name, "inner") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_DIR); + names[count] = "inner"; + ++count; + } else if (strcasecmp(de->d_name, "boo.bin") == 0) { + TEST_ASSERT_TRUE(de->d_type == DT_REG); + names[count] = "boo.bin"; + ++count; + } else { + TEST_FAIL_MESSAGE("unexpected directory entry"); + } + } + TEST_ASSERT_EQUAL(count, 4); + + rewinddir(dir); + struct dirent* de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0])); + seekdir(dir, 3); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3])); + seekdir(dir, 1); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1])); + seekdir(dir, 2); + de = readdir(dir); + TEST_ASSERT_NOT_NULL(de); + TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2])); + + TEST_ASSERT_EQUAL(0, closedir(dir)); +} + + +typedef struct { + const char* filename; + bool write; + size_t word_count; + int seed; + SemaphoreHandle_t done; + int result; +} read_write_test_arg_t; + +#define READ_WRITE_TEST_ARG_INIT(name, seed_) \ + { \ + .filename = name, \ + .seed = seed_, \ + .word_count = 8192, \ + .write = true, \ + .done = xSemaphoreCreateBinary() \ + } + +static void read_write_task(void* param) +{ + read_write_test_arg_t* args = (read_write_test_arg_t*) param; + FILE* f = fopen(args->filename, args->write ? "wb" : "rb"); + if (f == NULL) { + args->result = ESP_ERR_NOT_FOUND; + goto done; + } + + srand(args->seed); + for (size_t i = 0; i < args->word_count; ++i) { + uint32_t val = rand(); + if (args->write) { + int cnt = fwrite(&val, sizeof(val), 1, f); + if (cnt != 1) { + ets_printf("E(w): i=%d, cnt=%d val=%d\n\n", i, cnt, val); + args->result = ESP_FAIL; + goto close; + } + } else { + uint32_t rval; + int cnt = fread(&rval, sizeof(rval), 1, f); + if (cnt != 1 || rval != val) { + ets_printf("E(r): i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val); + args->result = ESP_FAIL; + goto close; + } + } + } + args->result = ESP_OK; + +close: + fclose(f); + +done: + xSemaphoreGive(args->done); + vTaskDelay(1); + vTaskDelete(NULL); +} + +void test_fatfs_concurrent(const char* filename_prefix) +{ + char names[4][64]; + for (size_t i = 0; i < 4; ++i) { + snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1); + unlink(names[i]); + } + + read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1); + read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2); + + printf("writing f1 and f2\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + + args1.write = false; + args2.write = false; + read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT(names[2], 3); + read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4); + + printf("reading f1 and f2, writing f3 and f4\n"); + + xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1); + xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0); + xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1); + + xSemaphoreTake(args1.done, portMAX_DELAY); + printf("f1 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args1.result); + xSemaphoreTake(args2.done, portMAX_DELAY); + printf("f2 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args2.result); + xSemaphoreTake(args3.done, portMAX_DELAY); + printf("f3 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args3.result); + xSemaphoreTake(args4.done, portMAX_DELAY); + printf("f4 done\n"); + TEST_ASSERT_EQUAL(ESP_OK, args4.result); + + vSemaphoreDelete(args1.done); + vSemaphoreDelete(args2.done); + vSemaphoreDelete(args3.done); + vSemaphoreDelete(args4.done); +} + + +void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write) +{ + const size_t buf_count = file_size / buf_size; + + FILE* f = fopen(filename, (write) ? "wb" : "rb"); + TEST_ASSERT_NOT_NULL(f); + + struct timeval tv_start; + gettimeofday(&tv_start, NULL); + for (size_t n = 0; n < buf_count; ++n) { + if (write) { + TEST_ASSERT_EQUAL(1, fwrite(buf, buf_size, 1, f)); + } else { + if (fread(buf, buf_size, 1, f) != 1) { + printf("reading at n=%d, eof=%d", n, feof(f)); + TEST_FAIL(); + } + } + } + + struct timeval tv_end; + gettimeofday(&tv_end, NULL); + + TEST_ASSERT_EQUAL(0, fclose(f)); + + float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec); + printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n", + (write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3, + file_size / (1024.0f * 1024.0f * t_s)); +} + diff --git a/components/fatfs/test/test_fatfs_common.h b/components/fatfs/test/test_fatfs_common.h new file mode 100644 index 0000000000..84258c1dab --- /dev/null +++ b/components/fatfs/test/test_fatfs_common.h @@ -0,0 +1,58 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** + * @file test_fatfs_common.h + * @brief Common routines for FAT-on-SDMMC and FAT-on-WL tests + */ + +#define HEAP_SIZE_CAPTURE(heap_size) \ + heap_size = esp_get_free_heap_size(); + +#define HEAP_SIZE_CHECK(heap_size, tolerance) \ + do {\ + size_t final_heap_size = esp_get_free_heap_size(); \ + if (final_heap_size < heap_size - tolerance) { \ + printf("Initial heap size: %d, final: %d, diff=%d\n", heap_size, final_heap_size, heap_size - final_heap_size); \ + } \ + } while(0) + + +const char* fatfs_test_hello_str; + +void test_fatfs_create_file_with_text(const char* name, const char* text); + +void test_fatfs_overwrite_append(const char* filename); + +void test_fatfs_read_file(const char* filename); + +void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count); + +void test_fatfs_lseek(const char* filename); + +void test_fatfs_stat(const char* filename); + +void test_fatfs_unlink(const char* filename); + +void test_fatfs_link_rename(const char* filename_prefix); + +void test_fatfs_concurrent(const char* filename_prefix); + +void test_fatfs_mkdir_rmdir(const char* filename_prefix); + +void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix); + +void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool write); diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c new file mode 100644 index 0000000000..bc77d94c81 --- /dev/null +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -0,0 +1,233 @@ +// Copyright 2015-2017 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. + +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "diskio.h" +#include "ff.h" +#include "test_fatfs_common.h" + + +static void test_setup(void) +{ + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); +} + +static void test_teardown(void) +{ + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); +} + +static const char* test_filename = "/sdcard/hello.txt"; + +TEST_CASE("Mount fails cleanly without card inserted", "[fatfs][ignore]") +{ + size_t heap_size; + HEAP_SIZE_CAPTURE(heap_size); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + + for (int i = 0; i < 3; ++i) { + printf("Initializing card, attempt %d ", i); + esp_err_t err = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL); + printf(" err=%d\n", err); + TEST_ESP_ERR(ESP_FAIL, err); + } + HEAP_SIZE_CHECK(heap_size, 0); +} + +TEST_CASE("(SD) can create and write file", "[fatfs][sdcard][ignore]") +{ + test_setup(); + test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); + test_teardown(); +} + +TEST_CASE("(SD) can read file", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_create_file_with_text(test_filename, fatfs_test_hello_str); + test_fatfs_read_file(test_filename); + test_teardown(); +} + + +TEST_CASE("(SD) overwrite and append file", "[fatfs][sdcard][ignore]") +{ + test_setup(); + test_fatfs_overwrite_append(test_filename); + test_teardown(); +} + + +TEST_CASE("(SD) can lseek", "[fatfs][sdcard][ignore]") +{ + test_setup(); + test_fatfs_lseek("/sdcard/seek.txt"); + test_teardown(); +} + +TEST_CASE("(SD) stat returns correct values", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_stat("/sdcard/stat.txt"); + test_teardown(); +} + +TEST_CASE("(SD) unlink removes a file", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_unlink("/sdcard/unlink.txt"); + test_teardown(); +} + +TEST_CASE("(SD) link copies a file, rename moves a file", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_link_rename("/sdcard/link"); + test_teardown(); +} + +TEST_CASE("(SD) can create and remove directories", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_mkdir_rmdir("/sdcard/dir"); + test_teardown(); +} + +TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_opendir_readdir_rewinddir("/sdcard/dir"); + test_teardown(); +} + +TEST_CASE("(SD) multiple tasks can use same volume", "[fatfs][ignore]") +{ + test_setup(); + test_fatfs_concurrent("/sdcard/f"); + test_teardown(); +} + +static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write); + +TEST_CASE("(SD) write/read speed test", "[fatfs][sdcard][ignore]") +{ + size_t heap_size; + HEAP_SIZE_CAPTURE(heap_size); + + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + for (size_t i = 0; i < buf_size / 4; ++i) { + buf[i] = esp_random(); + } + const size_t file_size = 4 * 1024 * 1024; + + speed_test(buf, 4 * 1024, file_size, true); + speed_test(buf, 8 * 1024, file_size, true); + speed_test(buf, 16 * 1024, file_size, true); + + speed_test(buf, 4 * 1024, file_size, false); + speed_test(buf, 8 * 1024, file_size, false); + speed_test(buf, 16 * 1024, file_size, false); + + free(buf); + + HEAP_SIZE_CHECK(heap_size, 0); +} + +static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write) +{ + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = write, + .max_files = 5 + }; + TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL)); + + test_fatfs_rw_speed("/sdcard/4mb.bin", buf, buf_size, file_size, write); + + TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount()); +} + +TEST_CASE("(SD) mount two FAT partitions, SDMMC and WL, at the same time", "[fatfs][sdcard][ignore]") +{ + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + + const char* filename_sd = "/sdcard/sd.txt"; + const char* filename_wl = "/spiflash/wl.txt"; + const char* str_sd = "this is sd\n"; + const char* str_wl = "this is spiflash\n"; + + /* 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_ESP_OK(esp_vfs_fat_spiflash_mount("/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("/spiflash", wl_handle)); + test_teardown(); + + /* Check that the file "sd.txt" was created on FS in SD, and has the right data */ + test_setup(); + TEST_ASSERT_NULL(fopen(filename_wl, "r")); + FILE* f = fopen(filename_sd, "r"); + TEST_ASSERT_NOT_NULL(f); + char buf[64]; + TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf) - 1, f)); + TEST_ASSERT_EQUAL(0, strcmp(buf, str_sd)); + fclose(f); + test_teardown(); + + /* 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("/spiflash", NULL, &mount_config, &wl_handle)); + TEST_ASSERT_NULL(fopen(filename_sd, "r")); + f = fopen(filename_wl, "r"); + TEST_ASSERT_NOT_NULL(f); + TEST_ASSERT_NOT_NULL(fgets(buf, sizeof(buf) - 1, f)); + TEST_ASSERT_EQUAL(0, strcmp(buf, str_wl)); + fclose(f); + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", wl_handle)); +} diff --git a/components/fatfs/test/test_fatfs_spiflash.c b/components/fatfs/test/test_fatfs_spiflash.c new file mode 100644 index 0000000000..971838db96 --- /dev/null +++ b/components/fatfs/test/test_fatfs_spiflash.c @@ -0,0 +1,170 @@ +// Copyright 2015-2017 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. + +#include +#include +#include +#include +#include +#include +#include "unity.h" +#include "test_utils.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_vfs.h" +#include "esp_vfs_fat.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "test_fatfs_common.h" +#include "wear_levelling.h" +#include "esp_partition.h" + + +static wl_handle_t s_test_wl_handle; +static void test_setup() +{ + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = true, + .max_files = 5 + }; + + TEST_ESP_OK(esp_vfs_fat_spiflash_mount("/spiflash", NULL, &mount_config, &s_test_wl_handle)); +} + +static void test_teardown() +{ + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", s_test_wl_handle)); +} + +TEST_CASE("(WL) can format partition", "[fatfs][wear_levelling]") +{ + const esp_partition_t* part = get_test_data_partition(); + esp_partition_erase_range(part, 0, part->size); + test_setup(); + test_teardown(); +} + +TEST_CASE("(WL) can create and write file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str); + test_teardown(); +} + +TEST_CASE("(WL) can read file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_create_file_with_text("/spiflash/hello.txt", fatfs_test_hello_str); + test_fatfs_read_file("/spiflash/hello.txt"); + test_teardown(); +} + +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 = { + .format_if_mount_failed = true, + .max_files = max_files + }; + TEST_ESP_OK(esp_vfs_fat_spiflash_mount("/spiflash", NULL, &mount_config, &s_test_wl_handle)); + test_fatfs_open_max_files("/spiflash/f", max_files); + TEST_ESP_OK(esp_vfs_fat_spiflash_unmount("/spiflash", s_test_wl_handle)); +} + +TEST_CASE("(WL) overwrite and append file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_overwrite_append("/spiflash/hello.txt"); + test_teardown(); +} + +TEST_CASE("(WL) can lseek", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_lseek("/spiflash/seek.txt"); + test_teardown(); +} + + +TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_stat("/spiflash/stat.txt"); + test_teardown(); +} + +TEST_CASE("(WL) unlink removes a file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_unlink("/spiflash/unlink.txt"); + test_teardown(); +} + +TEST_CASE("(WL) link copies a file, rename moves a file", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_link_rename("/spiflash/link"); + test_teardown(); +} + +TEST_CASE("(WL) can create and remove directories", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_mkdir_rmdir("/spiflash/dir"); + test_teardown(); +} + +TEST_CASE("(WL) opendir, readdir, rewinddir, seekdir work as expected", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_opendir_readdir_rewinddir("/spiflash/dir"); + test_teardown(); +} + +TEST_CASE("(WL) multiple tasks can use same volume", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_concurrent("/spiflash/f"); + test_teardown(); +} + +TEST_CASE("(WL) write/read speed test", "[fatfs][wear_levelling]") +{ + /* Erase partition before running the test to get consistent results */ + const esp_partition_t* part = get_test_data_partition(); + esp_partition_erase_range(part, 0, part->size); + + test_setup(); + + const size_t buf_size = 16 * 1024; + uint32_t* buf = (uint32_t*) calloc(1, buf_size); + for (size_t i = 0; i < buf_size / 4; ++i) { + buf[i] = esp_random(); + } + const size_t file_size = 256 * 1024; + const char* file = "/spiflash/256k.bin"; + + test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, true); + test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, true); + test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, true); + + test_fatfs_rw_speed(file, buf, 4 * 1024, file_size, false); + test_fatfs_rw_speed(file, buf, 8 * 1024, file_size, false); + test_fatfs_rw_speed(file, buf, 16 * 1024, file_size, false); + + unlink(file); + + free(buf); + test_teardown(); +} diff --git a/components/newlib/include/limits.h b/components/newlib/include/limits.h index 190f1f783a..633b44593e 100644 --- a/components/newlib/include/limits.h +++ b/components/newlib/include/limits.h @@ -142,5 +142,5 @@ #endif #ifndef PATH_MAX -#define PATH_MAX 4096 +#define PATH_MAX 1024 #endif diff --git a/components/newlib/include/sys/syslimits.h b/components/newlib/include/sys/syslimits.h index ba9dbd6674..c0bb3a2ce0 100644 --- a/components/newlib/include/sys/syslimits.h +++ b/components/newlib/include/sys/syslimits.h @@ -37,7 +37,7 @@ #ifndef _SYS_SYSLIMITS_H_ #define _SYS_SYSLIMITS_H_ -#define ARG_MAX 65536 /* max bytes for an exec function */ +#define ARG_MAX 4096 /* max bytes for an exec function */ #ifndef CHILD_MAX #define CHILD_MAX 40 /* max simultaneous processes */ #endif diff --git a/components/partition_table/test/test_partition.c b/components/partition_table/test/test_partition.c index 6cb13bd9a7..64ead9692c 100644 --- a/components/partition_table/test/test_partition.c +++ b/components/partition_table/test/test_partition.c @@ -27,7 +27,7 @@ TEST_CASE("Can read partition table", "[partition]") ++count; } esp_partition_iterator_release(it); - TEST_ASSERT_EQUAL(3, count); + TEST_ASSERT_EQUAL(4, count); } TEST_CASE("Can write, read, mmap partition", "[partition][ignore]") diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index d80972a533..fff3a93a1d 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -125,6 +125,9 @@ static const vfs_entry_t* get_vfs_for_path(const char* path) size_t len = strlen(path); for (size_t i = 0; i < s_vfs_count; ++i) { const vfs_entry_t* vfs = s_vfs[i]; + if (!vfs) { + continue; + } if (len < vfs->path_prefix_len + 1) { // +1 is for the trailing slash after base path continue; } diff --git a/tools/unit-test-app/components/unity/test_utils.c b/tools/unit-test-app/components/unity/test_utils.c index 416853a7f3..08826d319d 100644 --- a/tools/unit-test-app/components/unity/test_utils.c +++ b/tools/unit-test-app/components/unity/test_utils.c @@ -17,9 +17,9 @@ const esp_partition_t *get_test_data_partition() { - /* This user type/subtype (0x55) is set in - partition_table_unit_test_app.csv */ - const esp_partition_t *result = esp_partition_find_first(0x55, 0x55, NULL); + /* This finds "flash_test" partition defined in partition_table_unit_test_app.csv */ + const esp_partition_t *result = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_ANY, "flash_test"); TEST_ASSERT_NOT_NULL(result); /* means partition table set wrong */ return result; } diff --git a/tools/unit-test-app/partition_table_unit_test_app.csv b/tools/unit-test-app/partition_table_unit_test_app.csv index 129d0d3f64..279f5b69de 100644 --- a/tools/unit-test-app/partition_table_unit_test_app.csv +++ b/tools/unit-test-app/partition_table_unit_test_app.csv @@ -10,5 +10,7 @@ factory, 0, 0, 0x10000, 1M # (done this way so tests can run in 2MB of flash.) ota_0, 0, ota_0, , 128K ota_1, 0, ota_1, , 128K -# flash_test partition used for SPI flash tests -flash_test, 0x55, 0x55, , 512K +# flash_test partition used for SPI flash tests and WL FAT partition +# 528K is the minimal size needed to create a FAT partition +# (128 sectors for FAT + 4 sectors for WL) +flash_test, data, fat, , 528K