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 <pab@pabigot.com>
This commit is contained in:
Peter A. Bigot 2019-08-02 19:48:05 -05:00 committed by Carles Cufí
commit 167eb53e74
13 changed files with 651 additions and 125 deletions

View file

@ -19,19 +19,86 @@ extern "C" {
/** @brief Filesystem info structure for LittleFS mount */ /** @brief Filesystem info structure for LittleFS mount */
struct fs_littlefs { 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. */ /* These structures are filled automatically at mount. */
struct lfs lfs; struct lfs lfs;
struct lfs_config cfg;
const struct flash_area *area; const struct flash_area *area;
struct k_mutex mutex; 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -10,8 +10,10 @@
# fs_dirent structures are big. # fs_dirent structures are big.
CONFIG_MAIN_STACK_SIZE=2048 CONFIG_MAIN_STACK_SIZE=2048
# Let __ASSERT do its job
CONFIG_DEBUG=y
CONFIG_LOG=y CONFIG_LOG=y
#CONFIG_FS_LOG_LEVEL_DBG=y
CONFIG_FLASH=y CONFIG_FLASH=y
CONFIG_FLASH_MAP=y CONFIG_FLASH_MAP=y

View file

@ -17,10 +17,10 @@
/* Matches LFS_NAME_MAX */ /* Matches LFS_NAME_MAX */
#define MAX_PATH_LEN 255 #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 = { static struct fs_mount_t lfs_storage_mnt = {
.type = FS_LITTLEFS, .type = FS_LITTLEFS,
.fs_data = &lfs_storage, .fs_data = &storage,
.storage_dev = (void *)DT_FLASH_AREA_STORAGE_ID, .storage_dev = (void *)DT_FLASH_AREA_STORAGE_ID,
.mnt_point = "/lfs", .mnt_point = "/lfs",
}; };

View file

@ -128,65 +128,9 @@ config NFFS_FILESYSTEM_MAX_BLOCK_SIZE
endmenu 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 endif # FILE_SYSTEM
source "subsys/fs/Kconfig.littlefs"
source "subsys/fs/fcb/Kconfig" source "subsys/fs/fcb/Kconfig"
source "subsys/fs/nvs/Kconfig" source "subsys/fs/nvs/Kconfig"

View file

@ -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

View file

@ -22,20 +22,35 @@
#include "fs_impl.h" #include "fs_impl.h"
struct lfs_file_cache { struct lfs_file_data {
struct lfs_file file; struct lfs_file file;
struct lfs_file_config config; 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 */ /* 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); CONFIG_FS_LITTLEFS_NUM_FILES, 4);
K_MEM_SLAB_DEFINE(lfs_dir_pool, sizeof(struct lfs_dir), K_MEM_SLAB_DEFINE(lfs_dir_pool, sizeof(struct lfs_dir),
CONFIG_FS_LITTLEFS_NUM_DIRS, 4); 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) static inline void fs_lock(struct fs_littlefs *fs)
{ {
k_mutex_lock(&fs->mutex, K_FOREVER); 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; 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) static int littlefs_open(struct fs_file_t *fp, const char *path)
{ {
struct fs_littlefs *fs = fp->mp->fs_data; struct fs_littlefs *fs = fp->mp->fs_data;
struct lfs *lfs = &fs->lfs;
int flags = LFS_O_CREAT | LFS_O_RDWR; int flags = LFS_O_CREAT | LFS_O_RDWR;
int ret;
if (k_mem_slab_alloc(&lfs_file_pool, &fp->filep, K_NO_WAIT) != 0) { ret = k_mem_slab_alloc(&file_data_pool, &fp->filep, K_NO_WAIT);
return -ENOMEM; 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); fs_lock(fs);
/* Use cache inside the slab allocation, instead of letting ret = lfs_file_opencfg(&fs->lfs, &fdp->file,
* littlefs allocate it from the heap. path, flags, &fdp->config);
*/
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);
fs_unlock(fs); fs_unlock(fs);
out:
if (ret < 0) { if (ret < 0) {
k_mem_slab_free(&lfs_file_pool, &fp->filep); release_file_data(fp);
} }
return lfs_to_errno(ret); 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)); int ret = lfs_file_close(&fs->lfs, LFS_FILEP(fp));
k_mem_slab_free(&lfs_file_pool, &fp->filep);
fs_unlock(fs); fs_unlock(fs);
release_file_data(fp);
return lfs_to_errno(ret); 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 BUILD_ASSERT((CONFIG_FS_LITTLEFS_CACHE_SIZE
% CONFIG_FS_LITTLEFS_PROG_SIZE) == 0); % CONFIG_FS_LITTLEFS_PROG_SIZE) == 0);
lfs_size_t read_size = CONFIG_FS_LITTLEFS_READ_SIZE; struct lfs_config *lcp = &fs->cfg;
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;
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) { if (block_cycles <= 0) {
/* Disable leveling (littlefs v2.1+ semantics) */ /* Disable leveling (littlefs v2.1+ semantics) */
block_cycles = -1; 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; 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); 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); 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, __ASSERT((fs->area->fa_size % block_size) == 0,
"partition size must be multiple of block size"); "partition size must be multiple of block size");
__ASSERT((block_size % prog_size) == 0, __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, __ASSERT((block_size % cache_size) == 0,
"cache size incompatible with block size"); "cache size incompatible with block size");
/* Build littlefs config */ /* Set the validated/defaulted values. */
fs->cfg = (struct lfs_config) { lcp->context = (void *)fs->area;
.context = (void *)fs->area, lcp->read = lfs_api_read;
.read = lfs_api_read, lcp->prog = lfs_api_prog;
.prog = lfs_api_prog, lcp->erase = lfs_api_erase;
.erase = lfs_api_erase, lcp->sync = lfs_api_sync;
.sync = lfs_api_sync, lcp->read_size = read_size;
.read_size = read_size, lcp->prog_size = prog_size;
.prog_size = prog_size, lcp->block_size = block_size;
.block_size = block_size, lcp->block_count = block_count;
.block_count = block_count, lcp->block_cycles = block_cycles;
.block_cycles = block_cycles, lcp->cache_size = cache_size;
.cache_size = cache_size, lcp->lookahead_size = lookahead_size;
.lookahead_size = lookahead_size,
.read_buffer = fs->read_buffer,
.prog_buffer = fs->prog_buffer,
.lookahead_buffer = fs->lookahead_buffer
};
/* Mount it, formatting if needed. */ /* Mount it, formatting if needed. */
ret = lfs_mount(&fs->lfs, &fs->cfg); ret = lfs_mount(&fs->lfs, &fs->cfg);

View file

@ -3,7 +3,17 @@ CONFIG_FILE_SYSTEM_LITTLEFS=y
CONFIG_MAIN_STACK_SIZE=4096 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=y
CONFIG_FLASH_MAP=y CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y CONFIG_FLASH_PAGE_LAYOUT=y

View file

@ -20,7 +20,8 @@ void test_main(void)
ztest_unit_test(test_util_path_extend_up), ztest_unit_test(test_util_path_extend_up),
ztest_unit_test(test_util_path_extend_overrun), ztest_unit_test(test_util_path_extend_overrun),
ztest_unit_test(test_lfs_basic), 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); ztest_run_test_suite(littlefs_test);
} }

View file

@ -409,6 +409,70 @@ static int verify_goodbye(const struct fs_mount_t *mp)
return TC_PASS; 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) void test_lfs_basic(void)
{ {
struct fs_mount_t *mp = &testfs_small_mnt; struct fs_mount_t *mp = &testfs_small_mnt;
@ -457,4 +521,11 @@ void test_lfs_basic(void)
zassert_equal(fs_unmount(mp), 0, zassert_equal(fs_unmount(mp), 0,
"unmount2 small failed"); "unmount2 small failed");
zassert_equal(check_medium(), TC_PASS,
"check medium failed");
zassert_equal(check_large(), TC_PASS,
"check large failed");
} }

View file

@ -0,0 +1,243 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
/* littlefs performance testing */
#include <string.h>
#include <stdlib.h>
#include <kernel.h>
#include <ztest.h>
#include "testfs_tests.h"
#include "testfs_lfs.h"
#include <lfs.h>
#include <fs/littlefs.h>
#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");
}

View file

@ -9,26 +9,41 @@
#include <storage/flash_map.h> #include <storage/flash_map.h>
#include "testfs_lfs.h" #include "testfs_lfs.h"
static struct fs_littlefs small_data; FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(small);
struct fs_mount_t testfs_small_mnt = { struct fs_mount_t testfs_small_mnt = {
.type = FS_LITTLEFS, .type = FS_LITTLEFS,
.fs_data = &small_data, .fs_data = &small,
.storage_dev = (void *)DT_FLASH_AREA_SMALL_ID, .storage_dev = (void *)DT_FLASH_AREA_SMALL_ID,
.mnt_point = TESTFS_MNT_POINT_SMALL, .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 = { struct fs_mount_t testfs_medium_mnt = {
.type = FS_LITTLEFS, .type = FS_LITTLEFS,
.fs_data = &medium_data, .fs_data = &medium,
.storage_dev = (void *)DT_FLASH_AREA_MEDIUM_ID, .storage_dev = (void *)DT_FLASH_AREA_MEDIUM_ID,
.mnt_point = TESTFS_MNT_POINT_MEDIUM, .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 = { struct fs_mount_t testfs_large_mnt = {
.type = FS_LITTLEFS, .type = FS_LITTLEFS,
.fs_data = &large_data, .fs_data = &large,
.storage_dev = (void *)DT_FLASH_AREA_LARGE_ID, .storage_dev = (void *)DT_FLASH_AREA_LARGE_ID,
.mnt_point = TESTFS_MNT_POINT_LARGE, .mnt_point = TESTFS_MNT_POINT_LARGE,
}; };

View file

@ -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_medium_mnt;
extern struct fs_mount_t testfs_large_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 /** Wipe all data from the flash partition associated with the given
* mount point. * mount point.
* *

View file

@ -21,4 +21,7 @@ void test_lfs_basic(void);
/* Tests in test_lfs_dirops */ /* Tests in test_lfs_dirops */
void test_lfs_dirops(void); void test_lfs_dirops(void);
/* Tests in test_lfs_perf */
void test_lfs_perf(void);
#endif /* _ZEPHYR_TESTS_SUBSYS_FS_LITTLEFS_TESTFS_TESTS_H_ */ #endif /* _ZEPHYR_TESTS_SUBSYS_FS_LITTLEFS_TESTFS_TESTS_H_ */