From 167eb53e7491b039b95671f09e3768ed64690728 Mon Sep 17 00:00:00 2001 From: "Peter A. Bigot" Date: Fri, 2 Aug 2019 19:48:05 -0500 Subject: [PATCH] subsys/fs/littlefs: allow customization of file system configuration There's desire to be able to customize parameters on a per-filesystem basis, which means we need a way to override the Kconfig defaults which are global. This also means the littlefs data structure cannot own the cache and lookahead buffers. Switch to using a macro to define the littlefs data structure. The default version uses the Kconfig constants. A custom one takes arguments providing the most likely partition-specific parameters. Finally the user is free to bypass the helper macros and set any parameters desired, though validation is limited and only present when CONFIG_DEBUG is enabled. Extend the test suite with a performance module, which confirms that these settings have an impact proportional to the log of changes to the cache or IO sizes. Signed-off-by: Peter A. Bigot --- include/fs/littlefs.h | 81 +++++- samples/subsys/fs/littlefs/prj.conf | 4 +- samples/subsys/fs/littlefs/src/main.c | 4 +- subsys/fs/Kconfig | 58 +---- subsys/fs/Kconfig.littlefs | 95 +++++++ subsys/fs/littlefs_fs.c | 167 ++++++++---- tests/subsys/fs/littlefs/prj.conf | 12 +- tests/subsys/fs/littlefs/src/main.c | 3 +- tests/subsys/fs/littlefs/src/test_lfs_basic.c | 71 +++++ tests/subsys/fs/littlefs/src/test_lfs_perf.c | 243 ++++++++++++++++++ tests/subsys/fs/littlefs/src/testfs_lfs.c | 27 +- tests/subsys/fs/littlefs/src/testfs_lfs.h | 8 + tests/subsys/fs/littlefs/src/testfs_tests.h | 3 + 13 files changed, 651 insertions(+), 125 deletions(-) create mode 100644 subsys/fs/Kconfig.littlefs create mode 100644 tests/subsys/fs/littlefs/src/test_lfs_perf.c diff --git a/include/fs/littlefs.h b/include/fs/littlefs.h index d5927c914f4..ac18e890456 100644 --- a/include/fs/littlefs.h +++ b/include/fs/littlefs.h @@ -19,19 +19,86 @@ extern "C" { /** @brief Filesystem info structure for LittleFS mount */ struct fs_littlefs { + /* Defaulted in driver, customizable before mount. */ + struct lfs_config cfg; + + /* Must be cfg.cache_size */ + u8_t *read_buffer; + + /* Must be cfg.cache_size */ + u8_t *prog_buffer; + + /* Mustbe cfg.lookahead_size/4 elements, and + * cfg.lookahead_size must be a multiple of 8. + */ + u32_t *lookahead_buffer[CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE / sizeof(u32_t)]; + /* These structures are filled automatically at mount. */ struct lfs lfs; - struct lfs_config cfg; const struct flash_area *area; struct k_mutex mutex; - - /* Static buffers */ - u8_t read_buffer[CONFIG_FS_LITTLEFS_CACHE_SIZE]; - u8_t prog_buffer[CONFIG_FS_LITTLEFS_CACHE_SIZE]; - /* Multiple of 8 bytes, but 4-byte aligned (littlefs #239) */ - u32_t lookahead_buffer[CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE / sizeof(u32_t)]; }; +/** @brief Define a littlefs configuration with customized size + * characteristics. + * + * This defines static arrays required for caches, and initializes the + * littlefs configuration structure to use the provided values instead + * of the global Kconfig defaults. A pointer to the named object must + * be stored in the ``.fs_data`` field of a :c:type:`struct fs_mount` + * object. + * + * To define an instance for the Kconfig defaults, use + * :cpp:func:`FS_LITTLEFS_DECLARE_DEFAULT_CONFIG`. + * + * To completely control file system configuration the application can + * directly define and initialize a :c:type:`struct fs_littlefs` + * object. The application is responsible for ensuring the configured + * values are consistent with littlefs requirements. + * + * @note If you use a non-default configuration for cache size, you + * must also select :option:`CONFIG_FS_LITTLEFS_FC_MEM_POOL` to relax + * the size constraints on per-file cache allocations. + * + * @param name the name for the structure. The defined object has + * file scope. + * @param read_sz see :option:`CONFIG_FS_LITTLEFS_READ_SIZE` + * @param prog_sz see :option:`CONFIG_FS_LITTLEFS_PROG_SIZE` + * @param cache_sz see :option:`CONFIG_FS_LITTLEFS_CACHE_SIZE` + * @param lookahead_sz see :option:`CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE` + */ +#define FS_LITTLEFS_DECLARE_CUSTOM_CONFIG(name, read_sz, prog_sz, cache_sz, lookahead_sz) \ + static u8_t name ## _read_buffer[cache_sz]; \ + static u8_t name ## _prog_buffer[cache_sz]; \ + static u32_t name ## _lookahead_buffer[(lookahead_sz) / sizeof(u32_t)]; \ + static struct fs_littlefs name = { \ + .cfg = { \ + .read_size = (read_sz), \ + .prog_size = (prog_sz), \ + .cache_size = (cache_sz), \ + .lookahead_size = (lookahead_sz), \ + .read_buffer = name ## _read_buffer, \ + .prog_buffer = name ## _prog_buffer, \ + .lookahead_buffer = name ## _lookahead_buffer, \ + }, \ + } + +/** @brief Define a littlefs configuration with default characteristics. + * + * This defines static arrays and initializes the littlefs + * configuration structure to use the default size configuration + * provided by Kconfig. + * + * @param name the name for the structure. The defined object has + * file scope. + */ +#define FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(name) \ + FS_LITTLEFS_DECLARE_CUSTOM_CONFIG(name, \ + CONFIG_FS_LITTLEFS_READ_SIZE, \ + CONFIG_FS_LITTLEFS_PROG_SIZE, \ + CONFIG_FS_LITTLEFS_CACHE_SIZE, \ + CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE) + #ifdef __cplusplus } #endif diff --git a/samples/subsys/fs/littlefs/prj.conf b/samples/subsys/fs/littlefs/prj.conf index 5aedc837f16..833d700fefb 100644 --- a/samples/subsys/fs/littlefs/prj.conf +++ b/samples/subsys/fs/littlefs/prj.conf @@ -10,8 +10,10 @@ # fs_dirent structures are big. CONFIG_MAIN_STACK_SIZE=2048 +# Let __ASSERT do its job +CONFIG_DEBUG=y + CONFIG_LOG=y -#CONFIG_FS_LOG_LEVEL_DBG=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y diff --git a/samples/subsys/fs/littlefs/src/main.c b/samples/subsys/fs/littlefs/src/main.c index 1b056a81f45..2d87a0d461a 100644 --- a/samples/subsys/fs/littlefs/src/main.c +++ b/samples/subsys/fs/littlefs/src/main.c @@ -17,10 +17,10 @@ /* Matches LFS_NAME_MAX */ #define MAX_PATH_LEN 255 -static struct fs_littlefs lfs_storage; +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage); static struct fs_mount_t lfs_storage_mnt = { .type = FS_LITTLEFS, - .fs_data = &lfs_storage, + .fs_data = &storage, .storage_dev = (void *)DT_FLASH_AREA_STORAGE_ID, .mnt_point = "/lfs", }; diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index 4815e6c481c..e6f31f9fda3 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -128,65 +128,9 @@ config NFFS_FILESYSTEM_MAX_BLOCK_SIZE endmenu -menu "LittleFS Settings" - visible if FILE_SYSTEM_LITTLEFS - -config FS_LITTLEFS_NUM_FILES - int "Maximum number of opened files" - default 4 - help - This is a global maximum across all mounted littlefs filesystems. - -config FS_LITTLEFS_NUM_DIRS - int "Maximum number of opened directories" - default 4 - help - This is a global maximum across all mounted littlefs filesystems. - -config FS_LITTLEFS_READ_SIZE - int "Minimum size of a block read" - default 16 - help - All read operations will be a multiple of this value. - -config FS_LITTLEFS_PROG_SIZE - int "Minimum size of a block program" - default 16 - help - All program operations will be a multiple of this value. - -config FS_LITTLEFS_CACHE_SIZE - int "Size of block caches in bytes" - default 64 - help - Each cache buffers a portion of a block in RAM. The littlefs - needs a read cache, a program cache, and one additional cache - per file. Larger caches can improve performance by storing - more data and reducing the number of disk accesses. Must be a - multiple of the read and program sizes of the underlying flash - device, and a factor of the block size. - -config FS_LITTLEFS_LOOKAHEAD_SIZE - int "Size of lookahead buffer in bytes" - default 32 - help - A larger lookahead buffer increases the number of blocks found - during an allocation pass. The lookahead buffer is stored as a - compact bitmap, so each byte of RAM can track 8 blocks. Must - be a multiple of 8. - -config FS_LITTLEFS_BLOCK_CYCLES - int "Number of erase cycles before moving data to another block" - default 512 - help - For dynamic wear leveling, the number of erase cycles before data - is moved to another block. Set to a non-positive value to - disable leveling. - -endmenu - endif # FILE_SYSTEM +source "subsys/fs/Kconfig.littlefs" source "subsys/fs/fcb/Kconfig" source "subsys/fs/nvs/Kconfig" diff --git a/subsys/fs/Kconfig.littlefs b/subsys/fs/Kconfig.littlefs new file mode 100644 index 00000000000..4935ad267bf --- /dev/null +++ b/subsys/fs/Kconfig.littlefs @@ -0,0 +1,95 @@ +# +# Copyright (c) 2019 Bolt Innovation Management, LLC +# Copyright (c) 2019 Peter Bigot Consulting, LLC +# +# SPDX-License-Identifier: Apache-2.0 +# + +menu "LittleFS Settings" + visible if FILE_SYSTEM_LITTLEFS + +config FS_LITTLEFS_NUM_FILES + int "Maximum number of opened files" + default 4 + help + This is a global maximum across all mounted littlefs filesystems. + +config FS_LITTLEFS_NUM_DIRS + int "Maximum number of opened directories" + default 4 + help + This is a global maximum across all mounted littlefs filesystems. + +config FS_LITTLEFS_READ_SIZE + int "Minimum size of a block read" + default 16 + help + All read operations will be a multiple of this value. + +config FS_LITTLEFS_PROG_SIZE + int "Minimum size of a block program" + default 16 + help + All program operations will be a multiple of this value. + +config FS_LITTLEFS_CACHE_SIZE + int "Size of block caches in bytes" + default 64 + help + Each cache buffers a portion of a block in RAM. The littlefs + needs a read cache, a program cache, and one additional cache + per file. Larger caches can improve performance by storing + more data and reducing the number of disk accesses. Must be a + multiple of the read and program sizes of the underlying flash + device, and a factor of the block size. + +config FS_LITTLEFS_LOOKAHEAD_SIZE + int "Size of lookahead buffer in bytes" + default 32 + help + A larger lookahead buffer increases the number of blocks found + during an allocation pass. The lookahead buffer is stored as a + compact bitmap, so each byte of RAM can track 8 blocks. Must + be a multiple of 8. + +config FS_LITTLEFS_BLOCK_CYCLES + int "Number of erase cycles before moving data to another block" + default 512 + help + For dynamic wear leveling, the number of erase cycles before data + is moved to another block. Set to a non-positive value to + disable leveling. + +menuconfig FS_LITTLEFS_FC_MEM_POOL + bool "Enable flexible file cache sizes for littlefs" + help + littlefs requires a per-file buffer to cache data. For + applications that use the default configuration parameters a + memory slab is reserved to support up to + FS_LITTLE_FS_NUM_FILES blocks of FS_LITTLEFS_CACHE_SIZE bytes. + + When applications customize littlefs configurations and + support different cache sizes for different partitions this + preallocation is inadequate. + + Select this feature to enable a memory pool allocator for + littlefs file caches. + +if FS_LITTLEFS_FC_MEM_POOL + +config FS_LITTLEFS_FC_MEM_POOL_MIN_SIZE + int "Minimum block size for littlefs file cache memory pool" + default 16 + +config FS_LITTLEFS_FC_MEM_POOL_MAX_SIZE + int "Maximum block size for littlefs file cache memory pool" + default 1024 + +config FS_LITTLEFS_FC_MEM_POOL_NUM_BLOCKS + int "Number of maximum sized blocks in littlefs file cache memory pool" + default 2 + +endif # FS_LITTLEFS_FC_MEM_POOL + +endmenu + diff --git a/subsys/fs/littlefs_fs.c b/subsys/fs/littlefs_fs.c index e31010c139f..da9e89385fe 100644 --- a/subsys/fs/littlefs_fs.c +++ b/subsys/fs/littlefs_fs.c @@ -22,20 +22,35 @@ #include "fs_impl.h" -struct lfs_file_cache { +struct lfs_file_data { struct lfs_file file; struct lfs_file_config config; - u8_t cache[CONFIG_FS_LITTLEFS_CACHE_SIZE]; + struct k_mem_block cache_block; }; -#define LFS_FILEP(fp) (&((struct lfs_file_cache *)(fp->filep))->file) +#define LFS_FILEP(fp) (&((struct lfs_file_data *)(fp->filep))->file) /* Global memory pool for open files and dirs */ -K_MEM_SLAB_DEFINE(lfs_file_pool, sizeof(struct lfs_file_cache), +K_MEM_SLAB_DEFINE(file_data_pool, sizeof(struct lfs_file_data), CONFIG_FS_LITTLEFS_NUM_FILES, 4); K_MEM_SLAB_DEFINE(lfs_dir_pool, sizeof(struct lfs_dir), CONFIG_FS_LITTLEFS_NUM_DIRS, 4); +/* If not explicitly customizing provide a default that's appropriate + * based on other configuration options. + */ +#ifndef CONFIG_FS_LITTLEFS_FC_MEM_POOL +BUILD_ASSERT(CONFIG_FS_LITTLEFS_CACHE_SIZE >= 4); +#define CONFIG_FS_LITTLEFS_FC_MEM_POOL_MIN_SIZE 4 +#define CONFIG_FS_LITTLEFS_FC_MEM_POOL_MAX_SIZE CONFIG_FS_LITTLEFS_CACHE_SIZE +#define CONFIG_FS_LITTLEFS_FC_MEM_POOL_NUM_BLOCKS CONFIG_FS_LITTLEFS_NUM_FILES +#endif + +K_MEM_POOL_DEFINE(file_cache_pool, + CONFIG_FS_LITTLEFS_FC_MEM_POOL_MIN_SIZE, + CONFIG_FS_LITTLEFS_FC_MEM_POOL_MAX_SIZE, + CONFIG_FS_LITTLEFS_FC_MEM_POOL_NUM_BLOCKS, 4); + static inline void fs_lock(struct fs_littlefs *fs) { k_mutex_lock(&fs->mutex, K_FOREVER); @@ -154,36 +169,53 @@ static int lfs_api_sync(const struct lfs_config *c) return LFS_ERR_OK; } +static void release_file_data(struct fs_file_t *fp) +{ + struct lfs_file_data *fdp = fp->filep; + + if (fdp->config.buffer) { + k_mem_pool_free(&fdp->cache_block); + } + + k_mem_slab_free(&file_data_pool, &fp->filep); + fp->filep = NULL; +} + static int littlefs_open(struct fs_file_t *fp, const char *path) { struct fs_littlefs *fs = fp->mp->fs_data; + struct lfs *lfs = &fs->lfs; int flags = LFS_O_CREAT | LFS_O_RDWR; + int ret; - if (k_mem_slab_alloc(&lfs_file_pool, &fp->filep, K_NO_WAIT) != 0) { - return -ENOMEM; + ret = k_mem_slab_alloc(&file_data_pool, &fp->filep, K_NO_WAIT); + if (ret != 0) { + return ret; } + struct lfs_file_data *fdp = fp->filep; + + memset(fdp, 0, sizeof(*fdp)); + + ret = k_mem_pool_alloc(&file_cache_pool, &fdp->cache_block, + lfs->cfg->cache_size, K_NO_WAIT); + LOG_DBG("alloc %u file cache: %d", lfs->cfg->cache_size, ret); + if (ret != 0) { + goto out; + } + + fdp->config.buffer = fdp->cache_block.data; + path = fs_impl_strip_prefix(path, fp->mp); + fs_lock(fs); - /* Use cache inside the slab allocation, instead of letting - * littlefs allocate it from the heap. - */ - struct lfs_file_cache *fc = fp->filep; - - fc->config = (struct lfs_file_config) { - .buffer = fc->cache, - }; - memset(&fc->file, 0, sizeof(struct lfs_file)); - - path = fs_impl_strip_prefix(path, fp->mp); - - int ret = lfs_file_opencfg(&fs->lfs, &fc->file, - path, flags, &fc->config); + ret = lfs_file_opencfg(&fs->lfs, &fdp->file, + path, flags, &fdp->config); fs_unlock(fs); - +out: if (ret < 0) { - k_mem_slab_free(&lfs_file_pool, &fp->filep); + release_file_data(fp); } return lfs_to_errno(ret); @@ -197,9 +229,10 @@ static int littlefs_close(struct fs_file_t *fp) int ret = lfs_file_close(&fs->lfs, LFS_FILEP(fp)); - k_mem_slab_free(&lfs_file_pool, &fp->filep); - fs_unlock(fs); + + release_file_data(fp); + return lfs_to_errno(ret); } @@ -542,25 +575,64 @@ static int littlefs_mount(struct fs_mount_t *mountp) BUILD_ASSERT((CONFIG_FS_LITTLEFS_CACHE_SIZE % CONFIG_FS_LITTLEFS_PROG_SIZE) == 0); - lfs_size_t read_size = CONFIG_FS_LITTLEFS_READ_SIZE; - lfs_size_t prog_size = CONFIG_FS_LITTLEFS_PROG_SIZE; - lfs_size_t block_size = get_block_size(fs->area); - s32_t block_cycles = CONFIG_FS_LITTLEFS_BLOCK_CYCLES; - lfs_size_t cache_size = CONFIG_FS_LITTLEFS_CACHE_SIZE; - lfs_size_t lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE; + struct lfs_config *lcp = &fs->cfg; + lfs_size_t read_size = lcp->read_size; + + if (read_size == 0) { + read_size = CONFIG_FS_LITTLEFS_READ_SIZE; + } + + lfs_size_t prog_size = lcp->prog_size; + + if (prog_size == 0) { + prog_size = CONFIG_FS_LITTLEFS_PROG_SIZE; + } + + /* Yes, you can override block size. */ + lfs_size_t block_size = lcp->block_size; + + if (block_size == 0) { + block_size = get_block_size(fs->area); + } + + s32_t block_cycles = lcp->block_cycles; + + if (block_cycles == 0) { + block_cycles = CONFIG_FS_LITTLEFS_BLOCK_CYCLES; + } if (block_cycles <= 0) { /* Disable leveling (littlefs v2.1+ semantics) */ block_cycles = -1; } + lfs_size_t cache_size = lcp->cache_size; + + if (cache_size == 0) { + cache_size = CONFIG_FS_LITTLEFS_CACHE_SIZE; + } + + lfs_size_t lookahead_size = lcp->lookahead_size; + + if (lookahead_size == 0) { + lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE; + } + + + /* No, you don't get to override this. */ lfs_size_t block_count = fs->area->fa_size / block_size; - LOG_DBG("FS at %s is %u 0x%x-byte blocks with %u cycle", dev->config->name, + LOG_INF("FS at %s:0x%x is %u 0x%x-byte blocks with %u cycle", + dev->config->name, (u32_t)fs->area->fa_off, block_count, block_size, block_cycles); - LOG_DBG("sizes: rd %u ; pr %u ; ca %u ; la %u", + LOG_INF("sizes: rd %u ; pr %u ; ca %u ; la %u", read_size, prog_size, cache_size, lookahead_size); + __ASSERT_NO_MSG(prog_size != 0); + __ASSERT_NO_MSG(read_size != 0); + __ASSERT_NO_MSG(cache_size != 0); + __ASSERT_NO_MSG(block_size != 0); + __ASSERT((fs->area->fa_size % block_size) == 0, "partition size must be multiple of block size"); __ASSERT((block_size % prog_size) == 0, @@ -568,24 +640,19 @@ static int littlefs_mount(struct fs_mount_t *mountp) __ASSERT((block_size % cache_size) == 0, "cache size incompatible with block size"); - /* Build littlefs config */ - fs->cfg = (struct lfs_config) { - .context = (void *)fs->area, - .read = lfs_api_read, - .prog = lfs_api_prog, - .erase = lfs_api_erase, - .sync = lfs_api_sync, - .read_size = read_size, - .prog_size = prog_size, - .block_size = block_size, - .block_count = block_count, - .block_cycles = block_cycles, - .cache_size = cache_size, - .lookahead_size = lookahead_size, - .read_buffer = fs->read_buffer, - .prog_buffer = fs->prog_buffer, - .lookahead_buffer = fs->lookahead_buffer - }; + /* Set the validated/defaulted values. */ + lcp->context = (void *)fs->area; + lcp->read = lfs_api_read; + lcp->prog = lfs_api_prog; + lcp->erase = lfs_api_erase; + lcp->sync = lfs_api_sync; + lcp->read_size = read_size; + lcp->prog_size = prog_size; + lcp->block_size = block_size; + lcp->block_count = block_count; + lcp->block_cycles = block_cycles; + lcp->cache_size = cache_size; + lcp->lookahead_size = lookahead_size; /* Mount it, formatting if needed. */ ret = lfs_mount(&fs->lfs, &fs->cfg); diff --git a/tests/subsys/fs/littlefs/prj.conf b/tests/subsys/fs/littlefs/prj.conf index 74025e6823e..3458e1fb9ba 100644 --- a/tests/subsys/fs/littlefs/prj.conf +++ b/tests/subsys/fs/littlefs/prj.conf @@ -3,7 +3,17 @@ CONFIG_FILE_SYSTEM_LITTLEFS=y CONFIG_MAIN_STACK_SIZE=4096 -CONFIG_LOG=y +# Performance tests need custom buffer allocation +CONFIG_MINIMAL_LIBC_MALLOC_ARENA_SIZE=8192 + +# Configuration tests require variable cache sizes +CONFIG_FS_LITTLEFS_FC_MEM_POOL=y + +# FS abstraction layer is noisy so it's off. Turn it on to see the +# littlefs configuration parameters. +#CONFIG_LOG=y +#CONFIG_FS_LOG_LEVEL_DBG=y + CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FLASH_PAGE_LAYOUT=y diff --git a/tests/subsys/fs/littlefs/src/main.c b/tests/subsys/fs/littlefs/src/main.c index 8daaf341495..15b4176eba9 100644 --- a/tests/subsys/fs/littlefs/src/main.c +++ b/tests/subsys/fs/littlefs/src/main.c @@ -20,7 +20,8 @@ void test_main(void) ztest_unit_test(test_util_path_extend_up), ztest_unit_test(test_util_path_extend_overrun), ztest_unit_test(test_lfs_basic), - ztest_unit_test(test_lfs_dirops) + ztest_unit_test(test_lfs_dirops), + ztest_unit_test(test_lfs_perf) ); ztest_run_test_suite(littlefs_test); } diff --git a/tests/subsys/fs/littlefs/src/test_lfs_basic.c b/tests/subsys/fs/littlefs/src/test_lfs_basic.c index 59fda9e9e5f..80448267ec3 100644 --- a/tests/subsys/fs/littlefs/src/test_lfs_basic.c +++ b/tests/subsys/fs/littlefs/src/test_lfs_basic.c @@ -409,6 +409,70 @@ static int verify_goodbye(const struct fs_mount_t *mp) return TC_PASS; } +static int check_medium(void) +{ + struct fs_mount_t *mp = &testfs_medium_mnt; + struct fs_statvfs stat; + + zassert_equal(clear_partition(mp), TC_PASS, + "clear partition failed"); + + zassert_equal(fs_mount(mp), 0, + "medium mount failed"); + + zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, + "statvfs failed"); + + TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", + mp->mnt_point, + stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); + zassert_equal(stat.f_bsize, MEDIUM_IO_SIZE, + "bsize fail"); + zassert_equal(stat.f_frsize, 4096, + "frsize fail"); + zassert_equal(stat.f_blocks, 240, + "blocks fail"); + zassert_equal(stat.f_bfree, stat.f_blocks - 2U, + "bfree fail"); + + zassert_equal(fs_unmount(mp), 0, + "medium unmount failed"); + + return TC_PASS; +} + +static int check_large(void) +{ + struct fs_mount_t *mp = &testfs_large_mnt; + struct fs_statvfs stat; + + zassert_equal(clear_partition(mp), TC_PASS, + "clear partition failed"); + + zassert_equal(fs_mount(mp), 0, + "large mount failed"); + + zassert_equal(fs_statvfs(mp->mnt_point, &stat), 0, + "statvfs failed"); + + TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", + mp->mnt_point, + stat.f_bsize, stat.f_frsize, stat.f_blocks, stat.f_bfree); + zassert_equal(stat.f_bsize, LARGE_IO_SIZE, + "bsize fail"); + zassert_equal(stat.f_frsize, 32768, + "frsize fail"); + zassert_equal(stat.f_blocks, 96, + "blocks fail"); + zassert_equal(stat.f_bfree, stat.f_blocks - 2U, + "bfree fail"); + + zassert_equal(fs_unmount(mp), 0, + "large unmount failed"); + + return TC_PASS; +} + void test_lfs_basic(void) { struct fs_mount_t *mp = &testfs_small_mnt; @@ -457,4 +521,11 @@ void test_lfs_basic(void) zassert_equal(fs_unmount(mp), 0, "unmount2 small failed"); + + zassert_equal(check_medium(), TC_PASS, + "check medium failed"); + + zassert_equal(check_large(), TC_PASS, + "check large failed"); + } diff --git a/tests/subsys/fs/littlefs/src/test_lfs_perf.c b/tests/subsys/fs/littlefs/src/test_lfs_perf.c new file mode 100644 index 00000000000..69fe51b3cda --- /dev/null +++ b/tests/subsys/fs/littlefs/src/test_lfs_perf.c @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* littlefs performance testing */ + +#include +#include +#include +#include +#include "testfs_tests.h" +#include "testfs_lfs.h" +#include + +#include + +#define HELLO "hello" +#define GOODBYE "goodbye" + +static int write_read(const char *tag, + struct fs_mount_t *mp, + size_t buf_size, + size_t nbuf) +{ + const struct lfs_config *lcp = &((const struct fs_littlefs *)mp->fs_data)->cfg; + struct testfs_path path; + struct fs_statvfs vfs; + struct fs_dirent stat; + struct fs_file_t file; + size_t total = nbuf * buf_size; + u32_t t0; + u32_t t1; + u8_t *buf; + int rc; + int rv = TC_FAIL; + + TC_PRINT("clearing %s for %s write/read test\n", + mp->mnt_point, tag); + if (testfs_lfs_wipe_partition(mp) != TC_PASS) { + return TC_FAIL; + } + + rc = fs_mount(mp); + if (rc != 0) { + TC_PRINT("Mount %s failed: %d\n", mp->mnt_point, rc); + return TC_FAIL; + } + + rc = fs_statvfs(mp->mnt_point, &vfs); + if (rc != 0) { + TC_PRINT("statvfs %s failed: %d\n", mp->mnt_point, rc); + goto out_mnt; + } + + TC_PRINT("%s: bsize %lu ; frsize %lu ; blocks %lu ; bfree %lu\n", + mp->mnt_point, + vfs.f_bsize, vfs.f_frsize, vfs.f_blocks, vfs.f_bfree); + TC_PRINT("read_size %u ; prog_size %u ; cache_size %u ; lookahead_size %u\n", + lcp->read_size, lcp->prog_size, lcp->cache_size, lcp->lookahead_size); + + testfs_path_init(&path, mp, + "data", + TESTFS_PATH_END); + + buf = calloc(buf_size, sizeof(u8_t)); + if (buf == NULL) { + TC_PRINT("Failed to allocate %zu-byte buffer\n", buf_size); + goto out_mnt; + } + + for (size_t i = 0; i < buf_size; ++i) { + buf[i] = i; + } + + TC_PRINT("creating and writing %zu %zu-byte blocks\n", + nbuf, buf_size); + + rc = fs_open(&file, path.path); + if (rc != 0) { + TC_PRINT("Failed to open %s for write: %d\n", path.path, rc); + goto out_buf; + } + + t0 = k_uptime_get_32(); + for (size_t i = 0; i < nbuf; ++i) { + rc = fs_write(&file, buf, buf_size); + if (buf_size != rc) { + TC_PRINT("Failed to write buf %u: %d\n", i, rc); + goto out_file; + } + } + t1 = k_uptime_get_32(); + + (void)fs_close(&file); + + rc = fs_stat(path.path, &stat); + if (rc != 0) { + TC_PRINT("Failed to stat %s: %d\n", path.path, rc); + goto out_buf; + } + + if (stat.size != total) { + TC_PRINT("File size %zu not %zu\n", stat.size, total); + goto out_buf; + } + + TC_PRINT("%s write %zu * %zu = %zu bytes in %u ms: " + "%u By/s, %u KiBy/s\n", + tag, nbuf, buf_size, total, (t1 - t0), + (u32_t)(total * 1000U / (t1 - t0)), + (u32_t)(total * 1000U / (t1 - t0) / 1024U)); + + rc = fs_open(&file, path.path); + if (rc != 0) { + TC_PRINT("Failed to open %s for write: %d\n", path.path, rc); + goto out_buf; + } + + t0 = k_uptime_get_32(); + for (size_t i = 0; i < nbuf; ++i) { + rc = fs_read(&file, buf, buf_size); + if (buf_size != rc) { + TC_PRINT("Failed to read buf %u: %d\n", i, rc); + goto out_file; + } + } + t1 = k_uptime_get_32(); + + TC_PRINT("%s read %zu * %zu = %zu bytes in %u ms: " + "%u By/s, %u KiBy/s\n", + tag, nbuf, buf_size, total, (t1 - t0), + (u32_t)(total * 1000U / (t1 - t0)), + (u32_t)(total * 1000U / (t1 - t0) / 1024U)); + + rv = TC_PASS; + +out_file: + (void)fs_close(&file); + +out_buf: + free(buf); + +out_mnt: + (void)fs_unmount(mp); + + return rv; +} + +static int custom_write_test(const char *tag, + const struct fs_mount_t *mp, + const struct lfs_config *cfgp, + size_t buf_size, + size_t nbuf) +{ + struct fs_littlefs data = { + .cfg = *cfgp, + }; + struct fs_mount_t lfs_mnt = { + .type = FS_LITTLEFS, + .fs_data = &data, + .storage_dev = mp->storage_dev, + .mnt_point = mp->mnt_point, + }; + struct lfs_config *lcp = &data.cfg; + int rv = TC_FAIL; + + if (lcp->cache_size == 0) { + lcp->cache_size = CONFIG_FS_LITTLEFS_CACHE_SIZE; + } + if (lcp->lookahead_size == 0) { + lcp->lookahead_size = CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE; + } + + lcp->read_buffer = malloc(lcp->cache_size); + lcp->prog_buffer = malloc(lcp->cache_size); + lcp->lookahead_buffer = malloc(lcp->lookahead_size); + + TC_PRINT("bufs %p %p %p\n", lcp->read_buffer, lcp->prog_buffer, lcp->lookahead_buffer); + + if ((lcp->read_buffer == NULL) + || (lcp->prog_buffer == NULL) + || (lcp->lookahead_buffer == NULL)) { + TC_PRINT("%s buffer allocation failed\n", tag); + goto out_free; + } + + rv = write_read(tag, &lfs_mnt, buf_size, nbuf); + +out_free: + if (lcp->read_buffer) { + free(lcp->read_buffer); + } + if (lcp->prog_buffer) { + free(lcp->prog_buffer); + } + if (lcp->lookahead_buffer) { + free(lcp->lookahead_buffer); + } + + return rv; +} + +static int small_8_1K_cust(void) +{ + struct lfs_config cfg = { + .read_size = LARGE_IO_SIZE, + .prog_size = LARGE_IO_SIZE, + .cache_size = LARGE_CACHE_SIZE, + .lookahead_size = LARGE_LOOKAHEAD_SIZE + }; + + return custom_write_test("small 8x1K bigfile", &testfs_small_mnt, &cfg, 1024, 8); +} + +void test_lfs_perf(void) +{ + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(write_read("small 8x1K dflt", + &testfs_small_mnt, + 1024, 8), + TC_PASS, + "failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(small_8_1K_cust(), TC_PASS, + "failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(write_read("medium 32x2K dflt", + &testfs_medium_mnt, + 2048, 32), + TC_PASS, + "failed"); + + k_sleep(K_MSEC(100)); /* flush log messages */ + zassert_equal(write_read("large 64x4K dflt", + &testfs_large_mnt, + 4096, 64), + TC_PASS, + "failed"); +} diff --git a/tests/subsys/fs/littlefs/src/testfs_lfs.c b/tests/subsys/fs/littlefs/src/testfs_lfs.c index 53c270a1eb6..4e59cc27be8 100644 --- a/tests/subsys/fs/littlefs/src/testfs_lfs.c +++ b/tests/subsys/fs/littlefs/src/testfs_lfs.c @@ -9,26 +9,41 @@ #include #include "testfs_lfs.h" -static struct fs_littlefs small_data; +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(small); struct fs_mount_t testfs_small_mnt = { .type = FS_LITTLEFS, - .fs_data = &small_data, + .fs_data = &small, .storage_dev = (void *)DT_FLASH_AREA_SMALL_ID, .mnt_point = TESTFS_MNT_POINT_SMALL, }; -static struct fs_littlefs medium_data; +FS_LITTLEFS_DECLARE_CUSTOM_CONFIG(medium, MEDIUM_IO_SIZE, MEDIUM_IO_SIZE, + MEDIUM_CACHE_SIZE, MEDIUM_LOOKAHEAD_SIZE); struct fs_mount_t testfs_medium_mnt = { .type = FS_LITTLEFS, - .fs_data = &medium_data, + .fs_data = &medium, .storage_dev = (void *)DT_FLASH_AREA_MEDIUM_ID, .mnt_point = TESTFS_MNT_POINT_MEDIUM, }; -static struct fs_littlefs large_data; +static u8_t large_read_buffer[LARGE_CACHE_SIZE]; +static u8_t large_prog_buffer[LARGE_CACHE_SIZE]; +static u32_t large_lookahead_buffer[LARGE_LOOKAHEAD_SIZE / 4U]; +static struct fs_littlefs large = { + .cfg = { + .read_size = LARGE_IO_SIZE, + .prog_size = LARGE_IO_SIZE, + .cache_size = LARGE_CACHE_SIZE, + .lookahead_size = LARGE_LOOKAHEAD_SIZE, + .block_size = 32768, /* increase erase size */ + .read_buffer = large_read_buffer, + .prog_buffer = large_prog_buffer, + .lookahead_buffer = large_lookahead_buffer, + }, +}; struct fs_mount_t testfs_large_mnt = { .type = FS_LITTLEFS, - .fs_data = &large_data, + .fs_data = &large, .storage_dev = (void *)DT_FLASH_AREA_LARGE_ID, .mnt_point = TESTFS_MNT_POINT_LARGE, }; diff --git a/tests/subsys/fs/littlefs/src/testfs_lfs.h b/tests/subsys/fs/littlefs/src/testfs_lfs.h index e0d0a0b952c..6e5402e1328 100644 --- a/tests/subsys/fs/littlefs/src/testfs_lfs.h +++ b/tests/subsys/fs/littlefs/src/testfs_lfs.h @@ -18,6 +18,14 @@ extern struct fs_mount_t testfs_small_mnt; extern struct fs_mount_t testfs_medium_mnt; extern struct fs_mount_t testfs_large_mnt; +#define MEDIUM_IO_SIZE 64 +#define MEDIUM_CACHE_SIZE 256 +#define MEDIUM_LOOKAHEAD_SIZE 64 + +#define LARGE_IO_SIZE 256 +#define LARGE_CACHE_SIZE 1024 +#define LARGE_LOOKAHEAD_SIZE 128 + /** Wipe all data from the flash partition associated with the given * mount point. * diff --git a/tests/subsys/fs/littlefs/src/testfs_tests.h b/tests/subsys/fs/littlefs/src/testfs_tests.h index 033f41bdcf6..a40ab59e4e3 100644 --- a/tests/subsys/fs/littlefs/src/testfs_tests.h +++ b/tests/subsys/fs/littlefs/src/testfs_tests.h @@ -21,4 +21,7 @@ void test_lfs_basic(void); /* Tests in test_lfs_dirops */ void test_lfs_dirops(void); +/* Tests in test_lfs_perf */ +void test_lfs_perf(void); + #endif /* _ZEPHYR_TESTS_SUBSYS_FS_LITTLEFS_TESTFS_TESTS_H_ */