fs: ext2: Implementation of basic operations

Included operations:
  - mount
  - unmount
  - mkfs
  - statvfs

Signed-off-by: Franciszek Zdobylak <fzdobylak@antmicro.com>
This commit is contained in:
Franciszek Zdobylak 2023-02-24 10:12:03 +01:00 committed by Anas Nashif
parent d7bcac091c
commit 1eccf55102
13 changed files with 1396 additions and 0 deletions

45
include/zephyr/fs/ext2.h Normal file
View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_FS_EXT2_H_
#define ZEPHYR_INCLUDE_FS_EXT2_H_
#include <stdint.h>
/** @brief Configuration used to format ext2 file system.
*
* If a field is set to 0 then default value is used.
* (In volume name the first cell of an array must be 0 to use default value.)
*
* @param block_size Requested size of block.
* @param fs_size Requested size of file system. If 0 then whole available memory is used.
* @param bytes_per_inode Requested memory for one inode. It is used to calculate number of inodes
* in created file system.
* @param uuid UUID for created file system. Used when set_uuid is true.
* @param volume_name Name for created file system.
* @param set_uuid If true then UUID from that structure is used in created file system.
* If false then UUID (ver4) is generated.
*/
struct ext2_cfg {
uint32_t block_size;
uint32_t fs_size; /* Number of blocks that we want to take. */
uint32_t bytes_per_inode;
uint8_t uuid[16];
uint8_t volume_name[17]; /* If first byte is 0 then name ext2" is given. */
bool set_uuid;
};
#define FS_EXT2_DECLARE_DEFAULT_CONFIG(name) \
static struct ext2_cfg name = { \
.block_size = 1024, \
.fs_size = 0x800000, \
.bytes_per_inode = 4096, \
.volume_name = {'e', 'x', 't', '2', '\0'}, \
.set_uuid = false, \
}
#endif /* ZEPHYR_INCLUDE_FS_EXT2_H_ */

View file

@ -56,6 +56,9 @@ enum {
/** Identifier for in-tree LittleFS file system. */
FS_LITTLEFS,
/** Identifier for in-tree Ext2 file system. */
FS_EXT2,
/** Base identifier for external file systems. */
FS_TYPE_EXTERNAL_BASE,
};

View file

@ -14,10 +14,13 @@ if(CONFIG_FILE_SYSTEM)
LFS_CONFIG=zephyr_lfs_config.h
)
add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_EXT2 ext2)
zephyr_library_link_libraries(FS)
target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT)
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS FS INTERFACE LITTLEFS)
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_EXT2 FS INTERFACE EXT2)
endif()
add_subdirectory_ifdef(CONFIG_FCB ./fcb)

View file

@ -85,6 +85,7 @@ config FUSE_FS_ACCESS
rsource "Kconfig.fatfs"
rsource "Kconfig.littlefs"
rsource "ext2/Kconfig"
endif # FILE_SYSTEM

View file

@ -0,0 +1,15 @@
# Copyright (c) 2023 Antmicro
# SPDX-License-Identifier: Apache-2.0
add_library(EXT2 INTERFACE)
target_include_directories(EXT2 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
zephyr_library()
zephyr_library_sources(
ext2_ops.c
ext2_impl.c
ext2_disk_access.c
)
zephyr_library_sources_ifdef(CONFIG_FILE_SYSTEM_MKFS ext2_format.c)
zephyr_library_link_libraries(EXT2)

44
subsys/fs/ext2/Kconfig Normal file
View file

@ -0,0 +1,44 @@
# Copyright (c) 2023 Antmicro
# SPDX-License-Identifier: Apache-2.0
# Ext2 file system
config FILE_SYSTEM_EXT2
bool "Ext2 file system support"
depends on FILE_SYSTEM
help
Enable Ext2 file system support.
module = EXT2
module-str = Ext2
source "subsys/logging/Kconfig.template.log_config"
if FILE_SYSTEM_EXT2
menu "Ext2 file system Settings"
visible if FILE_SYSTEM_EXT2
config EXT2_MAX_BLOCK_SIZE
int "Maximum size of supported block"
range 1024 4096
default 4096
help
This flag is used to determine size of internal structures that
are used to store fetched blocks.
config EXT2_MAX_BLOCK_COUNT
int "Maximum number of blocks that might be used"
default 10
help
This flag is used to determine size of internal structures that
are used to store fetched blocks.
config EXT2_DISK_STARTING_SECTOR
int "Ext2 starting sector"
default 0
help
The current Ext2 implementation does not support GUID Partition Table. The starting sector
of the file system must be specified by this option.
endmenu
endif

77
subsys/fs/ext2/ext2.h Normal file
View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __EXT2_H__
#define __EXT2_H__
#define EXT2_SUPERBLOCK_OFFSET 1024
#define EXT2_MAGIC_NUMBER 0xEF53
#define EXT2_MAX_FILE_NAME 255
#define EXT2_ROOT_INODE 2
#define EXT2_RESERVED_INODES 10
#define EXT2_GOOD_OLD_INODE_SIZE 128
#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 /* Disk/File compression is used */
#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 /* Directory entries record the file type */
#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Filesystem needs recovery */
#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Filesystem has a separate journal device */
#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 /* Meta block groups */
#define EXT2_FEATURE_INCOMPAT_SUPPORTED (EXT2_FEATURE_INCOMPAT_FILETYPE)
#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 /* Sparse Superblock */
#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 /* Large file support, 64-bit file size */
#define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 /* Binary tree sorted directory files */
#define EXT2_FEATURE_RO_COMPAT_SUPPORTED (0)
#define EXT2_INODE_BLOCKS 15 /* number of blocks referenced by inode i_block field */
#define EXT2_INODE_BLOCK_DIRECT 12
#define EXT2_INODE_BLOCK_1LVL 12
#define EXT2_INODE_BLOCK_2LVL 13
#define EXT2_INODE_BLOCK_3LVL 14
/* Inode mode flags */
#define EXT2_S_IFMT 0xF000 /* format mask */
#define EXT2_S_IFSOCK 0xC000 /* socket */
#define EXT2_S_IFLNK 0xA000 /* symbolic link */
#define EXT2_S_IFREG 0x8000 /* regular file */
#define EXT2_S_IFBLK 0x6000 /* block device */
#define EXT2_S_IFDIR 0x4000 /* directory */
#define EXT2_S_IFCHR 0x2000 /* character device */
#define EXT2_S_IFIFO 0x1000 /* fifo */
#define IS_REG_FILE(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFREG)
#define IS_DIR(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFDIR)
/* Directory file type flags */
#define EXT2_FT_UNKNOWN 0
#define EXT2_FT_REG_FILE 1
#define EXT2_FT_DIR 2
#define EXT2_FT_CHRDEV 3
#define EXT2_FT_BLKDEV 4
#define EXT2_FT_FIFO 5
#define EXT2_FT_SOCK 6
#define EXT2_FT_SYMLINK 7
#define EXT2_FT_MAX 8
/* Superblock status flags.
* When file system is mounted the status is set to EXT2_ERROR_FS.
* When file system is cleanly unmounted then flag is reset to EXT2_VALID_FS.
*/
#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */
#define EXT2_ERROR_FS 0x0002 /* Errors detected */
/* Revision flags. */
#define EXT2_GOOD_OLD_REV 0x0 /* Revision 0 */
#define EXT2_DYNAMIC_REV 0x1 /* Revision 1 */
/* Strategy when error detected. */
#define EXT2_ERRORS_CONTINUE 1 /* Continue as if nothing happened. */
#define EXT2_ERRORS_RO 2 /* Mount read only. */
#define EXT2_ERRORS_PANIC 3 /* Cause kernel panic. */
#endif /* __EXT2_H__ */

View file

@ -0,0 +1,171 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/storage/disk_access.h>
#include "ext2.h"
#include "ext2_struct.h"
LOG_MODULE_DECLARE(ext2);
static struct disk_data {
const char *name;
uint32_t sector_size;
uint32_t sector_count;
} disk_data;
static int64_t disk_access_device_size(struct ext2_data *fs)
{
struct disk_data *disk = fs->backend;
return disk->sector_count * disk->sector_size;
}
static int64_t disk_access_write_size(struct ext2_data *fs)
{
struct disk_data *disk = fs->backend;
return disk->sector_size;
}
static int disk_read(const char *disk, uint8_t *buf, uint32_t start, uint32_t num)
{
int rc, loop = 0;
do {
rc = disk_access_ioctl(disk, DISK_IOCTL_CTRL_SYNC, NULL);
if (rc == 0) {
rc = disk_access_read(disk, buf, start, num);
LOG_DBG("disk read: (start:%d, num:%d) (ret: %d)", start, num, rc);
}
} while ((rc == -EBUSY) && (loop++ < 16));
return rc;
}
static int disk_write(const char *disk, const uint8_t *buf, uint32_t start, uint32_t num)
{
int rc, loop = 0;
do {
rc = disk_access_ioctl(disk, DISK_IOCTL_CTRL_SYNC, NULL);
if (rc == 0) {
rc = disk_access_write(disk, buf, start, num);
LOG_DBG("disk write: (start:%d, num:%d) (ret: %d)", start, num, rc);
}
} while ((rc == -EBUSY) && (loop++ < 16));
return rc;
}
static int disk_prepare_range(struct disk_data *disk, uint32_t addr, uint32_t size,
uint32_t *s_start, uint32_t *s_count)
{
*s_start = CONFIG_EXT2_DISK_STARTING_SECTOR + addr / disk->sector_size;
*s_count = size / disk->sector_size;
LOG_DBG("addr:0x%x size:0x%x -> sector_start:%d sector_count:%d",
addr, size, *s_start, *s_count);
/* Check for overflow. */
if (*s_count > UINT32_MAX - *s_start) {
LOG_ERR("Requested range (%d:+%d) can't be accessed due to overflow.",
*s_start, *s_count);
return -ENOSPC;
}
/* Cannot read or write outside the disk. */
if (*s_start + *s_count > disk->sector_count) {
LOG_ERR("Requested sectors: %d-%d are outside of disk (num_sectors: %d)",
*s_start, *s_start + *s_count, disk->sector_count);
return -ENOSPC;
}
return 0;
}
static int disk_access_read_block(struct ext2_data *fs, void *buf, uint32_t block)
{
int rc;
struct disk_data *disk = fs->backend;
uint32_t sector_start, sector_count;
rc = disk_prepare_range(disk, block * fs->block_size, fs->block_size,
&sector_start, &sector_count);
if (rc < 0) {
return rc;
}
return disk_read(disk->name, buf, sector_start, sector_count);
}
static int disk_access_write_block(struct ext2_data *fs, const void *buf, uint32_t block)
{
int rc;
struct disk_data *disk = fs->backend;
uint32_t sector_start, sector_count;
rc = disk_prepare_range(disk, block * fs->block_size, fs->block_size,
&sector_start, &sector_count);
if (rc < 0) {
return rc;
}
return disk_write(disk->name, buf, sector_start, sector_count);
}
static int disk_access_read_superblock(struct ext2_data *fs, struct ext2_disk_superblock *sb)
{
int rc;
struct disk_data *disk = fs->backend;
uint32_t sector_start, sector_count;
rc = disk_prepare_range(disk, EXT2_SUPERBLOCK_OFFSET, sizeof(struct ext2_disk_superblock),
&sector_start, &sector_count);
if (rc < 0) {
return rc;
}
return disk_read(disk->name, (uint8_t *)sb, sector_start, sector_count);
}
static const struct ext2_backend_ops disk_access_ops = {
.get_device_size = disk_access_device_size,
.get_write_size = disk_access_write_size,
.read_block = disk_access_read_block,
.write_block = disk_access_write_block,
.read_superblock = disk_access_read_superblock,
};
int ext2_init_disk_access_backend(struct ext2_data *fs, const void *storage_dev, int flags)
{
int rc;
uint32_t sector_size, sector_count;
const char *name = (const char *)storage_dev;
rc = disk_access_init(name);
if (rc < 0) {
LOG_ERR("FAIL: unable to find disk %s: %d\n", name, rc);
return rc;
}
rc = disk_access_ioctl(name, DISK_IOCTL_GET_SECTOR_COUNT, &sector_count);
if (rc < 0) {
LOG_ERR("Disk access (sector count) error: %d", rc);
return rc;
}
rc = disk_access_ioctl(name, DISK_IOCTL_GET_SECTOR_SIZE, &sector_size);
if (rc < 0) {
LOG_ERR("Disk access (sector size) error: %d", rc);
return rc;
}
disk_data = (struct disk_data) {
.name = storage_dev,
.sector_size = sector_size,
.sector_count = sector_count,
};
fs->backend = &disk_data;
fs->backend_ops = &disk_access_ops;
return 0;
}

View file

@ -0,0 +1,295 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
#include <zephyr/random/rand32.h>
#include <zephyr/fs/ext2.h>
#include "ext2.h"
#include "ext2_impl.h"
#include "ext2_struct.h"
LOG_MODULE_DECLARE(ext2);
FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default);
static void validate_config(struct ext2_cfg *cfg)
{
if (cfg->block_size == 0) {
cfg->block_size = ext2_default.block_size;
}
if (cfg->bytes_per_inode == 0) {
cfg->bytes_per_inode = ext2_default.bytes_per_inode;
}
if (cfg->volume_name[0] == '\0') {
strcpy(cfg->volume_name, "ext2");
}
if (!cfg->set_uuid) {
/* Generate random UUID */
sys_rand_get(cfg->uuid, 16);
/* Set version of UUID (ver. 4 variant 1) */
cfg->uuid[6] = (cfg->uuid[6] & 0x0f) | 0x40;
cfg->uuid[8] = (cfg->uuid[8] & 0x3f) | 0x80;
}
}
int ext2_format(struct ext2_data *fs, struct ext2_cfg *cfg)
{
int ret = 0;
validate_config(cfg);
LOG_INF("[Config] blk_sz:%d fs_sz:%d ino_bytes:%d uuid:'%s' vol:'%s'",
cfg->block_size, cfg->fs_size, cfg->bytes_per_inode, cfg->uuid,
cfg->volume_name);
uint32_t fs_memory = cfg->fs_size ? MIN(cfg->fs_size, fs->device_size) : fs->device_size;
/* Calculate value that will be stored in superblock field 's_log_block_size'. That value
* tells how much we have to shift 1024 to obtain block size.
* To obtain it we calculate: log(block_size) - 11
*/
uint8_t block_log_size = find_msb_set(cfg->block_size) - 11;
LOG_INF("[Memory] available:%lld requested:%d", fs->device_size, fs_memory);
if (fs_memory > fs->device_size) {
LOG_ERR("No enough space on storage device");
return -ENOSPC;
}
uint32_t blocks_count = fs_memory / cfg->block_size;
uint32_t blocks_per_group = cfg->block_size * 8;
/* Require at least 24 blocks to have at least 1 block per inode */
if (blocks_count < 24) {
LOG_ERR("Storage device too small to fit ext2 file system");
return -ENOSPC;
}
/* We want to have only 1 block group (and one extra block) */
if (blocks_count > blocks_per_group + 1) {
LOG_ERR("File systems with more than 1 block group are not supported.");
return -ENOTSUP;
}
/* Superblock, group descriptor table, 2 x bitmap. */
uint32_t reserved_blocks = 4;
uint32_t mem_for_inodes = fs_memory - reserved_blocks * cfg->block_size;
uint32_t mem_per_inode = cfg->bytes_per_inode + sizeof(struct ext2_disk_inode);
uint32_t inodes_per_block = cfg->block_size / sizeof(struct ext2_disk_inode);
uint32_t inodes_count = mem_for_inodes / mem_per_inode;
if (inodes_count % inodes_per_block) {
/* Increase inodes_count to use entire blocks that are reserved for inode table. */
inodes_count += inodes_per_block - (inodes_count % inodes_per_block);
}
uint32_t itable_blocks = inodes_count / inodes_per_block;
uint32_t reserved_inodes = 10;
/* Used blocks:
* Reserved blocks:
* 0 - boot sector
* 1 - superblock
* 2 - block group descriptor table
* 3 - block bitmap
* 4 - inode bitmap
*
* Inode table blocks:
* 5-x - inode table (x = 5 + inode_count / inodes per block )
*
* Other used blocks:
* x+1 - root dir
*/
uint32_t used_blocks = reserved_blocks + itable_blocks + 1;
uint32_t free_blocks = blocks_count - used_blocks - 1; /* boot sector block also counts */
LOG_INF("[Blocks] total:%d per_grp:%d reserved:%d",
blocks_count, blocks_per_group, used_blocks);
LOG_INF("[Inodes] total:%d reserved:%d itable_blocks:%d",
inodes_count, reserved_inodes, itable_blocks);
uint32_t sb_offset;
uint32_t sb_block_num, bg_block_num, bbitmap_block_num, ibitmap_block_num, in1_block_num,
root_dir_blk_num;
if (cfg->block_size == 1024) {
sb_offset = 0;
sb_block_num = 1;
bg_block_num = 2;
bbitmap_block_num = 3;
ibitmap_block_num = 4;
in1_block_num = 5;
root_dir_blk_num = used_blocks; /* last used block */
} else {
sb_offset = 1024;
sb_block_num = 0;
bg_block_num = 1;
bbitmap_block_num = 2;
ibitmap_block_num = 3;
in1_block_num = 4;
root_dir_blk_num = used_blocks; /* last used block */
}
struct ext2_block *sb_block = ext2_get_block(fs, sb_block_num);
struct ext2_block *bg_block = ext2_get_block(fs, bg_block_num);
struct ext2_block *bbitmap_block = ext2_get_block(fs, bbitmap_block_num);
struct ext2_block *ibitmap_block = ext2_get_block(fs, ibitmap_block_num);
struct ext2_block *in1_block = ext2_get_block(fs, in1_block_num);
struct ext2_block *root_dir_blk_block = ext2_get_block(fs, root_dir_blk_num);
if (root_dir_blk_block == NULL || in1_block == NULL || ibitmap_block == NULL ||
bbitmap_block == NULL || bg_block == NULL || sb_block == NULL) {
ret = -ENOMEM;
goto out;
}
struct ext2_disk_superblock *sb =
(struct ext2_disk_superblock *)((uint8_t *)sb_block->data + sb_offset);
memset(sb, 0, 1024);
sb->s_inodes_count = inodes_count;
sb->s_blocks_count = blocks_count;
sb->s_r_blocks_count = 0;
sb->s_free_blocks_count = free_blocks;
sb->s_free_inodes_count = inodes_count - reserved_inodes;
sb->s_first_data_block = sb_block_num;
sb->s_log_block_size = block_log_size;
sb->s_log_frag_size = block_log_size;
sb->s_blocks_per_group = cfg->block_size * 8;
sb->s_frags_per_group = cfg->block_size * 8;
sb->s_inodes_per_group = inodes_count;
sb->s_mtime = 0;
sb->s_wtime = 0;
sb->s_mnt_count = 0;
sb->s_max_mnt_count = -1;
sb->s_magic = 0xEF53;
sb->s_state = EXT2_VALID_FS;
sb->s_errors = EXT2_ERRORS_RO;
sb->s_minor_rev_level = 0;
sb->s_lastcheck = 0;
sb->s_checkinterval = 0;
sb->s_creator_os = 5; /* Unknown OS */
sb->s_rev_level = EXT2_DYNAMIC_REV;
sb->s_def_resuid = 0;
sb->s_def_resgid = 0;
sb->s_first_ino = 11;
sb->s_inode_size = sizeof(struct ext2_disk_inode);
sb->s_block_group_nr = 0;
sb->s_feature_compat = 0;
sb->s_feature_incompat = EXT2_FEATURE_INCOMPAT_FILETYPE;
sb->s_feature_ro_compat = 0;
memcpy(sb->s_uuid, cfg->uuid, 16);
memcpy(sb->s_volume_name, cfg->uuid, 16);
sb->s_algo_bitmap = 0;
sb->s_prealloc_blocks = 0;
sb->s_prealloc_dir_blocks = 0;
sb->s_journal_inum = 0;
sb->s_journal_dev = 0;
sb->s_last_orphan = 0;
/* Block descriptor table */
struct ext2_disk_bgroup *bg = (struct ext2_disk_bgroup *)bg_block->data;
memset(bg, 0, cfg->block_size);
bg->bg_block_bitmap = bbitmap_block_num;
bg->bg_inode_bitmap = ibitmap_block_num;
bg->bg_inode_table = in1_block_num;
bg->bg_free_blocks_count = free_blocks;
bg->bg_free_inodes_count = inodes_count - reserved_inodes;
bg->bg_used_dirs_count = 1;
/* Inode table */
struct ext2_disk_inode *in1 = (struct ext2_disk_inode *)in1_block->data;
memset(in1, 0, cfg->block_size);
in1[1].i_mode = 0x4000 | 0755;
in1[1].i_uid = 0;
in1[1].i_size = cfg->block_size;
in1[1].i_atime = 0;
in1[1].i_ctime = 0;
in1[1].i_mtime = 0;
in1[1].i_dtime = 0;
in1[1].i_gid = 0;
in1[1].i_links_count = 2;
in1[1].i_blocks = cfg->block_size / 512;
in1[1].i_flags = 0;
in1[1].i_osd1 = 0;
in1[1].i_generation = 0;
in1[1].i_file_acl = 0;
in1[1].i_dir_acl = 0;
in1[1].i_faddr = 0;
in1[1].i_block[0] = root_dir_blk_num;
/* Block bitmap */
uint8_t *bbitmap = bbitmap_block->data;
memset(bbitmap, 0, cfg->block_size);
int i = 0, blocks = used_blocks;
int bits;
uint16_t to_set;
while (blocks > 0) {
bits = MIN(8, blocks);
to_set = (1 << bits) - 1;
bbitmap[i] = (uint8_t)to_set;
blocks -= 8;
i++;
}
/* Inode bitmap */
uint8_t *ibitmap = ibitmap_block->data;
memset(ibitmap, 0, cfg->block_size);
ibitmap[0] = 0xff;
ibitmap[1] = 0x03;
memset(root_dir_blk_block->data, 0, cfg->block_size);
struct ext2_disk_dentry *de = (struct ext2_disk_dentry *)root_dir_blk_block->data;
de->de_inode = 2;
de->de_rec_len = sizeof(struct ext2_disk_dentry) + 4;
de->de_name_len = 1;
de->de_file_type = EXT2_FT_DIR;
memset(de->de_name, '.', 1);
de = (struct ext2_disk_dentry *)(((uint8_t *)de) + de->de_rec_len);
de->de_inode = 2;
de->de_rec_len = cfg->block_size - 12;
de->de_name_len = 2;
de->de_file_type = EXT2_FT_DIR;
memset(de->de_name, '.', 2);
ext2_sync_block(fs, sb_block);
ext2_sync_block(fs, bg_block);
ext2_sync_block(fs, in1_block);
ext2_sync_block(fs, bbitmap_block);
ext2_sync_block(fs, ibitmap_block);
ext2_sync_block(fs, root_dir_blk_block);
out:
ext2_drop_block(sb_block);
ext2_drop_block(bg_block);
ext2_drop_block(in1_block);
ext2_drop_block(bbitmap_block);
ext2_drop_block(ibitmap_block);
ext2_drop_block(root_dir_blk_block);
return ret;
}

291
subsys/fs/ext2/ext2_impl.c Normal file
View file

@ -0,0 +1,291 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/fs/fs.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include "ext2.h"
#include "ext2_impl.h"
#include "ext2_struct.h"
LOG_MODULE_REGISTER(ext2, CONFIG_EXT2_LOG_LEVEL);
static struct ext2_data __fs;
static bool initialized;
#define BLOCK_MEMORY_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * CONFIG_EXT2_MAX_BLOCK_SIZE)
#define BLOCK_STRUCT_BUFFER_SIZE (CONFIG_EXT2_MAX_BLOCK_COUNT * sizeof(struct ext2_block))
/* Structures for blocks slab alocator */
struct k_mem_slab ext2_block_memory_slab, ext2_block_struct_slab;
char __aligned(sizeof(void *)) __ext2_block_memory_buffer[BLOCK_MEMORY_BUFFER_SIZE];
char __aligned(sizeof(void *)) __ext2_block_struct_buffer[BLOCK_STRUCT_BUFFER_SIZE];
/* Initialize heap memory allocator */
K_HEAP_DEFINE(ext2_heap, CONFIG_EXT2_HEAP_SIZE);
/* Helper functions --------------------------------------------------------- */
void *ext2_heap_alloc(size_t size)
{
return k_heap_alloc(&ext2_heap, size, K_NO_WAIT);
}
void ext2_heap_free(void *ptr)
{
k_heap_free(&ext2_heap, ptr);
}
/* Block operations --------------------------------------------------------- */
struct ext2_block *ext2_get_block(struct ext2_data *fs, uint32_t block)
{
int ret;
struct ext2_block *b;
ret = k_mem_slab_alloc(&ext2_block_struct_slab, (void **)&b, K_NO_WAIT);
if (ret < 0) {
LOG_ERR("get block: alloc block struct error %d", ret);
return NULL;
}
ret = k_mem_slab_alloc(&ext2_block_memory_slab, (void **)&b->data, K_NO_WAIT);
if (ret < 0) {
LOG_ERR("get block: alloc block memory error %d", ret);
goto fail_alloc;
}
b->num = block;
ret = fs->backend_ops->read_block(fs, b->data, block);
if (ret < 0) {
LOG_ERR("get block: read block error %d", ret);
goto fail_read;
}
return b;
fail_read:
k_mem_slab_free(&ext2_block_memory_slab, (void **)&b->data);
fail_alloc:
k_mem_slab_free(&ext2_block_struct_slab, (void **)&b);
return NULL;
}
int ext2_sync_block(struct ext2_data *fs, struct ext2_block *b)
{
int ret;
ret = fs->backend_ops->write_block(fs, b->data, b->num);
if (ret < 0) {
return ret;
}
return 0;
}
void ext2_drop_block(struct ext2_block *b)
{
if (b->data != NULL) {
k_mem_slab_free(&ext2_block_memory_slab, (void **)&b->data);
k_mem_slab_free(&ext2_block_struct_slab, (void **)&b);
}
}
void ext2_init_blocks_slab(struct ext2_data *fs)
{
memset(__ext2_block_memory_buffer, 0, BLOCK_MEMORY_BUFFER_SIZE);
memset(__ext2_block_struct_buffer, 0, BLOCK_STRUCT_BUFFER_SIZE);
/* These calls will always succeed because sizes and memory buffers are properly aligned. */
k_mem_slab_init(&ext2_block_struct_slab, __ext2_block_struct_buffer,
sizeof(struct ext2_block), CONFIG_EXT2_MAX_BLOCK_COUNT);
k_mem_slab_init(&ext2_block_memory_slab, __ext2_block_memory_buffer, fs->block_size,
CONFIG_EXT2_MAX_BLOCK_COUNT);
}
/* FS operations ------------------------------------------------------------ */
int ext2_init_storage(struct ext2_data **fsp, const void *storage_dev, int flags)
{
if (initialized) {
return -EBUSY;
}
int ret = 0;
struct ext2_data *fs = &__fs;
int64_t dev_size, write_size;
*fsp = fs;
ret = ext2_init_disk_access_backend(fs, storage_dev, flags);
if (ret < 0) {
return ret;
}
dev_size = fs->backend_ops->get_device_size(fs);
if (dev_size < 0) {
ret = dev_size;
goto err;
}
write_size = fs->backend_ops->get_write_size(fs);
if (write_size < 0) {
ret = write_size;
goto err;
}
if (write_size < 1024 && 1024 % write_size != 0) {
ret = -EINVAL;
LOG_ERR("expecting sector size that divides 1024 (got: %lld)", write_size);
goto err;
}
LOG_DBG("Device size: %lld", dev_size);
LOG_DBG("Write size: %lld", write_size);
fs->device_size = dev_size;
fs->write_size = write_size;
initialized = true;
err:
return ret;
}
int ext2_verify_superblock(struct ext2_disk_superblock *sb)
{
/* Check if it is a valid Ext2 file system. */
if (sb->s_magic != EXT2_MAGIC_NUMBER) {
LOG_ERR("Wrong file system magic number (%x)", sb->s_magic);
return -EINVAL;
}
/* For now we don't support file systems with frag size different from block size */
if (sb->s_log_block_size != sb->s_log_frag_size) {
LOG_ERR("Filesystem with frag_size != block_size is not supported");
return -ENOTSUP;
}
/* Support only second revision */
if (sb->s_rev_level != EXT2_DYNAMIC_REV) {
LOG_ERR("Filesystem with revision %d is not supported", sb->s_rev_level);
return -ENOTSUP;
}
if (sb->s_inode_size != EXT2_GOOD_OLD_INODE_SIZE) {
LOG_ERR("Filesystem with inode size %d is not supported", sb->s_inode_size);
return -ENOTSUP;
}
/* Check if file system may contain errors. */
if (sb->s_state == EXT2_ERROR_FS) {
LOG_WRN("File system may contain errors.");
switch (sb->s_errors) {
case EXT2_ERRORS_CONTINUE:
break;
case EXT2_ERRORS_RO:
LOG_WRN("File system can be mounted read only");
return -EROFS;
case EXT2_ERRORS_PANIC:
LOG_ERR("File system can't be mounted");
/* panic or return that fs is invalid */
__ASSERT(sb->s_state == EXT2_VALID_FS, "Error detected in superblock");
return -EINVAL;
default:
LOG_WRN("Unknown option for superblock s_errors field.");
}
}
if ((sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) == 0) {
LOG_ERR("File system without file type stored in de is not supported");
return -ENOTSUP;
}
if ((sb->s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED) > 0) {
LOG_ERR("File system can't be mounted. Incompat features %d not supported",
(sb->s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED));
return -ENOTSUP;
}
if ((sb->s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED) > 0) {
LOG_WRN("File system can be mounted read only. RO features %d detected.",
(sb->s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED));
return -EROFS;
}
LOG_DBG("ino_cnt:%d blk_cnt:%d blk_per_grp:%d ino_per_grp:%d free_ino:%d free_blk:%d "
"blk_size:%d ino_size:%d mntc:%d",
sb->s_inodes_count, sb->s_blocks_count, sb->s_blocks_per_group,
sb->s_inodes_per_group, sb->s_free_inodes_count, sb->s_free_blocks_count,
1024 << sb->s_log_block_size, sb->s_inode_size, sb->s_mnt_count);
return 0;
}
int ext2_init_fs(struct ext2_data *fs)
{
int ret = 0;
/* Fetch superblock */
if (fs->block_size == 1024) {
fs->sblock_offset = 0;
fs->sblock = ext2_get_block(fs, 1);
} else {
fs->sblock_offset = 1024;
fs->sblock = ext2_get_block(fs, 0);
}
if (fs->sblock == NULL) {
ret = ENOENT;
goto out;
}
if (!(fs->flags & EXT2_DATA_FLAGS_RO)) {
/* Update sblock fields set during the successful mount. */
EXT2_DATA_SBLOCK(fs)->s_state = EXT2_ERROR_FS;
EXT2_DATA_SBLOCK(fs)->s_mnt_count += 1;
ret = ext2_sync_block(fs, fs->sblock);
if (ret < 0) {
goto out;
}
}
return 0;
out:
ext2_drop_block(fs->sblock);
fs->sblock = NULL;
return ret;
}
int ext2_close_fs(struct ext2_data *fs)
{
int ret = 0;
if (!(fs->flags & EXT2_DATA_FLAGS_RO)) {
EXT2_DATA_SBLOCK(fs)->s_state = EXT2_VALID_FS;
ret = ext2_sync_block(fs, fs->sblock);
if (ret < 0) {
goto out;
}
}
ext2_drop_block(fs->sblock);
fs->sblock = NULL;
out:
return ret;
}
int ext2_close_struct(struct ext2_data *fs)
{
memset(fs, 0, sizeof(struct ext2_data));
initialized = false;
return 0;
}

104
subsys/fs/ext2/ext2_impl.h Normal file
View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __EXT2_IMPL_H__
#define __EXT2_IMPL_H__
#include <zephyr/fs/fs.h>
#include <zephyr/fs/ext2.h>
#include "ext2_struct.h"
/* Memory allocation for ext2 implementation */
void *ext2_heap_alloc(size_t size);
void ext2_heap_free(void *ptr);
/* Initialization of disk storage. */
int ext2_init_disk_access_backend(struct ext2_data *fs, const void *storage_dev, int flags);
/**
* @brief Get block from the disk.
*/
struct ext2_block *ext2_get_block(struct ext2_data *fs, uint32_t block);
/**
* @brief Free the block structure.
*/
void ext2_drop_block(struct ext2_block *b);
/**
* @brief Write block to the disk.
*/
int ext2_sync_block(struct ext2_data *fs, struct ext2_block *b);
void ext2_init_blocks_slab(struct ext2_data *fs);
/* FS operations */
/**
* @brief Initialize structure with data needed to access the storage device
*
* @param fs File system data structure to initialize
* @param storage_dev Pointer to storage
* @param flags Additional flags (e.g. RO flag)
*
* @retval 0 on success
* @retval -EINVAL when superblock of ext2 was not detected
* @retval -ENOTSUP when described file system is not supported
* @retval <0 other error
*/
int ext2_init_storage(struct ext2_data **fsp, const void *storage_dev, int flags);
/**
* @brief Verify superblock of file system
*
* Checks if file system is supported by the implementation.
* @retval 0 when superblock is valid
* @retval -EROFS when superblock is not valid but file system may be mounted read only
* @retval -EINVAL when superblock is not valid and file system cannot be mounted at all
* @retval -ENOTSUP when superblock has some field set to value that we don't support
*/
int ext2_verify_superblock(struct ext2_disk_superblock *sb);
/**
* @brief Initialize all data needed to perform operations on file system
*
* Fetches the superblock. Initializes structure fields.
*/
int ext2_init_fs(struct ext2_data *fs);
/**
* @brief Clear the data used by file system implementation
*
*/
int ext2_close_fs(struct ext2_data *fs);
/**
* @brief Clear the data used to communicate with storage device
*
*/
int ext2_close_struct(struct ext2_data *fs);
/**
* @brief Create the ext2 file system
*
* This function uses functions stored in `ext2_data` structure to create new
* file system on storage device.
*
* NOTICE: fs structure must be first initialized with `ext2_init_fs` function.
*
* After this function succeeds the `ext2_clean` function must be called.
*
* @param fs File system data (must be initialized before)
*
* @retval 0 on success
* @retval -ENOSPC when storage device is too small for ext2 file system
* @retval -ENOTSUP when storage device is too big (file systems with more than
* 8192 blocks are not supported)
*/
int ext2_format(struct ext2_data *fs, struct ext2_cfg *cfg);
#endif /* __EXT2_IMPL_H__ */

208
subsys/fs/ext2/ext2_ops.c Normal file
View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/init.h>
#include <zephyr/fs/fs.h>
#include <zephyr/fs/fs_sys.h>
#include <zephyr/fs/ext2.h>
#include <zephyr/logging/log.h>
#include "../fs_impl.h"
#include "ext2.h"
#include "ext2_impl.h"
#include "ext2_struct.h"
LOG_MODULE_DECLARE(ext2);
/* File system level operations */
#ifdef CONFIG_FILE_SYSTEM_MKFS
FS_EXT2_DECLARE_DEFAULT_CONFIG(ext2_default_cfg);
#endif
static int ext2_mount(struct fs_mount_t *mountp)
{
int ret = 0;
struct ext2_data *fs = NULL;
struct ext2_disk_superblock *sb;
#ifdef CONFIG_FILE_SYSTEM_MKFS
bool do_format = false;
bool possible_format = (mountp->flags & FS_MOUNT_FLAG_NO_FORMAT) == 0 &&
(mountp->flags & FS_MOUNT_FLAG_READ_ONLY) == 0;
#endif
/* Allocate superblock structure for temporary use */
sb = ext2_heap_alloc(sizeof(struct ext2_disk_superblock));
if (sb == NULL) {
ret = -ENOMEM;
goto err;
}
ret = ext2_init_storage(&fs, mountp->storage_dev, mountp->flags);
if (ret < 0) {
goto err;
}
fs->flags = 0;
if (mountp->flags & FS_MOUNT_FLAG_READ_ONLY) {
fs->flags |= EXT2_DATA_FLAGS_RO;
}
ret = fs->backend_ops->read_superblock(fs, sb);
if (ret < 0) {
goto err;
}
ret = ext2_verify_superblock(sb);
if (ret == 0) {
fs->block_size = 1024 << sb->s_log_block_size;
} else if (ret == -EROFS) {
fs->block_size = 1024 << sb->s_log_block_size;
fs->flags |= EXT2_DATA_FLAGS_RO;
#ifdef CONFIG_FILE_SYSTEM_MKFS
} else if (ret == -EINVAL && possible_format) {
do_format = true;
fs->block_size = ext2_default_cfg.block_size;
#endif
} else {
goto err;
}
if (fs->block_size % fs->write_size != 0) {
LOG_ERR("Blocks size isn't multiple of sector size. (bsz: %d, ssz: %d)",
fs->block_size, fs->write_size);
ret = -ENOTSUP;
goto err;
}
/* Temporary superblock won't be used anymore */
ext2_heap_free(sb);
ext2_init_blocks_slab(fs);
#ifdef CONFIG_FILE_SYSTEM_MKFS
if (do_format) {
LOG_INF("Formatting the storage device");
ret = ext2_format(fs, &ext2_default_cfg);
if (ret < 0) {
goto err;
}
/* We don't need to verify superblock here again. Format has succeeded hence
* superblock must be valid.
*/
}
#endif
ret = ext2_init_fs(fs);
if (ret < 0) {
goto err;
}
mountp->fs_data = fs;
return 0;
err:
ext2_heap_free(sb);
ext2_close_struct(fs);
return ret;
}
#if defined(CONFIG_FILE_SYSTEM_MKFS)
static int ext2_mkfs(uintptr_t dev_id, void *vcfg, int flags)
{
int ret = 0;
struct ext2_data *fs;
struct ext2_cfg *cfg = vcfg;
if (cfg == NULL) {
cfg = &ext2_default_cfg;
}
ret = ext2_init_storage(&fs, (const void *)dev_id, flags);
if (ret < 0) {
LOG_ERR("Initialization of %ld device failed (%d)", dev_id, ret);
goto out;
}
fs->block_size = cfg->block_size;
ext2_init_blocks_slab(fs);
LOG_INF("Formatting the storage device");
ret = ext2_format(fs, cfg);
if (ret < 0) {
LOG_ERR("Format of %ld device failed (%d)", dev_id, ret);
}
out:
ext2_close_struct(fs);
return ret;
}
#endif /* CONFIG_FILE_SYSTEM_MKFS */
static int ext2_unmount(struct fs_mount_t *mountp)
{
int ret;
struct ext2_data *fs = mountp->fs_data;
ret = ext2_close_fs(fs);
if (ret < 0) {
return ret;
}
ret = ext2_close_struct(fs);
if (ret < 0) {
return ret;
}
mountp->fs_data = NULL;
return 0;
}
static int ext2_statvfs(struct fs_mount_t *mountp, const char *path, struct fs_statvfs *stat)
{
ARG_UNUSED(path);
struct ext2_data *fs = mountp->fs_data;
stat->f_bsize = fs->block_size;
stat->f_frsize = fs->block_size;
stat->f_blocks = EXT2_DATA_SBLOCK(fs)->s_blocks_count;
stat->f_bfree = EXT2_DATA_SBLOCK(fs)->s_free_blocks_count;
return 0;
}
/* File system interface */
static const struct fs_file_system_t ext2_fs = {
.mount = ext2_mount,
.unmount = ext2_unmount,
.statvfs = ext2_statvfs,
#if defined(CONFIG_FILE_SYSTEM_MKFS)
.mkfs = ext2_mkfs,
#endif
};
static int ext2_init(void)
{
int rc = fs_register(FS_EXT2, &ext2_fs);
if (rc < 0) {
LOG_WRN("Ext2 register error (%d)\n", rc);
} else {
LOG_DBG("Ext2 fs registered\n");
}
return rc;
}
SYS_INIT(ext2_init, POST_KERNEL, 99);

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2023 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __EXT2_STRUCT_H__
#define __EXT2_STRUCT_H__
#include <zephyr/kernel.h>
#include "ext2.h"
/* Disk structures ---------------------------------------------------------- */
struct ext2_disk_superblock {
uint32_t s_inodes_count;
uint32_t s_blocks_count;
uint32_t s_r_blocks_count;
uint32_t s_free_blocks_count;
uint32_t s_free_inodes_count;
uint32_t s_first_data_block;
uint32_t s_log_block_size;
uint32_t s_log_frag_size;
uint32_t s_blocks_per_group;
uint32_t s_frags_per_group;
uint32_t s_inodes_per_group;
uint32_t s_mtime;
uint32_t s_wtime;
uint16_t s_mnt_count;
uint16_t s_max_mnt_count;
uint16_t s_magic;
uint16_t s_state;
uint16_t s_errors;
uint16_t s_minor_rev_level;
uint32_t s_lastcheck;
uint32_t s_checkinterval;
uint32_t s_creator_os;
uint32_t s_rev_level;
uint16_t s_def_resuid;
uint16_t s_def_resgid;
uint32_t s_first_ino;
uint16_t s_inode_size;
uint16_t s_block_group_nr;
uint32_t s_feature_compat;
uint32_t s_feature_incompat;
uint32_t s_feature_ro_compat;
uint8_t s_uuid[16];
uint8_t s_volume_name[16];
uint8_t s_last_mounted[64];
uint32_t s_algo_bitmap;
uint8_t s_prealloc_blocks;
uint8_t s_prealloc_dir_blocks;
uint8_t s_align[2];
uint8_t s_journal_uuid[16];
uint32_t s_journal_inum;
uint32_t s_journal_dev;
uint32_t s_last_orphan;
uint8_t s_padding[788];
};
struct ext2_disk_bgroup {
uint32_t bg_block_bitmap;
uint32_t bg_inode_bitmap;
uint32_t bg_inode_table;
uint16_t bg_free_blocks_count;
uint16_t bg_free_inodes_count;
uint16_t bg_used_dirs_count;
uint16_t bg_pad;
uint8_t bg_reserved[12];
};
struct ext2_disk_inode {
uint16_t i_mode;
uint16_t i_uid;
uint32_t i_size;
uint32_t i_atime;
uint32_t i_ctime;
uint32_t i_mtime;
uint32_t i_dtime;
uint16_t i_gid;
uint16_t i_links_count;
uint32_t i_blocks;
uint32_t i_flags;
uint32_t i_osd1;
uint32_t i_block[15];
uint32_t i_generation;
uint32_t i_file_acl;
uint32_t i_dir_acl;
uint32_t i_faddr;
uint8_t i_osd2[12];
};
struct ext2_disk_dentry {
uint32_t de_inode;
uint16_t de_rec_len;
uint8_t de_name_len;
uint8_t de_file_type;
char de_name[];
};
/* Program structures ------------------------------------------------------- */
struct ext2_block {
uint32_t num;
uint8_t *data;
} __aligned(sizeof(void *));
#define EXT2_DATA_FLAGS_RO BIT(0)
/* Accessing superblock disk structure (it is at some offset in stored block) */
#define EXT2_DATA_SBLOCK(fs) \
((struct ext2_disk_superblock *)((uint8_t *)(fs)->sblock->data + (fs)->sblock_offset))
struct ext2_data;
struct ext2_backend_ops {
int64_t (*get_device_size)(struct ext2_data *fs);
int64_t (*get_write_size)(struct ext2_data *fs);
int (*read_block)(struct ext2_data *fs, void *buf, uint32_t num);
int (*write_block)(struct ext2_data *fs, const void *buf, uint32_t num);
int (*read_superblock)(struct ext2_data *fs, struct ext2_disk_superblock *sb);
int (*sync)(struct ext2_data *fs);
};
struct ext2_data {
struct ext2_block *sblock; /* superblock */
uint32_t sblock_offset;
uint32_t block_size; /* fs block size */
uint32_t write_size; /* dev minimal write size */
uint64_t device_size;
struct k_thread sync_thr;
void *backend; /* pointer to implementation specific resource */
const struct ext2_backend_ops *backend_ops;
uint8_t flags;
};
#endif /* __EXT2_STRUCT_H__ */