From fb73fcd4baf360666b51627bfca38b659a74a4e1 Mon Sep 17 00:00:00 2001 From: "Peter A. Bigot" Date: Sun, 21 Jul 2019 11:28:29 -0500 Subject: [PATCH] subsys/fs: add support for littlefs littlefs is a fail-safe filesystem from ARM Mbed that has wear-leveling capabilities. Signed-off-by: Peter A. Bigot Signed-off-by: Jim Paris --- CODEOWNERS | 1 + include/fs/fs.h | 1 + include/fs/fs_interface.h | 2 +- include/fs/littlefs.h | 39 +++ subsys/fs/CMakeLists.txt | 12 +- subsys/fs/Kconfig | 64 ++++ subsys/fs/littlefs_fs.c | 665 ++++++++++++++++++++++++++++++++++++++ west.yml | 3 + 8 files changed, 781 insertions(+), 6 deletions(-) create mode 100644 include/fs/littlefs.h create mode 100644 subsys/fs/littlefs_fs.c diff --git a/CODEOWNERS b/CODEOWNERS index 19625de86c54..430873ef195f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -337,6 +337,7 @@ /subsys/fs/ @nashif /subsys/fs/fcb/ @nvlsianpu /subsys/fs/fuse_fs_access.c @vanwinkeljan +/subsys/fs/littlefs_fs.c @pabigot /subsys/fs/nvs/ @Laczen /subsys/logging/ @nordic-krch /subsys/mgmt/ @carlescufi @nvlsianpu diff --git a/include/fs/fs.h b/include/fs/fs.h index d8157445f8c9..50ccf1fb64da 100644 --- a/include/fs/fs.h +++ b/include/fs/fs.h @@ -48,6 +48,7 @@ enum fs_dir_entry_type { enum fs_type { FS_FATFS = 0, FS_NFFS, + FS_LITTLEFS, FS_TYPE_END, }; diff --git a/include/fs/fs_interface.h b/include/fs/fs_interface.h index 4284bfc2ab99..684ec18d5718 100644 --- a/include/fs/fs_interface.h +++ b/include/fs/fs_interface.h @@ -11,7 +11,7 @@ extern "C" { #endif -#ifdef CONFIG_FILE_SYSTEM_NFFS +#if defined(CONFIG_FILE_SYSTEM_LITTLEFS) || defined(CONFIG_FILE_SYSTEM_NFFS) #define MAX_FILE_NAME 256 #else /* FAT_FS */ #define MAX_FILE_NAME 12 /* Uses 8.3 SFN */ diff --git a/include/fs/littlefs.h b/include/fs/littlefs.h new file mode 100644 index 000000000000..d5927c914f4f --- /dev/null +++ b/include/fs/littlefs.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Bolt Innovation Management, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_FS_LITTLEFS_H_ +#define ZEPHYR_INCLUDE_FS_LITTLEFS_H_ + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Filesystem info structure for LittleFS mount */ +struct fs_littlefs { + /* 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)]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_FS_LITTLEFS_H_ */ diff --git a/subsys/fs/CMakeLists.txt b/subsys/fs/CMakeLists.txt index 03b9abe007da..54af259b33fb 100644 --- a/subsys/fs/CMakeLists.txt +++ b/subsys/fs/CMakeLists.txt @@ -6,14 +6,16 @@ if(CONFIG_FILE_SYSTEM) zephyr_library() zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR}) zephyr_library_sources(fs.c fs_impl.c) - zephyr_library_sources_ifdef(CONFIG_FAT_FILESYSTEM_ELM fat_fs.c) - zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_NFFS nffs_fs.c) - zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_SHELL shell.c) + zephyr_library_sources_ifdef(CONFIG_FAT_FILESYSTEM_ELM fat_fs.c) + zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS littlefs_fs.c) + zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_NFFS nffs_fs.c) + zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_SHELL shell.c) zephyr_library_link_libraries(FS) - target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT) - target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_NFFS FS INTERFACE NFFS) + target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT) + target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS FS INTERFACE LITTLEFS) + target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_NFFS FS INTERFACE NFFS) endif() add_subdirectory_ifdef(CONFIG_FCB ./fcb) diff --git a/subsys/fs/Kconfig b/subsys/fs/Kconfig index 5acd7fe85367..4815e6c481c4 100644 --- a/subsys/fs/Kconfig +++ b/subsys/fs/Kconfig @@ -39,6 +39,13 @@ config FILE_SYSTEM_NFFS Note: NFFS requires 1-byte unaligned access to flash thus it will not work on devices that support only aligned flash access. +config FILE_SYSTEM_LITTLEFS + bool "LittleFS file system support" + depends on FLASH_MAP + depends on FLASH_PAGE_LAYOUT + help + Enables LittleFS file system support. + config FILE_SYSTEM_SHELL bool "Enable file system shell" depends on SHELL @@ -121,6 +128,63 @@ 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/fcb/Kconfig" diff --git a/subsys/fs/littlefs_fs.c b/subsys/fs/littlefs_fs.c new file mode 100644 index 000000000000..e31010c139f3 --- /dev/null +++ b/subsys/fs/littlefs_fs.c @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2019 Bolt Innovation Management, LLC + * Copyright (c) 2019 Peter Bigot Consulting, LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define LFS_LOG_REGISTER +#include + +#include +#include +#include +#include + +#include "fs_impl.h" + +struct lfs_file_cache { + struct lfs_file file; + struct lfs_file_config config; + u8_t cache[CONFIG_FS_LITTLEFS_CACHE_SIZE]; +}; + +#define LFS_FILEP(fp) (&((struct lfs_file_cache *)(fp->filep))->file) + +/* Global memory pool for open files and dirs */ +K_MEM_SLAB_DEFINE(lfs_file_pool, sizeof(struct lfs_file_cache), + CONFIG_FS_LITTLEFS_NUM_FILES, 4); +K_MEM_SLAB_DEFINE(lfs_dir_pool, sizeof(struct lfs_dir), + CONFIG_FS_LITTLEFS_NUM_DIRS, 4); + +static inline void fs_lock(struct fs_littlefs *fs) +{ + k_mutex_lock(&fs->mutex, K_FOREVER); +} + +static inline void fs_unlock(struct fs_littlefs *fs) +{ + k_mutex_unlock(&fs->mutex); +} + +static int lfs_to_errno(int error) +{ + if (error >= 0) { + return error; + } + + switch (error) { + default: + case LFS_ERR_IO: /* Error during device operation */ + return -EIO; + case LFS_ERR_CORRUPT: /* Corrupted */ + return -EFAULT; + case LFS_ERR_NOENT: /* No directory entry */ + return -ENOENT; + case LFS_ERR_EXIST: /* Entry already exists */ + return -EEXIST; + case LFS_ERR_NOTDIR: /* Entry is not a dir */ + return -ENOTDIR; + case LFS_ERR_ISDIR: /* Entry is a dir */ + return -EISDIR; + case LFS_ERR_NOTEMPTY: /* Dir is not empty */ + return -ENOTEMPTY; + case LFS_ERR_BADF: /* Bad file number */ + return -EBADF; + case LFS_ERR_FBIG: /* File too large */ + return -EFBIG; + case LFS_ERR_INVAL: /* Invalid parameter */ + return -EINVAL; + case LFS_ERR_NOSPC: /* No space left on device */ + return -ENOSPC; + case LFS_ERR_NOMEM: /* No more memory available */ + return -ENOMEM; + } +} + +static int errno_to_lfs(int error) +{ + if (error >= 0) { + return LFS_ERR_OK; + } + + switch (error) { + default: + case -EIO: /* Error during device operation */ + return LFS_ERR_IO; + case -EFAULT: /* Corrupted */ + return LFS_ERR_CORRUPT; + case -ENOENT: /* No directory entry */ + return LFS_ERR_NOENT; + case -EEXIST: /* Entry already exists */ + return LFS_ERR_EXIST; + case -ENOTDIR: /* Entry is not a dir */ + return LFS_ERR_NOTDIR; + case -EISDIR: /* Entry is a dir */ + return LFS_ERR_ISDIR; + case -ENOTEMPTY: /* Dir is not empty */ + return LFS_ERR_NOTEMPTY; + case -EBADF: /* Bad file number */ + return LFS_ERR_BADF; + case -EFBIG: /* File too large */ + return LFS_ERR_FBIG; + case -EINVAL: /* Invalid parameter */ + return LFS_ERR_INVAL; + case -ENOSPC: /* No space left on device */ + return LFS_ERR_NOSPC; + case -ENOMEM: /* No more memory available */ + return LFS_ERR_NOMEM; + } +} + + +static int lfs_api_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) +{ + const struct flash_area *fa = c->context; + size_t offset = block * c->block_size + off; + + int rc = flash_area_read(fa, offset, buffer, size); + + return errno_to_lfs(rc); +} + +static int lfs_api_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) +{ + const struct flash_area *fa = c->context; + size_t offset = block * c->block_size + off; + + int rc = flash_area_write(fa, offset, buffer, size); + + return errno_to_lfs(rc); +} + +static int lfs_api_erase(const struct lfs_config *c, lfs_block_t block) +{ + const struct flash_area *fa = c->context; + size_t offset = block * c->block_size; + + int rc = flash_area_erase(fa, offset, c->block_size); + + return errno_to_lfs(rc); +} + +static int lfs_api_sync(const struct lfs_config *c) +{ + return LFS_ERR_OK; +} + +static int littlefs_open(struct fs_file_t *fp, const char *path) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + int flags = LFS_O_CREAT | LFS_O_RDWR; + + if (k_mem_slab_alloc(&lfs_file_pool, &fp->filep, K_NO_WAIT) != 0) { + return -ENOMEM; + } + + 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); + + fs_unlock(fs); + + if (ret < 0) { + k_mem_slab_free(&lfs_file_pool, &fp->filep); + } + + return lfs_to_errno(ret); +} + +static int littlefs_close(struct fs_file_t *fp) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + int ret = lfs_file_close(&fs->lfs, LFS_FILEP(fp)); + + k_mem_slab_free(&lfs_file_pool, &fp->filep); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static int littlefs_unlink(struct fs_mount_t *mountp, const char *path) +{ + struct fs_littlefs *fs = mountp->fs_data; + + path = fs_impl_strip_prefix(path, mountp); + + fs_lock(fs); + + int ret = lfs_remove(&fs->lfs, path); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static int littlefs_rename(struct fs_mount_t *mountp, const char *from, + const char *to) +{ + struct fs_littlefs *fs = mountp->fs_data; + + from = fs_impl_strip_prefix(from, mountp); + to = fs_impl_strip_prefix(to, mountp); + + fs_lock(fs); + + int ret = lfs_rename(&fs->lfs, from, to); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static ssize_t littlefs_read(struct fs_file_t *fp, void *ptr, size_t len) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + ssize_t ret = lfs_file_read(&fs->lfs, LFS_FILEP(fp), ptr, len); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static ssize_t littlefs_write(struct fs_file_t *fp, const void *ptr, size_t len) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + ssize_t ret = lfs_file_write(&fs->lfs, LFS_FILEP(fp), ptr, len); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +BUILD_ASSERT((FS_SEEK_SET == LFS_SEEK_SET) + && (FS_SEEK_CUR == LFS_SEEK_CUR) + && (FS_SEEK_END == LFS_SEEK_END)); + +static int littlefs_seek(struct fs_file_t *fp, off_t off, int whence) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + off_t ret = lfs_file_seek(&fs->lfs, LFS_FILEP(fp), off, whence); + + fs_unlock(fs); + + if (ret >= 0) { + ret = 0; + } + + return lfs_to_errno(ret); +} + +static off_t littlefs_tell(struct fs_file_t *fp) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + off_t ret = lfs_file_tell(&fs->lfs, LFS_FILEP(fp)); + + fs_unlock(fs); + return ret; +} + +static int littlefs_truncate(struct fs_file_t *fp, off_t length) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + int ret = lfs_file_truncate(&fs->lfs, LFS_FILEP(fp), length); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static int littlefs_sync(struct fs_file_t *fp) +{ + struct fs_littlefs *fs = fp->mp->fs_data; + + fs_lock(fs); + + int ret = lfs_file_sync(&fs->lfs, LFS_FILEP(fp)); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static int littlefs_mkdir(struct fs_mount_t *mountp, const char *path) +{ + struct fs_littlefs *fs = mountp->fs_data; + + path = fs_impl_strip_prefix(path, mountp); + fs_lock(fs); + + int ret = lfs_mkdir(&fs->lfs, path); + + fs_unlock(fs); + return lfs_to_errno(ret); +} + +static int littlefs_opendir(struct fs_dir_t *dp, const char *path) +{ + struct fs_littlefs *fs = dp->mp->fs_data; + + if (k_mem_slab_alloc(&lfs_dir_pool, &dp->dirp, K_NO_WAIT) != 0) { + return -ENOMEM; + } + + memset(dp->dirp, 0, sizeof(struct lfs_dir)); + + path = fs_impl_strip_prefix(path, dp->mp); + + fs_lock(fs); + + int ret = lfs_dir_open(&fs->lfs, dp->dirp, path); + + fs_unlock(fs); + + if (ret < 0) { + k_mem_slab_free(&lfs_dir_pool, &dp->dirp); + } + + return lfs_to_errno(ret); +} + +static void info_to_dirent(const struct lfs_info *info, struct fs_dirent *entry) +{ + entry->type = ((info->type == LFS_TYPE_DIR) ? + FS_DIR_ENTRY_DIR : FS_DIR_ENTRY_FILE); + entry->size = info->size; + strncpy(entry->name, info->name, sizeof(entry->name)); + entry->name[sizeof(entry->name) - 1] = '\0'; +} + +static int littlefs_readdir(struct fs_dir_t *dp, struct fs_dirent *entry) +{ + struct fs_littlefs *fs = dp->mp->fs_data; + + fs_lock(fs); + + struct lfs_info info; + int ret = lfs_dir_read(&fs->lfs, dp->dirp, &info); + + fs_unlock(fs); + + if (ret > 0) { + info_to_dirent(&info, entry); + ret = 0; + } else if (ret == 0) { + entry->name[0] = 0; + } + + return lfs_to_errno(ret); +} + +static int littlefs_closedir(struct fs_dir_t *dp) +{ + struct fs_littlefs *fs = dp->mp->fs_data; + + fs_lock(fs); + + int ret = lfs_dir_close(&fs->lfs, dp->dirp); + + fs_unlock(fs); + + k_mem_slab_free(&lfs_dir_pool, &dp->dirp); + + return lfs_to_errno(ret); +} + +static int littlefs_stat(struct fs_mount_t *mountp, + const char *path, struct fs_dirent *entry) +{ + struct fs_littlefs *fs = mountp->fs_data; + + path = fs_impl_strip_prefix(path, mountp); + + fs_lock(fs); + + struct lfs_info info; + int ret = lfs_stat(&fs->lfs, path, &info); + + fs_unlock(fs); + + if (ret >= 0) { + info_to_dirent(&info, entry); + ret = 0; + } + + return lfs_to_errno(ret); +} + +static int littlefs_statvfs(struct fs_mount_t *mountp, + const char *path, struct fs_statvfs *stat) +{ + struct fs_littlefs *fs = mountp->fs_data; + struct lfs *lfs = &fs->lfs; + + stat->f_bsize = lfs->cfg->prog_size; + stat->f_frsize = lfs->cfg->block_size; + stat->f_blocks = lfs->cfg->block_count; + + path = fs_impl_strip_prefix(path, mountp); + + fs_lock(fs); + + ssize_t ret = lfs_fs_size(lfs); + + fs_unlock(fs); + + if (ret >= 0) { + stat->f_bfree = stat->f_blocks - ret; + ret = 0; + } + + return lfs_to_errno(ret); +} + +/* Return maximum page size in a flash area. There's no flash_area + * API to implement this, so we have to make one here. + */ +struct get_page_ctx { + const struct flash_area *area; + lfs_size_t max_size; +}; + +static bool get_page_cb(const struct flash_pages_info *info, void *ctxp) +{ + struct get_page_ctx *ctx = ctxp; + + size_t info_start = info->start_offset; + size_t info_end = info_start + info->size - 1U; + size_t area_start = ctx->area->fa_off; + size_t area_end = area_start + ctx->area->fa_size - 1U; + + /* Ignore pages outside the area */ + if (info_end < area_start) { + return true; + } + if (info_start > area_end) { + return false; + } + + if (info->size > ctx->max_size) { + ctx->max_size = info->size; + } + + return true; +} + +/* Iterate over all page groups in the flash area and return the + * largest page size we see. This works as long as the partition is + * aligned so that erasing with this size is supported throughout the + * partition. + */ +static lfs_size_t get_block_size(const struct flash_area *fa) +{ + struct get_page_ctx ctx = { + .area = fa, + .max_size = 0, + }; + struct device *dev = flash_area_get_device(fa); + + flash_page_foreach(dev, get_page_cb, &ctx); + + return ctx.max_size; +} + +static int littlefs_mount(struct fs_mount_t *mountp) +{ + int ret; + struct fs_littlefs *fs = mountp->fs_data; + unsigned int area_id = (uintptr_t)mountp->storage_dev; + struct device *dev; + + LOG_INF("LittleFS version %u.%u, disk version %u.%u", + LFS_VERSION_MAJOR, LFS_VERSION_MINOR, + LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); + + if (fs->area) { + return -EBUSY; + } + + /* Create and take mutex. */ + k_mutex_init(&fs->mutex); + fs_lock(fs); + + /* Open flash area */ + ret = flash_area_open(area_id, &fs->area); + if ((ret < 0) || (fs->area == NULL)) { + LOG_ERR("can't open flash area %d", area_id); + ret = -ENODEV; + goto out; + } + LOG_DBG("FS area %u at 0x%x for %u bytes", + area_id, (u32_t)fs->area->fa_off, + (u32_t)fs->area->fa_size); + + dev = flash_area_get_device(fs->area); + if (dev == NULL) { + LOG_ERR("can't get flash device: %s", fs->area->fa_dev_name); + ret = -ENODEV; + goto out; + } + + BUILD_ASSERT(CONFIG_FS_LITTLEFS_READ_SIZE > 0); + BUILD_ASSERT(CONFIG_FS_LITTLEFS_PROG_SIZE > 0); + BUILD_ASSERT(CONFIG_FS_LITTLEFS_CACHE_SIZE > 0); + BUILD_ASSERT(CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE > 0); + BUILD_ASSERT((CONFIG_FS_LITTLEFS_LOOKAHEAD_SIZE % 8) == 0); + BUILD_ASSERT((CONFIG_FS_LITTLEFS_CACHE_SIZE + % CONFIG_FS_LITTLEFS_READ_SIZE) == 0); + 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; + + if (block_cycles <= 0) { + /* Disable leveling (littlefs v2.1+ semantics) */ + block_cycles = -1; + } + + 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, + block_count, block_size, block_cycles); + LOG_DBG("sizes: rd %u ; pr %u ; ca %u ; la %u", + read_size, prog_size, cache_size, lookahead_size); + + __ASSERT((fs->area->fa_size % block_size) == 0, + "partition size must be multiple of block size"); + __ASSERT((block_size % prog_size) == 0, + "erase size must be multiple of write size"); + __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 + }; + + /* Mount it, formatting if needed. */ + ret = lfs_mount(&fs->lfs, &fs->cfg); + if (ret < 0) { + LOG_WRN("can't mount (LFS %d); formatting", ret); + ret = lfs_format(&fs->lfs, &fs->cfg); + if (ret < 0) { + LOG_ERR("format failed (LFS %d)", ret); + ret = lfs_to_errno(ret); + goto out; + } + ret = lfs_mount(&fs->lfs, &fs->cfg); + if (ret < 0) { + LOG_ERR("remount after format failed (LFS %d)", ret); + ret = lfs_to_errno(ret); + goto out; + } + } + + LOG_INF("%s mounted", log_strdup(mountp->mnt_point)); + +out: + if (ret < 0) { + fs->area = NULL; + } + + fs_unlock(fs); + + return ret; +} + +static int littlefs_unmount(struct fs_mount_t *mountp) +{ + struct fs_littlefs *fs = mountp->fs_data; + + fs_lock(fs); + + lfs_unmount(&fs->lfs); + flash_area_close(fs->area); + fs->area = NULL; + + fs_unlock(fs); + + LOG_INF("%s unmounted", log_strdup(mountp->mnt_point)); + + return 0; +} + +/* File system interface */ +static struct fs_file_system_t littlefs_fs = { + .open = littlefs_open, + .close = littlefs_close, + .read = littlefs_read, + .write = littlefs_write, + .lseek = littlefs_seek, + .tell = littlefs_tell, + .truncate = littlefs_truncate, + .sync = littlefs_sync, + .opendir = littlefs_opendir, + .readdir = littlefs_readdir, + .closedir = littlefs_closedir, + .mount = littlefs_mount, + .unmount = littlefs_unmount, + .unlink = littlefs_unlink, + .rename = littlefs_rename, + .mkdir = littlefs_mkdir, + .stat = littlefs_stat, + .statvfs = littlefs_statvfs, +}; + +static int littlefs_init(struct device *dev) +{ + ARG_UNUSED(dev); + return fs_register(FS_LITTLEFS, &littlefs_fs); +} + +SYS_INIT(littlefs_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/west.yml b/west.yml index e5784d1ec4a7..e24408e4fcf2 100644 --- a/west.yml +++ b/west.yml @@ -85,6 +85,9 @@ manifest: - name: tinycbor path: modules/lib/tinycbor revision: 31ae89e4b768612722620cb6cb173a0de4a19cc9 + - name: littlefs + path: modules/fs/littlefs + revision: 52b038794b1ba34bd9aaf4df3e37e65558046342 self: path: zephyr