| /* |
| * 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 <zephyr/kernel.h> |
| #include <errno.h> |
| #include <zephyr/init.h> |
| #include <zephyr/fs/fs.h> |
| #include <zephyr/fs/fs_sys.h> |
| |
| #define LFS_LOG_REGISTER |
| #include <lfs_util.h> |
| |
| #include <lfs.h> |
| #include <zephyr/fs/littlefs.h> |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/storage/flash_map.h> |
| #endif |
| #ifdef CONFIG_FS_LITTLEFS_BLK_DEV |
| #include <zephyr/storage/disk_access.h> |
| #endif |
| |
| #include "fs_impl.h" |
| |
| /* Used on devices that have no explicit erase */ |
| #define LITTLEFS_DEFAULT_BLOCK_SIZE 4096 |
| |
| /* note: one of the next options have to be enabled, at least */ |
| BUILD_ASSERT(IS_ENABLED(CONFIG_FS_LITTLEFS_BLK_DEV) || |
| IS_ENABLED(CONFIG_FS_LITTLEFS_FMP_DEV)); |
| |
| struct lfs_file_data { |
| struct lfs_file file; |
| struct lfs_file_config config; |
| void *cache_block; |
| }; |
| |
| #define LFS_FILEP(fp) (&((struct lfs_file_data *)(fp->filep))->file) |
| |
| /* Global memory pool for open files and dirs */ |
| K_MEM_SLAB_DEFINE_STATIC(file_data_pool, sizeof(struct lfs_file_data), |
| CONFIG_FS_LITTLEFS_NUM_FILES, 4); |
| K_MEM_SLAB_DEFINE_STATIC(lfs_dir_pool, sizeof(struct lfs_dir), |
| CONFIG_FS_LITTLEFS_NUM_DIRS, 4); |
| |
| /* Inferred overhead, in bytes, for each k_heap_aligned allocation for |
| * the filecache heap. This relates to the CHUNK_UNIT parameter in |
| * the heap implementation, but that value is not visible outside the |
| * kernel. |
| * FIXME: value for this macro should be rather taken from the Kernel |
| * internals than set by user, but we do not have a way to do so now. |
| */ |
| #define FC_HEAP_PER_ALLOC_OVERHEAD CONFIG_FS_LITTLEFS_HEAP_PER_ALLOC_OVERHEAD_SIZE |
| |
| #if (CONFIG_FS_LITTLEFS_FC_HEAP_SIZE - 0) <= 0 |
| BUILD_ASSERT((CONFIG_FS_LITTLEFS_HEAP_PER_ALLOC_OVERHEAD_SIZE % 8) == 0); |
| /* Auto-generate heap size from cache size and number of files */ |
| #undef CONFIG_FS_LITTLEFS_FC_HEAP_SIZE |
| #define CONFIG_FS_LITTLEFS_FC_HEAP_SIZE \ |
| ((CONFIG_FS_LITTLEFS_CACHE_SIZE + FC_HEAP_PER_ALLOC_OVERHEAD) * \ |
| CONFIG_FS_LITTLEFS_NUM_FILES) |
| #endif /* CONFIG_FS_LITTLEFS_FC_HEAP_SIZE */ |
| |
| static K_HEAP_DEFINE(file_cache_heap, CONFIG_FS_LITTLEFS_FC_HEAP_SIZE); |
| |
| static inline bool littlefs_on_blkdev(int flags) |
| { |
| return (flags & FS_MOUNT_FLAG_USE_DISK_ACCESS) ? true : false; |
| } |
| |
| static inline void *fc_allocate(size_t size) |
| { |
| void *ret = NULL; |
| |
| ret = k_heap_alloc(&file_cache_heap, size, K_NO_WAIT); |
| |
| return ret; |
| } |
| |
| static inline void fc_release(void *buf) |
| { |
| k_heap_free(&file_cache_heap, buf); |
| } |
| |
| 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; |
| } |
| } |
| |
| |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| |
| 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_flatten(fa, offset, c->block_size); |
| |
| return errno_to_lfs(rc); |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| |
| #ifdef CONFIG_FS_LITTLEFS_BLK_DEV |
| static int lfs_api_read_blk(const struct lfs_config *c, lfs_block_t block, |
| lfs_off_t off, void *buffer, lfs_size_t size) |
| { |
| const char *disk = c->context; |
| int rc = disk_access_read(disk, buffer, block, |
| size / c->block_size); |
| |
| return errno_to_lfs(rc); |
| } |
| |
| static int lfs_api_prog_blk(const struct lfs_config *c, lfs_block_t block, |
| lfs_off_t off, const void *buffer, lfs_size_t size) |
| { |
| const char *disk = c->context; |
| int rc = disk_access_write(disk, buffer, block, size / c->block_size); |
| |
| return errno_to_lfs(rc); |
| } |
| |
| static int lfs_api_sync_blk(const struct lfs_config *c) |
| { |
| const char *disk = c->context; |
| int rc = disk_access_ioctl(disk, DISK_IOCTL_CTRL_SYNC, NULL); |
| |
| return errno_to_lfs(rc); |
| } |
| #else |
| static int lfs_api_read_blk(const struct lfs_config *c, lfs_block_t block, |
| lfs_off_t off, void *buffer, lfs_size_t size) |
| { |
| return 0; |
| } |
| |
| static int lfs_api_prog_blk(const struct lfs_config *c, lfs_block_t block, |
| lfs_off_t off, const void *buffer, lfs_size_t size) |
| { |
| return 0; |
| } |
| |
| static int lfs_api_sync_blk(const struct lfs_config *c) |
| { |
| return 0; |
| } |
| #endif /* CONFIG_FS_LITTLEFS_BLK_DEV */ |
| static int lfs_api_erase_blk(const struct lfs_config *c, lfs_block_t block) |
| { |
| return 0; |
| } |
| |
| 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) { |
| fc_release(fdp->cache_block); |
| } |
| |
| k_mem_slab_free(&file_data_pool, fp->filep); |
| fp->filep = NULL; |
| } |
| |
| static int lfs_flags_from_zephyr(unsigned int zflags) |
| { |
| int flags = (zflags & FS_O_CREATE) ? LFS_O_CREAT : 0; |
| |
| /* LFS_O_READONLY and LFS_O_WRONLY can be selected at the same time, |
| * this is not a mistake, together they create RDWR access. |
| */ |
| flags |= (zflags & FS_O_READ) ? LFS_O_RDONLY : 0; |
| flags |= (zflags & FS_O_WRITE) ? LFS_O_WRONLY : 0; |
| |
| flags |= (zflags & FS_O_APPEND) ? LFS_O_APPEND : 0; |
| |
| return flags; |
| } |
| |
| static int littlefs_open(struct fs_file_t *fp, const char *path, |
| fs_mode_t zflags) |
| { |
| struct fs_littlefs *fs = fp->mp->fs_data; |
| struct lfs *lfs = &fs->lfs; |
| int flags = lfs_flags_from_zephyr(zflags); |
| int 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)); |
| |
| fdp->cache_block = fc_allocate(lfs->cfg->cache_size); |
| if (fdp->cache_block == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| fdp->config.buffer = fdp->cache_block; |
| path = fs_impl_strip_prefix(path, fp->mp); |
| |
| fs_lock(fs); |
| |
| ret = lfs_file_opencfg(&fs->lfs, &fdp->file, |
| path, flags, &fdp->config); |
| |
| fs_unlock(fs); |
| out: |
| if (ret < 0) { |
| release_file_data(fp); |
| } |
| |
| 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)); |
| |
| fs_unlock(fs); |
| |
| release_file_data(fp); |
| |
| 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); |
| } |
| |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| |
| #if defined(CONFIG_FLASH_HAS_EXPLICIT_ERASE) |
| /* 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; |
| } |
| #endif |
| |
| /* 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) |
| { |
| #if defined(CONFIG_FLASH_HAS_EXPLICIT_ERASE) |
| struct get_page_ctx ctx = { |
| .area = fa, |
| .max_size = 0, |
| }; |
| const struct device *dev = flash_area_get_device(fa); |
| #if defined(CONFIG_FLASH_HAS_NO_EXPLICIT_ERASE) |
| const struct flash_parameters *fparams = flash_get_parameters(dev); |
| |
| if (!(flash_params_get_erase_cap(fparams) & FLASH_ERASE_C_EXPLICIT)) { |
| return LITTLEFS_DEFAULT_BLOCK_SIZE; |
| } |
| #endif |
| |
| flash_page_foreach(dev, get_page_cb, &ctx); |
| |
| return ctx.max_size; |
| #else |
| return LITTLEFS_DEFAULT_BLOCK_SIZE; |
| #endif |
| } |
| |
| static int littlefs_flash_init(struct fs_littlefs *fs, void *dev_id) |
| { |
| unsigned int area_id = POINTER_TO_UINT(dev_id); |
| const struct flash_area **fap = (const struct flash_area **)&fs->backend; |
| const struct device *dev; |
| int ret; |
| |
| /* Open flash area */ |
| ret = flash_area_open(area_id, fap); |
| if ((ret < 0) || (*fap == NULL)) { |
| LOG_ERR("can't open flash area %d", area_id); |
| return -ENODEV; |
| } |
| |
| LOG_DBG("FS area %u at 0x%x for %u bytes", area_id, |
| (uint32_t)(*fap)->fa_off, (uint32_t)(*fap)->fa_size); |
| |
| dev = flash_area_get_device(*fap); |
| if (dev == NULL) { |
| LOG_ERR("can't get flash device: %s", |
| (*fap)->fa_dev->name); |
| return -ENODEV; |
| } |
| |
| fs->backend = (void *) *fap; |
| return 0; |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| |
| static int littlefs_init_backend(struct fs_littlefs *fs, void *dev_id, int flags) |
| { |
| int ret = 0; |
| |
| if (!(IS_ENABLED(CONFIG_FS_LITTLEFS_FMP_DEV) && !littlefs_on_blkdev(flags)) && |
| !(IS_ENABLED(CONFIG_FS_LITTLEFS_BLK_DEV) && littlefs_on_blkdev(flags))) { |
| LOG_ERR("Can't init littlefs backend, review configs and flags 0x%08x", flags); |
| return -ENOTSUP; |
| } |
| |
| #ifdef CONFIG_FS_LITTLEFS_BLK_DEV |
| if (littlefs_on_blkdev(flags)) { |
| fs->backend = dev_id; |
| ret = disk_access_init((char *) fs->backend); |
| if (ret < 0) { |
| LOG_ERR("Storage init ERROR!"); |
| return ret; |
| } |
| } |
| #endif /* CONFIG_FS_LITTLEFS_BLK_DEV */ |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| if (!littlefs_on_blkdev(flags)) { |
| ret = littlefs_flash_init(fs, dev_id); |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| return 0; |
| } |
| |
| static int littlefs_init_cfg(struct fs_littlefs *fs, int flags) |
| { |
| 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); |
| |
| 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 (!(IS_ENABLED(CONFIG_FS_LITTLEFS_FMP_DEV) && !littlefs_on_blkdev(flags)) && |
| !(IS_ENABLED(CONFIG_FS_LITTLEFS_BLK_DEV) && littlefs_on_blkdev(flags))) { |
| LOG_ERR("Can't init littlefs config, review configs and flags 0x%08x", flags); |
| return -ENOTSUP; |
| } |
| |
| if (block_size == 0) { |
| #ifdef CONFIG_FS_LITTLEFS_BLK_DEV |
| if (littlefs_on_blkdev(flags)) { |
| int ret = disk_access_ioctl((char *) fs->backend, |
| DISK_IOCTL_GET_SECTOR_SIZE, |
| &block_size); |
| if (ret < 0) { |
| LOG_ERR("Unable to get sector size"); |
| return ret; |
| } |
| } |
| #endif /* CONFIG_FS_LITTLEFS_BLK_DEV */ |
| |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| if (!littlefs_on_blkdev(flags)) { |
| block_size = get_block_size((struct flash_area *)fs->backend); |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| } |
| |
| if (block_size == 0) { |
| __ASSERT_NO_MSG(block_size != 0); |
| return -EINVAL; |
| } |
| |
| int32_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 = 0; |
| |
| #ifdef CONFIG_FS_LITTLEFS_BLK_DEV |
| if (littlefs_on_blkdev(flags)) { |
| int ret = disk_access_ioctl((char *) fs->backend, |
| DISK_IOCTL_GET_SECTOR_COUNT, |
| &block_count); |
| if (ret < 0) { |
| LOG_ERR("Unable to get sector count!"); |
| return -EINVAL; |
| } |
| LOG_INF("FS at %s: is %u 0x%x-byte blocks with %u cycle", |
| (char *) fs->backend, block_count, block_size, |
| block_cycles); |
| } |
| #endif /* CONFIG_FS_LITTLEFS_BLK_DEV */ |
| |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| if (!littlefs_on_blkdev(flags)) { |
| block_count = ((struct flash_area *)fs->backend)->fa_size |
| / block_size; |
| const struct device *dev = |
| flash_area_get_device((struct flash_area *)fs->backend); |
| LOG_INF("FS at %s:0x%x is %u 0x%x-byte blocks with %u cycle", |
| dev->name, |
| (uint32_t)((struct flash_area *)fs->backend)->fa_off, |
| block_count, block_size, block_cycles); |
| LOG_INF("sizes: rd %u ; pr %u ; ca %u ; la %u", |
| read_size, prog_size, cache_size, lookahead_size); |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| |
| __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_NO_MSG(block_count != 0); |
| |
| __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"); |
| |
| lcp->context = fs->backend; |
| /* Set the validated/defaulted values. */ |
| if (littlefs_on_blkdev(flags)) { |
| lcp->read = lfs_api_read_blk; |
| lcp->prog = lfs_api_prog_blk; |
| lcp->erase = lfs_api_erase_blk; |
| |
| lcp->read_size = block_size; |
| lcp->prog_size = block_size; |
| lcp->cache_size = block_size; |
| lcp->lookahead_size = block_size * 4; |
| lcp->sync = lfs_api_sync_blk; |
| |
| LOG_INF("sizes: rd %u ; pr %u ; ca %u ; la %u", |
| lcp->read_size, lcp->prog_size, lcp->cache_size, |
| lcp->lookahead_size); |
| } else { |
| __ASSERT((((struct flash_area *)fs->backend)->fa_size % |
| block_size) == 0, |
| "partition size must be multiple of block size"); |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| lcp->read = lfs_api_read; |
| lcp->prog = lfs_api_prog; |
| lcp->erase = lfs_api_erase; |
| #endif |
| |
| lcp->read_size = read_size; |
| lcp->prog_size = prog_size; |
| lcp->cache_size = cache_size; |
| lcp->lookahead_size = lookahead_size; |
| lcp->sync = lfs_api_sync; |
| } |
| |
| lcp->block_size = block_size; |
| lcp->block_count = block_count; |
| lcp->block_cycles = block_cycles; |
| return 0; |
| } |
| |
| static int littlefs_init_fs(struct fs_littlefs *fs, void *dev_id, int flags) |
| { |
| int ret = 0; |
| |
| 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->backend) { |
| return -EBUSY; |
| } |
| |
| ret = littlefs_init_backend(fs, dev_id, flags); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| ret = littlefs_init_cfg(fs, flags); |
| if (ret < 0) { |
| return ret; |
| } |
| return 0; |
| } |
| |
| static int littlefs_mount(struct fs_mount_t *mountp) |
| { |
| int ret = 0; |
| struct fs_littlefs *fs = mountp->fs_data; |
| |
| /* Create and take mutex. */ |
| k_mutex_init(&fs->mutex); |
| fs_lock(fs); |
| |
| ret = littlefs_init_fs(fs, mountp->storage_dev, mountp->flags); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| /* Mount it, formatting if needed. */ |
| ret = lfs_mount(&fs->lfs, &fs->cfg); |
| if (ret < 0 && |
| (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0) { |
| if ((mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 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; |
| } |
| } else { |
| LOG_ERR("can not format read-only system"); |
| ret = -EROFS; |
| 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; |
| } |
| } else { |
| ret = lfs_to_errno(ret); |
| goto out; |
| } |
| |
| LOG_INF("%s mounted", mountp->mnt_point); |
| |
| out: |
| if (ret < 0) { |
| fs->backend = NULL; |
| } |
| |
| fs_unlock(fs); |
| |
| return ret; |
| } |
| |
| #if defined(CONFIG_FILE_SYSTEM_MKFS) |
| |
| FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_cfg); |
| |
| static int littlefs_mkfs(uintptr_t dev_id, void *cfg, int flags) |
| { |
| int ret = 0; |
| struct fs_littlefs *fs = &fs_cfg; |
| |
| if (cfg != NULL) { |
| fs = (struct fs_littlefs *)cfg; |
| } |
| |
| fs->backend = NULL; |
| |
| /* Create and take mutex. */ |
| k_mutex_init(&fs->mutex); |
| fs_lock(fs); |
| |
| ret = littlefs_init_fs(fs, UINT_TO_POINTER(dev_id), flags); |
| if (ret < 0) { |
| goto out; |
| } |
| |
| ret = lfs_format(&fs->lfs, &fs->cfg); |
| if (ret < 0) { |
| LOG_ERR("format failed (LFS %d)", ret); |
| ret = lfs_to_errno(ret); |
| goto out; |
| } |
| out: |
| fs->backend = NULL; |
| fs_unlock(fs); |
| return ret; |
| } |
| |
| #endif /* CONFIG_FILE_SYSTEM_MKFS */ |
| |
| static int littlefs_unmount(struct fs_mount_t *mountp) |
| { |
| struct fs_littlefs *fs = mountp->fs_data; |
| |
| fs_lock(fs); |
| |
| lfs_unmount(&fs->lfs); |
| |
| #ifdef CONFIG_FS_LITTLEFS_FMP_DEV |
| if (!littlefs_on_blkdev(mountp->flags)) { |
| flash_area_close(fs->backend); |
| } |
| #endif /* CONFIG_FS_LITTLEFS_FMP_DEV */ |
| |
| fs->backend = NULL; |
| fs_unlock(fs); |
| |
| LOG_INF("%s unmounted", mountp->mnt_point); |
| |
| return 0; |
| } |
| |
| /* File system interface */ |
| static const 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, |
| #if defined(CONFIG_FILE_SYSTEM_MKFS) |
| .mkfs = littlefs_mkfs, |
| #endif |
| }; |
| |
| #define DT_DRV_COMPAT zephyr_fstab_littlefs |
| #define FS_PARTITION(inst) DT_PHANDLE_BY_IDX(DT_DRV_INST(inst), partition, 0) |
| |
| #define DEFINE_FS(inst) \ |
| static uint8_t __aligned(4) \ |
| read_buffer_##inst[DT_INST_PROP(inst, cache_size)]; \ |
| static uint8_t __aligned(4) \ |
| prog_buffer_##inst[DT_INST_PROP(inst, cache_size)]; \ |
| static uint32_t lookahead_buffer_##inst[DT_INST_PROP(inst, lookahead_size) \ |
| / sizeof(uint32_t)]; \ |
| BUILD_ASSERT(DT_INST_PROP(inst, read_size) > 0); \ |
| BUILD_ASSERT(DT_INST_PROP(inst, prog_size) > 0); \ |
| BUILD_ASSERT(DT_INST_PROP(inst, cache_size) > 0); \ |
| BUILD_ASSERT(DT_INST_PROP(inst, lookahead_size) > 0); \ |
| BUILD_ASSERT((DT_INST_PROP(inst, lookahead_size) % 8) == 0); \ |
| BUILD_ASSERT((DT_INST_PROP(inst, cache_size) \ |
| % DT_INST_PROP(inst, read_size)) == 0); \ |
| BUILD_ASSERT((DT_INST_PROP(inst, cache_size) \ |
| % DT_INST_PROP(inst, prog_size)) == 0); \ |
| static struct fs_littlefs fs_data_##inst = { \ |
| .cfg = { \ |
| .read_size = DT_INST_PROP(inst, read_size), \ |
| .prog_size = DT_INST_PROP(inst, prog_size), \ |
| .cache_size = DT_INST_PROP(inst, cache_size), \ |
| .lookahead_size = DT_INST_PROP(inst, lookahead_size), \ |
| .read_buffer = read_buffer_##inst, \ |
| .prog_buffer = prog_buffer_##inst, \ |
| .lookahead_buffer = lookahead_buffer_##inst, \ |
| }, \ |
| }; \ |
| struct fs_mount_t FS_FSTAB_ENTRY(DT_DRV_INST(inst)) = { \ |
| .type = FS_LITTLEFS, \ |
| .mnt_point = DT_INST_PROP(inst, mount_point), \ |
| .fs_data = &fs_data_##inst, \ |
| .storage_dev = (void *)DT_FIXED_PARTITION_ID(FS_PARTITION(inst)), \ |
| .flags = FSTAB_ENTRY_DT_MOUNT_FLAGS(DT_DRV_INST(inst)), \ |
| }; |
| |
| DT_INST_FOREACH_STATUS_OKAY(DEFINE_FS) |
| |
| #define REFERENCE_MOUNT(inst) (&FS_FSTAB_ENTRY(DT_DRV_INST(inst))), |
| |
| static void mount_init(struct fs_mount_t *mp) |
| { |
| |
| LOG_INF("littlefs partition at %s", mp->mnt_point); |
| if ((mp->flags & FS_MOUNT_FLAG_AUTOMOUNT) != 0) { |
| int rc = fs_mount(mp); |
| |
| if (rc < 0) { |
| LOG_ERR("Automount %s failed: %d", |
| mp->mnt_point, rc); |
| } else { |
| LOG_INF("Automount %s succeeded", |
| mp->mnt_point); |
| } |
| } |
| } |
| |
| static int littlefs_init(void) |
| { |
| static struct fs_mount_t *partitions[] = { |
| DT_INST_FOREACH_STATUS_OKAY(REFERENCE_MOUNT) |
| }; |
| |
| int rc = fs_register(FS_LITTLEFS, &littlefs_fs); |
| |
| if (rc == 0) { |
| struct fs_mount_t **mpi = partitions; |
| |
| while (mpi < (partitions + ARRAY_SIZE(partitions))) { |
| mount_init(*mpi++); |
| } |
| } |
| |
| return rc; |
| } |
| |
| SYS_INIT(littlefs_init, POST_KERNEL, CONFIG_FILE_SYSTEM_INIT_PRIORITY); |