diff --git a/pkg/littlefs2/Makefile b/pkg/littlefs2/Makefile new file mode 100644 index 0000000000..19e23a5453 --- /dev/null +++ b/pkg/littlefs2/Makefile @@ -0,0 +1,12 @@ +PKG_NAME=littlefs2 +PKG_URL=https://github.com/ARMmbed/littlefs.git +# v2.1.4 +PKG_VERSION=ce2c01f098f4d2b9479de5a796c3bb531f1fe14c +PKG_LICENSE=Apache-2.0 + +include $(RIOTBASE)/pkg/pkg.mk + +.PHONY: all + +all: + "$(MAKE)" -C $(PKG_BUILDDIR) -f $(CURDIR)/Makefile.littlefs2 diff --git a/pkg/littlefs2/Makefile.dep b/pkg/littlefs2/Makefile.dep new file mode 100644 index 0000000000..46de66a114 --- /dev/null +++ b/pkg/littlefs2/Makefile.dep @@ -0,0 +1,5 @@ +USEMODULE += vfs +USEMODULE += littlefs2_fs +USEMODULE += mtd + +FEATURES_BLACKLIST += arch_msp430 diff --git a/pkg/littlefs2/Makefile.include b/pkg/littlefs2/Makefile.include new file mode 100644 index 0000000000..de5a032eef --- /dev/null +++ b/pkg/littlefs2/Makefile.include @@ -0,0 +1,5 @@ +INCLUDES += -I$(PKGDIRBASE)/littlefs2 + +ifneq (,$(filter littlefs2_fs,$(USEMODULE))) + DIRS += $(RIOTBASE)/pkg/littlefs2/fs +endif diff --git a/pkg/littlefs2/Makefile.littlefs2 b/pkg/littlefs2/Makefile.littlefs2 new file mode 100644 index 0000000000..97d309f3b0 --- /dev/null +++ b/pkg/littlefs2/Makefile.littlefs2 @@ -0,0 +1,13 @@ +MODULE := littlefs2 + +CFLAGS += -Wno-format +# GCC 4.9 bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64480) +# used by MIPS +CFLAGS += -Wno-missing-field-initializers + +# Disable debug printing +ifneq ($(DEVELHELP),1) + CFLAGS += -DLFS_NO_DEBUG -DLFS_NO_WARN -DLFS_NO_ERROR +endif + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/littlefs2/doc.txt b/pkg/littlefs2/doc.txt new file mode 100644 index 0000000000..31580d1422 --- /dev/null +++ b/pkg/littlefs2/doc.txt @@ -0,0 +1,16 @@ +/** + * @defgroup pkg_littlefs2 littlefs v2.x.y file system + * @ingroup pkg + * @ingroup sys_fs + * @brief A little fail-safe filesystem designed for embedded systems, + * v2.x.y release + * @see https://github.com/ARMmbed/littlefs/ + * + * ### Migrating + * + * Migrating from Littlefs 1.x.y to 2.x.y can be hard, the disk format changed + * enough to be binary incompatible. See also + * https://github.com/ARMmbed/littlefs/releases/tag/v2.0.0 for a change set + * and a possible migration path. + * + */ diff --git a/pkg/littlefs2/fs/Makefile b/pkg/littlefs2/fs/Makefile new file mode 100644 index 0000000000..20098c6463 --- /dev/null +++ b/pkg/littlefs2/fs/Makefile @@ -0,0 +1,3 @@ +MODULE := littlefs2_fs + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/littlefs2/fs/littlefs2_fs.c b/pkg/littlefs2/fs/littlefs2_fs.c new file mode 100644 index 0000000000..2bcf2f125f --- /dev/null +++ b/pkg/littlefs2/fs/littlefs2_fs.c @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_littlefs2 + * @{ + * + * @file + * @brief littlefs v2 integration with vfs + * + * @author Vincent Dupont + * + * @} + */ + + +#include +#include +#include +#include + +#include "fs/littlefs2_fs.h" + +#include "kernel_defines.h" + +#define ENABLE_DEBUG (0) +#include + +static int littlefs_err_to_errno(ssize_t err) +{ + switch (err) { + case LFS_ERR_OK: + return 0; + case LFS_ERR_IO: + return -EIO; + case LFS_ERR_CORRUPT: + return -ENODEV; + case LFS_ERR_NOENT: + return -ENOENT; + case LFS_ERR_EXIST: + return -EEXIST; + case LFS_ERR_NOTDIR: + return -ENOTDIR; + case LFS_ERR_ISDIR: + return -EISDIR; + case LFS_ERR_NOTEMPTY: + return -ENOTEMPTY; + case LFS_ERR_BADF: + return -EBADF; + case LFS_ERR_INVAL: + return -EINVAL; + case LFS_ERR_NOSPC: + return -ENOSPC; + case LFS_ERR_NOMEM: + return -ENOMEM; + default: + return err; + } +} + +static int _dev_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) +{ + littlefs_desc_t *fs = c->context; + mtd_dev_t *mtd = fs->dev; + + DEBUG("lfs_read: c=%p, block=%" PRIu32 ", off=%" PRIu32 ", buf=%p, size=%" PRIu32 "\n", + (void *)c, block, off, buffer, size); + + int ret = mtd_read(mtd, buffer, ((fs->base_addr + block) * c->block_size) + off, size); + if (ret >= 0) { + return 0; + } + + return ret; +} + +static int _dev_write(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) +{ + littlefs_desc_t *fs = c->context; + mtd_dev_t *mtd = fs->dev; + + DEBUG("lfs_write: c=%p, block=%" PRIu32 ", off=%" PRIu32 ", buf=%p, size=%" PRIu32 "\n", + (void *)c, block, off, buffer, size); + + const uint8_t *buf = buffer; + uint32_t addr = ((fs->base_addr + block) * c->block_size) + off; + for (const uint8_t *part = buf; part < buf + size; part += c->prog_size, + addr += c->prog_size) { + int ret = mtd_write(mtd, part, addr, c->prog_size); + if (ret < 0) { + return ret; + } + else if ((unsigned)ret != c->prog_size) { + return -EIO; + } + } + + return 0; +} + +static int _dev_erase(const struct lfs_config *c, lfs_block_t block) +{ + littlefs_desc_t *fs = c->context; + mtd_dev_t *mtd = fs->dev; + + DEBUG("lfs_erase: c=%p, block=%" PRIu32 "\n", (void *)c, block); + + int ret = mtd_erase(mtd, ((fs->base_addr + block) * c->block_size), c->block_size); + if (ret >= 0) { + return 0; + } + + return ret; +} + +static int _dev_sync(const struct lfs_config *c) +{ + (void)c; + + return 0; +} + +static int prepare(littlefs_desc_t *fs) +{ + mutex_init(&fs->lock); + mutex_lock(&fs->lock); + + memset(&fs->fs, 0, sizeof(fs->fs)); + + if (!fs->config.block_count) { + fs->config.block_count = fs->dev->sector_count - fs->base_addr; + } + if (!fs->config.block_size) { + fs->config.block_size = fs->dev->page_size * fs->dev->pages_per_sector; + } + if (!fs->config.prog_size) { + fs->config.prog_size = fs->dev->page_size; + } + if (!fs->config.read_size) { + fs->config.read_size = fs->dev->page_size; + } + if (!fs->config.cache_size) { + fs->config.cache_size = fs->dev->page_size * CONFIG_LITTLEFS2_CACHE_PAGES; + } + if (!fs->config.block_cycles) { + fs->config.block_cycles = CONFIG_LITTLEFS2_BLOCK_CYCLES; + } + fs->config.lookahead_size = CONFIG_LITTLEFS2_LOOKAHEAD_SIZE; + fs->config.lookahead_buffer = fs->lookahead_buf; + fs->config.context = fs; + fs->config.read = _dev_read; + fs->config.prog = _dev_write; + fs->config.erase = _dev_erase; + fs->config.sync = _dev_sync; +#if CONFIG_LITTLEFS2_FILE_BUFFER_SIZE + fs->config.file_buffer = fs->file_buf; +#endif +#if CONFIG_LITTLEFS2_READ_BUFFER_SIZE + fs->config.read_buffer = fs->read_buf; +#endif +#if CONFIG_LITTLEFS2_PROG_BUFFER_SIZE + fs->config.prog_buffer = fs->prog_buf; +#endif + + return mtd_init(fs->dev); +} + +static int _format(vfs_mount_t *mountp) +{ + littlefs_desc_t *fs = mountp->private_data; + + DEBUG("littlefs: format: mountp=%p\n", (void *)mountp); + int ret = prepare(fs); + if (ret) { + return -ENODEV; + } + + ret = lfs_format(&fs->fs, &fs->config); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _mount(vfs_mount_t *mountp) +{ + /* if one of the lines below fail to compile you probably need to adjust + vfs buffer sizes ;) */ + BUILD_BUG_ON(VFS_DIR_BUFFER_SIZE < sizeof(lfs_dir_t)); + BUILD_BUG_ON(VFS_FILE_BUFFER_SIZE < sizeof(lfs_file_t)); + + littlefs_desc_t *fs = mountp->private_data; + + DEBUG("littlefs: mount: mountp=%p\n", (void *)mountp); + int ret = prepare(fs); + if (ret) { + return -ENODEV; + } + + ret = lfs_mount(&fs->fs, &fs->config); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _umount(vfs_mount_t *mountp) +{ + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: umount: mountp=%p\n", (void *)mountp); + + int ret = lfs_unmount(&fs->fs); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _unlink(vfs_mount_t *mountp, const char *name) +{ + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: unlink: mountp=%p, name=%s\n", + (void *)mountp, name); + + int ret = lfs_remove(&fs->fs, name); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _rename(vfs_mount_t *mountp, const char *from_path, const char *to_path) +{ + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: rename: mountp=%p, from=%s, to=%s\n", + (void *)mountp, from_path, to_path); + + int ret = lfs_rename(&fs->fs, from_path, to_path); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _mkdir(vfs_mount_t *mountp, const char *name, mode_t mode) +{ + (void)mode; + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: mkdir: mountp=%p, name=%s, mode=%" PRIu32 "\n", + (void *)mountp, name, (uint32_t)mode); + + int ret = lfs_mkdir(&fs->fs, name); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _rmdir(vfs_mount_t *mountp, const char *name) +{ + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: rmdir: mountp=%p, name=%s\n", + (void *)mountp, name); + + int ret = lfs_remove(&fs->fs, name); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _open(vfs_file_t *filp, const char *name, int flags, mode_t mode, const char *abs_path) +{ + littlefs_desc_t *fs = filp->mp->private_data; + lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer; + (void) abs_path; + (void) mode; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: open: filp=%p, fp=%p\n", (void *)filp, (void *)fp); + + int l_flags = 0; + if ((flags & O_ACCMODE) == O_RDONLY) { + l_flags |= LFS_O_RDONLY; + } + if ((flags & O_APPEND) == O_APPEND) { + l_flags |= LFS_O_APPEND; + } + if ((flags & O_TRUNC) == O_TRUNC) { + l_flags |= LFS_O_TRUNC; + } + if ((flags & O_CREAT) == O_CREAT) { + l_flags |= LFS_O_CREAT; + } + if ((flags & O_ACCMODE) == O_WRONLY) { + l_flags |= LFS_O_WRONLY; + } + if ((flags & O_ACCMODE) == O_RDWR) { + l_flags |= LFS_O_RDWR; + } + if ((flags & O_EXCL) == O_EXCL) { + l_flags |= LFS_O_EXCL; + } + + DEBUG("littlefs: open: %s (abs_path: %s), flags: 0x%x\n", name, abs_path, (int) l_flags); + + int ret = lfs_file_open(&fs->fs, fp, name, l_flags); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _close(vfs_file_t *filp) +{ + littlefs_desc_t *fs = filp->mp->private_data; + lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: close: filp=%p, fp=%p\n", (void *)filp, (void *)fp); + + int ret = lfs_file_close(&fs->fs, fp); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static ssize_t _write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + littlefs_desc_t *fs = filp->mp->private_data; + lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: write: filp=%p, fp=%p, src=%p, nbytes=%u\n", + (void *)filp, (void *)fp, (void *)src, (unsigned)nbytes); + + ssize_t ret = lfs_file_write(&fs->fs, fp, src, nbytes); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static ssize_t _read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + littlefs_desc_t *fs = filp->mp->private_data; + lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: read: filp=%p, fp=%p, dest=%p, nbytes=%u\n", + (void *)filp, (void *)fp, (void *)dest, (unsigned)nbytes); + + ssize_t ret = lfs_file_read(&fs->fs, fp, dest, nbytes); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static off_t _lseek(vfs_file_t *filp, off_t off, int whence) +{ + littlefs_desc_t *fs = filp->mp->private_data; + lfs_file_t *fp = (lfs_file_t *)&filp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: seek: filp=%p, fp=%p, off=%ld, whence=%d\n", + (void *)filp, (void *)fp, (long)off, whence); + + int ret = lfs_file_seek(&fs->fs, fp, off, whence); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _stat(vfs_mount_t *mountp, const char *restrict path, struct stat *restrict buf) +{ + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: stat: mountp=%p, path=%s, buf=%p\n", + (void *)mountp, path, (void *)buf); + + struct lfs_info info; + int ret = lfs_stat(&fs->fs, path, &info); + mutex_unlock(&fs->lock); + /* info.name */ + buf->st_size = info.size; + switch (info.type) { + case LFS_TYPE_REG: + buf->st_mode = S_IFREG; + break; + case LFS_TYPE_DIR: + buf->st_mode = S_IFDIR; + break; + } + + return littlefs_err_to_errno(ret); +} + +static int _traverse_cb(void *param, lfs_block_t block) +{ + (void)block; + unsigned long *nb_blocks = param; + (*nb_blocks)++; + + return 0; +} + +static int _statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf) +{ + (void)path; + littlefs_desc_t *fs = mountp->private_data; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: statvfs: mountp=%p, path=%s, buf=%p\n", + (void *)mountp, path, (void *)buf); + + unsigned long nb_blocks = 0; + int ret = lfs_fs_traverse(&fs->fs, _traverse_cb, &nb_blocks); + mutex_unlock(&fs->lock); + + buf->f_bsize = fs->fs.cfg->block_size; /* block size */ + buf->f_frsize = fs->fs.cfg->block_size; /* fundamental block size */ + buf->f_blocks = fs->fs.cfg->block_count; /* Blocks total */ + buf->f_bfree = buf->f_blocks - nb_blocks; /* Blocks free */ + buf->f_bavail = buf->f_blocks - nb_blocks; /* Blocks available to non-privileged processes */ + buf->f_flag = ST_NOSUID; + buf->f_namemax = LFS_NAME_MAX; + + return littlefs_err_to_errno(ret); +} + +static int _opendir(vfs_DIR *dirp, const char *dirname, const char *abs_path) +{ + (void)abs_path; + littlefs_desc_t *fs = dirp->mp->private_data; + lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: opendir: dirp=%p, dirname=%s (abs_path=%s)\n", + (void *)dirp, dirname, abs_path); + + int ret = lfs_dir_open(&fs->fs, dir, dirname); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + littlefs_desc_t *fs = dirp->mp->private_data; + lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: readdir: dirp=%p, entry=%p\n", + (void *)dirp, (void *)entry); + + struct lfs_info info; + int ret = lfs_dir_read(&fs->fs, dir, &info); + if (ret >= 0) { + entry->d_ino = info.type; + entry->d_name[0] = '/'; + strncpy(entry->d_name + 1, info.name, VFS_NAME_MAX - 1); + } + + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static int _closedir(vfs_DIR *dirp) +{ + littlefs_desc_t *fs = dirp->mp->private_data; + lfs_dir_t *dir = (lfs_dir_t *)&dirp->private_data.buffer; + + mutex_lock(&fs->lock); + + DEBUG("littlefs: closedir: dirp=%p\n", (void *)dirp); + + int ret = lfs_dir_close(&fs->fs, dir); + mutex_unlock(&fs->lock); + + return littlefs_err_to_errno(ret); +} + +static const vfs_file_system_ops_t littlefs_fs_ops = { + .format = _format, + .mount = _mount, + .umount = _umount, + .unlink = _unlink, + .mkdir = _mkdir, + .rmdir = _rmdir, + .rename = _rename, + .stat = _stat, + .statvfs = _statvfs, +}; + +static const vfs_file_ops_t littlefs_file_ops = { + .open = _open, + .close = _close, + .read = _read, + .write = _write, + .lseek = _lseek, +}; + +static const vfs_dir_ops_t littlefs_dir_ops = { + .opendir = _opendir, + .readdir = _readdir, + .closedir = _closedir, +}; + +const vfs_file_system_t littlefs2_file_system = { + .fs_op = &littlefs_fs_ops, + .f_op = &littlefs_file_ops, + .d_op = &littlefs_dir_ops, +}; diff --git a/sys/include/fs/littlefs2_fs.h b/sys/include/fs/littlefs2_fs.h new file mode 100644 index 0000000000..aa323944f1 --- /dev/null +++ b/sys/include/fs/littlefs2_fs.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * Copyright (C) 2020 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_littlefs2 littlefs v2 integration + * @ingroup pkg_littlefs2 + * @brief RIOT integration of littlefs version 2.x.y + * + * @{ + * + * @file + * @brief littlefs v2 integration with vfs + * + * @author Vincent Dupont + * @author Koen Zandberg + */ + +#ifndef FS_LITTLEFS2_FS_H +#define FS_LITTLEFS2_FS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "vfs.h" +#include "lfs.h" +#include "mtd.h" +#include "mutex.h" + +/** + * @name littlefs configuration + * @ingroup config + * @{ + */ +#ifndef CONFIG_LITTLEFS2_LOOKAHEAD_SIZE +/** Default lookahead size */ +#define CONFIG_LITTLEFS2_LOOKAHEAD_SIZE (16) +#endif + +#ifndef CONFIG_LITTLEFS2_FILE_BUFFER_SIZE +/** File buffer size, if 0, dynamic allocation is used. + * If set, only one file can be used at a time, must be program size (mtd page + * size is used internally as program size) */ +#define CONFIG_LITTLEFS2_FILE_BUFFER_SIZE (0) +#endif + +#ifndef CONFIG_LITTLEFS2_READ_BUFFER_SIZE +/** Read buffer size, if 0, dynamic allocation is used. + * If set, it must be read size (mtd page size is used internally as read + * size) */ +#define CONFIG_LITTLEFS2_READ_BUFFER_SIZE (0) +#endif + +#ifndef CONFIG_LITTLEFS2_PROG_BUFFER_SIZE +/** Prog buffer size, if 0, dynamic allocation is used. + * If set, it must be program size */ +#define CONFIG_LITTLEFS2_PROG_BUFFER_SIZE (0) +#endif + +#ifndef CONFIG_LITTLEFS2_CACHE_PAGES +/** Sets the number of pages used as cache. Has to be at least 1. + */ +#define CONFIG_LITTLEFS2_CACHE_PAGES (1) +#endif + +#ifndef CONFIG_LITTLEFS2_BLOCK_CYCLES +/** Sets the maximum number of erase cycles before blocks are evicted as a part + * of wear leveling. -1 disables wear-leveling. */ +#define CONFIG_LITTLEFS2_BLOCK_CYCLES (512) +#endif +/** @} */ + +/** + * @brief littlefs descriptor for vfs integration + */ +typedef struct { + lfs_t fs; /**< littlefs descriptor */ + struct lfs_config config; /**< littlefs config */ + mtd_dev_t *dev; /**< mtd device to use */ + mutex_t lock; /**< mutex */ + /** first block number to use, + * total number of block is defined in @p config. + * if set to 0, the total number of sectors from the mtd is used */ + uint32_t base_addr; +#if CONFIG_LITTLEFS2_FILE_BUFFER_SIZE || DOXYGEN + /** file buffer to use internally if CONFIG_LITTLEFS2_FILE_BUFFER_SIZE + * is set */ + uint8_t file_buf[CONFIG_LITTLEFS2_FILE_BUFFER_SIZE]; +#endif +#if CONFIG_LITTLEFS2_READ_BUFFER_SIZE || DOXYGEN + /** read buffer to use internally if CONFIG_LITTLEFS2_READ_BUFFER_SIZE + * is set */ + uint8_t read_buf[CONFIG_LITTLEFS2_READ_BUFFER_SIZE]; +#endif +#if CONFIG_LITTLEFS2_PROG_BUFFER_SIZE || DOXYGEN + /** prog buffer to use internally if CONFIG_LITTLEFS2_PROG_BUFFER_SIZE + * is set */ + uint8_t prog_buf[CONFIG_LITTLEFS2_PROG_BUFFER_SIZE]; +#endif + /** lookahead buffer to use internally */ + uint8_t lookahead_buf[CONFIG_LITTLEFS2_LOOKAHEAD_SIZE]; +} littlefs_desc_t; + +/** The littlefs vfs driver */ +extern const vfs_file_system_t littlefs2_file_system; + +#ifdef __cplusplus +} +#endif + +#endif /* FS_LITTLEFS2_FS_H */ +/** @} */ diff --git a/tests/pkg_littlefs2/Makefile b/tests/pkg_littlefs2/Makefile new file mode 100644 index 0000000000..54838f5621 --- /dev/null +++ b/tests/pkg_littlefs2/Makefile @@ -0,0 +1,11 @@ +include ../Makefile.tests_common + +# Set vfs file and dir buffer sizes +CFLAGS += -DVFS_FILE_BUFFER_SIZE=84 -DVFS_DIR_BUFFER_SIZE=52 +# Reduce LFS_NAME_MAX to 31 (as VFS_NAME_MAX default) +CFLAGS += -DLFS_NAME_MAX=31 + +USEPKG += littlefs2 +USEMODULE += embunit + +include $(RIOTBASE)/Makefile.include diff --git a/tests/pkg_littlefs2/Makefile.ci b/tests/pkg_littlefs2/Makefile.ci new file mode 100644 index 0000000000..6726f1d2b6 --- /dev/null +++ b/tests/pkg_littlefs2/Makefile.ci @@ -0,0 +1,17 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + i-nucleo-lrwan1 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-f030r8 \ + stm32f030f4-demo\ + stm32f0discovery \ + stm32l0538-disco \ + waspmote-pro \ + # diff --git a/tests/pkg_littlefs2/main.c b/tests/pkg_littlefs2/main.c new file mode 100644 index 0000000000..949c2c44db --- /dev/null +++ b/tests/pkg_littlefs2/main.c @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2017 OTA keys S.A. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + */ +#include "fs/littlefs2_fs.h" +#include "vfs.h" +#include "mtd.h" +#include "board.h" + +#include +#include +#include +#include + +#include "embUnit.h" + +/* Define MTD_0 in board.h to use the board mtd if any */ +#ifdef MTD_0 +#define _dev (MTD_0) +#else +/* Test mock object implementing a simple RAM-based mtd */ +#ifndef SECTOR_COUNT +#define SECTOR_COUNT 16 +#endif +#ifndef PAGE_PER_SECTOR +#define PAGE_PER_SECTOR 4 +#endif +#ifndef PAGE_SIZE +#define PAGE_SIZE 64 +#endif + +static uint8_t dummy_memory[PAGE_PER_SECTOR * PAGE_SIZE * SECTOR_COUNT]; + +static int _init(mtd_dev_t *dev) +{ + (void)dev; + + return 0; +} + +static int _read(mtd_dev_t *dev, void *buff, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + memcpy(buff, dummy_memory + addr, size); + + return size; +} + +static int _write(mtd_dev_t *dev, const void *buff, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + if (size > PAGE_SIZE) { + return -EOVERFLOW; + } + memcpy(dummy_memory + addr, buff, size); + + return size; +} + +static int _erase(mtd_dev_t *dev, uint32_t addr, uint32_t size) +{ + (void)dev; + + if (size % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) { + return -EOVERFLOW; + } + if (addr % (PAGE_PER_SECTOR * PAGE_SIZE) != 0) { + return -EOVERFLOW; + } + if (addr + size > sizeof(dummy_memory)) { + return -EOVERFLOW; + } + memset(dummy_memory + addr, 0xff, size); + + return 0; +} + +static int _power(mtd_dev_t *dev, enum mtd_power_state power) +{ + (void)dev; + (void)power; + return 0; +} + +static const mtd_desc_t driver = { + .init = _init, + .read = _read, + .write = _write, + .erase = _erase, + .power = _power, +}; + +static mtd_dev_t dev = { + .driver = &driver, + .sector_count = SECTOR_COUNT, + .pages_per_sector = PAGE_PER_SECTOR, + .page_size = PAGE_SIZE, +}; + +static mtd_dev_t *_dev = (mtd_dev_t*) &dev; +#endif /* MTD_0 */ + +static littlefs_desc_t littlefs_desc; + +static vfs_mount_t _test_littlefs_mount = { + .fs = &littlefs2_file_system, + .mount_point = "/test-littlefs", + .private_data = &littlefs_desc, +}; + +static void test_littlefs_setup(void) +{ + littlefs_desc.dev = _dev; + vfs_mount(&_test_littlefs_mount); +} + +static void test_littlefs_teardown(void) +{ + vfs_unlink("/test-littlefs/test.txt"); + vfs_unlink("/test-littlefs/test0.txt"); + vfs_unlink("/test-littlefs/test1.txt"); + vfs_unlink("/test-littlefs/a/test2.txt"); + vfs_rmdir("/test-littlefs/a"); + vfs_umount(&_test_littlefs_mount); +} + +static void tests_littlefs_format(void) +{ + int res; + vfs_umount(&_test_littlefs_mount); + res = mtd_erase(_dev, 0, _dev->page_size * _dev->pages_per_sector * _dev->sector_count); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_mount(&_test_littlefs_mount); + TEST_ASSERT(res < 0); + + /* 1. format an invalid file system (failed mount) */ + res = vfs_format(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_mount(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_umount(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + /* 2. format a valid file system */ + res = vfs_format(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_mount_umount(void) +{ + int res; + res = vfs_umount(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_mount(&_test_littlefs_mount); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_open_close(void) +{ + int res; + res = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(res >= 0); + + res = vfs_close(res); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_write(void) +{ + const char buf[] = "TESTSTRING"; + char r_buf[2 * sizeof(buf)]; + + int res; + int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd >= 0); + + res = vfs_write(fd, buf, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), res); + + res = vfs_lseek(fd, 0, SEEK_SET); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_read(fd, r_buf, sizeof(r_buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), res); + TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + fd = vfs_open("/test-littlefs/test.txt", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + + res = vfs_read(fd, r_buf, sizeof(r_buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), res); + TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_unlink(void) +{ + const char buf[] = "TESTSTRING"; + + int res; + int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd >= 0); + + res = vfs_write(fd, buf, sizeof(buf)); + TEST_ASSERT_EQUAL_INT(sizeof(buf), res); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_unlink("/test-littlefs/test.txt"); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_readdir(void) +{ + const char buf0[] = "TESTSTRING"; + const char buf1[] = "TESTTESTSTRING"; + const char buf2[] = "TESTSTRINGSTRING"; + + int res; + int fd0 = vfs_open("/test-littlefs/test0.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd0 >= 0); + + int fd1 = vfs_open("/test-littlefs/test1.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd1 >= 0); + + int fd2 = vfs_open("/test-littlefs/a/test2.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd2 < 0); + + res = vfs_mkdir("/test-littlefs/a", 0); + TEST_ASSERT_EQUAL_INT(0, res); + + fd2 = vfs_open("/test-littlefs/a/test2.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd2 >= 0); + + res = vfs_write(fd0, buf0, sizeof(buf0)); + TEST_ASSERT_EQUAL_INT(sizeof(buf0), res); + + res = vfs_write(fd1, buf1, sizeof(buf1)); + TEST_ASSERT_EQUAL_INT(sizeof(buf1), res); + + res = vfs_write(fd2, buf2, sizeof(buf2)); + TEST_ASSERT_EQUAL_INT(sizeof(buf2), res); + + res = vfs_close(fd0); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_close(fd1); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_close(fd2); + TEST_ASSERT_EQUAL_INT(0, res); + + vfs_DIR dirp; + res = vfs_opendir(&dirp, "/test-littlefs"); + TEST_ASSERT_EQUAL_INT(0, res); + + vfs_dirent_t entry; + int nb_files = 0; + do { + res = vfs_readdir(&dirp, &entry); + if (res == 1 && (strcmp("/test0.txt", &(entry.d_name[0])) == 0 || + strcmp("/test1.txt", &(entry.d_name[0])) == 0)) { + nb_files++; + } + } while (res == 1); + + TEST_ASSERT_EQUAL_INT(2, nb_files); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_closedir(&dirp); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_opendir(&dirp, "/test-littlefs/a"); + TEST_ASSERT_EQUAL_INT(0, res); + nb_files = 0; + do { + res = vfs_readdir(&dirp, &entry); + if (res == 1 && strcmp("/test2.txt", &(entry.d_name[0])) == 0) { + nb_files++; + } + } while (res == 1); + + TEST_ASSERT_EQUAL_INT(1, nb_files); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_unlink("/test-littlefs/test0.txt"); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_unlink("/test-littlefs/test1.txt"); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_unlink("/test-littlefs/a/test2.txt"); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_closedir(&dirp); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_rmdir("/test-littlefs/a"); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_rename(void) +{ + const char buf[] = "TESTSTRING"; + char r_buf[2 * sizeof(buf)]; + + int res; + int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd >= 0); + + res = vfs_write(fd, buf, sizeof(buf)); + TEST_ASSERT(res == sizeof(buf)); + + res = vfs_lseek(fd, 0, SEEK_SET); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_read(fd, r_buf, sizeof(r_buf)); + TEST_ASSERT(res == sizeof(buf)); + TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_rename("/test-littlefs/test.txt", "/test-littlefs/test1.txt"); + TEST_ASSERT_EQUAL_INT(0, res); + + fd = vfs_open("/test-littlefs/test.txt", O_RDONLY, 0); + TEST_ASSERT(fd < 0); + + fd = vfs_open("/test-littlefs/test1.txt", O_RDONLY, 0); + TEST_ASSERT(fd >= 0); + + res = vfs_lseek(fd, 0, SEEK_SET); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_read(fd, r_buf, sizeof(r_buf)); + TEST_ASSERT(res == sizeof(buf)); + TEST_ASSERT_EQUAL_STRING(&buf[0], &r_buf[0]); + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_unlink("/test-littlefs/test1.txt"); + TEST_ASSERT_EQUAL_INT(0, res); +} + +static void tests_littlefs_statvfs(void) +{ + const char buf[] = "TESTSTRING"; + struct statvfs stat1; + struct statvfs stat2; + + int res = vfs_statvfs("/test-littlefs/", &stat1); + TEST_ASSERT_EQUAL_INT(0, res); + TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat1.f_bsize); + TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat1.f_frsize); + TEST_ASSERT((_dev->pages_per_sector * _dev->page_size * _dev->sector_count) >= + stat1.f_blocks); + + int fd = vfs_open("/test-littlefs/test.txt", O_CREAT | O_RDWR, 0); + TEST_ASSERT(fd >= 0); + + for (int i = 0; i < 128; ++i) { + res = vfs_write(fd, buf, sizeof(buf)); + TEST_ASSERT(res == sizeof(buf)); + } + + res = vfs_close(fd); + TEST_ASSERT_EQUAL_INT(0, res); + + res = vfs_statvfs("/test-littlefs/", &stat2); + TEST_ASSERT_EQUAL_INT(0, res); + + TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat2.f_bsize); + TEST_ASSERT_EQUAL_INT(_dev->page_size * _dev->pages_per_sector, stat2.f_frsize); + TEST_ASSERT(stat1.f_bfree > stat2.f_bfree); + TEST_ASSERT(stat1.f_bavail > stat2.f_bavail); +} + +Test *tests_littlefs(void) +{ +#ifndef MTD_0 + memset(dummy_memory, 0xff, sizeof(dummy_memory)); +#endif + + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(tests_littlefs_format), + new_TestFixture(tests_littlefs_mount_umount), + new_TestFixture(tests_littlefs_open_close), + new_TestFixture(tests_littlefs_write), + new_TestFixture(tests_littlefs_unlink), + new_TestFixture(tests_littlefs_readdir), + new_TestFixture(tests_littlefs_rename), + new_TestFixture(tests_littlefs_statvfs), + }; + + EMB_UNIT_TESTCALLER(littlefs_tests, test_littlefs_setup, test_littlefs_teardown, fixtures); + + return (Test *)&littlefs_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_littlefs()); + TESTS_END(); + return 0; +} +/** @} */ diff --git a/tests/pkg_littlefs2/tests/01-run.py b/tests/pkg_littlefs2/tests/01-run.py new file mode 100755 index 0000000000..5fc8788f24 --- /dev/null +++ b/tests/pkg_littlefs2/tests/01-run.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect(r'OK \(\d+ tests\)') + + +if __name__ == "__main__": + sys.exit(run(testfunc))