diff --git a/Makefile.dep b/Makefile.dep index ad9c764824..a323f6b25b 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -72,6 +72,11 @@ ifneq (,$(filter fatfs_vfs,$(USEMODULE))) USEMODULE += vfs endif +ifneq (,$(filter lwext%_vfs,$(USEMODULE))) + USEPKG += lwext4 + USEMODULE += vfs +endif + ifneq (,$(filter nimble_%,$(USEMODULE))) USEPKG += nimble endif diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 1b43208dff..9481fbe33e 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -129,6 +129,11 @@ PSEUDOMODULES += log PSEUDOMODULES += log_printfnoformat PSEUDOMODULES += log_color PSEUDOMODULES += lora +PSEUDOMODULES += lwext4_no_gpl +PSEUDOMODULES += lwext2_vfs +PSEUDOMODULES += lwext3_vfs +PSEUDOMODULES += lwext4_vfs +PSEUDOMODULES += lwext4_vfs_format ## @defgroup pseudomodule_libc_gettimeofday libc_gettimeofday ## @brief Includes implementation of gettimeofday() ## diff --git a/pkg/lwext4/Makefile b/pkg/lwext4/Makefile new file mode 100644 index 0000000000..fb66041901 --- /dev/null +++ b/pkg/lwext4/Makefile @@ -0,0 +1,13 @@ +PKG_NAME=lwext4 +PKG_URL=https://github.com/gkostka/lwext4.git +PKG_VERSION=58bcf89a121b72d4fb66334f1693d3b30e4cb9c5 +PKG_LICENSE=GPLv2 + +CFLAGS += -Wno-cast-align +CFLAGS += -Wno-error=format +CFLAGS += -Wno-unused-parameter -Wno-unused-variable + +include $(RIOTBASE)/pkg/pkg.mk + +all: + $(QQ)"$(MAKE)" -C $(PKG_SOURCE_DIR)/src -f $(RIOTBASE)/Makefile.base MODULE=$(PKG_NAME) diff --git a/pkg/lwext4/Makefile.dep b/pkg/lwext4/Makefile.dep new file mode 100644 index 0000000000..5baf14887e --- /dev/null +++ b/pkg/lwext4/Makefile.dep @@ -0,0 +1,5 @@ +USEMODULE += lwext_fs + +ifneq (,$(filter vfs_auto_format,$(USEMODULE))) + DEFAULT_MODULE += lwext4_vfs_format +endif diff --git a/pkg/lwext4/Makefile.include b/pkg/lwext4/Makefile.include new file mode 100644 index 0000000000..3f50cff979 --- /dev/null +++ b/pkg/lwext4/Makefile.include @@ -0,0 +1,23 @@ +INCLUDES += -I$(PKGDIRBASE)/lwext4/include + +DIRS += $(RIOTPKG)/lwext4/fs + +CFLAGS += -DCONFIG_USE_DEFAULT_CFG=1 +CFLAGS += -DCONFIG_HAVE_OWN_OFLAGS=0 + +# select ext2/3/4 feature level based on module name +ifneq (,$(filter lwext4_vfs,$(USEMODULE))) + CFLAGS += -DCONFIG_EXT_FEATURE_SET_LVL=F_SET_EXT4 +endif +ifneq (,$(filter lwext3_vfs,$(USEMODULE))) + CFLAGS += -DCONFIG_EXT_FEATURE_SET_LVL=F_SET_EXT3 +endif +ifneq (,$(filter lwext2_vfs,$(USEMODULE))) + CFLAGS += -DCONFIG_EXT_FEATURE_SET_LVL=F_SET_EXT2 +endif + +# Disable GPL-only features +ifneq (,$(filter lwext4_no_gpl,$(USEMODULE))) + CFLAGS += -DCONFIG_EXTENTS_ENABLE=0 + CFLAGS += -DCONFIG_XATTR_ENABLE=0 +endif diff --git a/pkg/lwext4/doc.txt b/pkg/lwext4/doc.txt new file mode 100644 index 0000000000..f1f102aeeb --- /dev/null +++ b/pkg/lwext4/doc.txt @@ -0,0 +1,19 @@ +/** + * @defgroup pkg_lwext4 lightweight ext2/3/4 implementation + * @ingroup pkg + * @ingroup sys_fs + * @brief Provides a lightweight implementation of the ext2/3/4 + * filesystem with optional journaling transactions & recovery + * + * Lwext4 is an excellent choice for SD/MMC card, USB flash drive + * or any other wear leveled memory types. However it is not good + * for raw flash devices. + * + * Some of the source files are licensed under GPLv2. + * It makes whole lwext4 GPLv2 licensed. To use library as a BSD3, + * GPLv2 licensed source files must be disabled. + * To do so, enable the `lwext4_no_gpl` module. + * This will disable support for extends and extended attributes. + * + * @see https://github.com/gkostka/lwext4 + */ diff --git a/pkg/lwext4/fs/Makefile b/pkg/lwext4/fs/Makefile new file mode 100644 index 0000000000..145ecd9806 --- /dev/null +++ b/pkg/lwext4/fs/Makefile @@ -0,0 +1,3 @@ +MODULE := lwext_fs + +include $(RIOTBASE)/Makefile.base diff --git a/pkg/lwext4/fs/lwext4_fs.c b/pkg/lwext4/fs/lwext4_fs.c new file mode 100644 index 0000000000..47e1989156 --- /dev/null +++ b/pkg/lwext4/fs/lwext4_fs.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2022 ML!PA Consulting GmbH + * + * 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_lwext4 + * @{ + * + * @file + * @brief lwEXT4 integration with vfs + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include +#include +#include +#include +#include + +#include "fs/lwext4_fs.h" +#include +#include + +#define ENABLE_DEBUG 0 +#include + +/** + * @brief file system block size to use when formatting a new file system + */ +#ifndef CONFIG_EXT_BLOCKSIZE_DEFAULT +#define CONFIG_EXT_BLOCKSIZE_DEFAULT (1024) +#endif + +/** + * @brief Automatic mountpoints + */ +XFA_USE(vfs_mount_t, vfs_mountpoints_xfa); + +/** + * @brief Number of automatic mountpoints + */ +#define MOUNTPOINTS_NUMOF XFA_LEN(vfs_mount_t, vfs_mountpoints_xfa) + +struct ext4_mountpoint *ext4_get_mount(const char *path) +{ + size_t strlen_path = strlen(path); + for (unsigned i = 0; i < MOUNTPOINTS_NUMOF; ++i) { + /* lwext4 wants terminating '/' for mountpoint, but VFS does not */ + size_t strlen_mp = strlen(vfs_mountpoints_xfa[i].mount_point) - 1; + + if (strlen_mp < strlen_path && + strncmp(path, vfs_mountpoints_xfa[i].mount_point, strlen_mp) == 0) { + lwext4_desc_t *fs = vfs_mountpoints_xfa[i].private_data; + return &fs->mp; + } + } + + DEBUG("lwext4: no mountpoint found for '%s'\n", path); + + return NULL; +} + +static int _noop(struct ext4_blockdev *bdev) +{ + (void)bdev; + return 0; +} + +static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, + uint32_t blk_cnt) +{ + mtd_dev_t *dev = bdev->bdif->p_user; + + uint32_t page = blk_id * dev->pages_per_sector; + uint32_t size = blk_cnt * dev->pages_per_sector * dev->page_size; + + assert(blk_id <= UINT32_MAX); + + DEBUG("lwext4: read %"PRIu32" bytes from page %"PRIu32"\n", size, page); + + return -mtd_read_page(dev, buf, page, 0, size); +} + +static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, + uint64_t blk_id, uint32_t blk_cnt) +{ + mtd_dev_t *dev = bdev->bdif->p_user; + + uint32_t page = blk_id * dev->pages_per_sector; + uint32_t size = blk_cnt * dev->pages_per_sector * dev->page_size; + + assert(blk_id <= UINT32_MAX); + + DEBUG("lwext4: erase %"PRIu32" sectors starting with %"PRIu64"\n", blk_cnt, blk_id); + int res = mtd_erase_sector(dev, blk_id, blk_cnt); + if (res) { + return -res; + } + + DEBUG("lwext4: write %"PRIu32" bytes to page %"PRIu32"\n", size, page); + + return -mtd_write_page_raw(dev, buf, page, 0, size); +} + +static int prepare(lwext4_desc_t *fs, const char *mount_point) +{ + mtd_dev_t *dev = fs->dev; + struct ext4_blockdev_iface *iface = &fs->iface; + + memset(&fs->mp, 0, sizeof(fs->mp)); + memset(&fs->bdev, 0, sizeof(fs->bdev)); + memset(&fs->iface, 0, sizeof(fs->iface)); + + strncpy(fs->mp.name, mount_point, CONFIG_EXT4_MAX_MP_NAME); + strcat(fs->mp.name, "/"); + + int res = mtd_init(dev); + if (res) { + return res; + } + + iface->open = _noop; + iface->bread = blockdev_bread; + iface->bwrite = blockdev_bwrite; + iface->close = _noop; + iface->lock = _noop; + iface->unlock = _noop; + + iface->p_user = dev; + iface->ph_bcnt = dev->sector_count; + iface->ph_bsize = dev->pages_per_sector * dev->page_size; + iface->ph_bbuf = malloc(iface->ph_bsize); + + fs->bdev.bdif = iface; + fs->bdev.part_size = iface->ph_bcnt * iface->ph_bsize; + + return -ext4_block_init(&fs->bdev); +} + +static mutex_t _lwext4_mutex; +static void _lock(void) +{ + mutex_lock(&_lwext4_mutex); +} + +static void _unlock(void) +{ + mutex_unlock(&_lwext4_mutex); +} + +static const struct ext4_lock _lwext4_os_lock = { + .lock = _lock, + .unlock = _unlock, +}; + +static int _mount(vfs_mount_t *mountp) +{ + /* if one of the lines below fail to compile you probably need to adjust + vfs buffer sizes ;) */ + static_assert(VFS_DIR_BUFFER_SIZE >= sizeof(ext4_dir), + "ext4_dir must fit in VFS_DIR_BUFFER_SIZE"); + static_assert(VFS_FILE_BUFFER_SIZE >= sizeof(ext4_file), + "ext4_file must fit in VFS_FILE_BUFFER_SIZE"); + + lwext4_desc_t *fs = mountp->private_data; + struct ext4_mountpoint *mp = &fs->mp; + struct ext4_bcache *bc = &mp->bc; + + if (mp->mounted) { + return 0; + } + + int res = prepare(fs, mountp->mount_point); + if (res) { + return res; + } + + res = ext4_fs_init(&mp->fs, &fs->bdev, false); + DEBUG("lwext4 mount: %s\n", strerror(res)); + + if (res != EOK) { + return -res; + } + + size_t bsize = ext4_sb_get_block_size(&mp->fs.sb); + ext4_block_set_lb_size(&fs->bdev, bsize); + + res = ext4_bcache_init_dynamic(bc, CONFIG_BLOCK_DEV_CACHE_SIZE, bsize); + if (res != EOK) { + return -res; + } + + assert(bsize == bc->itemsize); + + bc->bdev = &fs->bdev; + fs->bdev.bc = bc; + fs->bdev.fs = &mp->fs; + + /*Bind block cache to block device*/ + res = ext4_block_bind_bcache(bc->bdev, bc); + if (res != EOK) { + ext4_bcache_cleanup(bc); + ext4_block_fini(bc->bdev); + ext4_bcache_fini_dynamic(bc); + return -res; + } + + mp->os_locks = &_lwext4_os_lock; + mp->mounted = true; + + res = ext4_recover(fs->mp.name); + if (res != EOK && res != ENOTSUP) { + DEBUG("ext4_recover: rc = %d\n", res); + return -res; + } + + res = ext4_journal_start(fs->mp.name); + if (res != EOK) { + DEBUG("ext4_journal_start: rc = %d\n", res); + return -res; + } + + ext4_cache_write_back(fs->mp.name, 1); + + return -res; +} + +static int _umount(vfs_mount_t *mountp) +{ + lwext4_desc_t *fs = mountp->private_data; + struct ext4_mountpoint *mp = &fs->mp; + + int res; + + ext4_cache_write_back(fs->mp.name, 0); + + res = ext4_journal_stop(fs->mp.name); + if (res != EOK) { + DEBUG("ext4_journal_stop: fail %d", res); + return false; + } + + res = ext4_fs_fini(&mp->fs); + if (res != EOK) { + goto out; + } + + ext4_bcache_cleanup(mp->fs.bdev->bc); + ext4_bcache_fini_dynamic(mp->fs.bdev->bc); + + res = ext4_block_fini(mp->fs.bdev); + + if (fs->iface.ph_bbuf) { + free(fs->iface.ph_bbuf); + fs->iface.ph_bbuf = NULL; + } + +out: + mp->mounted = false; + mp->fs.bdev->fs = NULL; + + return -res; +} + +#ifdef MODULE_LWEXT4_VFS_FORMAT +static int _format(vfs_mount_t *mountp) +{ + lwext4_desc_t *fs = mountp->private_data; + struct ext4_mountpoint *mp = &fs->mp; + + if (mp->mounted) { + return -EBUSY; + } + + int res = prepare(fs, mountp->mount_point); + if (res) { + return res; + } + + struct ext4_mkfs_info info = { + .block_size = CONFIG_EXT_BLOCKSIZE_DEFAULT, + .journal = CONFIG_JOURNALING_ENABLE, + }; + assert(fs->dev->pages_per_sector * fs->dev->page_size <= info.block_size); + + res = ext4_mkfs(&mp->fs, &fs->bdev, &info, CONFIG_EXT_FEATURE_SET_LVL); + + free(fs->iface.ph_bbuf); + fs->iface.ph_bbuf = NULL; + + return -res; +} +#endif /* MODULE_LWEXT4_VFS_FORMAT */ + +static int _mkdir(vfs_mount_t *mountp, const char *name, mode_t mode) +{ + (void)mountp; + + int res = ext4_dir_mk(name); + if (res != EOK) { + return -res; + } + + if (mode) { + return -ext4_mode_set(name, mode); + } + return 0; +} + +static int _rmdir(vfs_mount_t *mountp, const char *name) +{ + (void)mountp; + + return -ext4_dir_rm(name); +} + +static int _statvfs(vfs_mount_t *mountp, const char *restrict path, struct statvfs *restrict buf) +{ + (void)path; + + struct ext4_mount_stats stats; + lwext4_desc_t *fs = mountp->private_data; + + int res = ext4_mount_point_stats(fs->mp.name, &stats); + if (res) { + return -res; + } + + buf->f_blocks = stats.blocks_count; + buf->f_bfree = stats.free_blocks_count; + buf->f_bavail = stats.free_blocks_count; + buf->f_bsize = stats.block_size; + + return 0; +} + +static inline ext4_file * _get_ext4_file(vfs_file_t *f) +{ + /* The buffer in `private_data` is part of a union that also contains a + * pointer, so the alignment is fine. Adding an intermediate cast to + * uintptr_t to silence -Wcast-align + */ + return (ext4_file *)(uintptr_t)f->private_data.buffer; +} + +static int _open(vfs_file_t *filp, const char *name, int flags, mode_t mode) +{ + int res; + + ext4_file *file = _get_ext4_file(filp); + + res = ext4_fopen2(file, name, flags); + if (res != EOK) { + return -res; + } + + file->mp = ext4_get_mount(name); + + if (mode && (flags & O_CREAT)) { + return -ext4_mode_set(name, mode); + } + return 0; +} + +static ssize_t _read(vfs_file_t *filp, void *dest, size_t nbytes) +{ + ext4_file *file = _get_ext4_file(filp); + + int res = ext4_fread(file, dest, nbytes, &nbytes); + + if (res != EOK) { + return -res; + } else { + return nbytes; + } +} + +static ssize_t _write(vfs_file_t *filp, const void *src, size_t nbytes) +{ + ext4_file *file = _get_ext4_file(filp); + + int res = ext4_fwrite(file, src, nbytes, &nbytes); + + if (res != EOK) { + return -res; + } else { + return nbytes; + } +} + +static off_t _lseek(vfs_file_t *filp, off_t off, int whence) +{ + ext4_file *file = _get_ext4_file(filp); + int res = ext4_fseek(file, off, whence); + if (res) { + return -res; + } + + return ext4_ftell(file); +} + +static int _fstat(vfs_file_t *filp, struct stat *buf) +{ + ext4_file *file = _get_ext4_file(filp); + + buf->st_ino = file->inode; + buf->st_size = file->fsize; + buf->st_mode = S_IFREG; + + return 0; +} + +static int _fsync(vfs_file_t *filp) +{ + return -ext4_cache_flush(filp->mp->mount_point); +} + +static int _close(vfs_file_t *filp) +{ + ext4_file *file = _get_ext4_file(filp); + + return ext4_fclose(file); +} + +static int _unlink(vfs_mount_t *mountp, const char *name) +{ + (void)mountp; + return -ext4_fremove(name); +} + +static inline ext4_dir *_get_ext4_dir(vfs_DIR *dirp) +{ + return (ext4_dir *)(uintptr_t)dirp->private_data.buffer; +} + +static int _rename(vfs_mount_t *mountp, const char *from_path, const char *to_path) +{ + (void)mountp; + return -ext4_frename(from_path, to_path); +} + +static int _opendir(vfs_DIR *dirp, const char *dirname) +{ + ext4_dir *dir = _get_ext4_dir(dirp); + + /* lwext4 doesn't like an empty path (relative to mount point) */ + if (strcmp(dirp->mp->mount_point, dirname) == 0) { + lwext4_desc_t *fs = dirp->mp->private_data; + dirname = fs->mp.name; + } + + return -ext4_dir_open(dir, dirname); +} + +static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry) +{ + ext4_dir *dir = _get_ext4_dir(dirp); + + const ext4_direntry *dirent = ext4_dir_entry_next(dir); + if (dirent == NULL) { + return 0; + } + + strncpy(entry->d_name, (char *)dirent->name, sizeof(entry->d_name)); + + return 1; +} + +static int _closedir(vfs_DIR *dirp) +{ + ext4_dir *dir = _get_ext4_dir(dirp); + + return -ext4_dir_close(dir); +} + +static const vfs_file_system_ops_t lwext4_fs_ops = { +#ifdef MODULE_LWEXT4_VFS_FORMAT + .format = _format, +#endif + .mount = _mount, + .umount = _umount, + .unlink = _unlink, + .mkdir = _mkdir, + .rmdir = _rmdir, + .rename = _rename, + .stat = vfs_sysop_stat_from_fstat, + .statvfs = _statvfs, +}; + +static const vfs_file_ops_t lwext4_file_ops = { + .open = _open, + .close = _close, + .read = _read, + .write = _write, + .lseek = _lseek, + .fstat = _fstat, + .fsync = _fsync, +}; + +static const vfs_dir_ops_t lwext4_dir_ops = { + .opendir = _opendir, + .readdir = _readdir, + .closedir = _closedir, +}; + +const vfs_file_system_t lwext4_file_system = { + .fs_op = &lwext4_fs_ops, + .f_op = &lwext4_file_ops, + .d_op = &lwext4_dir_ops, + .flags = VFS_FS_FLAG_WANT_ABS_PATH, +}; diff --git a/pkg/lwext4/patches/0001-make-struct-ext4_mountpoint-public.patch b/pkg/lwext4/patches/0001-make-struct-ext4_mountpoint-public.patch new file mode 100644 index 0000000000..0366b91945 Binary files /dev/null and b/pkg/lwext4/patches/0001-make-struct-ext4_mountpoint-public.patch differ diff --git a/sys/include/fs/lwext4_fs.h b/sys/include/fs/lwext4_fs.h new file mode 100644 index 0000000000..3cfe55f822 --- /dev/null +++ b/sys/include/fs/lwext4_fs.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 ML!PA Consulting GmbH + * + * 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_lwext4 lwEXT4 integration + * @ingroup pkg_lwext4 + * @brief RIOT integration of lwEXT4 + * + * @{ + * + * @file + * @brief lwext4 integration with vfs + * + * @author Benjamin Valentin + */ + +#ifndef FS_LWEXT4_FS_H +#define FS_LWEXT4_FS_H + +#include + +#include "vfs.h" +#include "mtd.h" +#include "mutex.h" +#include "ext4.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief lwext4 descriptor for vfs integration + */ +typedef struct { + struct ext4_mountpoint mp; /**< lwext4 mountpoint struct */ + struct ext4_blockdev bdev; /**< lwext4 block device struct */ + struct ext4_blockdev_iface iface; /**< lwext4 block device interface */ + + mtd_dev_t *dev; /**< mtd device to use */ + mutex_t lock; /**< mutex */ +} lwext4_desc_t; + +/** + * @brief The littlefs vfs driver + */ +extern const vfs_file_system_t lwext4_file_system; + +#ifdef __cplusplus +} +#endif + +#endif /* FS_LWEXT4_FS_H */ +/** @} */ diff --git a/sys/include/vfs.h b/sys/include/vfs.h index 499c27e046..a7df6110f8 100644 --- a/sys/include/vfs.h +++ b/sys/include/vfs.h @@ -89,8 +89,11 @@ extern "C" { #ifndef _MAX #define _MAX(a, b) ((a) > (b) ? (a) : (b)) #endif -#ifndef MAX4 -#define MAX4(a, b, c, d) _MAX(_MAX((a), (b)), _MAX((c),(d))) +#ifndef MAX5 +/** + * @brief MAX5 Function to get the largest of 5 values + */ +#define MAX5(a, b, c, d, e) _MAX(_MAX(_MAX((a), (b)), _MAX((c),(d))), (e)) #endif /** @} */ @@ -174,6 +177,19 @@ extern "C" { #endif /** @} */ +/** + * @brief VFS parameters for lwext4 + * @{ + */ +#if defined(MODULE_LWEXT4) || DOXYGEN +#define LWEXT4_VFS_DIR_BUFFER_SIZE (308) /**< sizeof(ext4_dir) */ +#define LWEXT4_VFS_FILE_BUFFER_SIZE (32) /**< sizeof(ext4_file) */ +#else +#define LWEXT4_VFS_DIR_BUFFER_SIZE (1) +#define LWEXT4_VFS_FILE_BUFFER_SIZE (1) +#endif +/** @} */ + #ifndef VFS_MAX_OPEN_FILES /** * @brief Maximum number of simultaneous open files @@ -209,10 +225,11 @@ extern "C" { * @attention Put the check in the public header file (.h), do not put the check in the * implementation (.c) file. */ -#define VFS_DIR_BUFFER_SIZE MAX4(FATFS_VFS_DIR_BUFFER_SIZE, \ +#define VFS_DIR_BUFFER_SIZE MAX5(FATFS_VFS_DIR_BUFFER_SIZE, \ LITTLEFS_VFS_DIR_BUFFER_SIZE, \ LITTLEFS2_VFS_DIR_BUFFER_SIZE, \ - SPIFFS_VFS_DIR_BUFFER_SIZE \ + SPIFFS_VFS_DIR_BUFFER_SIZE, \ + LWEXT4_VFS_DIR_BUFFER_SIZE \ ) #endif @@ -236,10 +253,11 @@ extern "C" { * @attention Put the check in the public header file (.h), do not put the check in the * implementation (.c) file. */ -#define VFS_FILE_BUFFER_SIZE MAX4(FATFS_VFS_FILE_BUFFER_SIZE, \ +#define VFS_FILE_BUFFER_SIZE MAX5(FATFS_VFS_FILE_BUFFER_SIZE, \ LITTLEFS_VFS_FILE_BUFFER_SIZE, \ LITTLEFS2_VFS_FILE_BUFFER_SIZE,\ - SPIFFS_VFS_FILE_BUFFER_SIZE \ + SPIFFS_VFS_FILE_BUFFER_SIZE, \ + LWEXT4_VFS_FILE_BUFFER_SIZE \ ) #endif diff --git a/sys/include/vfs_default.h b/sys/include/vfs_default.h index a33a2e920e..a580f692cd 100644 --- a/sys/include/vfs_default.h +++ b/sys/include/vfs_default.h @@ -35,6 +35,9 @@ #if IS_USED(MODULE_SPIFFS) #include "fs/spiffs_fs.h" #endif +#if IS_USED(MODULE_LWEXT4) +#include "fs/lwext4_fs.h" +#endif #ifdef __cplusplus extern "C" {