[PATCH 00/26] fs: ext4l: Add support for mounting ext4 filesystems (part G)
From: Simon Glass <simon.glass@canonical.com> This series adds the ext4l filesystem driver, which is a port of the Linux ext4 driver to U-Boot. It allows mounting ext4 filesystems in read-only mode. The driver uses Linux kernel code directly where possible, with compatibility shims to handle differences between Linux and U-Boot. This approach makes it easier to keep the driver up to date with upstream changes. Key features: - Read-only mounting of ext4 filesystems - Support for extents, flex_bg, and other ext4 features - Buffer cache for improved performance - CRC32C checksums for metadata verification Simon Glass (26): fs: ext4l: Add minimal probe support linux: Move common types to types.h fs: ext4l: Add CONFIG_EXT4_JOURNAL option linux: Add jiffies.h with MAX_JIFFY_OFFSET linux: Add blk_types.h with blk_opf_t linux: Add BDEVNAME_SIZE to blkdev.h linux: Update jbd2.h includes linux: printk: Fix KERN_* macros for string concatenation linux: Add rwlock support to spinlock.h fs: ext4l: Use types from linux headers fs: jbd2: Add jbd2_journal_init_global() for ext4l fs: ext4: Allow EXT4_WRITE with FS_EXT4L fs: ext4l: Initialise multi-block allocator fs: ext4l: Initialise extent status cache fs: ext4l: Add super_block and block_device allocations fs: ext4l: Allocate fs_context fs: ext4l: Allocate ext4_fs_context fs: ext4l: Initialise super_block and call ext4_fill_super() fs: ext4l: Add buffer_head I/O infrastructure fs: ext4l: Add CONFIG_EXT4L_DEBUG to control messages fs: ext4l: Use real rbtree implementation fs: ext4l: Add CRC32C implementation linux: crc32c: Use ext4l CRC32C implementation fs: ext4l: Mount filesystems read-only fs: ext4l: Add inode and bmap functions fs: ext4l: Support mounting a filesystem read-only .pickman-history | 121 +++ arch/arm/dts/k3-am62a-ddr.dtsi | 7 +- arch/arm/dts/k3-j721s2-ddr.dtsi | 12 +- arch/arm/dts/k3-j784s4-ddr.dtsi | 24 +- arch/arm/mach-k3/Kconfig | 3 + arch/arm/mach-k3/Makefile | 2 +- arch/arm/mach-k3/am62px/am62p5_fdt.c | 83 ++ arch/arm/mach-k3/am62px/am62p5_init.c | 62 ++ .../arm/mach-k3/include/mach/am62p_hardware.h | 65 ++ arch/arm/mach-k3/include/mach/k3-ddr.h | 15 + arch/arm/mach-k3/k3-ddr.c | 72 ++ board/ti/am62ax/evm.c | 17 +- board/ti/am62px/evm.c | 17 +- board/ti/am62x/evm.c | 63 +- board/ti/am64x/evm.c | 73 +- board/ti/am65x/evm.c | 29 +- board/ti/common/Makefile | 1 + board/ti/common/k3-ddr.c | 33 + board/ti/common/k3-ddr.h | 12 + board/ti/j721e/evm.c | 29 +- board/ti/j721s2/evm.c | 35 +- board/ti/j722s/evm.c | 17 +- board/ti/j784s4/evm.c | 17 +- configs/am62ax_evm_a53_defconfig | 1 - configs/am62px_evm_a53_defconfig | 1 - configs/am62x_evm_a53_defconfig | 1 - configs/am62x_evm_r5_defconfig | 1 - configs/am64x_evm_a53_defconfig | 1 - configs/am64x_evm_r5_defconfig | 1 - configs/am65x_evm_a53_defconfig | 1 - configs/am65x_evm_r5_defconfig | 1 - configs/am65x_evm_r5_usbdfu_defconfig | 1 - configs/am65x_evm_r5_usbmsc_defconfig | 1 - configs/j7200_evm_a72_defconfig | 1 - configs/j721e_evm_a72_defconfig | 1 - configs/j721s2_evm_a72_defconfig | 1 - configs/j722s_evm_a53_defconfig | 1 - configs/j784s4_evm_a72_defconfig | 1 - drivers/ram/Kconfig | 10 + drivers/ram/k3-ddrss/k3-ddrss.c | 219 +++++- fs/Makefile | 2 +- fs/ext4/Kconfig | 2 +- fs/ext4l/Kconfig | 23 + fs/ext4l/Makefile | 2 +- fs/ext4l/ext4.h | 31 + fs/ext4l/ext4_uboot.h | 151 ++-- fs/ext4l/extents_status.c | 5 +- fs/ext4l/interface.c | 224 +++++- fs/ext4l/stub.c | 49 +- fs/ext4l/super.c | 38 +- fs/ext4l/support.c | 555 +++++++++++++ fs/fs_legacy.c | 5 +- fs/jbd2/journal.c | 22 + include/ext4l.h | 31 + include/linux/blk_types.h | 18 + include/linux/blkdev.h | 3 + include/linux/buffer_head.h | 8 +- include/linux/compat.h | 16 +- include/linux/crc32c.h | 19 +- include/linux/jbd2.h | 5 + include/linux/jiffies.h | 18 + include/linux/printk.h | 18 +- include/linux/spinlock.h | 21 + include/linux/types.h | 36 + tools/buildman/func_test.py | 1 + tools/buildman/main.py | 4 +- tools/buildman/test_boards.py | 739 ++++++++++++++++++ tools/pickman/README.rst | 70 +- tools/pickman/agent.py | 69 +- tools/pickman/control.py | 73 ++ tools/pickman/ftest.py | 82 ++ 71 files changed, 2907 insertions(+), 486 deletions(-) create mode 100644 arch/arm/mach-k3/include/mach/k3-ddr.h create mode 100644 arch/arm/mach-k3/k3-ddr.c create mode 100644 board/ti/common/k3-ddr.c create mode 100644 board/ti/common/k3-ddr.h create mode 100644 fs/ext4l/support.c create mode 100644 include/ext4l.h create mode 100644 include/linux/blk_types.h create mode 100644 include/linux/jiffies.h create mode 100644 tools/buildman/test_boards.py -- 2.43.0 base-commit: 69cc5aeabff7ea0aa4da6bc6acd13e3d3144a7dc branch: extg
From: Simon Glass <simon.glass@canonical.com> Add a minimal ext4l probe function that: - Reads the superblock from the block device - Validates the ext4 magic number - Returns proper error codes (-EINVAL, -ENOMEM, -EIO) Create include/ext4l.h header with function declarations and documentation. Update fs_legacy.c to use the header. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 57 ++++++++++++++++++++++++++++++++++++++++++-- fs/fs_legacy.c | 5 ++-- include/ext4l.h | 31 ++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 include/ext4l.h diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 455e101b65f..eb625e0b1a5 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -2,19 +2,72 @@ /* * U-Boot interface for ext4l filesystem (Linux port) * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * * This provides the minimal interface between U-Boot and the ext4l driver. - * Currently just stubs - the filesystem doesn't work yet. */ #include <blk.h> #include <part.h> +#include <malloc.h> +#include <asm/byteorder.h> +#include <linux/errno.h> +#include <linux/types.h> + +#include "ext4_uboot.h" +#include "ext4.h" + +/* Global state */ +static struct blk_desc *ext4l_dev_desc; +static struct disk_partition ext4l_part; int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { - return -1; + loff_t part_offset; + __le16 *magic; + u8 *buf; + int ret; + + if (!fs_dev_desc) + return -EINVAL; + + buf = malloc(BLOCK_SIZE + 512); + if (!buf) + return -ENOMEM; + + /* Calculate partition offset in bytes */ + part_offset = fs_partition ? (loff_t)fs_partition->start * fs_dev_desc->blksz : 0; + + /* Read sectors containing the superblock */ + if (blk_dread(fs_dev_desc, + (part_offset + BLOCK_SIZE) / fs_dev_desc->blksz, + 2, buf) != 2) { + ret = -EIO; + goto out; + } + + /* Check magic number within superblock */ + magic = (__le16 *)(buf + (BLOCK_SIZE % fs_dev_desc->blksz) + + offsetof(struct ext4_super_block, s_magic)); + if (le16_to_cpu(*magic) != EXT4_SUPER_MAGIC) { + ret = -EINVAL; + goto out; + } + + /* Save device info for later operations */ + ext4l_dev_desc = fs_dev_desc; + if (fs_partition) + memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); + + ret = 0; +out: + free(buf); + return ret; } void ext4l_close(void) { + ext4l_dev_desc = NULL; } diff --git a/fs/fs_legacy.c b/fs/fs_legacy.c index 405aa8aba54..5b96e1465d8 100644 --- a/fs/fs_legacy.c +++ b/fs/fs_legacy.c @@ -18,6 +18,7 @@ #include <mapmem.h> #include <part.h> #include <ext4fs.h> +#include <ext4l.h> #include <fat.h> #include <fs_legacy.h> #include <sandboxfs.h> @@ -262,8 +263,8 @@ static struct fstype_info fstypes[] = { .fstype = FS_TYPE_EXT, .name = "ext4", .null_dev_desc_ok = false, - .probe = fs_probe_unsupported, - .close = fs_close_unsupported, + .probe = ext4l_probe, + .close = ext4l_close, .ls = fs_ls_unsupported, .exists = fs_exists_unsupported, .size = fs_size_unsupported, diff --git a/include/ext4l.h b/include/ext4l.h new file mode 100644 index 00000000000..5a300fd6559 --- /dev/null +++ b/include/ext4l.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * ext4l filesystem interface + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + */ + +#ifndef __EXT4L_H__ +#define __EXT4L_H__ + +struct blk_desc; +struct disk_partition; + +/** + * ext4l_probe() - Probe a block device for an ext4 filesystem + * + * @fs_dev_desc: Block device descriptor + * @fs_partition: Partition information + * Return: 0 on success, -EINVAL if no device or invalid magic, + * -ENOMEM on allocation failure, -EIO on read error + */ +int ext4l_probe(struct blk_desc *fs_dev_desc, + struct disk_partition *fs_partition); + +/** + * ext4l_close() - Close the ext4 filesystem + */ +void ext4l_close(void); + +#endif /* __EXT4L_H__ */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Move ktime_t, sector_t, blkcnt_t, atomic_t, and atomic64_t to linux/types.h to match Linux kernel header organisation. Remove the duplicate definitions from linux/compat.h and fs/ext4l/ext4_uboot.h since they are now in the canonical location. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 24 ++++++++++-------------- include/linux/compat.h | 16 +--------------- include/linux/types.h | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 1ad9ad7f47a..d0e60c02d77 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -2,6 +2,9 @@ /* * U-Boot compatibility header for ext4l filesystem * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * * This provides minimal definitions to allow Linux ext4 code to compile * in U-Boot. */ @@ -52,8 +55,11 @@ struct timespec64 { long tv_nsec; }; -/* ktime_t - kernel time type */ -typedef s64 ktime_t; +/* + * ktime_t, sector_t are in linux/types.h + * atomic_t, atomic64_t are in asm-generic/atomic.h + */ +#include <asm-generic/atomic.h> /* Jiffy constants */ #define MAX_JIFFY_OFFSET ((~0UL >> 1) - 1) @@ -61,16 +67,7 @@ typedef s64 ktime_t; /* Block device name size */ #define BDEVNAME_SIZE 32 -/* Atomic types - stubs for single-threaded U-Boot */ -typedef struct { int counter; } atomic_t; -typedef struct { long counter; } atomic64_t; - -#define atomic_read(v) ((v)->counter) -#define atomic_set(v, i) ((v)->counter = (i)) -#define atomic_inc(v) ((v)->counter++) -#define atomic_dec(v) ((v)->counter--) -#define atomic64_read(v) ((v)->counter) -#define atomic64_set(v, i) ((v)->counter = (i)) +/* Extra atomic operations not in asm-generic/atomic.h */ #define atomic_dec_if_positive(v) (--(v)->counter) /* SMP stubs - U-Boot is single-threaded */ @@ -510,8 +507,7 @@ struct sb_writers { /* mapping_large_folio_support stub */ #define mapping_large_folio_support(m) (0) -/* sector_t - needed before buffer_head.h */ -typedef unsigned long sector_t; +/* sector_t is now in linux/types.h */ /* Buffer head - from linux/buffer_head.h */ #include <linux/buffer_head.h> diff --git a/include/linux/compat.h b/include/linux/compat.h index 26f0ef3f101..df67b775b5b 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h @@ -110,21 +110,7 @@ static inline void led_trigger_event(struct led_trigger *trigger, /* uapi/linux/limits.h */ #define XATTR_LIST_MAX 65536 /* size of extended attribute namelist (64k) */ -/** - * The type used for indexing onto a disc or disc partition. - * - * Linux always considers sectors to be 512 bytes long independently - * of the devices real block size. - * - * blkcnt_t is the type of the inode's block count. - */ -#ifdef CONFIG_LBDAF -typedef u64 sector_t; -typedef u64 blkcnt_t; -#else -typedef unsigned long sector_t; -typedef unsigned long blkcnt_t; -#endif +/* sector_t and blkcnt_t are now defined in linux/types.h */ /* character device */ diff --git a/include/linux/types.h b/include/linux/types.h index 9df930afd13..957284d37ab 100644 --- a/include/linux/types.h +++ b/include/linux/types.h @@ -151,6 +151,20 @@ typedef __u32 __bitwise __wsum; typedef unsigned __bitwise__ gfp_t; +/* Nanosecond scalar representation for kernel time values */ +typedef s64 ktime_t; + +/** + * The type used for indexing onto a disc or disc partition. + * + * Linux always considers sectors to be 512 bytes long independently + * of the devices real block size. + * + * blkcnt_t is the type of the inode's block count. + */ +typedef u64 sector_t; +typedef u64 blkcnt_t; + #ifdef __linux__ struct ustat { __kernel_daddr_t f_tfree; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a config option to control whether journal (jbd2) support is included for ext4 filesystems. This defaults to y Update fs/Makefile to conditionally include jbd2/ based on this config instead of FS_EXT4L. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/Makefile | 2 +- fs/ext4l/Kconfig | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/fs/Makefile b/fs/Makefile index b6d4f7a9cf7..969888a81da 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -22,7 +22,7 @@ obj-$(CONFIG_CMD_CRAMFS) += cramfs/ obj-$(CONFIG_FS_EXFAT) += exfat/ obj-$(CONFIG_FS_EXT4) += ext4/ obj-$(CONFIG_FS_EXT4L) += ext4l/ -obj-$(CONFIG_FS_EXT4L) += jbd2/ +obj-$(CONFIG_EXT4_JOURNAL) += jbd2/ obj-$(CONFIG_FS_FAT) += fat/ obj-$(CONFIG_FS_JFFS2) += jffs2/ obj-$(CONFIG_SANDBOX) += sandbox/ diff --git a/fs/ext4l/Kconfig b/fs/ext4l/Kconfig index b6c2329db4a..f1fbe3b1000 100644 --- a/fs/ext4l/Kconfig +++ b/fs/ext4l/Kconfig @@ -1,4 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only +# +# Copyright 2025 Canonical Ltd +# Written by Simon Glass <simon.glass@canonical.com> config FS_EXT4L bool "Enable ext4 filesystem support (Linux port)" @@ -8,3 +11,14 @@ config FS_EXT4L FS_EXT4 (the existing U-Boot ext4 implementation). This requires 64-bit LBA support for proper block counting. + +config EXT4_JOURNAL + bool "Enable ext4 journal support" + depends on FS_EXT4L + default y + help + This enables journal (jbd2) support for ext4 filesystems. The + journal provides crash-consistency by logging metadata changes + before committing them to the filesystem. + + If unsure, say Y. -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a minimal jiffies.h header with MAX_JIFFY_OFFSET definition, matching the Linux kernel header location for this constant. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/jiffies.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 include/linux/jiffies.h diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h new file mode 100644 index 00000000000..daef4a337a2 --- /dev/null +++ b/include/linux/jiffies.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Jiffies and time conversion + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * + * Minimal version for U-Boot - based on Linux + */ +#ifndef _LINUX_JIFFIES_H +#define _LINUX_JIFFIES_H + +#include <linux/types.h> +#include <limits.h> + +#define MAX_JIFFY_OFFSET ((LONG_MAX >> 1) - 1) + +#endif /* _LINUX_JIFFIES_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a minimal blk_types.h header with blk_opf_t type definition, matching the Linux kernel header location. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/blk_types.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 include/linux/blk_types.h diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h new file mode 100644 index 00000000000..aa7ea50d233 --- /dev/null +++ b/include/linux/blk_types.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Block I/O types + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * + * Minimal version for U-Boot - based on Linux + */ +#ifndef _LINUX_BLK_TYPES_H +#define _LINUX_BLK_TYPES_H + +#include <linux/types.h> + +/* Block I/O operation flags */ +typedef __u32 __bitwise blk_opf_t; + +#endif /* _LINUX_BLK_TYPES_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add BDEVNAME_SIZE definition to blkdev.h, matching the Linux kernel header location for this constant. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/blkdev.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 1a8851c4b4c..92878b01c3e 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -12,6 +12,9 @@ struct block_device; struct gendisk; +/* Largest string for a blockdev identifier */ +#define BDEVNAME_SIZE 32 + /* Block size helpers */ #define bdev_logical_block_size(bdev) 512 -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add includes for the new header files (blk_types.h, jiffies.h, percpu_counter.h, spinlock.h) to jbd2.h so it can be included standalone. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/jbd2.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/jbd2.h b/include/linux/jbd2.h index 35b68ffc0fd..41725fb42af 100644 --- a/include/linux/jbd2.h +++ b/include/linux/jbd2.h @@ -20,14 +20,18 @@ #else #include <linux/types.h> +#include <linux/blk_types.h> #include <linux/buffer_head.h> #include <linux/journal-head.h> #include <linux/stddef.h> #include <linux/mutex.h> #include <linux/timer.h> #include <linux/slab.h> +#include <linux/spinlock.h> #include <linux/bit_spinlock.h> #include <linux/blkdev.h> +#include <linux/jiffies.h> +#include <linux/percpu_counter.h> #include <linux/crc32c.h> #include <linux/wait.h> #include <linux/init.h> @@ -70,6 +74,7 @@ void __jbd2_debug(int level, const char *file, const char *func, extern void *jbd2_alloc(size_t size, gfp_t flags); extern void jbd2_free(void *ptr, size_t size); +extern int jbd2_journal_init_global(void); #define JBD2_MIN_JOURNAL_BLOCKS 1024 #define JBD2_DEFAULT_FAST_COMMIT_BLOCKS 256 -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Change KERN_* macros from empty definitions to empty strings (""). This fixes string concatenation in printf-style calls like: printk(KERN_ERR "message") Without this fix, KERN_ERR expands to nothing and the string concatenation fails. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/printk.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/linux/printk.h b/include/linux/printk.h index edf149f52c7..00452944c48 100644 --- a/include/linux/printk.h +++ b/include/linux/printk.h @@ -5,15 +5,15 @@ #include <stdio.h> #include <linux/compiler.h> -#define KERN_EMERG -#define KERN_ALERT -#define KERN_CRIT -#define KERN_ERR -#define KERN_WARNING -#define KERN_NOTICE -#define KERN_INFO -#define KERN_DEBUG -#define KERN_CONT +#define KERN_EMERG "" +#define KERN_ALERT "" +#define KERN_CRIT "" +#define KERN_ERR "" +#define KERN_WARNING "" +#define KERN_NOTICE "" +#define KERN_INFO "" +#define KERN_DEBUG "" +#define KERN_CONT "" #define printk(fmt, ...) \ printf(fmt, ##__VA_ARGS__) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add rwlock_t type and read/write lock operation stubs to spinlock.h. These are no-ops for single-threaded U-Boot but provide the API needed by Linux-derived code. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/spinlock.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h index 906766931b4..75afad92b9e 100644 --- a/include/linux/spinlock.h +++ b/include/linux/spinlock.h @@ -78,4 +78,25 @@ typedef struct { /* Assert variants */ #define assert_spin_locked(lock) do { } while (0) +/* Read-write lock type - just an int for U-Boot */ +typedef int rwlock_t; + +#define __RW_LOCK_UNLOCKED(lockname) (0) +#define DEFINE_RWLOCK(x) rwlock_t x = __RW_LOCK_UNLOCKED(x) + +/* Read-write lock operations - all no-ops for single-threaded U-Boot */ +#define rwlock_init(lock) do { } while (0) +#define read_lock(lock) do { } while (0) +#define read_unlock(lock) do { } while (0) +#define write_lock(lock) do { } while (0) +#define write_unlock(lock) do { } while (0) +#define read_lock_irq(lock) do { } while (0) +#define read_unlock_irq(lock) do { } while (0) +#define write_lock_irq(lock) do { } while (0) +#define write_unlock_irq(lock) do { } while (0) +#define read_lock_irqsave(lock, flags) do { (void)(flags); } while (0) +#define read_unlock_irqrestore(lock, flags) do { (void)(flags); } while (0) +#define write_lock_irqsave(lock, flags) do { (void)(flags); } while (0) +#define write_unlock_irqrestore(lock, flags) do { (void)(flags); } while (0) + #endif /* __LINUX_SPINLOCK_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update ext4_uboot.h to include the new linux headers instead of defining types locally. Remove duplicate definitions of: - MAX_JIFFY_OFFSET (now in linux/jiffies.h) - BDEVNAME_SIZE (now in linux/blkdev.h) - rwlock_t (now in linux/spinlock.h) Remove rwlock_init() stub from stub.c since it's now a macro. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 23 +++++++---------------- fs/ext4l/stub.c | 8 ++++---- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index d0e60c02d77..1f823ba6ea3 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -58,14 +58,12 @@ struct timespec64 { /* * ktime_t, sector_t are in linux/types.h * atomic_t, atomic64_t are in asm-generic/atomic.h + * MAX_JIFFY_OFFSET is in linux/jiffies.h + * BDEVNAME_SIZE is in linux/blkdev.h */ #include <asm-generic/atomic.h> - -/* Jiffy constants */ -#define MAX_JIFFY_OFFSET ((~0UL >> 1) - 1) - -/* Block device name size */ -#define BDEVNAME_SIZE 32 +#include <linux/jiffies.h> +#include <linux/blkdev.h> /* Extra atomic operations not in asm-generic/atomic.h */ #define atomic_dec_if_positive(v) (--(v)->counter) @@ -86,14 +84,8 @@ struct timespec64 { /* Reference count type */ typedef struct { atomic_t refs; } refcount_t; -/* Lock types - stubs for single-threaded U-Boot */ -typedef int rwlock_t; -/* spinlock_t is defined in linux/compat.h */ - -#define read_lock(l) do { } while (0) -#define read_unlock(l) do { } while (0) -#define write_lock(l) do { } while (0) -#define write_unlock(l) do { } while (0) +/* rwlock_t and read_lock/read_unlock are now in linux/spinlock.h */ +#include <linux/spinlock.h> /* RB tree types - stubs */ struct rb_node { @@ -2202,8 +2194,7 @@ void *alloc_inode_sb(struct super_block *sb, struct kmem_cache *cache, void inode_set_iversion(struct inode *inode, u64 version); int inode_generic_drop(struct inode *inode); -/* Lock init - declaration for stub.c */ -void rwlock_init(rwlock_t *lock); +/* rwlock_init is a macro in linux/spinlock.h */ /* Trace stubs */ #define trace_ext4_drop_inode(i, d) do { } while (0) diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index 93315d10fa8..e13685bf8fa 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -2,6 +2,9 @@ /* * Stub functions for ext4l filesystem * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * * These stubs allow the ext4l code to link during development while not all * source files are present. They will be removed once the full ext4l * implementation is complete. @@ -352,10 +355,7 @@ void inode_set_iversion(struct inode *inode, u64 version) { } -/* rwlock stubs */ -void rwlock_init(rwlock_t *lock) -{ -} +/* rwlock_init is now a macro in linux/spinlock.h */ /* trace_ext4_drop_inode is now a macro in ext4_uboot.h */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add jbd2_journal_init_global() which initializes the JBD2 journal subsystem caches. This wraps the existing journal_init() with a static guard to ensure it's only called once. Call this from ext4l_probe() when CONFIG_EXT4_JOURNAL is enabled to initialize the journal subsystem before any journal operations. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 8 ++++++++ fs/jbd2/journal.c | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index eb625e0b1a5..546017efd16 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -13,6 +13,7 @@ #include <malloc.h> #include <asm/byteorder.h> #include <linux/errno.h> +#include <linux/jbd2.h> #include <linux/types.h> #include "ext4_uboot.h" @@ -33,6 +34,13 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (!fs_dev_desc) return -EINVAL; + /* Initialise journal subsystem if enabled */ + if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) { + ret = jbd2_journal_init_global(); + if (ret) + return ret; + } + buf = malloc(BLOCK_SIZE + 512); if (!buf) return -ENOMEM; diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index 0b77fd0f34b..0cd95df8192 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -3118,6 +3118,28 @@ static int __init journal_init(void) return ret; } +/** + * jbd2_journal_init_global() - Initialize JBD2 global state + * + * This must be called before any journal operations. It initializes + * the journal caches and other global state. + * + * Return: 0 on success, negative error code on failure + */ +int jbd2_journal_init_global(void) +{ + static bool initialized; + + if (initialized) + return 0; + + if (journal_init()) + return -ENOMEM; + + initialized = true; + return 0; +} + static void __exit journal_exit(void) { #ifdef CONFIG_JBD2_DEBUG -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update CONFIG_EXT4_WRITE to depend on either FS_EXT4 or FS_EXT4L, allowing write support to be enabled for the Linux-ported ext4l filesystem as well. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig index 26cf7469d73..d7798cfb6b9 100644 --- a/fs/ext4/Kconfig +++ b/fs/ext4/Kconfig @@ -8,7 +8,7 @@ config FS_EXT4 config EXT4_WRITE bool "Enable ext4 filesystem write support" - depends on FS_EXT4 + depends on FS_EXT4 || FS_EXT4L help This provides support for creating and writing new files to an existing ext4 filesystem partition. -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Call ext4_init_mballoc() from ext4l_probe() when CONFIG_EXT4_WRITE is enabled to initialise the multi-block allocator caches before any write operations. Include ext4.h to get the function declaration. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 546017efd16..ca71843b58e 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -41,6 +41,13 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, return ret; } + /* Initialise multi-block allocator for write support */ + if (IS_ENABLED(CONFIG_EXT4_WRITE)) { + ret = ext4_init_mballoc(); + if (ret) + return ret; + } + buf = malloc(BLOCK_SIZE + 512); if (!buf) return -ENOMEM; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add ext4_init_es() call to initialise the extent status cache. This is needed for ext4 to track the status of extents in memory. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4.h | 2 ++ fs/ext4l/interface.c | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/fs/ext4l/ext4.h b/fs/ext4l/ext4.h index 836ea2612b9..8fed7194d84 100644 --- a/fs/ext4l/ext4.h +++ b/fs/ext4l/ext4.h @@ -2960,6 +2960,8 @@ extern ext4_fsblk_t ext4_mb_new_blocks(handle_t *, extern void ext4_discard_preallocations(struct inode *); extern int __init ext4_init_mballoc(void); extern void ext4_exit_mballoc(void); +extern int __init ext4_init_es(void); +extern void ext4_exit_es(void); extern ext4_group_t ext4_mb_prefetch(struct super_block *sb, ext4_group_t group, unsigned int nr, int *cnt); diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index ca71843b58e..c18ed306f1f 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -48,6 +48,11 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, return ret; } + /* Initialise extent status cache */ + ret = ext4_init_es(); + if (ret) + return ret; + buf = malloc(BLOCK_SIZE + 512); if (!buf) return -ENOMEM; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add allocations for super_block, block_device, and address_space structures in ext4l_probe(). These will be needed for the full filesystem mount operation. Use goto-based error handling for proper cleanup on failure. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 49 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index c18ed306f1f..792928b4b6c 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -26,6 +26,7 @@ static struct disk_partition ext4l_part; int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { + struct super_block *sb; loff_t part_offset; __le16 *magic; u8 *buf; @@ -53,9 +54,31 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (ret) return ret; + /* Allocate super_block */ + sb = kzalloc(sizeof(struct super_block), GFP_KERNEL); + if (!sb) { + ret = -ENOMEM; + goto err_exit_es; + } + + /* Allocate block_device */ + sb->s_bdev = kzalloc(sizeof(struct block_device), GFP_KERNEL); + if (!sb->s_bdev) { + ret = -ENOMEM; + goto err_free_sb; + } + + sb->s_bdev->bd_mapping = kzalloc(sizeof(struct address_space), GFP_KERNEL); + if (!sb->s_bdev->bd_mapping) { + ret = -ENOMEM; + goto err_free_bdev; + } + buf = malloc(BLOCK_SIZE + 512); - if (!buf) - return -ENOMEM; + if (!buf) { + ret = -ENOMEM; + goto err_free_mapping; + } /* Calculate partition offset in bytes */ part_offset = fs_partition ? (loff_t)fs_partition->start * fs_dev_desc->blksz : 0; @@ -65,7 +88,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, (part_offset + BLOCK_SIZE) / fs_dev_desc->blksz, 2, buf) != 2) { ret = -EIO; - goto out; + goto err_free_buf; } /* Check magic number within superblock */ @@ -73,7 +96,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, offsetof(struct ext4_super_block, s_magic)); if (le16_to_cpu(*magic) != EXT4_SUPER_MAGIC) { ret = -EINVAL; - goto out; + goto err_free_buf; } /* Save device info for later operations */ @@ -81,9 +104,23 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (fs_partition) memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); - ret = 0; -out: free(buf); + kfree(sb->s_bdev->bd_mapping); + kfree(sb->s_bdev); + kfree(sb); + + return 0; + +err_free_buf: + free(buf); +err_free_mapping: + kfree(sb->s_bdev->bd_mapping); +err_free_bdev: + kfree(sb->s_bdev); +err_free_sb: + kfree(sb); +err_exit_es: + ext4_exit_es(); return ret; } -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add allocation of fs_context structure which will be needed for mounting the filesystem via ext4_fill_super(). Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/interface.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 792928b4b6c..b653b7ef981 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -27,6 +27,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { struct super_block *sb; + struct fs_context *fc; loff_t part_offset; __le16 *magic; u8 *buf; @@ -74,10 +75,17 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, goto err_free_bdev; } + /* Allocate fs_context */ + fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL); + if (!fc) { + ret = -ENOMEM; + goto err_free_mapping; + } + buf = malloc(BLOCK_SIZE + 512); if (!buf) { ret = -ENOMEM; - goto err_free_mapping; + goto err_free_fc; } /* Calculate partition offset in bytes */ @@ -105,6 +113,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); free(buf); + kfree(fc); kfree(sb->s_bdev->bd_mapping); kfree(sb->s_bdev); kfree(sb); @@ -113,6 +122,8 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, err_free_buf: free(buf); +err_free_fc: + kfree(fc); err_free_mapping: kfree(sb->s_bdev->bd_mapping); err_free_bdev: -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add allocation of ext4_fs_context structure which holds mount options and will be needed for mounting the filesystem. Move struct ext4_fs_context definition from super.c to ext4.h so it can be used by interface.c. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4.h | 29 +++++++++++++++++++++++++++++ fs/ext4l/interface.c | 17 ++++++++++++++++- fs/ext4l/super.c | 31 +------------------------------ 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/fs/ext4l/ext4.h b/fs/ext4l/ext4.h index 8fed7194d84..5c37fcd76f9 100644 --- a/fs/ext4l/ext4.h +++ b/fs/ext4l/ext4.h @@ -2962,6 +2962,35 @@ extern int __init ext4_init_mballoc(void); extern void ext4_exit_mballoc(void); extern int __init ext4_init_es(void); extern void ext4_exit_es(void); + +struct ext4_fs_context { + char *s_qf_names[EXT4_MAXQUOTAS]; + struct fscrypt_dummy_policy dummy_enc_policy; + int s_jquota_fmt; + unsigned short qname_spec; + unsigned long vals_s_flags; + unsigned long mask_s_flags; + unsigned long journal_devnum; + unsigned long s_commit_interval; + unsigned long s_stripe; + unsigned int s_inode_readahead_blks; + unsigned int s_want_extra_isize; + unsigned int s_li_wait_mult; + unsigned int s_max_dir_size_kb; + unsigned int journal_ioprio; + unsigned int vals_s_mount_opt; + unsigned int mask_s_mount_opt; + unsigned int vals_s_mount_opt2; + unsigned int mask_s_mount_opt2; + unsigned int opt_flags; + unsigned int spec; + u32 s_max_batch_time; + u32 s_min_batch_time; + kuid_t s_resuid; + kgid_t s_resgid; + ext4_fsblk_t s_sb_block; +}; + extern ext4_group_t ext4_mb_prefetch(struct super_block *sb, ext4_group_t group, unsigned int nr, int *cnt); diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index b653b7ef981..20fdf3c908c 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -26,6 +26,7 @@ static struct disk_partition ext4l_part; int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { + struct ext4_fs_context *ctx; struct super_block *sb; struct fs_context *fc; loff_t part_offset; @@ -82,10 +83,21 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, goto err_free_mapping; } + /* Allocate ext4_fs_context */ + ctx = kzalloc(sizeof(struct ext4_fs_context), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err_free_fc; + } + + /* Initialise fs_context fields */ + fc->fs_private = ctx; + fc->sb_flags |= SB_I_VERSION; + buf = malloc(BLOCK_SIZE + 512); if (!buf) { ret = -ENOMEM; - goto err_free_fc; + goto err_free_ctx; } /* Calculate partition offset in bytes */ @@ -113,6 +125,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); free(buf); + kfree(ctx); kfree(fc); kfree(sb->s_bdev->bd_mapping); kfree(sb->s_bdev); @@ -122,6 +135,8 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, err_free_buf: free(buf); +err_free_ctx: + kfree(ctx); err_free_fc: kfree(fc); err_free_mapping: diff --git a/fs/ext4l/super.c b/fs/ext4l/super.c index 2a67c6d8156..af5b9b1986d 100644 --- a/fs/ext4l/super.c +++ b/fs/ext4l/super.c @@ -1926,36 +1926,7 @@ ext4_sb_read_encoding(const struct ext4_super_block *es) #define EXT4_SPEC_s_sb_block (1 << 18) #define EXT4_SPEC_mb_optimize_scan (1 << 19) -struct ext4_fs_context { - char *s_qf_names[EXT4_MAXQUOTAS]; - struct fscrypt_dummy_policy dummy_enc_policy; - int s_jquota_fmt; /* Format of quota to use */ -#ifdef CONFIG_EXT4_DEBUG - int s_fc_debug_max_replay; -#endif - unsigned short qname_spec; - unsigned long vals_s_flags; /* Bits to set in s_flags */ - unsigned long mask_s_flags; /* Bits changed in s_flags */ - unsigned long journal_devnum; - unsigned long s_commit_interval; - unsigned long s_stripe; - unsigned int s_inode_readahead_blks; - unsigned int s_want_extra_isize; - unsigned int s_li_wait_mult; - unsigned int s_max_dir_size_kb; - unsigned int journal_ioprio; - unsigned int vals_s_mount_opt; - unsigned int mask_s_mount_opt; - unsigned int vals_s_mount_opt2; - unsigned int mask_s_mount_opt2; - unsigned int opt_flags; /* MOPT flags */ - unsigned int spec; - u32 s_max_batch_time; - u32 s_min_batch_time; - kuid_t s_resuid; - kgid_t s_resgid; - ext4_fsblk_t s_sb_block; -}; +/* struct ext4_fs_context is defined in ext4.h */ static void ext4_fc_free(struct fs_context *fc) { -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Set up essential super_block fields after allocation: - Link bd_super back to super_block - Set initial blocksize to 1024 (will be updated by ext4_fill_super) - Clear flags for read-write mount - Clear s_fs_info (will be set by ext4_fill_super) Add the call to ext4_fill_super() which performs the actual filesystem mount. This requires making ext4_fill_super() non-static in super.c. Also fix an uninitialised variable warning in ext4_journalled_submit_inode_data_buffers(). Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 9 ++++++--- fs/ext4l/extents_status.c | 5 ++++- fs/ext4l/interface.c | 22 ++++++++++++++++------ fs/ext4l/super.c | 4 ++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 1f823ba6ea3..7c65aa8567e 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -457,8 +457,8 @@ int __ext4_xattr_set_credits(struct super_block *sb, struct inode *inode, /* Memory allocation - use linux/slab.h which is already available */ #include <linux/slab.h> -/* KMEM_CACHE macro - not in U-Boot's slab.h */ -#define KMEM_CACHE(s, flags) ((struct kmem_cache *)1) +/* KMEM_CACHE macro - use kmem_cache_create */ +#define KMEM_CACHE(s, flags) kmem_cache_create(#s, sizeof(struct s), 0, flags, NULL) /* RB tree operations - stubs */ #define rb_entry(ptr, type, member) \ @@ -650,7 +650,7 @@ struct super_block { struct rw_semaphore s_umount; struct sb_writers s_writers; struct block_device *s_bdev; - const char *s_id; + char s_id[32]; struct dentry *s_root; uuid_t s_uuid; struct file_system_type *s_type; @@ -2026,6 +2026,9 @@ struct fs_context { bool silent; }; +/* ext4 superblock initialisation */ +int ext4_fill_super(struct super_block *sb, struct fs_context *fc); + /* fs_parameter stubs */ struct fs_parameter { const char *key; diff --git a/fs/ext4l/extents_status.c b/fs/ext4l/extents_status.c index 485f3a2f5cc..a3ab26624e7 100644 --- a/fs/ext4l/extents_status.c +++ b/fs/ext4l/extents_status.c @@ -197,7 +197,10 @@ int __init ext4_init_es(void) void ext4_exit_es(void) { - kmem_cache_destroy(ext4_es_cachep); + if (ext4_es_cachep) { + kmem_cache_destroy(ext4_es_cachep); + ext4_es_cachep = NULL; + } } void ext4_es_init_tree(struct ext4_es_tree *tree) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 20fdf3c908c..4652f8c835f 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -76,6 +76,15 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, goto err_free_bdev; } + /* Initialise super_block fields */ + sb->s_bdev->bd_super = sb; + sb->s_blocksize = 1024; + sb->s_blocksize_bits = 10; + snprintf(sb->s_id, sizeof(sb->s_id), "ext4l_mmc%d", + fs_dev_desc->devnum); + sb->s_flags = 0; + sb->s_fs_info = NULL; + /* Allocate fs_context */ fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL); if (!fc) { @@ -93,6 +102,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, /* Initialise fs_context fields */ fc->fs_private = ctx; fc->sb_flags |= SB_I_VERSION; + fc->root = (struct dentry *)sb; /* Hack: store sb for ext4_fill_super */ buf = malloc(BLOCK_SIZE + 512); if (!buf) { @@ -119,17 +129,17 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, goto err_free_buf; } + free(buf); + /* Save device info for later operations */ ext4l_dev_desc = fs_dev_desc; if (fs_partition) memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); - free(buf); - kfree(ctx); - kfree(fc); - kfree(sb->s_bdev->bd_mapping); - kfree(sb->s_bdev); - kfree(sb); + /* Mount the filesystem */ + ret = ext4_fill_super(sb, fc); + if (ret) + goto err_free_ctx; return 0; diff --git a/fs/ext4l/super.c b/fs/ext4l/super.c index af5b9b1986d..f986ca6e0b6 100644 --- a/fs/ext4l/super.c +++ b/fs/ext4l/super.c @@ -526,7 +526,7 @@ static int ext4_journalled_submit_inode_data_buffers(struct jbd2_inode *jinode) .range_end = jinode->i_dirty_end, }; struct folio *folio = NULL; - int error; + int error = 0; /* * writeback_iter() already checks for dirty pages and calls @@ -5654,7 +5654,7 @@ out_fail: return err; } -static int ext4_fill_super(struct super_block *sb, struct fs_context *fc) +int ext4_fill_super(struct super_block *sb, struct fs_context *fc) { struct ext4_fs_context *ctx = fc->fs_private; struct ext4_sb_info *sbi; -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add support.c with buffer_head I/O infrastructure for ext4l: - Buffer cache for caching buffer_heads across lookups - Buffer allocation/free functions - Block I/O functions (sb_getblk, sb_bread, brelse, submit_bh, bh_read) This keeps interface.c focused on the U-Boot filesystem layer interface. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/Makefile | 2 +- fs/ext4l/ext4_uboot.h | 36 ++- fs/ext4l/interface.c | 60 ++++- fs/ext4l/stub.c | 18 +- fs/ext4l/support.c | 428 ++++++++++++++++++++++++++++++++++++ include/linux/buffer_head.h | 8 +- 6 files changed, 528 insertions(+), 24 deletions(-) create mode 100644 fs/ext4l/support.c diff --git a/fs/ext4l/Makefile b/fs/ext4l/Makefile index e99b900ca6d..7bb843c3fad 100644 --- a/fs/ext4l/Makefile +++ b/fs/ext4l/Makefile @@ -3,7 +3,7 @@ # Makefile for the ext4l filesystem (Linux port) # -obj-y := interface.o stub.o +obj-y := interface.o support.o stub.o obj-y += balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \ extents_status.o file.o fsmap.o fsync.o hash.o ialloc.o \ diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 7c65aa8567e..dfa362389a8 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -313,9 +313,9 @@ extern struct user_namespace init_user_ns; #define __bforget(bh) do { } while (0) #define mark_buffer_dirty_inode(bh, i) do { } while (0) #define mark_buffer_dirty(bh) do { } while (0) -#define lock_buffer(bh) do { } while (0) -#define unlock_buffer(bh) do { } while (0) -#define sb_getblk(sb, block) ((struct buffer_head *)NULL) +#define lock_buffer(bh) set_buffer_locked(bh) +#define unlock_buffer(bh) clear_buffer_locked(bh) +struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); #define test_clear_buffer_dirty(bh) ({ (void)(bh); 0; }) #define wait_on_bit_io(addr, bit, mode) do { (void)(addr); (void)(bit); (void)(mode); } while (0) @@ -1026,7 +1026,7 @@ static inline unsigned long memweight(const void *ptr, size_t bytes) #define rwsem_is_locked(sem) (1) /* Buffer operations */ -#define sb_getblk_gfp(sb, blk, gfp) ((struct buffer_head *)NULL) +#define sb_getblk_gfp(sb, blk, gfp) sb_getblk((sb), (blk)) #define bh_uptodate_or_lock(bh) (1) /* ext4_read_bh is stubbed in interface.c */ @@ -1870,7 +1870,14 @@ struct file_system_type { #define FS_ALLOW_IDMAP 32 /* Buffer read sync */ -#define end_buffer_read_sync NULL +static inline void end_buffer_read_sync(struct buffer_head *bh, int uptodate) +{ + if (uptodate) + set_buffer_uptodate(bh); + else + clear_buffer_uptodate(bh); + unlock_buffer(bh); +} #define REQ_OP_READ 0 /* Superblock flags */ @@ -2377,7 +2384,7 @@ void dquot_free_block(struct inode *inode, loff_t nr); /* Block device file operations - stubs */ #define set_blocksize(f, size) ({ (void)(f); (void)(size); 0; }) -#define __bread(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; }) +struct buffer_head *__bread(struct block_device *bdev, sector_t block, unsigned size); /* Trace stubs for super.c */ #define trace_ext4_sync_fs(sb, wait) do { (void)(sb); (void)(wait); } while (0) @@ -2823,7 +2830,16 @@ struct wait_bit_entry { #define filemap_fdatawait_range_keep_errors(m, s, e) \ ({ (void)(m); (void)(s); (void)(e); 0; }) #define crc32_be(crc, p, len) crc32(crc, p, len) -#define free_buffer_head(bh) kfree(bh) +void free_buffer_head(struct buffer_head *bh); + +/* ext4l support functions (support.c) */ +void bh_cache_clear(void); +int ext4l_read_block(sector_t block, size_t size, void *buffer); + +/* ext4l interface functions (interface.c) */ +struct blk_desc *ext4l_get_blk_dev(void); +struct disk_partition *ext4l_get_partition(void); + #define sb_is_blkdev_sb(sb) ({ (void)(sb); 0; }) /* DEFINE_WAIT stub - creates a wait queue entry */ @@ -2850,7 +2866,7 @@ struct wait_bit_entry { #define trace_jbd2_lock_buffer_stall(...) do { } while (0) /* JBD2 journal.c stubs */ -#define alloc_buffer_head(gfp) ((struct buffer_head *)kzalloc(sizeof(struct buffer_head), gfp)) +struct buffer_head *alloc_buffer_head(gfp_t gfp_mask); #define __getblk(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; }) #define bmap(inode, block) ({ (void)(inode); (void)(block); 0; }) #define trace_jbd2_update_log_tail(j, t, b, f) \ @@ -2897,8 +2913,8 @@ loff_t seq_lseek(struct file *f, loff_t o, int w); do { (void)(j); (void)(f); } while (0) /* Block device operations for journal.c */ -#define bh_read(bh, flags) ({ (void)(bh); (void)(flags); 0; }) -#define bh_read_nowait(bh, flags) do { (void)(bh); (void)(flags); } while (0) +int bh_read(struct buffer_head *bh, int flags); +#define bh_read_nowait(bh, flags) bh_read(bh, flags) #define bh_readahead_batch(n, bhs, f) do { (void)(n); (void)(bhs); (void)(f); } while (0) #define truncate_inode_pages_range(m, s, e) \ do { (void)(m); (void)(s); (void)(e); } while (0) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 4652f8c835f..e1458ea6bfe 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -5,13 +5,13 @@ * Copyright 2025 Canonical Ltd * Written by Simon Glass <simon.glass@canonical.com> * - * This provides the minimal interface between U-Boot and the ext4l driver. + * This provides the interface between U-Boot's filesystem layer and + * the ext4l driver. */ #include <blk.h> #include <part.h> #include <malloc.h> -#include <asm/byteorder.h> #include <linux/errno.h> #include <linux/jbd2.h> #include <linux/types.h> @@ -23,6 +23,58 @@ static struct blk_desc *ext4l_dev_desc; static struct disk_partition ext4l_part; +/* Global block device tracking for buffer I/O */ +static struct blk_desc *ext4l_blk_dev; +static struct disk_partition ext4l_partition; +static int ext4l_mounted; + +/** + * ext4l_get_blk_dev() - Get the current block device + * Return: Block device descriptor or NULL if not mounted + */ +struct blk_desc *ext4l_get_blk_dev(void) +{ + if (!ext4l_mounted) + return NULL; + return ext4l_blk_dev; +} + +/** + * ext4l_get_partition() - Get the current partition info + * Return: Partition info pointer + */ +struct disk_partition *ext4l_get_partition(void) +{ + return &ext4l_partition; +} + +/** + * ext4l_set_blk_dev() - Set the block device for ext4l operations + * @blk_dev: Block device descriptor + * @partition: Partition info (can be NULL for whole disk) + */ +void ext4l_set_blk_dev(struct blk_desc *blk_dev, struct disk_partition *partition) +{ + ext4l_blk_dev = blk_dev; + if (partition) + memcpy(&ext4l_partition, partition, sizeof(struct disk_partition)); + else + memset(&ext4l_partition, 0, sizeof(struct disk_partition)); + ext4l_mounted = 1; +} + +/** + * ext4l_clear_blk_dev() - Clear block device (unmount) + */ +void ext4l_clear_blk_dev(void) +{ + /* Clear buffer cache before unmounting */ + bh_cache_clear(); + + ext4l_blk_dev = NULL; + ext4l_mounted = 0; +} + int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { @@ -37,6 +89,9 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (!fs_dev_desc) return -EINVAL; + /* Set up block device for buffer I/O */ + ext4l_set_blk_dev(fs_dev_desc, fs_partition); + /* Initialise journal subsystem if enabled */ if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) { ret = jbd2_journal_init_global(); @@ -162,5 +217,6 @@ err_exit_es: void ext4l_close(void) { + ext4l_clear_blk_dev(); ext4l_dev_desc = NULL; } diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index e13685bf8fa..eff54bb540c 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -309,20 +309,14 @@ void *bdev_file_open_by_dev(dev_t dev, int flags, void *holder, return ERR_PTR(-ENODEV); } -struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block, - unsigned int size, gfp_t gfp) -{ - return NULL; -} +/* bdev_getblk implemented in interface.c */ int trylock_buffer(struct buffer_head *bh) { return 1; } -void submit_bh(int op, struct buffer_head *bh) -{ -} +/* submit_bh implemented in interface.c */ /* NFS export stubs */ struct dentry *generic_fh_to_parent(struct super_block *sb, struct fid *fid, @@ -519,6 +513,14 @@ void fs_put_dax(void *dax, void *holder) /* Block size */ int sb_set_blocksize(struct super_block *sb, int size) { + /* Validate block size */ + if (size != 1024 && size != 2048 && size != 4096) + return 0; + + /* Update superblock fields */ + sb->s_blocksize = size; + sb->s_blocksize_bits = ffs(size) - 1; + return size; } diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c new file mode 100644 index 00000000000..0765065a99f --- /dev/null +++ b/fs/ext4l/support.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Internal support functions for ext4l filesystem + * + * Copyright 2025 Canonical Ltd + * Written by Simon Glass <simon.glass@canonical.com> + * + * This provides internal support functions for the ext4l driver, + * including buffer_head I/O and buffer cache. + */ + +#include <blk.h> +#include <part.h> +#include <malloc.h> +#include <linux/errno.h> +#include <linux/types.h> + +#include "ext4_uboot.h" +#include "ext4.h" + +/* + * Buffer cache implementation + * + * Linux's sb_getblk() returns the same buffer_head for the same block number, + * allowing flags like BH_Verified, BH_Uptodate, etc. to persist across calls. + * This is critical for ext4's bitmap validation which sets buffer_verified() + * and expects it to remain set on subsequent lookups. + */ +#define BH_CACHE_BITS 8 +#define BH_CACHE_SIZE (1 << BH_CACHE_BITS) +#define BH_CACHE_MASK (BH_CACHE_SIZE - 1) + +struct bh_cache_entry { + struct buffer_head *bh; + struct bh_cache_entry *next; +}; + +static struct bh_cache_entry *bh_cache[BH_CACHE_SIZE]; + +static inline unsigned int bh_cache_hash(sector_t block) +{ + return (unsigned int)(block & BH_CACHE_MASK); +} + +/** + * bh_cache_lookup() - Look up a buffer in the cache + * @block: Block number to look up + * @size: Expected block size + * Return: Buffer head if found with matching size, NULL otherwise + */ +static struct buffer_head *bh_cache_lookup(sector_t block, size_t size) +{ + unsigned int hash = bh_cache_hash(block); + struct bh_cache_entry *entry; + + for (entry = bh_cache[hash]; entry; entry = entry->next) { + if (entry->bh && entry->bh->b_blocknr == block && + entry->bh->b_size == size) { + atomic_inc(&entry->bh->b_count); + return entry->bh; + } + } + return NULL; +} + +/** + * bh_cache_insert() - Insert a buffer into the cache + * @bh: Buffer head to insert + */ +static void bh_cache_insert(struct buffer_head *bh) +{ + unsigned int hash = bh_cache_hash(bh->b_blocknr); + struct bh_cache_entry *entry; + + /* Check if already in cache */ + for (entry = bh_cache[hash]; entry; entry = entry->next) { + if (entry->bh && entry->bh->b_blocknr == bh->b_blocknr) + return; /* Already cached */ + } + + entry = malloc(sizeof(struct bh_cache_entry)); + if (!entry) + return; /* Silently fail - cache is optional */ + + entry->bh = bh; + entry->next = bh_cache[hash]; + bh_cache[hash] = entry; + + /* Add a reference to keep the buffer alive in cache */ + atomic_inc(&bh->b_count); +} + +/** + * bh_cache_clear() - Clear the entire buffer cache + * + * Called on unmount to free all cached buffers. + */ +void bh_cache_clear(void) +{ + int i; + struct bh_cache_entry *entry, *next; + + for (i = 0; i < BH_CACHE_SIZE; i++) { + for (entry = bh_cache[i]; entry; entry = next) { + next = entry->next; + if (entry->bh) { + /* Release the cache's reference */ + if (atomic_dec_and_test(&entry->bh->b_count)) + free_buffer_head(entry->bh); + } + free(entry); + } + bh_cache[i] = NULL; + } +} + +/** + * alloc_buffer_head() - Allocate a buffer_head structure + * @gfp_mask: Allocation flags (ignored in U-Boot) + * Return: Pointer to buffer_head or NULL on error + */ +struct buffer_head *alloc_buffer_head(gfp_t gfp_mask) +{ + struct buffer_head *bh; + + bh = malloc(sizeof(struct buffer_head)); + if (!bh) + return NULL; + + memset(bh, 0, sizeof(struct buffer_head)); + + /* Note: b_data will be allocated when needed by read functions */ + atomic_set(&bh->b_count, 1); + + return bh; +} + +/** + * alloc_buffer_head_with_data() - Allocate a buffer_head with data buffer + * @size: Size of the data buffer to allocate + * Return: Pointer to buffer_head or NULL on error + */ +static struct buffer_head *alloc_buffer_head_with_data(size_t size) +{ + struct buffer_head *bh; + + bh = malloc(sizeof(struct buffer_head)); + if (!bh) + return NULL; + + memset(bh, 0, sizeof(struct buffer_head)); + + bh->b_data = malloc(size); + if (!bh->b_data) { + free(bh); + return NULL; + } + + bh->b_size = size; + /* Allocate a folio for kmap_local_folio() to work */ + bh->b_folio = malloc(sizeof(struct folio)); + if (bh->b_folio) { + memset(bh->b_folio, 0, sizeof(struct folio)); + bh->b_folio->data = bh->b_data; + } + atomic_set(&bh->b_count, 1); + /* Mark that this buffer owns its b_data and should free it */ + set_bit(BH_OwnsData, &bh->b_state); + + return bh; +} + +/** + * free_buffer_head() - Free a buffer_head + * @bh: Buffer head to free + * + * Only free b_data if BH_OwnsData is set. Shadow buffers created by + * jbd2_journal_write_metadata_buffer() share b_data with the original + * buffer and should not free it. + */ +void free_buffer_head(struct buffer_head *bh) +{ + if (!bh) + return; + + /* Only free b_data if this buffer owns it */ + if (bh->b_data && test_bit(BH_OwnsData, &bh->b_state)) + free(bh->b_data); + if (bh->b_folio) + free(bh->b_folio); + free(bh); +} + +/** + * ext4l_read_block() - Read a block from the block device + * @block: Block number (filesystem block, not sector) + * @size: Block size in bytes + * @buffer: Destination buffer + * Return: 0 on success, negative on error + */ +int ext4l_read_block(sector_t block, size_t size, void *buffer) +{ + struct blk_desc *blk_dev; + struct disk_partition *part; + lbaint_t sector; + lbaint_t sector_count; + unsigned long n; + + blk_dev = ext4l_get_blk_dev(); + part = ext4l_get_partition(); + if (!blk_dev) + return -EIO; + + /* Convert block to sector */ + sector = (block * size) / blk_dev->blksz + part->start; + sector_count = size / blk_dev->blksz; + + if (sector_count == 0) + sector_count = 1; + + n = blk_dread(blk_dev, sector, sector_count, buffer); + if (n != sector_count) + return -EIO; + + return 0; +} + +/** + * sb_getblk() - Get a buffer, using cache if available + * @sb: Super block + * @block: Block number + * Return: Buffer head or NULL on error + */ +struct buffer_head *sb_getblk(struct super_block *sb, sector_t block) +{ + struct buffer_head *bh; + + if (!sb) + return NULL; + + /* Check cache first - must match block number AND size */ + bh = bh_cache_lookup(block, sb->s_blocksize); + if (bh) + return bh; + + /* Allocate new buffer */ + bh = alloc_buffer_head_with_data(sb->s_blocksize); + if (!bh) + return NULL; + + bh->b_blocknr = block; + bh->b_bdev = sb->s_bdev; + bh->b_size = sb->s_blocksize; + + /* Don't read - just allocate with zeroed data */ + memset(bh->b_data, '\0', bh->b_size); + + /* Add to cache */ + bh_cache_insert(bh); + + return bh; +} + +/** + * sb_bread() - Read a block via super_block + * @sb: Super block + * @block: Block number to read + * Return: Buffer head or NULL on error + */ +struct buffer_head *sb_bread(struct super_block *sb, sector_t block) +{ + struct buffer_head *bh; + int ret; + + if (!sb) + return NULL; + + bh = sb_getblk(sb, block); + if (!bh) + return NULL; + + /* If buffer is already up-to-date, return it without re-reading */ + if (buffer_uptodate(bh)) + return bh; + + bh->b_blocknr = block; + bh->b_bdev = sb->s_bdev; + bh->b_size = sb->s_blocksize; + + ret = ext4l_read_block(block, sb->s_blocksize, bh->b_data); + if (ret) { + brelse(bh); + return NULL; + } + + /* Mark buffer as up-to-date */ + set_buffer_uptodate(bh); + + return bh; +} + +/** + * brelse() - Release a buffer_head + * @bh: Buffer head to release + */ +void brelse(struct buffer_head *bh) +{ + if (!bh) + return; + + if (atomic_dec_and_test(&bh->b_count)) + free_buffer_head(bh); +} + +/** + * __brelse() - Release a buffer_head (alternate API) + * @bh: Buffer head to release + */ +void __brelse(struct buffer_head *bh) +{ + brelse(bh); +} + +/** + * bdev_getblk() - Get buffer via block_device + * @bdev: Block device + * @block: Block number + * @size: Block size + * @gfp: Allocation flags + * Return: Buffer head or NULL + */ +struct buffer_head *bdev_getblk(struct block_device *bdev, sector_t block, + unsigned size, gfp_t gfp) +{ + struct buffer_head *bh; + + /* Check cache first - must match block number AND size */ + bh = bh_cache_lookup(block, size); + if (bh) + return bh; + + bh = alloc_buffer_head_with_data(size); + if (!bh) + return NULL; + + bh->b_blocknr = block; + bh->b_bdev = bdev; + bh->b_size = size; + + /* Don't read - just allocate with zeroed data */ + memset(bh->b_data, 0, bh->b_size); + + /* Add to cache */ + bh_cache_insert(bh); + + return bh; +} + +/** + * __bread() - Read a block via block_device + * @bdev: Block device + * @block: Block number to read + * @size: Block size + * Return: Buffer head or NULL on error + */ +struct buffer_head *__bread(struct block_device *bdev, sector_t block, + unsigned size) +{ + struct buffer_head *bh; + int ret; + + bh = alloc_buffer_head_with_data(size); + if (!bh) + return NULL; + + bh->b_blocknr = block; + bh->b_bdev = bdev; + bh->b_size = size; + + ret = ext4l_read_block(block, size, bh->b_data); + if (ret) { + free_buffer_head(bh); + return NULL; + } + + /* Mark buffer as up-to-date */ + set_bit(BH_Uptodate, &bh->b_state); + + return bh; +} + +/** + * submit_bh() - Submit a buffer_head for I/O + * @op: Operation (REQ_OP_READ, REQ_OP_WRITE, etc.) + * @bh: Buffer head to submit + */ +void submit_bh(int op, struct buffer_head *bh) +{ + int ret; + int op_type = op & 0xff; /* Mask out flags, keep operation type */ + + if (op_type == REQ_OP_READ) { + ret = ext4l_read_block(bh->b_blocknr, bh->b_size, bh->b_data); + if (ret) { + clear_buffer_uptodate(bh); + return; + } + set_buffer_uptodate(bh); + } else if (op_type == REQ_OP_WRITE) { + /* Write support not implemented yet */ + clear_buffer_uptodate(bh); + } +} + +/** + * bh_read() - Read a buffer_head from disk + * @bh: Buffer head to read + * @flags: Read flags + * Return: 0 on success, negative on error + */ +int bh_read(struct buffer_head *bh, int flags) +{ + if (!bh || !bh->b_data) + return -EINVAL; + + submit_bh(REQ_OP_READ | flags, bh); + return buffer_uptodate(bh) ? 0 : -EIO; +} diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index b7596a74108..0f8f5b6caf1 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -40,6 +40,8 @@ enum bh_state_bits { BH_PrivateStart,/* not a state bit, but the first bit available * for private allocation by other entities */ + /* U-Boot specific: marks buffer owns b_data and should free it */ + BH_OwnsData = BH_PrivateStart, }; #define MAX_BUF_PER_PAGE (PAGE_SIZE / 512) @@ -176,8 +178,8 @@ static inline void put_bh(struct buffer_head *bh) atomic_dec(&bh->b_count); } -/* Stubs for U-Boot */ -#define brelse(bh) do { if (bh) put_bh(bh); } while (0) -#define __brelse(bh) do { put_bh(bh); } while (0) +/* Buffer release functions - implemented in ext4l/interface.c */ +void brelse(struct buffer_head *bh); +void __brelse(struct buffer_head *bh); #endif /* _LINUX_BUFFER_HEAD_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a Kconfig option to control ext4_msg() output. By default, mount messages and other informational output are suppressed to reduce console noise. Enable CONFIG_EXT4L_DEBUG to restore these messages for debugging. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/Kconfig | 9 +++++++++ fs/ext4l/super.c | 3 +++ 2 files changed, 12 insertions(+) diff --git a/fs/ext4l/Kconfig b/fs/ext4l/Kconfig index f1fbe3b1000..998f0f45474 100644 --- a/fs/ext4l/Kconfig +++ b/fs/ext4l/Kconfig @@ -22,3 +22,12 @@ config EXT4_JOURNAL before committing them to the filesystem. If unsure, say Y. + +config EXT4L_DEBUG + bool "Enable ext4l debug messages" + depends on FS_EXT4L + help + Enable debug and informational messages from the ext4l filesystem. + This includes mount messages and other ext4_msg() output. + + If unsure, say N. diff --git a/fs/ext4l/super.c b/fs/ext4l/super.c index f986ca6e0b6..47e0b2c92e8 100644 --- a/fs/ext4l/super.c +++ b/fs/ext4l/super.c @@ -946,6 +946,9 @@ void __ext4_msg(struct super_block *sb, struct va_format vaf; va_list args; + if (!IS_ENABLED(CONFIG_EXT4L_DEBUG)) + return; + if (sb) { atomic_inc(&EXT4_SB(sb)->s_msg_count); if (!___ratelimit(&(EXT4_SB(sb)->s_msg_ratelimit_state), -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Replace broken rbtree stubs with the real implementation from lib/rbtree.c. The previous stubs had critical bugs: - rb_link_node() only did *(rb_link) = node without initializing rb_left and rb_right to NULL, causing crashes when traversing the extent status tree - rb_next/rb_prev were wrong (just returned right/left child) - rb_insert_color was a no-op, breaking tree balancing Include <linux/rbtree.h> and remove the conflicting struct definitions and broken operation macros. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index dfa362389a8..96b1c06a05b 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -34,6 +34,7 @@ #include <linux/fs.h> #include <linux/iomap.h> #include <linux/seq_file.h> +#include <linux/rbtree.h> /* Real rbtree implementation */ /* * Override no_printk to avoid format warnings in disabled debug prints. @@ -87,18 +88,7 @@ typedef struct { atomic_t refs; } refcount_t; /* rwlock_t and read_lock/read_unlock are now in linux/spinlock.h */ #include <linux/spinlock.h> -/* RB tree types - stubs */ -struct rb_node { - unsigned long __rb_parent_color; - struct rb_node *rb_right; - struct rb_node *rb_left; -}; - -struct rb_root { - struct rb_node *rb_node; -}; - -#define RB_ROOT (struct rb_root) { NULL, } +/* RB tree types - from <linux/rbtree.h> included above */ /* percpu_counter - use Linux header */ #include <linux/percpu_counter.h> @@ -460,18 +450,13 @@ int __ext4_xattr_set_credits(struct super_block *sb, struct inode *inode, /* KMEM_CACHE macro - use kmem_cache_create */ #define KMEM_CACHE(s, flags) kmem_cache_create(#s, sizeof(struct s), 0, flags, NULL) -/* RB tree operations - stubs */ -#define rb_entry(ptr, type, member) \ - container_of(ptr, type, member) -#define rb_first(root) ((root)->rb_node) -#define rb_next(node) ((node)->rb_right) -#define rb_prev(node) ((node)->rb_left) -#define rb_insert_color(node, root) do { } while (0) -#define rb_erase(node, root) do { } while (0) -#define rb_link_node(node, parent, rb_link) do { *(rb_link) = (node); } while (0) -#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) -#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ - for (pos = NULL, (void)(n); pos != NULL; ) +/* + * RB tree operations - use real rbtree implementation from lib/rbtree.c + * and include/linux/rbtree.h. rb_entry, rb_first, rb_next, rb_prev, + * rb_insert_color, rb_erase, rb_link_node, RB_EMPTY_ROOT, and + * rbtree_postorder_for_each_entry_safe are all provided by the real + * implementation - do not stub them! + */ /* RCU barrier - stub */ #define rcu_barrier() do { } while (0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add ext4l_crc32c() which uses the Castagnoli polynomial (0x82F63B78) required for ext4 checksums. The table is initialised on first mount. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 2 ++ fs/ext4l/interface.c | 2 ++ fs/ext4l/support.c | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 96b1c06a05b..8b70bbcb75c 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -35,6 +35,7 @@ #include <linux/iomap.h> #include <linux/seq_file.h> #include <linux/rbtree.h> /* Real rbtree implementation */ +#include <u-boot/crc.h> /* For crc32() used by crc32_be */ /* * Override no_printk to avoid format warnings in disabled debug prints. @@ -2818,6 +2819,7 @@ struct wait_bit_entry { void free_buffer_head(struct buffer_head *bh); /* ext4l support functions (support.c) */ +void ext4l_crc32c_init(void); void bh_cache_clear(void); int ext4l_read_block(sector_t block, size_t size, void *buffer); diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index e1458ea6bfe..141afc42c17 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -92,6 +92,8 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, /* Set up block device for buffer I/O */ ext4l_set_blk_dev(fs_dev_desc, fs_partition); + ext4l_crc32c_init(); + /* Initialise journal subsystem if enabled */ if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) { ret = jbd2_journal_init_global(); diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index 0765065a99f..2040ad5f480 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -12,12 +12,33 @@ #include <blk.h> #include <part.h> #include <malloc.h> +#include <u-boot/crc.h> #include <linux/errno.h> #include <linux/types.h> #include "ext4_uboot.h" #include "ext4.h" +/* + * CRC32C support - uses Castagnoli polynomial 0x82F63B78 + * Table is initialised on first mount + */ +static u32 ext4l_crc32c_table[256]; +static bool ext4l_crc32c_inited; + +void ext4l_crc32c_init(void) +{ + if (!ext4l_crc32c_inited) { + crc32c_init(ext4l_crc32c_table, 0x82F63B78); + ext4l_crc32c_inited = true; + } +} + +u32 ext4l_crc32c(u32 crc, const void *address, unsigned int length) +{ + return crc32c_cal(crc, address, length, ext4l_crc32c_table); +} + /* * Buffer cache implementation * -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update include/linux/crc32c.h to map crc32c() and crc32c_le() macros to ext4l_crc32c(), which uses the correct Castagnoli polynomial (0x82F63B78) required for ext4 checksums. This avoids conflicts with other filesystems like btrfs that have their own crc32c() implementation. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/linux/crc32c.h | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/include/linux/crc32c.h b/include/linux/crc32c.h index 44e64d3a3df..53498ce4bfb 100644 --- a/include/linux/crc32c.h +++ b/include/linux/crc32c.h @@ -1,21 +1,22 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * CRC32C definitions + * CRC32C definitions for ext4l * - * Minimal version for U-Boot ext4l - based on Linux 6.18 + * CRC32C (Castagnoli) uses polynomial 0x1EDC6F41 (bit-reflected: 0x82F63B78) + * This is different from standard CRC32 (IEEE 802.3) which uses 0x04C11DB7. + * + * ext4l provides its own implementation to avoid conflicts with other + * filesystems (e.g., btrfs) that have their own crc32c(). */ #ifndef _LINUX_CRC32C_H #define _LINUX_CRC32C_H +#include <asm/byteorder.h> #include <linux/types.h> -#include <u-boot/crc.h> -/* Use U-Boot's CRC32 implementation */ -static inline u32 crc32c(u32 crc, const void *address, unsigned int length) -{ - return crc32(crc, address, length); -} +u32 ext4l_crc32c(u32 crc, const void *address, unsigned int length); -#define crc32c_le(crc, p, len) crc32c(crc, p, len) +#define crc32c(crc, p, len) ext4l_crc32c(crc, p, len) +#define crc32c_le(crc, p, len) ext4l_crc32c(crc, p, len) #endif /* _LINUX_CRC32C_H */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Set sb_rdonly() to return 1 since U-Boot currently only supports read-only filesystem access. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 8b70bbcb75c..5beedb4bb3d 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -289,8 +289,8 @@ extern struct user_namespace init_user_ns; /* might_sleep - stub */ #define might_sleep() do { } while (0) -/* sb_rdonly - stub */ -#define sb_rdonly(sb) (0) +/* sb_rdonly - for now U-Boot mounts filesystems read-only */ +#define sb_rdonly(sb) 1 /* Trace stubs */ #define trace_ext4_journal_start_inode(...) do { } while (0) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add inode allocation and block mapping functions: - iget_locked() - allocate inode by number - new_inode() - allocate new empty inode - ext4_uboot_bmap() - map logical to physical block - bmap() - VFS interface for block mapping Also add i_count reference counter to struct inode. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- fs/ext4l/ext4_uboot.h | 9 ++-- fs/ext4l/support.c | 99 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 5beedb4bb3d..4c4dd2ca559 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -377,8 +377,8 @@ struct buffer_head *sb_getblk(struct super_block *sb, sector_t block); #define ktime_get_real_seconds() (0) #define time_before32(a, b) (0) -/* Inode operations - stubs */ -#define new_inode(sb) ((struct inode *)NULL) +/* Inode operations - iget_locked and new_inode are in interface.c */ +extern struct inode *new_inode(struct super_block *sb); #define i_uid_write(inode, uid) do { } while (0) #define i_gid_write(inode, gid) do { } while (0) #define inode_fsuid_set(inode, idmap) do { } while (0) @@ -820,6 +820,7 @@ struct inode { const struct inode_operations *i_op; const struct file_operations *i_fop; atomic_t i_writecount; /* Count of writers */ + atomic_t i_count; /* Reference count */ struct rw_semaphore i_rwsem; /* inode lock */ const char *i_link; /* Symlink target for fast symlinks */ unsigned short i_write_hint; /* Write life time hint */ @@ -1615,7 +1616,7 @@ static inline unsigned int i_gid_read(const struct inode *inode) #define fs_high2lowgid(gid) ((gid) & 0xFFFF) /* Inode allocation/state operations */ -#define iget_locked(sb, ino) ((struct inode *)NULL) +extern struct inode *iget_locked(struct super_block *sb, unsigned long ino); #define set_nlink(i, n) do { (i)->i_nlink = (n); } while (0) #define inc_nlink(i) do { (i)->i_nlink++; } while (0) #define drop_nlink(i) do { (i)->i_nlink--; } while (0) @@ -2855,7 +2856,7 @@ struct disk_partition *ext4l_get_partition(void); /* JBD2 journal.c stubs */ struct buffer_head *alloc_buffer_head(gfp_t gfp_mask); #define __getblk(bdev, block, size) ({ (void)(bdev); (void)(block); (void)(size); (struct buffer_head *)NULL; }) -#define bmap(inode, block) ({ (void)(inode); (void)(block); 0; }) +int bmap(struct inode *inode, sector_t *block); #define trace_jbd2_update_log_tail(j, t, b, f) \ do { (void)(j); (void)(t); (void)(b); (void)(f); } while (0) diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index 2040ad5f480..d1bc32ae8d6 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -39,6 +39,105 @@ u32 ext4l_crc32c(u32 crc, const void *address, unsigned int length) return crc32c_cal(crc, address, length, ext4l_crc32c_table); } +/* + * iget_locked - allocate a new inode + * @sb: super block of filesystem + * @ino: inode number to allocate + * + * U-Boot implementation: allocates ext4_inode_info and returns the embedded + * vfs_inode. In Linux, this would look up the inode in a hash table first. + * Since U-Boot is single-threaded and doesn't cache inodes, we always allocate. + */ +struct inode *iget_locked(struct super_block *sb, unsigned long ino) +{ + struct ext4_inode_info *ei; + struct inode *inode; + + ei = kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); + if (!ei) + return NULL; + + /* Get pointer to the embedded vfs_inode using offsetof */ + inode = (struct inode *)((char *)ei + + offsetof(struct ext4_inode_info, vfs_inode)); + inode->i_sb = sb; + inode->i_blkbits = sb->s_blocksize_bits; + inode->i_ino = ino; + inode->i_state = I_NEW; + inode->i_count.counter = 1; + inode->i_mapping = &inode->i_data; + inode->i_data.host = inode; + INIT_LIST_HEAD(&ei->i_es_list); + + return inode; +} + +/* + * new_inode - allocate a new empty inode + * @sb: super block of filesystem + * + * U-Boot implementation: allocates ext4_inode_info for a new inode that + * will be initialised by the caller (e.g., for creating new files). + */ +struct inode *new_inode(struct super_block *sb) +{ + struct ext4_inode_info *ei; + struct inode *inode; + + ei = kzalloc(sizeof(struct ext4_inode_info), GFP_KERNEL); + if (!ei) + return NULL; + + inode = &ei->vfs_inode; + inode->i_sb = sb; + inode->i_blkbits = sb->s_blocksize_bits; + inode->i_nlink = 1; + inode->i_count.counter = 1; + inode->i_mapping = &inode->i_data; + inode->i_data.host = inode; + INIT_LIST_HEAD(&ei->i_es_list); + + return inode; +} + +/* + * ext4_uboot_bmap - map a logical block to a physical block + * @inode: inode to map + * @block: on entry, logical block number; on exit, physical block number + * + * U-Boot implementation of bmap for ext4. Maps a logical block number + * to the corresponding physical block on disk. + */ +int ext4_uboot_bmap(struct inode *inode, sector_t *block) +{ + struct ext4_map_blocks map; + int ret; + + map.m_lblk = *block; + map.m_len = 1; + map.m_flags = 0; + + ret = ext4_map_blocks(NULL, inode, &map, 0); + if (ret > 0) { + *block = map.m_pblk; + return 0; + } + + return ret < 0 ? ret : -EINVAL; +} + +/* + * bmap - map a logical block to a physical block (VFS interface) + * @inode: inode to map + * @blockp: pointer to logical block number; updated to physical block number + * + * This is the VFS bmap interface used by jbd2. + */ +int bmap(struct inode *inode, sector_t *blockp) +{ + return ext4_uboot_bmap(inode, blockp); +} + /* * Buffer cache implementation * -- 2.43.0
participants (1)
-
Simon Glass