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 <pab@pabigot.com> Signed-off-by: Jim Paris <jim@bolt.io>
This commit is contained in:
parent
b8be48e6ca
commit
fb73fcd4ba
8 changed files with 781 additions and 6 deletions
|
@ -337,6 +337,7 @@
|
||||||
/subsys/fs/ @nashif
|
/subsys/fs/ @nashif
|
||||||
/subsys/fs/fcb/ @nvlsianpu
|
/subsys/fs/fcb/ @nvlsianpu
|
||||||
/subsys/fs/fuse_fs_access.c @vanwinkeljan
|
/subsys/fs/fuse_fs_access.c @vanwinkeljan
|
||||||
|
/subsys/fs/littlefs_fs.c @pabigot
|
||||||
/subsys/fs/nvs/ @Laczen
|
/subsys/fs/nvs/ @Laczen
|
||||||
/subsys/logging/ @nordic-krch
|
/subsys/logging/ @nordic-krch
|
||||||
/subsys/mgmt/ @carlescufi @nvlsianpu
|
/subsys/mgmt/ @carlescufi @nvlsianpu
|
||||||
|
|
|
@ -48,6 +48,7 @@ enum fs_dir_entry_type {
|
||||||
enum fs_type {
|
enum fs_type {
|
||||||
FS_FATFS = 0,
|
FS_FATFS = 0,
|
||||||
FS_NFFS,
|
FS_NFFS,
|
||||||
|
FS_LITTLEFS,
|
||||||
FS_TYPE_END,
|
FS_TYPE_END,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_FILE_SYSTEM_NFFS
|
#if defined(CONFIG_FILE_SYSTEM_LITTLEFS) || defined(CONFIG_FILE_SYSTEM_NFFS)
|
||||||
#define MAX_FILE_NAME 256
|
#define MAX_FILE_NAME 256
|
||||||
#else /* FAT_FS */
|
#else /* FAT_FS */
|
||||||
#define MAX_FILE_NAME 12 /* Uses 8.3 SFN */
|
#define MAX_FILE_NAME 12 /* Uses 8.3 SFN */
|
||||||
|
|
39
include/fs/littlefs.h
Normal file
39
include/fs/littlefs.h
Normal file
|
@ -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 <zephyr/types.h>
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <storage/flash_map.h>
|
||||||
|
|
||||||
|
#include <lfs.h>
|
||||||
|
|
||||||
|
#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_ */
|
|
@ -7,12 +7,14 @@ if(CONFIG_FILE_SYSTEM)
|
||||||
zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
zephyr_library_sources(fs.c fs_impl.c)
|
zephyr_library_sources(fs.c fs_impl.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_FAT_FILESYSTEM_ELM fat_fs.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_NFFS nffs_fs.c)
|
||||||
zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_SHELL shell.c)
|
zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_SHELL shell.c)
|
||||||
|
|
||||||
zephyr_library_link_libraries(FS)
|
zephyr_library_link_libraries(FS)
|
||||||
|
|
||||||
target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT)
|
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)
|
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_NFFS FS INTERFACE NFFS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,13 @@ config FILE_SYSTEM_NFFS
|
||||||
Note: NFFS requires 1-byte unaligned access to flash thus it
|
Note: NFFS requires 1-byte unaligned access to flash thus it
|
||||||
will not work on devices that support only aligned flash access.
|
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
|
config FILE_SYSTEM_SHELL
|
||||||
bool "Enable file system shell"
|
bool "Enable file system shell"
|
||||||
depends on SHELL
|
depends on SHELL
|
||||||
|
@ -121,6 +128,63 @@ 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/fcb/Kconfig"
|
source "subsys/fs/fcb/Kconfig"
|
||||||
|
|
665
subsys/fs/littlefs_fs.c
Normal file
665
subsys/fs/littlefs_fs.c
Normal file
|
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <kernel.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <init.h>
|
||||||
|
#include <fs.h>
|
||||||
|
|
||||||
|
#define LFS_LOG_REGISTER
|
||||||
|
#include <lfs_util.h>
|
||||||
|
|
||||||
|
#include <lfs.h>
|
||||||
|
#include <fs/littlefs.h>
|
||||||
|
#include <flash.h>
|
||||||
|
#include <flash_map.h>
|
||||||
|
|
||||||
|
#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);
|
3
west.yml
3
west.yml
|
@ -85,6 +85,9 @@ manifest:
|
||||||
- name: tinycbor
|
- name: tinycbor
|
||||||
path: modules/lib/tinycbor
|
path: modules/lib/tinycbor
|
||||||
revision: 31ae89e4b768612722620cb6cb173a0de4a19cc9
|
revision: 31ae89e4b768612722620cb6cb173a0de4a19cc9
|
||||||
|
- name: littlefs
|
||||||
|
path: modules/fs/littlefs
|
||||||
|
revision: 52b038794b1ba34bd9aaf4df3e37e65558046342
|
||||||
|
|
||||||
self:
|
self:
|
||||||
path: zephyr
|
path: zephyr
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue