1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

pkg/lwext4: add lwEXT4

This commit is contained in:
Benjamin Valentin 2022-09-23 11:39:21 +02:00
parent 49e1720d5c
commit e798d21b3b
12 changed files with 677 additions and 6 deletions

View File

@ -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

View File

@ -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()
##

13
pkg/lwext4/Makefile Normal file
View File

@ -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)

5
pkg/lwext4/Makefile.dep Normal file
View File

@ -0,0 +1,5 @@
USEMODULE += lwext_fs
ifneq (,$(filter vfs_auto_format,$(USEMODULE)))
DEFAULT_MODULE += lwext4_vfs_format
endif

View File

@ -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

19
pkg/lwext4/doc.txt Normal file
View File

@ -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
*/

3
pkg/lwext4/fs/Makefile Normal file
View File

@ -0,0 +1,3 @@
MODULE := lwext_fs
include $(RIOTBASE)/Makefile.base

519
pkg/lwext4/fs/lwext4_fs.c Normal file
View File

@ -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 <benjamin.valentin@ml-pa.com>
*
* @}
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "fs/lwext4_fs.h"
#include <ext4_super.h>
#include <ext4_mkfs.h>
#define ENABLE_DEBUG 0
#include <debug.h>
/**
* @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,
};

View File

@ -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 <benjamin.valentin@ml-pa.com>
*/
#ifndef FS_LWEXT4_FS_H
#define FS_LWEXT4_FS_H
#include <stdalign.h>
#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 */
/** @} */

View File

@ -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

View File

@ -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" {