[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
From: Simon Glass <simon.glass@canonical.com> Fix several issues preventing ext4_fill_super() from completing: - Fix shrinker_alloc() to return a dummy shrinker instead of NULL - Fix getblk_unmovable() to use sb_getblk() instead of returning NULL - Fix mb_cache_create() to allocate a cache structure - Implement d_make_root() to create root dentry for mount - Add ext4_init_system_zone() call for block validity checking - Add global task_struct for journal_info consistency - Store super_block pointer after successful mount The filesystem now mounts successfully in read-only mode. Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- .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/ext4l/ext4_uboot.h | 21 +- fs/ext4l/interface.c | 24 +- fs/ext4l/stub.c | 23 +- fs/ext4l/support.c | 7 + include/linux/types.h | 22 + 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 ++ 52 files changed, 1854 insertions(+), 334 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 tools/buildman/test_boards.py diff --git a/.pickman-history b/.pickman-history index 8c7190b8748..52d972c323d 100644 --- a/.pickman-history +++ b/.pickman-history @@ -1281,3 +1281,124 @@ The build completed successfully with no warnings or errors. --- +## 2025-12-21: us/next + +Branch: cherry-8ff96fb6d01 + +Commits: +- 8ff96fb6d01 arm: dts: k3-*-ddr: Add ss_cfg reg entry +- fed3b1eb9bf ram: k3-ddrss: Use the DDR controller BIST engine for ECC priming +- 22ce56a3ebd ram: k3-ddrss: Add k3_ddrss_ddr_bank_base_size_calc() to solve 'calculations restricted to 32 bits' issue +- 39d66893ef2 ram: k3-ddrss: Setup ECC region start and range +- 42957c3a8ba ram: k3-ddrss: Enable ECC interrupts +- 98dd3c126e3 drivers: ram: Kconfig: Add CONFIG_K3_INLINE_ECC +- 7a84969dcb8 ram: k3-ddrss: Remove 'ti,ecc-enable' support +- 01fa91bd5bb arm: mach-k3: Set NR_DRAM_BANKS to 2 +- bc07851897b board: ti: Pull redundant DDR functions to a common location and Fixup DDR size when ECC is enabled +- 08733bf3138 Merge patch series "Inline ECC Series" + +### Conversation log +The repository is clean. Now let me create and checkout the new branch based on ci/master: + +Now let me cherry-pick each commit in order. I'll start with the first one: + +Now for the merge commit - I need to use `-m 1 --allow-empty`: + +The merge commit is empty (which is expected since all changes were already applied). I need to commit it with --allow-empty to preserve the merge commit message: + +All 10 commits have been cherry-picked. Now let me verify with `git log --oneline -n 12`: + +All 10 commits are present. Now let me run the build verification: + +Excellent! The build completed successfully. Let me provide a final summary: + +## Cherry-pick Summary + +### Status: ✅ SUCCESS + +**Branch created:** `cherry-8ff96fb6d01` (based on `ci/master`) + +### Commits cherry-picked (10 total): + +| # | Original Hash | New Hash | Subject | +|---|---------------|----------|---------| +| 1 | 8ff96fb6d01 | 85552b44dad | arm: dts: k3-*-ddr: Add ss_cfg reg entry | +| 2 | fed3b1eb9bf | dd66f11e71a | ram: k3-ddrss: Use the DDR controller BIST engine for ECC priming | +| 3 | 22ce56a3ebd | 1717d982d29 | ram: k3-ddrss: Add k3_ddrss_ddr_bank_base_size_calc() to solve 'calculations restricted to 32 bits' issue | +| 4 | 39d66893ef2 | 3242b505bc9 | ram: k3-ddrss: Setup ECC region start and range | +| 5 | 42957c3a8ba | 781e6e0c5da | ram: k3-ddrss: Enable ECC interrupts | +| 6 | 98dd3c126e3 | a4fbb6d1d98 | drivers: ram: Kconfig: Add CONFIG_K3_INLINE_ECC | +| 7 | 7a84969dcb8 | dc4ba985ad9 | ram: k3-ddrss: Remove 'ti,ecc-enable' support | +| 8 | 01fa91bd5bb | 3ad4b71887d | arm: mach-k3: Set NR_DRAM_BANKS to 2 | +| 9 | bc07851897b | de285bc3a63 | board: ti: Pull redundant DDR functions to a common location and Fixup DDR size when ECC is enabled | +| 10 | 08733bf3138 | a95f049572f | Merge patch series "Inline ECC Series" (empty merge) | + +### Build Result: ✅ OK +- Board: sandbox +- Duration: 4 seconds +- No warnings or errors + +### Fix-ups Made: None +All commits applied cleanly with auto-merge. + +The branch `cherry-8ff96fb6d01` is ready for pushing. + +--- + +## 2025-12-21: us/next + +Branch: cherry-e44098d1aa5 + +Commits: +- e44098d1aa5 arm: mach-k3: am62p: Fixup CPU core, CAN-FD and Video-codec nodes in fdt +- a226a6422c9 arm: mach-k3: am62p: Fixup thermal zone critical points +- 8d05cbef73a arm: mach-k3: am62p: Fixup a53 max cpu frequency by speed-grade +- 969f43c563b arm: mach-k3: am62p: Set a53 cpu freq based on speed-grade +- e26a9ac4c69 Merge patch series "Add fdt-fixups for AM62P variants" + +### Conversation log +The repository is clean. Now let me create and checkout a new branch based on ci/master: + +Now let me cherry-pick the first commit: + +First commit successful. Now cherry-pick the second commit: + +Second commit successful. Now cherry-pick the third commit: + +Third commit successful. Now cherry-pick the fourth commit: + +Fourth commit successful. Now cherry-pick the fifth commit (the merge commit - needs -m 1 --allow-empty): + +The merge commit is empty (as expected since we already cherry-picked all the individual commits). Let me commit it with --allow-empty to preserve the merge commit message: + +All 5 commits have been cherry-picked. Let me verify with git log: + +All 5 commits are present. Now let me run the build verification: + +The build completed successfully. Let me provide the final summary: + +## Cherry-pick Summary + +### Commits Cherry-picked (5 total): +| Commit | Subject | +|--------|---------| +| 5f40cb915d8 | arm: mach-k3: am62p: Fixup CPU core, CAN-FD and Video-codec nodes in fdt | +| fec54c227bf | arm: mach-k3: am62p: Fixup thermal zone critical points | +| 02156be5da9 | arm: mach-k3: am62p: Fixup a53 max cpu frequency by speed-grade | +| f1ec3f59e22 | arm: mach-k3: am62p: Set a53 cpu freq based on speed-grade | +| 42106518f28 | Merge patch series "Add fdt-fixups for AM62P variants" | + +### Build Result: ✅ OK +- Build completed successfully with no warnings or errors +- Duration: 4 seconds +- Board: sandbox + +### Fix-ups Made: None +- All commits applied cleanly without conflicts + +### Branch Status: +- Branch `cherry-e44098d1aa5` is ready for pushing +- Currently ahead of `ci/master` by 5 commits + +--- + diff --git a/arch/arm/dts/k3-am62a-ddr.dtsi b/arch/arm/dts/k3-am62a-ddr.dtsi index 8629ea45b84..42e41f78505 100644 --- a/arch/arm/dts/k3-am62a-ddr.dtsi +++ b/arch/arm/dts/k3-am62a-ddr.dtsi @@ -4,11 +4,12 @@ */ / { - memorycontroller: memory-controller@f308000 { + memorycontroller: memory-controller@f300000 { compatible = "ti,am62a-ddrss"; reg = <0x00 0x0f308000 0x00 0x4000>, - <0x00 0x43014000 0x00 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x00 0x43014000 0x00 0x100>, + <0x00 0x0f300000 0x00 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; ti,ddr-freq1 = <DDRSS_PLL_FREQUENCY_1>; ti,ddr-freq2 = <DDRSS_PLL_FREQUENCY_2>; ti,ddr-fhs-cnt = <DDRSS_PLL_FHS_CNT>; diff --git a/arch/arm/dts/k3-j721s2-ddr.dtsi b/arch/arm/dts/k3-j721s2-ddr.dtsi index 345e2b84f9e..9764085163c 100644 --- a/arch/arm/dts/k3-j721s2-ddr.dtsi +++ b/arch/arm/dts/k3-j721s2-ddr.dtsi @@ -5,6 +5,8 @@ &main_navss { ranges = <0x00 0x00114000 0x00 0x00114000 0x00 0x00000100>, // ctrl_mmr_lpr + <0x00 0x02980000 0x00 0x02980000 0x00 0x00000200>, // ss cfg 0 + <0x00 0x029a0000 0x00 0x029a0000 0x00 0x00000200>, // ss cfg 1 <0x00 0x02990000 0x00 0x02990000 0x00 0x00004000>, // ddr0 cfg <0x00 0x029b0000 0x00 0x029b0000 0x00 0x00004000>, // ddr1 cfg <0x00 0x30000000 0x00 0x30000000 0x00 0x0c400000>; @@ -24,8 +26,9 @@ memorycontroller0: memorycontroller@2990000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x02990000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x02980000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 138 TI_SCI_PD_SHARED>, <&k3_pds 96 TI_SCI_PD_SHARED>; clocks = <&k3_clks 138 0>, <&k3_clks 43 2>; @@ -2232,8 +2235,9 @@ memorycontroller1: memorycontroller@29b0000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x029b0000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x029a0000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 139 TI_SCI_PD_SHARED>, <&k3_pds 97 TI_SCI_PD_SHARED>; clocks = <&k3_clks 139 0>, <&k3_clks 43 2>; diff --git a/arch/arm/dts/k3-j784s4-ddr.dtsi b/arch/arm/dts/k3-j784s4-ddr.dtsi index 1c3242b0870..fc74c539331 100644 --- a/arch/arm/dts/k3-j784s4-ddr.dtsi +++ b/arch/arm/dts/k3-j784s4-ddr.dtsi @@ -9,6 +9,10 @@ <0x00 0x029b0000 0x00 0x029b0000 0x00 0x00004000>, // ddr1 cfg <0x00 0x029d0000 0x00 0x029d0000 0x00 0x00004000>, // ddr2 cfg <0x00 0x029f0000 0x00 0x029f0000 0x00 0x00004000>, // ddr3 cfg + <0x00 0x02980000 0x00 0x02980000 0x00 0x00000200>, // ss cfg 0 + <0x00 0x029a0000 0x00 0x029a0000 0x00 0x00000200>, // ss cfg 1 + <0x00 0x029c0000 0x00 0x029c0000 0x00 0x00000200>, // ss cfg 2 + <0x00 0x029e0000 0x00 0x029e0000 0x00 0x00000200>, // ss cfg 3 <0x00 0x30000000 0x00 0x30000000 0x00 0x0c400000>; msmc0: msmc { @@ -26,8 +30,9 @@ memorycontroller0: memorycontroller@2990000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x02990000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x02980000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 191 TI_SCI_PD_SHARED>, <&k3_pds 131 TI_SCI_PD_SHARED>; clocks = <&k3_clks 191 1>, <&k3_clks 78 2>; @@ -2234,8 +2239,9 @@ memorycontroller1: memorycontroller@29b0000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x029b0000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x029a0000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 192 TI_SCI_PD_SHARED>, <&k3_pds 132 TI_SCI_PD_SHARED>; clocks = <&k3_clks 192 1>, <&k3_clks 78 2>; @@ -4442,8 +4448,9 @@ memorycontroller2: memorycontroller@29d0000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x029d0000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x029c0000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 193 TI_SCI_PD_SHARED>, <&k3_pds 133 TI_SCI_PD_SHARED>; clocks = <&k3_clks 193 1>, <&k3_clks 78 2>; @@ -6650,8 +6657,9 @@ memorycontroller3: memorycontroller@29f0000 { compatible = "ti,j721s2-ddrss"; reg = <0x0 0x029f0000 0x0 0x4000>, - <0x0 0x0114000 0x0 0x100>; - reg-names = "cfg", "ctrl_mmr_lp4"; + <0x0 0x0114000 0x0 0x100>, + <0x0 0x29e0000 0x0 0x200>; + reg-names = "cfg", "ctrl_mmr_lp4", "ss_cfg"; power-domains = <&k3_pds 194 TI_SCI_PD_SHARED>, <&k3_pds 139 TI_SCI_PD_SHARED>; clocks = <&k3_clks 194 1>, <&k3_clks 78 2>; diff --git a/arch/arm/mach-k3/Kconfig b/arch/arm/mach-k3/Kconfig index f3f42b39213..a3ac490f677 100644 --- a/arch/arm/mach-k3/Kconfig +++ b/arch/arm/mach-k3/Kconfig @@ -156,6 +156,9 @@ config K3_X509_SWRV help SWRV for X509 certificate used for boot images +config NR_DRAM_BANKS + default 2 + if CPU_V7R source "arch/arm/mach-k3/r5/Kconfig" endif diff --git a/arch/arm/mach-k3/Makefile b/arch/arm/mach-k3/Makefile index 8c4f6786a5b..5ce7fc62d80 100644 --- a/arch/arm/mach-k3/Makefile +++ b/arch/arm/mach-k3/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_ARM64) += arm64/ obj-$(CONFIG_CPU_V7R) += r5/ obj-$(CONFIG_OF_LIBFDT) += common_fdt.o -obj-y += common.o security.o +obj-y += common.o security.o k3-ddr.o obj-$(CONFIG_SOC_K3_AM62A7) += am62ax/ obj-$(CONFIG_SOC_K3_AM62P5) += am62px/ obj-$(CONFIG_SOC_K3_AM625) += am62x/ diff --git a/arch/arm/mach-k3/am62px/am62p5_fdt.c b/arch/arm/mach-k3/am62px/am62p5_fdt.c index 29c832d28ac..2c40fa5a594 100644 --- a/arch/arm/mach-k3/am62px/am62p5_fdt.c +++ b/arch/arm/mach-k3/am62px/am62p5_fdt.c @@ -7,8 +7,91 @@ #include "../common_fdt.h" #include <fdt_support.h> +static void fdt_fixup_cores_wdt_nodes_am62p(void *blob, int core_nr) +{ + char node_path[32]; + + if (core_nr < 1) + return; + + for (; core_nr < 4; core_nr++) { + snprintf(node_path, sizeof(node_path), "/cpus/cpu@%d", core_nr); + fdt_del_node_path(blob, node_path); + snprintf(node_path, sizeof(node_path), "/cpus/cpu-map/cluster0/core%d", core_nr); + fdt_del_node_path(blob, node_path); + snprintf(node_path, sizeof(node_path), "/bus@f0000/watchdog@e0%d0000", core_nr); + fdt_del_node_path(blob, node_path); + } +} + +static void fdt_fixup_video_codec_nodes_am62p(void *blob, bool has_video_codec) +{ + if (!has_video_codec) + fdt_del_node_path(blob, "/bus@f0000/video-codec@30210000"); +} + +static void fdt_fixup_canfd_nodes_am62p(void *blob, bool has_canfd) +{ + if (!has_canfd) { + fdt_del_node_path(blob, "/bus@f0000/can@20701000"); + fdt_del_node_path(blob, "/bus@f0000/can@20711000"); + } +} + +static int fdt_fixup_trips_node(void *blob, int zoneoffset, int maxc) +{ + int node, trip; + + node = fdt_subnode_offset(blob, zoneoffset, "trips"); + if (node < 0) + return -1; + + fdt_for_each_subnode(trip, blob, node) { + const char *type = fdt_getprop(blob, trip, "type", NULL); + + if (!type || (strncmp(type, "critical", 8) != 0)) + continue; + + if (fdt_setprop_u32(blob, trip, "temperature", 1000 * maxc) < 0) + return -1; + } + + return 0; +} + +static void fdt_fixup_thermal_zone_nodes_am62p(void *blob, int maxc) +{ + int node, zone; + + node = fdt_path_offset(blob, "/thermal-zones"); + if (node < 0) + return; + + fdt_for_each_subnode(zone, blob, node) { + if (fdt_fixup_trips_node(blob, zone, maxc) < 0) + printf("Failed to set temperature in %s critical trips\n", + fdt_get_name(blob, zone, NULL)); + } +} + +static void fdt_fixup_cpu_freq_nodes_am62p(void *blob, int max_freq) +{ + if (max_freq >= 1250000000) + return; + + if (max_freq <= 1000000000) { + fdt_del_node_path(blob, "/opp-table/opp-1250000000"); + fdt_del_node_path(blob, "/opp-table/opp-1400000000"); + } +} + int ft_system_setup(void *blob, struct bd_info *bd) { + fdt_fixup_cores_wdt_nodes_am62p(blob, k3_get_core_nr()); + fdt_fixup_video_codec_nodes_am62p(blob, k3_has_video_codec()); + fdt_fixup_canfd_nodes_am62p(blob, k3_has_canfd()); + fdt_fixup_thermal_zone_nodes_am62p(blob, k3_get_max_temp()); + fdt_fixup_cpu_freq_nodes_am62p(blob, k3_get_a53_max_frequency()); fdt_fixup_reserved(blob, "tfa", CONFIG_K3_ATF_LOAD_ADDR, 0x80000); fdt_fixup_reserved(blob, "optee", CONFIG_K3_OPTEE_LOAD_ADDR, 0x1800000); diff --git a/arch/arm/mach-k3/am62px/am62p5_init.c b/arch/arm/mach-k3/am62px/am62p5_init.c index 34ed01cd78c..74631a4ce93 100644 --- a/arch/arm/mach-k3/am62px/am62p5_init.c +++ b/arch/arm/mach-k3/am62px/am62p5_init.c @@ -11,10 +11,14 @@ #include <dm.h> #include <dm/uclass-internal.h> #include <dm/pinctrl.h> +#include <dm/ofnode.h> #include "../sysfw-loader.h" #include "../common.h" +/* TISCI DEV ID for A53 Clock */ +#define AM62PX_DEV_A53SS0_CORE_0_DEV_ID 135 + struct fwl_data cbass_main_fwls[] = { { "FSS_DAT_REG3", 7, 8 }, }; @@ -67,6 +71,62 @@ static void ctrl_mmr_unlock(void) mmr_unlock(PADCFG_MMR1_BASE, 1); } +#if CONFIG_IS_ENABLED(OF_CONTROL) +static int get_a53_cpu_clock_index(ofnode node) +{ + int count, i; + struct ofnode_phandle_args *args; + ofnode clknode; + + clknode = ofnode_path("/bus@f0000/system-controller@44043000/clock-controller"); + if (!ofnode_valid(clknode)) + return -1; + + count = ofnode_count_phandle_with_args(node, "assigned-clocks", "#clock-cells", 0); + + for (i = 0; i < count; i++) { + if (!ofnode_parse_phandle_with_args(node, "assigned-clocks", + "#clock-cells", 0, i, args)) { + if (ofnode_equal(clknode, args->node) && + args->args[0] == AM62PX_DEV_A53SS0_CORE_0_DEV_ID) + return i; + } + } + + return -1; +} + +static void fixup_a53_cpu_freq_by_speed_grade(void) +{ + int index, size; + u32 *rates; + ofnode node; + + node = ofnode_path("/a53@0"); + if (!ofnode_valid(node)) + return; + + rates = fdt_getprop_w(ofnode_to_fdt(node), ofnode_to_offset(node), + "assigned-clock-rates", &size); + + index = get_a53_cpu_clock_index(node); + + if (!rates || index < 0 || index >= (size / sizeof(u32))) { + printf("Wrong A53 assigned-clocks configuration\n"); + return; + } + + rates[index] = cpu_to_fdt32(k3_get_a53_max_frequency()); + + printf("Changed A53 CPU frequency to %dHz (%c grade) in DT\n", + k3_get_a53_max_frequency(), k3_get_speed_grade()); +} +#else +static void fixup_a53_cpu_freq_by_speed_grade(void) +{ +} +#endif + void board_init_f(ulong dummy) { struct udevice *dev; @@ -160,6 +220,8 @@ void board_init_f(ulong dummy) spl_enable_cache(); debug("am62px_init: %s done\n", __func__); + + fixup_a53_cpu_freq_by_speed_grade(); } u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device) diff --git a/arch/arm/mach-k3/include/mach/am62p_hardware.h b/arch/arm/mach-k3/include/mach/am62p_hardware.h index 923466c41f4..95af5c5c547 100644 --- a/arch/arm/mach-k3/include/mach/am62p_hardware.h +++ b/arch/arm/mach-k3/include/mach/am62p_hardware.h @@ -19,6 +19,22 @@ #define MCU_CTRL_MMR0_BASE 0x04500000 #define WKUP_CTRL_MMR0_BASE 0x43000000 +#define CTRLMMR_WKUP_JTAG_DEVICE_ID (WKUP_CTRL_MMR0_BASE + 0x18) +#define JTAG_DEV_CORE_NR_MASK GENMASK(19, 18) +#define JTAG_DEV_CORE_NR_SHIFT 18 +#define JTAG_DEV_CANFD_MASK BIT(15) +#define JTAG_DEV_CANFD_SHIFT 15 +#define JTAG_DEV_VIDEO_CODEC_MASK BIT(14) +#define JTAG_DEV_VIDEO_CODEC_SHIFT 14 +#define JTAG_DEV_SPEED_MASK GENMASK(10, 6) +#define JTAG_DEV_SPEED_SHIFT 6 +#define JTAG_DEV_TEMP_MASK GENMASK(5, 3) +#define JTAG_DEV_TEMP_SHIFT 3 + +#define JTAG_DEV_TEMP_AUTOMOTIVE 0x5 +#define JTAG_DEV_TEMP_EXTENDED_VALUE 105 +#define JTAG_DEV_TEMP_AUTOMOTIVE_VALUE 125 + #define CTRLMMR_MAIN_DEVSTAT (WKUP_CTRL_MMR0_BASE + 0x30) #define MAIN_DEVSTAT_PRIMARY_BOOTMODE_MASK GENMASK(6, 3) #define MAIN_DEVSTAT_PRIMARY_BOOTMODE_SHIFT 3 @@ -72,6 +88,55 @@ #define TI_SRAM_SCRATCH_BOARD_EEPROM_START 0x43c30000 +static inline int k3_get_core_nr(void) +{ + u32 dev_id = readl(CTRLMMR_WKUP_JTAG_DEVICE_ID); + + return ((dev_id & JTAG_DEV_CORE_NR_MASK) >> JTAG_DEV_CORE_NR_SHIFT) + 1; +} + +static inline int k3_has_video_codec(void) +{ + u32 dev_id = readl(CTRLMMR_WKUP_JTAG_DEVICE_ID); + + return !((dev_id & JTAG_DEV_VIDEO_CODEC_MASK) >> JTAG_DEV_VIDEO_CODEC_SHIFT); +} + +static inline int k3_has_canfd(void) +{ + u32 dev_id = readl(CTRLMMR_WKUP_JTAG_DEVICE_ID); + + return (dev_id & JTAG_DEV_CANFD_MASK) >> JTAG_DEV_CANFD_SHIFT; +} + +static inline int k3_get_max_temp(void) +{ + u32 dev_id = readl(CTRLMMR_WKUP_JTAG_DEVICE_ID); + u32 dev_temp = (dev_id & JTAG_DEV_TEMP_MASK) >> JTAG_DEV_TEMP_SHIFT; + + if (dev_temp == JTAG_DEV_TEMP_AUTOMOTIVE) + return JTAG_DEV_TEMP_AUTOMOTIVE_VALUE; + else + return JTAG_DEV_TEMP_EXTENDED_VALUE; +} + +static inline char k3_get_speed_grade(void) +{ + u32 dev_id = readl(CTRLMMR_WKUP_JTAG_DEVICE_ID); + u32 speed_grade = (dev_id & JTAG_DEV_SPEED_MASK) >> + JTAG_DEV_SPEED_SHIFT; + + return 'A' - 1 + speed_grade; +} + +static inline int k3_get_a53_max_frequency(void) +{ + if (k3_get_speed_grade() == 'O') + return 1000000000; + else + return 1250000000; +} + #if defined(CONFIG_SYS_K3_SPL_ATF) && !defined(__ASSEMBLY__) static const u32 put_device_ids[] = {}; diff --git a/arch/arm/mach-k3/include/mach/k3-ddr.h b/arch/arm/mach-k3/include/mach/k3-ddr.h new file mode 100644 index 00000000000..95496e1c59d --- /dev/null +++ b/arch/arm/mach-k3/include/mach/k3-ddr.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2024, Texas Instruments Incorporated - https://www.ti.com/ + */ + +#ifndef _K3_DDR_H_ +#define _K3_DDR_H_ + +int dram_init(void); +int dram_init_banksize(void); + +void fixup_ddr_driver_for_ecc(struct spl_image_info *spl_image); +void fixup_memory_node(struct spl_image_info *spl_image); + +#endif /* _K3_DDR_H_ */ diff --git a/arch/arm/mach-k3/k3-ddr.c b/arch/arm/mach-k3/k3-ddr.c new file mode 100644 index 00000000000..6e3e60cdc86 --- /dev/null +++ b/arch/arm/mach-k3/k3-ddr.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024, Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include <fdt_support.h> +#include <dm/uclass.h> +#include <k3-ddrss.h> +#include <spl.h> + +#include <asm/arch/k3-ddr.h> + +__weak int dram_init(void) +{ + return 0; +} + +__weak int dram_init_banksize(void) +{ + return 0; +} + +#if defined(CONFIG_SPL_BUILD) +void fixup_ddr_driver_for_ecc(struct spl_image_info *spl_image) +{ + struct udevice *dev; + int ret, ctr = 1; + + dram_init_banksize(); + + ret = uclass_get_device(UCLASS_RAM, 0, &dev); + if (ret) + panic("Cannnot get RAM device for ddr size fixup: %d\n", ret); + + ret = k3_ddrss_ddr_fdt_fixup(dev, spl_image->fdt_addr, gd->bd); + if (ret) + printf("Error fixing up ddr node for ECC use! %d\n", ret); + + ret = uclass_next_device_err(&dev); + + while (ret && ret != -ENODEV) { + ret = k3_ddrss_ddr_fdt_fixup(dev, spl_image->fdt_addr, gd->bd); + if (ret) + printf("Error fixing up ddr node %d for ECC use! %d\n", ctr, ret); + + ret = uclass_next_device_err(&dev); + ctr++; + } +} + +void fixup_memory_node(struct spl_image_info *spl_image) +{ + u64 start[CONFIG_NR_DRAM_BANKS]; + u64 size[CONFIG_NR_DRAM_BANKS]; + int bank; + int ret; + + dram_init(); + dram_init_banksize(); + + for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { + start[bank] = gd->bd->bi_dram[bank].start; + size[bank] = gd->bd->bi_dram[bank].size; + } + + ret = fdt_fixup_memory_banks(spl_image->fdt_addr, start, size, + CONFIG_NR_DRAM_BANKS); + + if (ret) + printf("Error fixing up memory node! %d\n", ret); +} +#endif diff --git a/board/ti/am62ax/evm.c b/board/ti/am62ax/evm.c index 62d3664936e..3351544c5b3 100644 --- a/board/ti/am62ax/evm.c +++ b/board/ti/am62ax/evm.c @@ -12,6 +12,7 @@ #include <env.h> #include <fdt_support.h> #include <spl.h> +#include <asm/arch/k3-ddr.h> #include "../common/fdt_ops.h" @@ -20,15 +21,17 @@ int board_init(void) return 0; } -int dram_init(void) +#if defined(CONFIG_XPL_BUILD) +void spl_perform_fixups(struct spl_image_info *spl_image) { - return fdtdec_setup_mem_size_base(); -} - -int dram_init_banksize(void) -{ - return fdtdec_setup_memory_banksize(); + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } +#endif #ifdef CONFIG_BOARD_LATE_INIT int board_late_init(void) diff --git a/board/ti/am62px/evm.c b/board/ti/am62px/evm.c index 7362fa4520a..75359fa1614 100644 --- a/board/ti/am62px/evm.c +++ b/board/ti/am62px/evm.c @@ -13,6 +13,7 @@ #include <env.h> #include <fdt_support.h> #include <spl.h> +#include <asm/arch/k3-ddr.h> #include "../common/fdt_ops.h" struct efi_fw_image fw_images[] = { @@ -53,15 +54,17 @@ int board_init(void) return 0; } -int dram_init(void) +#if defined(CONFIG_XPL_BUILD) +void spl_perform_fixups(struct spl_image_info *spl_image) { - return fdtdec_setup_mem_size_base(); -} - -int dram_init_banksize(void) -{ - return fdtdec_setup_memory_banksize(); + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } +#endif #if IS_ENABLED(CONFIG_BOARD_LATE_INIT) int board_late_init(void) diff --git a/board/ti/am62x/evm.c b/board/ti/am62x/evm.c index 9075df01cac..279ceba9554 100644 --- a/board/ti/am62x/evm.c +++ b/board/ti/am62x/evm.c @@ -20,6 +20,7 @@ #include <asm/io.h> #include <asm/arch/hardware.h> #include <dm/uclass.h> +#include <asm/arch/k3-ddr.h> #include "../common/fdt_ops.h" @@ -86,11 +87,6 @@ int board_init(void) return 0; } -int dram_init(void) -{ - return fdtdec_setup_mem_size_base(); -} - #ifdef CONFIG_BOARD_LATE_INIT int board_late_init(void) { @@ -99,13 +95,7 @@ int board_late_init(void) } #endif -int dram_init_banksize(void) -{ - return fdtdec_setup_memory_banksize(); -} - #if defined(CONFIG_XPL_BUILD) - void spl_board_init(void) { enable_caches(); @@ -114,53 +104,14 @@ void spl_board_init(void) } -#if defined(CONFIG_K3_AM64_DDRSS) -static void fixup_ddr_driver_for_ecc(struct spl_image_info *spl_image) -{ - struct udevice *dev; - int ret; - - dram_init_banksize(); - - ret = uclass_get_device(UCLASS_RAM, 0, &dev); - if (ret) - panic("Cannot get RAM device for ddr size fixup: %d\n", ret); - - ret = k3_ddrss_ddr_fdt_fixup(dev, spl_image->fdt_addr, gd->bd); - if (ret) - printf("Error fixing up ddr node for ECC use! %d\n", ret); -} -#else -static void fixup_memory_node(struct spl_image_info *spl_image) -{ - u64 start[CONFIG_NR_DRAM_BANKS]; - u64 size[CONFIG_NR_DRAM_BANKS]; - int bank; - int ret; - - dram_init(); - dram_init_banksize(); - - for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { - start[bank] = gd->bd->bi_dram[bank].start; - size[bank] = gd->bd->bi_dram[bank].size; - } - - /* dram_init functions use SPL fdt, and we must fixup u-boot fdt */ - ret = fdt_fixup_memory_banks(spl_image->fdt_addr, start, size, - CONFIG_NR_DRAM_BANKS); - if (ret) - printf("Error fixing up memory node! %d\n", ret); -} -#endif - void spl_perform_fixups(struct spl_image_info *spl_image) { -#if defined(CONFIG_K3_AM64_DDRSS) - fixup_ddr_driver_for_ecc(spl_image); -#else - fixup_memory_node(spl_image); -#endif + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } #endif diff --git a/board/ti/am64x/evm.c b/board/ti/am64x/evm.c index 00b8317d6bf..6f7e1f82866 100644 --- a/board/ti/am64x/evm.c +++ b/board/ti/am64x/evm.c @@ -15,6 +15,7 @@ #include <fdt_support.h> #include <asm/arch/hardware.h> #include <env.h> +#include <asm/arch/k3-ddr.h> #include "../common/board_detect.h" #include "../common/fdt_ops.h" @@ -66,28 +67,6 @@ int board_init(void) return 0; } -int dram_init(void) -{ - s32 ret; - - ret = fdtdec_setup_mem_size_base(); - if (ret) - printf("Error setting up mem size and base. %d\n", ret); - - return ret; -} - -int dram_init_banksize(void) -{ - s32 ret; - - ret = fdtdec_setup_memory_banksize(); - if (ret) - printf("Error setting up memory banksize. %d\n", ret); - - return ret; -} - #if defined(CONFIG_SPL_LOAD_FIT) int board_fit_config_name_match(const char *name) { @@ -132,52 +111,14 @@ static int fixup_usb_boot(const void *fdt_blob) } #endif -#if defined(CONFIG_K3_AM64_DDRSS) -static void fixup_ddr_driver_for_ecc(struct spl_image_info *spl_image) -{ - struct udevice *dev; - int ret; - - dram_init_banksize(); - - ret = uclass_get_device(UCLASS_RAM, 0, &dev); - if (ret) - panic("Cannot get RAM device for ddr size fixup: %d\n", ret); - - ret = k3_ddrss_ddr_fdt_fixup(dev, spl_image->fdt_addr, gd->bd); - if (ret) - printf("Error fixing up ddr node for ECC use! %d\n", ret); -} -#else -static void fixup_memory_node(struct spl_image_info *spl_image) -{ - u64 start[CONFIG_NR_DRAM_BANKS]; - u64 size[CONFIG_NR_DRAM_BANKS]; - int bank; - int ret; - - dram_init(); - dram_init_banksize(); - - for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { - start[bank] = gd->bd->bi_dram[bank].start; - size[bank] = gd->bd->bi_dram[bank].size; - } - - /* dram_init functions use SPL fdt, and we must fixup u-boot fdt */ - ret = fdt_fixup_memory_banks(spl_image->fdt_addr, start, size, CONFIG_NR_DRAM_BANKS); - if (ret) - printf("Error fixing up memory node! %d\n", ret); -} -#endif - void spl_perform_fixups(struct spl_image_info *spl_image) { -#if defined(CONFIG_K3_AM64_DDRSS) - fixup_ddr_driver_for_ecc(spl_image); -#else - fixup_memory_node(spl_image); -#endif + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } #if CONFIG_IS_ENABLED(USB_STORAGE) fixup_usb_boot(spl_image->fdt_addr); diff --git a/board/ti/am65x/evm.c b/board/ti/am65x/evm.c index 07073a5940b..6658794a137 100644 --- a/board/ti/am65x/evm.c +++ b/board/ti/am65x/evm.c @@ -20,6 +20,7 @@ #include <env.h> #include <spl.h> #include <linux/printk.h> +#include <asm/arch/k3-ddr.h> #include "../common/board_detect.h" #include "../common/fdt_ops.h" @@ -49,17 +50,6 @@ int board_init(void) return 0; } -int dram_init(void) -{ -#ifdef CONFIG_PHYS_64BIT - gd->ram_size = 0x100000000; -#else - gd->ram_size = 0x80000000; -#endif - - return 0; -} - phys_addr_t board_get_usable_ram_top(phys_size_t total_size) { #ifdef CONFIG_PHYS_64BIT @@ -71,23 +61,6 @@ phys_addr_t board_get_usable_ram_top(phys_size_t total_size) return gd->ram_top; } -int dram_init_banksize(void) -{ - /* Bank 0 declares the memory available in the DDR low region */ - gd->bd->bi_dram[0].start = 0x80000000; - gd->bd->bi_dram[0].size = 0x80000000; - gd->ram_size = 0x80000000; - -#ifdef CONFIG_PHYS_64BIT - /* Bank 1 declares the memory available in the DDR high region */ - gd->bd->bi_dram[1].start = 0x880000000; - gd->bd->bi_dram[1].size = 0x80000000; - gd->ram_size = 0x100000000; -#endif - - return 0; -} - #ifdef CONFIG_SPL_LOAD_FIT int board_fit_config_name_match(const char *name) { diff --git a/board/ti/common/Makefile b/board/ti/common/Makefile index 5ac361ba7fc..caf6b9fa8c1 100644 --- a/board/ti/common/Makefile +++ b/board/ti/common/Makefile @@ -4,3 +4,4 @@ obj-${CONFIG_TI_I2C_BOARD_DETECT} += board_detect.o obj-${CONFIG_CMD_EXTENSION} += cape_detect.o obj-${CONFIG_OF_LIBFDT} += fdt_ops.o +obj-${CONFIG_ARCH_K3} += k3-ddr.o diff --git a/board/ti/common/k3-ddr.c b/board/ti/common/k3-ddr.c new file mode 100644 index 00000000000..a8425da8de5 --- /dev/null +++ b/board/ti/common/k3-ddr.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024, Texas Instruments Incorporated - https://www.ti.com/ + */ + +#include <fdt_support.h> +#include <dm/uclass.h> +#include <k3-ddrss.h> +#include <spl.h> + +#include "k3-ddr.h" + +int dram_init(void) +{ + s32 ret; + + ret = fdtdec_setup_mem_size_base_lowest(); + if (ret) + printf("Error setting up mem size and base. %d\n", ret); + + return ret; +} + +int dram_init_banksize(void) +{ + s32 ret; + + ret = fdtdec_setup_memory_banksize(); + if (ret) + printf("Error setting up memory banksize. %d\n", ret); + + return ret; +} diff --git a/board/ti/common/k3-ddr.h b/board/ti/common/k3-ddr.h new file mode 100644 index 00000000000..737d45e2e50 --- /dev/null +++ b/board/ti/common/k3-ddr.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2024, Texas Instruments Incorporated - https://www.ti.com/ + */ + +#ifndef __K3_DDR_INIT_H +#define __K3_DDR_INIT_H + +int dram_init(void); +int dram_init_banksize(void); + +#endif /* __K3_DDR_INIT_H */ diff --git a/board/ti/j721e/evm.c b/board/ti/j721e/evm.c index 6221be9dcff..4b5b9dded7b 100644 --- a/board/ti/j721e/evm.c +++ b/board/ti/j721e/evm.c @@ -15,6 +15,7 @@ #include <asm/gpio.h> #include <spl.h> #include <dm.h> +#include <asm/arch/k3-ddr.h> #include "../common/board_detect.h" #include "../common/fdt_ops.h" @@ -77,17 +78,6 @@ int board_init(void) return 0; } -int dram_init(void) -{ -#ifdef CONFIG_PHYS_64BIT - gd->ram_size = 0x100000000; -#else - gd->ram_size = 0x80000000; -#endif - - return 0; -} - phys_addr_t board_get_usable_ram_top(phys_size_t total_size) { #ifdef CONFIG_PHYS_64BIT @@ -99,23 +89,6 @@ phys_addr_t board_get_usable_ram_top(phys_size_t total_size) return gd->ram_top; } -int dram_init_banksize(void) -{ - /* Bank 0 declares the memory available in the DDR low region */ - gd->bd->bi_dram[0].start = 0x80000000; - gd->bd->bi_dram[0].size = 0x80000000; - gd->ram_size = 0x80000000; - -#ifdef CONFIG_PHYS_64BIT - /* Bank 1 declares the memory available in the DDR high region */ - gd->bd->bi_dram[1].start = 0x880000000; - gd->bd->bi_dram[1].size = 0x80000000; - gd->ram_size = 0x100000000; -#endif - - return 0; -} - #ifdef CONFIG_SPL_LOAD_FIT int board_fit_config_name_match(const char *name) { diff --git a/board/ti/j721s2/evm.c b/board/ti/j721s2/evm.c index 2cfeb3bec6c..1b5ad19df7f 100644 --- a/board/ti/j721s2/evm.c +++ b/board/ti/j721s2/evm.c @@ -21,6 +21,7 @@ #include <dm.h> #include <dm/uclass-internal.h> #include <dm/root.h> +#include <asm/arch/k3-ddr.h> #include "../common/board_detect.h" #include "../common/fdt_ops.h" @@ -32,17 +33,6 @@ int board_init(void) return 0; } -int dram_init(void) -{ -#ifdef CONFIG_PHYS_64BIT - gd->ram_size = 0x100000000; -#else - gd->ram_size = 0x80000000; -#endif - - return 0; -} - phys_addr_t board_get_usable_ram_top(phys_size_t total_size) { #ifdef CONFIG_PHYS_64BIT @@ -54,22 +44,17 @@ phys_addr_t board_get_usable_ram_top(phys_size_t total_size) return gd->ram_top; } -int dram_init_banksize(void) +#if defined(CONFIG_XPL_BUILD) +void spl_perform_fixups(struct spl_image_info *spl_image) { - /* Bank 0 declares the memory available in the DDR low region */ - gd->bd->bi_dram[0].start = 0x80000000; - gd->bd->bi_dram[0].size = 0x7fffffff; - gd->ram_size = 0x80000000; - -#ifdef CONFIG_PHYS_64BIT - /* Bank 1 declares the memory available in the DDR high region */ - gd->bd->bi_dram[1].start = 0x880000000; - gd->bd->bi_dram[1].size = 0x37fffffff; - gd->ram_size = 0x400000000; -#endif - - return 0; + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } +#endif #ifdef CONFIG_TI_I2C_BOARD_DETECT /* diff --git a/board/ti/j722s/evm.c b/board/ti/j722s/evm.c index 29e06a5442f..f085ecfd37e 100644 --- a/board/ti/j722s/evm.c +++ b/board/ti/j722s/evm.c @@ -12,6 +12,7 @@ #include <env.h> #include <fdt_support.h> #include <spl.h> +#include <asm/arch/k3-ddr.h> #include "../common/fdt_ops.h" int board_init(void) @@ -19,15 +20,17 @@ int board_init(void) return 0; } -int dram_init(void) +#if defined(CONFIG_XPL_BUILD) +void spl_perform_fixups(struct spl_image_info *spl_image) { - return fdtdec_setup_mem_size_base(); -} - -int dram_init_banksize(void) -{ - return fdtdec_setup_memory_banksize(); + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } +#endif #if IS_ENABLED(CONFIG_BOARD_LATE_INIT) int board_late_init(void) diff --git a/board/ti/j784s4/evm.c b/board/ti/j784s4/evm.c index 548dbd5925d..d317f3eccbb 100644 --- a/board/ti/j784s4/evm.c +++ b/board/ti/j784s4/evm.c @@ -10,6 +10,7 @@ #include <efi_loader.h> #include <init.h> #include <spl.h> +#include <asm/arch/k3-ddr.h> #include "../common/fdt_ops.h" DECLARE_GLOBAL_DATA_PTR; @@ -52,15 +53,17 @@ int board_init(void) return 0; } -int dram_init(void) +#if defined(CONFIG_XPL_BUILD) +void spl_perform_fixups(struct spl_image_info *spl_image) { - return fdtdec_setup_mem_size_base(); -} - -int dram_init_banksize(void) -{ - return fdtdec_setup_memory_banksize(); + if (IS_ENABLED(CONFIG_K3_DDRSS)) { + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) + fixup_ddr_driver_for_ecc(spl_image); + } else { + fixup_memory_node(spl_image); + } } +#endif #ifdef CONFIG_BOARD_LATE_INIT int board_late_init(void) diff --git a/configs/am62ax_evm_a53_defconfig b/configs/am62ax_evm_a53_defconfig index 7d63340c903..107d6c6becf 100644 --- a/configs/am62ax_evm_a53_defconfig +++ b/configs/am62ax_evm_a53_defconfig @@ -3,7 +3,6 @@ CONFIG_ARCH_K3=y CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM62A7=y CONFIG_TARGET_AM62A7_A53_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am62px_evm_a53_defconfig b/configs/am62px_evm_a53_defconfig index 298fb9684ea..721dfa67d34 100644 --- a/configs/am62px_evm_a53_defconfig +++ b/configs/am62px_evm_a53_defconfig @@ -4,7 +4,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM62P5=y CONFIG_TARGET_AM62P5_A53_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am62x_evm_a53_defconfig b/configs/am62x_evm_a53_defconfig index abc061f1dbc..1a362d44d1b 100644 --- a/configs/am62x_evm_a53_defconfig +++ b/configs/am62x_evm_a53_defconfig @@ -4,7 +4,6 @@ CONFIG_SYS_MALLOC_LEN=0x2000000 CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM625=y CONFIG_TARGET_AM625_A53_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am62x_evm_r5_defconfig b/configs/am62x_evm_r5_defconfig index fcc5eb02867..b47f2f2d1f3 100644 --- a/configs/am62x_evm_r5_defconfig +++ b/configs/am62x_evm_r5_defconfig @@ -4,7 +4,6 @@ CONFIG_SYS_MALLOC_LEN=0x08000000 CONFIG_SYS_MALLOC_F_LEN=0x9000 CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM625=y CONFIG_TARGET_AM625_R5_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am64x_evm_a53_defconfig b/configs/am64x_evm_a53_defconfig index 80027a4d94b..feb021ff281 100644 --- a/configs/am64x_evm_a53_defconfig +++ b/configs/am64x_evm_a53_defconfig @@ -6,7 +6,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM642=y CONFIG_K3_ATF_LOAD_ADDR=0x701c0000 CONFIG_TARGET_AM642_A53_EVM=y diff --git a/configs/am64x_evm_r5_defconfig b/configs/am64x_evm_r5_defconfig index c4e498a6ae7..c7fc9d124c7 100644 --- a/configs/am64x_evm_r5_defconfig +++ b/configs/am64x_evm_r5_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x80000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM642=y CONFIG_TARGET_AM642_R5_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am65x_evm_a53_defconfig b/configs/am65x_evm_a53_defconfig index cf7a2114883..b30907a4f25 100644 --- a/configs/am65x_evm_a53_defconfig +++ b/configs/am65x_evm_a53_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_LEN=0x2000000 CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM654=y CONFIG_TARGET_AM654_A53_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/am65x_evm_r5_defconfig b/configs/am65x_evm_r5_defconfig index 083522ce9b4..4bd5e8f63ff 100644 --- a/configs/am65x_evm_r5_defconfig +++ b/configs/am65x_evm_r5_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x55000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM654=y CONFIG_K3_EARLY_CONS=y CONFIG_TARGET_AM654_R5_EVM=y diff --git a/configs/am65x_evm_r5_usbdfu_defconfig b/configs/am65x_evm_r5_usbdfu_defconfig index e60e0d6588d..ae57007d898 100644 --- a/configs/am65x_evm_r5_usbdfu_defconfig +++ b/configs/am65x_evm_r5_usbdfu_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x55000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM654=y CONFIG_K3_EARLY_CONS=y CONFIG_TARGET_AM654_R5_EVM=y diff --git a/configs/am65x_evm_r5_usbmsc_defconfig b/configs/am65x_evm_r5_usbmsc_defconfig index ecd48c45ea1..3aa17eb77f0 100644 --- a/configs/am65x_evm_r5_usbmsc_defconfig +++ b/configs/am65x_evm_r5_usbmsc_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x55000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_AM654=y CONFIG_K3_EARLY_CONS=y CONFIG_TARGET_AM654_R5_EVM=y diff --git a/configs/j7200_evm_a72_defconfig b/configs/j7200_evm_a72_defconfig index eb6203f2b4c..7e598eeadbe 100644 --- a/configs/j7200_evm_a72_defconfig +++ b/configs/j7200_evm_a72_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_J721E=y CONFIG_TARGET_J7200_A72_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/j721e_evm_a72_defconfig b/configs/j721e_evm_a72_defconfig index 5833e22a004..8ca2afeed04 100644 --- a/configs/j721e_evm_a72_defconfig +++ b/configs/j721e_evm_a72_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_J721E=y CONFIG_TARGET_J721E_A72_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/j721s2_evm_a72_defconfig b/configs/j721s2_evm_a72_defconfig index 2a0ccfb581e..640aa00a024 100644 --- a/configs/j721s2_evm_a72_defconfig +++ b/configs/j721s2_evm_a72_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_J721S2=y CONFIG_TARGET_J721S2_A72_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/j722s_evm_a53_defconfig b/configs/j722s_evm_a53_defconfig index 35329fb336b..17595e3c451 100644 --- a/configs/j722s_evm_a53_defconfig +++ b/configs/j722s_evm_a53_defconfig @@ -4,7 +4,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_J722S=y CONFIG_TARGET_J722S_A53_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/configs/j784s4_evm_a72_defconfig b/configs/j784s4_evm_a72_defconfig index 88a690fbf96..5b344cc4518 100644 --- a/configs/j784s4_evm_a72_defconfig +++ b/configs/j784s4_evm_a72_defconfig @@ -5,7 +5,6 @@ CONFIG_SYS_MALLOC_F_LEN=0x8000 CONFIG_SPL_GPIO=y CONFIG_SPL_LIBCOMMON_SUPPORT=y CONFIG_SPL_LIBGENERIC_SUPPORT=y -CONFIG_NR_DRAM_BANKS=2 CONFIG_SOC_K3_J784S4=y CONFIG_TARGET_J784S4_A72_EVM=y CONFIG_HAS_CUSTOM_SYS_INIT_SP_ADDR=y diff --git a/drivers/ram/Kconfig b/drivers/ram/Kconfig index f7e357f24da..533d0c629a6 100644 --- a/drivers/ram/Kconfig +++ b/drivers/ram/Kconfig @@ -116,6 +116,16 @@ config IMXRT_SDRAM to support external memories like sdram, psram & nand. This driver is for the sdram memory interface with the SEMC. +config K3_INLINE_ECC + bool "Enable TI Inline ECC support" + depends on K3_DDRSS + help + Enable Inline ECC support on K3 platforms. 1/9th of the SDRAM space + is used for ECC storage and the rest 8/9th is available for system + use. Enabling ECC increases boot time as the ECC protected regions + need to be primed with a predefined value prior to enabling ECC + check. + source "drivers/ram/aspeed/Kconfig" source "drivers/ram/cadence/Kconfig" source "drivers/ram/octeon/Kconfig" diff --git a/drivers/ram/k3-ddrss/k3-ddrss.c b/drivers/ram/k3-ddrss/k3-ddrss.c index 525b6d5b79f..80f81a34b35 100644 --- a/drivers/ram/k3-ddrss/k3-ddrss.c +++ b/drivers/ram/k3-ddrss/k3-ddrss.c @@ -6,6 +6,7 @@ */ #include <config.h> +#include <time.h> #include <clk.h> #include <div64.h> #include <dm.h> @@ -44,6 +45,11 @@ #define DDRSS_ECC_R2_STR_ADDR_REG 0x0140 #define DDRSS_ECC_R2_END_ADDR_REG 0x0144 #define DDRSS_ECC_1B_ERR_CNT_REG 0x0150 +#define DDRSS_V2A_INT_SET_REG 0x00a8 + +#define DDRSS_V2A_INT_SET_REG_ECC1BERR_EN BIT(3) +#define DDRSS_V2A_INT_SET_REG_ECC2BERR_EN BIT(4) +#define DDRSS_V2A_INT_SET_REG_ECCM1BERR_EN BIT(5) #define SINGLE_DDR_SUBSYSTEM 0x1 #define MULTI_DDR_SUBSYSTEM 0x2 @@ -120,8 +126,8 @@ struct k3_msmc { #define K3_DDRSS_MAX_ECC_REGIONS 3 struct k3_ddrss_ecc_region { - u32 start; - u32 range; + u64 start; + u64 range; }; struct k3_ddrss_desc { @@ -145,7 +151,9 @@ struct k3_ddrss_desc { lpddr4_privatedata pd; struct k3_ddrss_ecc_region ecc_regions[K3_DDRSS_MAX_ECC_REGIONS]; u64 ecc_reserved_space; - bool ti_ecc_enabled; + u64 ddr_bank_base[CONFIG_NR_DRAM_BANKS]; + u64 ddr_bank_size[CONFIG_NR_DRAM_BANKS]; + u64 ddr_ram_size; }; struct reginitdata { @@ -404,8 +412,6 @@ static int k3_ddrss_ofdata_to_priv(struct udevice *dev) if (ret) dev_err(dev, "ddr fhs cnt not populated %d\n", ret); - ddrss->ti_ecc_enabled = dev_read_bool(dev, "ti,ecc-enable"); - return ret; } @@ -547,27 +553,174 @@ void k3_lpddr4_start(struct k3_ddrss_desc *ddrss) } } -static void k3_ddrss_set_ecc_range_r0(u32 base, u32 start_address, u32 size) +static void k3_ddrss_set_ecc_range_r0(u32 base, u64 start_address, u64 size) { writel((start_address) >> 16, base + DDRSS_ECC_R0_STR_ADDR_REG); writel((start_address + size - 1) >> 16, base + DDRSS_ECC_R0_END_ADDR_REG); } -static void k3_ddrss_preload_ecc_mem_region(u32 *addr, u32 size, u32 word) +#define BIST_MODE_MEM_INIT 4 +#define BIST_MEM_INIT_TIMEOUT 10000 /* 1msec loops per block = 10s */ +static void k3_lpddr4_bist_init_mem_region(struct k3_ddrss_desc *ddrss, + u64 addr, u64 size, + u32 pattern) { - int i; + lpddr4_obj *driverdt = ddrss->driverdt; + lpddr4_privatedata *pd = &ddrss->pd; + u32 status, offset, regval; + bool int_status; + int i = 0; + + /* Set BIST_START_ADDR_0 [31:0] */ + regval = (u32)(addr & TH_FLD_MASK(LPDDR4__BIST_START_ADDRESS_0__FLD)); + TH_OFFSET_FROM_REG(LPDDR4__BIST_START_ADDRESS_0__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Set BIST_START_ADDR_1 [32 or 34:32] */ + regval = (u32)(addr >> TH_FLD_WIDTH(LPDDR4__BIST_START_ADDRESS_0__FLD)); + regval &= TH_FLD_MASK(LPDDR4__BIST_START_ADDRESS_1__FLD); + TH_OFFSET_FROM_REG(LPDDR4__BIST_START_ADDRESS_1__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Set ADDR_SPACE = log2(size) */ + regval = (u32)(ilog2(size) << TH_FLD_SHIFT(LPDDR4__ADDR_SPACE__FLD)); + TH_OFFSET_FROM_REG(LPDDR4__ADDR_SPACE__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Enable the BIST data check. On 32bit lpddr4 (e.g J7) this shares a + * register with ADDR_SPACE and BIST_GO. + */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_DATA_CHECK__REG, CTL_SHIFT, offset); + driverdt->readreg(pd, LPDDR4_CTL_REGS, offset, ®val); + regval |= TH_FLD_MASK(LPDDR4__BIST_DATA_CHECK__FLD); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + /* Clear the address check bit */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_ADDR_CHECK__REG, CTL_SHIFT, offset); + driverdt->readreg(pd, LPDDR4_CTL_REGS, offset, ®val); + regval &= ~TH_FLD_MASK(LPDDR4__BIST_ADDR_CHECK__FLD); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Set BIST_TEST_MODE[2:0] to memory initialize (4) */ + regval = BIST_MODE_MEM_INIT; + TH_OFFSET_FROM_REG(LPDDR4__BIST_TEST_MODE__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Set BIST_DATA_PATTERN[31:0] */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_DATA_PATTERN_0__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, pattern); + + /* Set BIST_DATA_PATTERN[63:32] */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_DATA_PATTERN_1__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, pattern); + + udelay(1000); + + /* Enable the programmed BIST operation - BIST_GO = 1 */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_GO__REG, CTL_SHIFT, offset); + driverdt->readreg(pd, LPDDR4_CTL_REGS, offset, ®val); + regval |= TH_FLD_MASK(LPDDR4__BIST_GO__FLD); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, regval); + + /* Wait for the BIST_DONE interrupt */ + while (i < BIST_MEM_INIT_TIMEOUT) { + status = driverdt->checkctlinterrupt(pd, LPDDR4_INTR_BIST_DONE, + &int_status); + if (!status & int_status) { + /* Clear LPDDR4_INTR_BIST_DONE */ + driverdt->ackctlinterrupt(pd, LPDDR4_INTR_BIST_DONE); + break; + } + udelay(1000); + i++; + } + + /* Before continuing we have to stop BIST - BIST_GO = 0 */ + TH_OFFSET_FROM_REG(LPDDR4__BIST_GO__REG, CTL_SHIFT, offset); + driverdt->writereg(pd, LPDDR4_CTL_REGS, offset, 0); + /* Timeout hit while priming the memory. We can't continue, + * since the memory is not fully initialized and we most + * likely get an uncorrectable error exception while booting. + */ + if (i == BIST_MEM_INIT_TIMEOUT) { + printf("ERROR: Timeout while priming the memory.\n"); + hang(); + } +} + +static void k3_ddrss_lpddr4_preload_full_mem(struct k3_ddrss_desc *ddrss, + u64 total_size, u32 pattern) +{ + u32 done, max_size2; + + /* Get the max size (log2) supported in this config (16/32 lpddr4) + * from the start_addess width - 16bit: 8G, 32bit: 32G + */ + max_size2 = TH_FLD_WIDTH(LPDDR4__BIST_START_ADDRESS_0__FLD) + + TH_FLD_WIDTH(LPDDR4__BIST_START_ADDRESS_1__FLD) + 1; + + /* ECC is enabled in dt but we can't preload the memory if + * the memory configuration is recognized and supported. + */ + if (!total_size || total_size > (1ull << max_size2) || + total_size & (total_size - 1)) { + printf("ECC: the memory configuration is not supported\n"); + hang(); + } printf("ECC is enabled, priming DDR which will take several seconds.\n"); + done = get_timer(0); + k3_lpddr4_bist_init_mem_region(ddrss, 0, total_size, pattern); + printf("ECC: priming DDR completed in %lu msec\n", get_timer(done)); +} + +static void k3_ddrss_ddr_bank_base_size_calc(struct k3_ddrss_desc *ddrss) +{ + int bank, na, ns, len, parent; + const fdt32_t *ptr, *end; + + for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { + ddrss->ddr_bank_base[bank] = 0; + ddrss->ddr_bank_size[bank] = 0; + } + + ofnode mem = ofnode_null(); + + do { + mem = ofnode_by_prop_value(mem, "device_type", "memory", 7); + } while (!ofnode_is_enabled(mem)); + + const void *fdt = ofnode_to_fdt(mem); + int node = ofnode_to_offset(mem); + const char *property = "reg"; + + parent = fdt_parent_offset(fdt, node); + na = fdt_address_cells(fdt, parent); + ns = fdt_size_cells(fdt, parent); + ptr = fdt_getprop(fdt, node, property, &len); + end = ptr + len / sizeof(*ptr); + + for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { + if (ptr + na + ns <= end) { + if (CONFIG_IS_ENABLED(OF_TRANSLATE)) + ddrss->ddr_bank_base[bank] = fdt_translate_address(fdt, node, ptr); + else + ddrss->ddr_bank_base[bank] = fdtdec_get_number(ptr, na); + + ddrss->ddr_bank_size[bank] = fdtdec_get_number(&ptr[na], ns); + } - for (i = 0; i < (size / 4); i++) - addr[i] = word; + ptr += na + ns; + } + + for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) + ddrss->ddr_ram_size += ddrss->ddr_bank_size[bank]; } static void k3_ddrss_lpddr4_ecc_calc_reserved_mem(struct k3_ddrss_desc *ddrss) { fdtdec_setup_mem_size_base_lowest(); - ddrss->ecc_reserved_space = gd->ram_size; + ddrss->ecc_reserved_space = ddrss->ddr_ram_size; do_div(ddrss->ecc_reserved_space, 9); /* Round to clean number */ @@ -576,25 +729,29 @@ static void k3_ddrss_lpddr4_ecc_calc_reserved_mem(struct k3_ddrss_desc *ddrss) static void k3_ddrss_lpddr4_ecc_init(struct k3_ddrss_desc *ddrss) { - u32 ecc_region_start = ddrss->ecc_regions[0].start; - u32 ecc_range = ddrss->ecc_regions[0].range; + u64 ecc_region_start = ddrss->ecc_regions[0].start; + u64 ecc_range = ddrss->ecc_regions[0].range; u32 base = (u32)ddrss->ddrss_ss_cfg; u32 val; /* Only Program region 0 which covers full ddr space */ - k3_ddrss_set_ecc_range_r0(base, ecc_region_start - gd->ram_base, ecc_range); + k3_ddrss_set_ecc_range_r0(base, ecc_region_start - ddrss->ddr_bank_base[0], ecc_range); /* Enable ECC, RMW, WR_ALLOC */ writel(DDRSS_ECC_CTRL_REG_ECC_EN | DDRSS_ECC_CTRL_REG_RMW_EN | DDRSS_ECC_CTRL_REG_WR_ALLOC, base + DDRSS_ECC_CTRL_REG); - /* Preload ECC Mem region with 0's */ - k3_ddrss_preload_ecc_mem_region((u32 *)ecc_region_start, ecc_range, - 0x00000000); + /* Preload the full memory with 0's using the BIST engine of + * the LPDDR4 controller. + */ + k3_ddrss_lpddr4_preload_full_mem(ddrss, ddrss->ddr_ram_size, 0); /* Clear Error Count Register */ writel(0x1, base + DDRSS_ECC_1B_ERR_CNT_REG); + writel(DDRSS_V2A_INT_SET_REG_ECC1BERR_EN | DDRSS_V2A_INT_SET_REG_ECC2BERR_EN | + DDRSS_V2A_INT_SET_REG_ECCM1BERR_EN, base + DDRSS_V2A_INT_SET_REG); + /* Enable ECC Check */ val = readl(base + DDRSS_ECC_CTRL_REG); val |= DDRSS_ECC_CTRL_REG_ECC_CK; @@ -635,7 +792,9 @@ static int k3_ddrss_probe(struct udevice *dev) k3_lpddr4_start(ddrss); - if (ddrss->ti_ecc_enabled) { + k3_ddrss_ddr_bank_base_size_calc(ddrss); + + if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) { if (!ddrss->ddrss_ss_cfg) { printf("%s: ss_cfg is required if ecc is enabled but not provided.", __func__); @@ -645,8 +804,8 @@ static int k3_ddrss_probe(struct udevice *dev) k3_ddrss_lpddr4_ecc_calc_reserved_mem(ddrss); /* Always configure one region that covers full DDR space */ - ddrss->ecc_regions[0].start = gd->ram_base; - ddrss->ecc_regions[0].range = gd->ram_size - ddrss->ecc_reserved_space; + ddrss->ecc_regions[0].start = ddrss->ddr_bank_base[0]; + ddrss->ecc_regions[0].range = ddrss->ddr_ram_size - ddrss->ecc_reserved_space; k3_ddrss_lpddr4_ecc_init(ddrss); } @@ -655,30 +814,24 @@ static int k3_ddrss_probe(struct udevice *dev) int k3_ddrss_ddr_fdt_fixup(struct udevice *dev, void *blob, struct bd_info *bd) { - struct k3_ddrss_desc *ddrss = dev_get_priv(dev); - u64 start[CONFIG_NR_DRAM_BANKS]; - u64 size[CONFIG_NR_DRAM_BANKS]; int bank; + struct k3_ddrss_desc *ddrss = dev_get_priv(dev); if (ddrss->ecc_reserved_space == 0) return 0; for (bank = CONFIG_NR_DRAM_BANKS - 1; bank >= 0; bank--) { - if (ddrss->ecc_reserved_space > bd->bi_dram[bank].size) { - ddrss->ecc_reserved_space -= bd->bi_dram[bank].size; - bd->bi_dram[bank].size = 0; + if (ddrss->ecc_reserved_space > ddrss->ddr_bank_size[bank]) { + ddrss->ecc_reserved_space -= ddrss->ddr_bank_size[bank]; + ddrss->ddr_bank_size[bank] = 0; } else { - bd->bi_dram[bank].size -= ddrss->ecc_reserved_space; + ddrss->ddr_bank_size[bank] -= ddrss->ecc_reserved_space; break; } } - for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) { - start[bank] = bd->bi_dram[bank].start; - size[bank] = bd->bi_dram[bank].size; - } - - return fdt_fixup_memory_banks(blob, start, size, CONFIG_NR_DRAM_BANKS); + return fdt_fixup_memory_banks(blob, ddrss->ddr_bank_base, + ddrss->ddr_bank_size, CONFIG_NR_DRAM_BANKS); } static int k3_ddrss_get_info(struct udevice *dev, struct ram_info *info) diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 4c4dd2ca559..a4e4e7095d4 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -58,16 +58,14 @@ 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 + * ktime_t, atomic_t, atomic64_t, sector_t are now in linux/types.h + * MAX_JIFFY_OFFSET is now in linux/jiffies.h + * BDEVNAME_SIZE is now in linux/blkdev.h */ -#include <asm-generic/atomic.h> #include <linux/jiffies.h> #include <linux/blkdev.h> -/* Extra atomic operations not in asm-generic/atomic.h */ +/* Extra atomic operation not in linux/types.h */ #define atomic_dec_if_positive(v) (--(v)->counter) /* SMP stubs - U-Boot is single-threaded */ @@ -1138,7 +1136,10 @@ struct shrinker { static inline struct shrinker *shrinker_alloc(unsigned int flags, const char *fmt, ...) { - return NULL; + /* Return static dummy - U-Boot doesn't need memory reclamation */ + static struct shrinker dummy_shrinker; + + return &dummy_shrinker; } static inline void shrinker_register(struct shrinker *s) @@ -1506,7 +1507,7 @@ static inline char *d_path(const struct path *path, char *buf, int buflen) #define filemap_splice_read(i, p, pi, l, f) ({ (void)(i); (void)(p); (void)(pi); (void)(l); (void)(f); 0L; }) /* Buffer operations - additional */ -#define getblk_unmovable(bd, b, s) ((struct buffer_head *)NULL) +#define getblk_unmovable(bdev, block, size) sb_getblk(bdev->bd_super, block) #define create_empty_buffers(f, s, flags) ({ (void)(f); (void)(s); (void)(flags); (struct buffer_head *)NULL; }) #define bh_offset(bh) (0UL) #define block_invalidate_folio(f, o, l) do { } while (0) @@ -2314,8 +2315,8 @@ struct mb_cache_entry { /* MB cache flags */ #define MBE_REUSABLE_B 0 -#define mb_cache_create(bits) ((struct mb_cache *)NULL) -#define mb_cache_destroy(cache) do { (void)(cache); } while (0) +#define mb_cache_create(bits) kzalloc(sizeof(struct mb_cache), GFP_KERNEL) +#define mb_cache_destroy(cache) do { kfree(cache); } while (0) #define mb_cache_entry_find_first(c, h) ((struct mb_cache_entry *)NULL) #define mb_cache_entry_find_next(c, e) ((struct mb_cache_entry *)NULL) #define mb_cache_entry_delete_or_get(c, k, v) ((struct mb_cache_entry *)NULL) diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index 141afc42c17..7360f44c92b 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -28,6 +28,9 @@ static struct blk_desc *ext4l_blk_dev; static struct disk_partition ext4l_partition; static int ext4l_mounted; +/* Global super_block pointer for filesystem operations */ +static struct super_block *ext4l_sb; + /** * ext4l_get_blk_dev() - Get the current block device * Return: Block device descriptor or NULL if not mounted @@ -89,9 +92,7 @@ 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 CRC32C table for checksum verification */ ext4l_crc32c_init(); /* Initialise journal subsystem if enabled */ @@ -113,6 +114,11 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (ret) return ret; + /* Initialise system zone for block validity checking */ + ret = ext4_init_system_zone(); + if (ret) + goto err_exit_es; + /* Allocate super_block */ sb = kzalloc(sizeof(struct super_block), GFP_KERNEL); if (!sb) { @@ -193,11 +199,18 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, if (fs_partition) memcpy(&ext4l_part, fs_partition, sizeof(ext4l_part)); + /* Set block device for buffer I/O */ + ext4l_set_blk_dev(fs_dev_desc, fs_partition); + /* Mount the filesystem */ ret = ext4_fill_super(sb, fc); - if (ret) + if (ret) { + printf("ext4l: ext4_fill_super failed: %d\n", ret); goto err_free_ctx; + } + /* Store super_block for later operations */ + ext4l_sb = sb; return 0; err_free_buf: @@ -219,6 +232,7 @@ err_exit_es: void ext4l_close(void) { - ext4l_clear_blk_dev(); ext4l_dev_desc = NULL; + ext4l_sb = NULL; + ext4l_clear_blk_dev(); } diff --git a/fs/ext4l/stub.c b/fs/ext4l/stub.c index eff54bb540c..2d066be4af3 100644 --- a/fs/ext4l/stub.c +++ b/fs/ext4l/stub.c @@ -561,9 +561,30 @@ void generic_set_sb_d_ops(struct super_block *sb) { } +/** + * d_make_root() - Create a root dentry for an inode + * @inode: Inode to create dentry for + * Return: Allocated dentry or NULL on failure + */ struct dentry *d_make_root(struct inode *inode) { - return NULL; + struct dentry *de; + + if (!inode) + return NULL; + + de = kzalloc(sizeof(struct dentry), GFP_KERNEL); + if (!de) { + iput(inode); + return NULL; + } + + de->d_inode = inode; + de->d_sb = inode->i_sb; + de->d_name.name = "/"; + de->d_name.len = 1; + + return de; } /* percpu init rwsem */ diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index d1bc32ae8d6..9e93426b565 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -19,6 +19,13 @@ #include "ext4_uboot.h" #include "ext4.h" +/* + * Global task_struct for U-Boot. + * This must be a single global instance shared across all translation units, + * so that journal_info remains consistent. + */ +struct task_struct ext4l_current_task = { .comm = "u-boot", .pid = 1 }; + /* * CRC32C support - uses Castagnoli polynomial 0x82F63B78 * Table is initialised on first mount diff --git a/include/linux/types.h b/include/linux/types.h index 957284d37ab..975040b7980 100644 --- a/include/linux/types.h +++ b/include/linux/types.h @@ -165,6 +165,28 @@ typedef s64 ktime_t; typedef u64 sector_t; typedef u64 blkcnt_t; +/* Atomic types - stubs for single-threaded U-Boot */ +typedef struct { + int counter; +} atomic_t; + +#ifdef CONFIG_64BIT +typedef struct { + s64 counter; +} atomic64_t; +#else +typedef struct { + long counter; +} atomic64_t; +#endif + +#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)) + #ifdef __linux__ struct ustat { __kernel_daddr_t f_tfree; diff --git a/tools/buildman/func_test.py b/tools/buildman/func_test.py index 6fc08e02fb8..5b112c81aea 100644 --- a/tools/buildman/func_test.py +++ b/tools/buildman/func_test.py @@ -1267,3 +1267,4 @@ something: me boards.ExtendedParser.parse_data('bert', 'name: katie was here') self.assertEqual('bert:1: Invalid name', str(exc.exception)) + diff --git a/tools/buildman/main.py b/tools/buildman/main.py index 77b9bebed27..9483e12e5d0 100755 --- a/tools/buildman/main.py +++ b/tools/buildman/main.py @@ -41,6 +41,7 @@ def run_tests(skip_net_tests, debug, verbose, args): # pylint: disable=C0415 from buildman import func_test from buildman import test + from buildman import test_boards test_name = args.terms and args.terms[0] or None if skip_net_tests: @@ -50,7 +51,8 @@ def run_tests(skip_net_tests, debug, verbose, args): # 'entry' module. result = test_util.run_test_suites( 'buildman', debug, verbose, False, False, args.threads, test_name, [], - [test.TestBuild, func_test.TestFunctional, 'buildman.toolchain']) + [test.TestBuild, func_test.TestFunctional, test_boards.TestBoards, + 'buildman.toolchain']) return (0 if result.wasSuccessful() else 1) diff --git a/tools/buildman/test_boards.py b/tools/buildman/test_boards.py new file mode 100644 index 00000000000..66eb82bc755 --- /dev/null +++ b/tools/buildman/test_boards.py @@ -0,0 +1,739 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2024 Google, Inc +# + +"""Tests for boards.py""" + +import errno +import multiprocessing +import os +from pathlib import Path +import shutil +import tempfile +import time +import unittest +from unittest import mock + +from buildman import board +from buildman import boards +from buildman.boards import Extended +from u_boot_pylib import terminal +from u_boot_pylib import tools + + +BOARDS = [ + ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 0', 'board0', ''], + ['Active', 'arm', 'armv7', '', 'Tester', 'ARM Board 1', 'board1', ''], + ['Active', 'powerpc', 'powerpc', '', 'Tester', 'PowerPC board 1', 'board2', ''], + ['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''], +] + + +class TestBoards(unittest.TestCase): + """Test boards.py functionality""" + + def setUp(self): + self._base_dir = tempfile.mkdtemp() + self._output_dir = tempfile.mkdtemp() + self._git_dir = os.path.join(self._base_dir, 'src') + self._buildman_dir = os.path.dirname(os.path.realpath(__file__)) + self._test_dir = os.path.join(self._buildman_dir, 'test') + + # Set up some fake source files + shutil.copytree(self._test_dir, self._git_dir) + + # Avoid sending any output and clear all terminal output + terminal.set_print_test_mode() + terminal.get_print_test_lines() + + self._boards = boards.Boards() + for brd in BOARDS: + self._boards.add_board(board.Board(*brd)) + + def tearDown(self): + shutil.rmtree(self._base_dir) + shutil.rmtree(self._output_dir) + + def test_try_remove(self): + """Test try_remove() function""" + # Test removing a file that doesn't exist - should not raise + boards.try_remove('/nonexistent/path/to/file') + + # Test removing a file that does exist + fname = os.path.join(self._base_dir, 'test_remove') + tools.write_file(fname, b'test') + self.assertTrue(os.path.exists(fname)) + boards.try_remove(fname) + self.assertFalse(os.path.exists(fname)) + + def test_read_boards(self): + """Test Boards.read_boards() with various field counts""" + # Test normal boards.cfg file + boards_cfg = os.path.join(self._base_dir, 'boards.cfg') + content = '''# Comment line +Active arm armv7 - Tester ARM_Board_0 board0 config0 maint@test.com +Active powerpc ppc mpc85xx Tester PPC_Board_1 board2 config2 maint2@test.com + +''' + tools.write_file(boards_cfg, content.encode('utf-8')) + + brds = boards.Boards() + brds.read_boards(boards_cfg) + board_list = brds.get_list() + self.assertEqual(2, len(board_list)) + self.assertEqual('board0', board_list[0].target) + self.assertEqual('arm', board_list[0].arch) + self.assertEqual('', board_list[0].soc) # '-' converted to '' + self.assertEqual('mpc85xx', board_list[1].soc) + + # Test with fewer than 8 fields + boards_cfg = os.path.join(self._base_dir, 'boards_short.cfg') + content = '''Active arm armv7 - Tester Board target config +''' + tools.write_file(boards_cfg, content.encode('utf-8')) + brds = boards.Boards() + brds.read_boards(boards_cfg) + self.assertEqual(1, len(brds.get_list())) + + # Test with more than 8 fields (extra fields ignored) + boards_cfg = os.path.join(self._base_dir, 'boards_extra.cfg') + content = '''Active arm armv7 soc Tester Board target config maint extra +''' + tools.write_file(boards_cfg, content.encode('utf-8')) + brds = boards.Boards() + brds.read_boards(boards_cfg) + self.assertEqual('config', brds.get_list()[0].cfg_name) + + def test_boards_methods(self): + """Test Boards helper methods: get_dict, get_selected_names, find_by_target""" + brds = boards.Boards() + for brd in BOARDS: + brds.add_board(board.Board(*brd)) + + # Test get_dict() + board_dict = brds.get_dict() + self.assertEqual(4, len(board_dict)) + self.assertEqual('arm', board_dict['board0'].arch) + self.assertEqual('sandbox', board_dict['board4'].arch) + + # Test get_selected_names() + brds.select_boards(['arm']) + self.assertEqual(['board0', 'board1'], brds.get_selected_names()) + + # Test select_boards warning for missing board + brds2 = boards.Boards() + for brd in BOARDS: + brds2.add_board(board.Board(*brd)) + result, warnings = brds2.select_boards([], brds=['nonexistent', 'board0']) + self.assertEqual(1, len(warnings)) + self.assertIn('nonexistent', warnings[0]) + + # Test find_by_target() + found = brds.find_by_target('board0') + self.assertEqual('arm', found.arch) + + with terminal.capture() as (stdout, stderr): + with self.assertRaises(ValueError) as exc: + brds.find_by_target('nonexistent') + self.assertIn('nonexistent', str(exc.exception)) + + def test_kconfig_riscv(self): + """Test KconfigScanner riscv architecture detection""" + src = self._git_dir + kc_file = os.path.join(src, 'Kconfig') + orig_kc_data = tools.read_file(kc_file) + + riscv_kconfig = orig_kc_data + b''' + +config RISCV +\tbool + +config ARCH_RV32I +\tbool + +config TARGET_RISCV_BOARD +\tbool "RISC-V Board" +\tselect RISCV +\tdefault n + +if TARGET_RISCV_BOARD +config SYS_ARCH +\tdefault "riscv" + +config SYS_CPU +\tdefault "generic" + +config SYS_VENDOR +\tdefault "RiscVendor" + +config SYS_BOARD +\tdefault "RISC-V Board" + +config SYS_CONFIG_NAME +\tdefault "riscv_config" +endif +''' + tools.write_file(kc_file, riscv_kconfig) + + try: + scanner = boards.KconfigScanner(src) + defconfig = os.path.join(src, 'riscv64_defconfig') + tools.write_file(defconfig, 'CONFIG_TARGET_RISCV_BOARD=y\n', False) + + # Test riscv64 (no RV32I) + res, warnings = scanner.scan(defconfig, False) + self.assertEqual('riscv64', res['arch']) + + # Test riscv32 (with RV32I) + riscv32_kconfig = riscv_kconfig + b''' +config ARCH_RV32I +\tdefault y if TARGET_RISCV_BOARD +''' + tools.write_file(kc_file, riscv32_kconfig) + scanner = boards.KconfigScanner(src) + res, warnings = scanner.scan(defconfig, False) + self.assertEqual('riscv32', res['arch']) + finally: + tools.write_file(kc_file, orig_kc_data) + + def test_maintainers_commented(self): + """Test MaintainersDatabase with commented maintainer lines""" + src = self._git_dir + main = os.path.join(src, 'boards', 'board0', 'MAINTAINERS') + config_dir = os.path.join(src, 'configs') + orig_data = tools.read_file(main, binary=False) + + new_data = '#M: Commented Maintainer <comment@test.com>\n' + orig_data + tools.write_file(main, new_data, binary=False) + + try: + params_list, warnings = self._boards.build_board_list(config_dir, src) + self.assertEqual(2, len(params_list)) + finally: + tools.write_file(main, orig_data, binary=False) + + def test_ensure_board_list_options(self): + """Test ensure_board_list() with force and quiet flags""" + outfile = os.path.join(self._output_dir, 'test-boards-opts.cfg') + brds = boards.Boards() + + # Test force=False, quiet=False (normal generation) + with terminal.capture() as (stdout, stderr): + brds.ensure_board_list(outfile, jobs=1, force=False, quiet=False) + self.assertTrue(os.path.exists(outfile)) + + # Test force=True (regenerate even if current) + with terminal.capture() as (stdout, stderr): + brds.ensure_board_list(outfile, jobs=1, force=True, quiet=False) + self.assertTrue(os.path.exists(outfile)) + + # Test quiet=True (minimal output) + with terminal.capture() as (stdout, stderr): + brds.ensure_board_list(outfile, jobs=1, force=False, quiet=True) + self.assertNotIn('Checking', stdout.getvalue()) + + # Test quiet=True when up to date (no output) + with terminal.capture() as (stdout, stderr): + result = brds.ensure_board_list(outfile, jobs=1, force=False, + quiet=True) + self.assertTrue(result) + self.assertEqual('', stdout.getvalue()) + + def test_output_is_new_old_format(self): + """Test output_is_new() with old format containing Options""" + src = self._git_dir + config_dir = os.path.join(src, 'configs') + boards_cfg = os.path.join(self._base_dir, 'boards_old.cfg') + + content = b'''# +# List of boards +# +# Status, Arch, CPU, SoC, Vendor, Board, Target, Options, Maintainers + +Active arm armv7 - Tester Board board0 options maint +''' + tools.write_file(boards_cfg, content) + self.assertFalse(boards.output_is_new(boards_cfg, config_dir, src)) + + def test_maintainers_status(self): + """Test MaintainersDatabase.get_status() with various statuses""" + database = boards.MaintainersDatabase() + + # Test missing target + self.assertEqual('-', database.get_status('missing')) + self.assertIn("no status info for 'missing'", database.warnings[-1]) + + # Test 'Supported' maps to Active + database.database['test1'] = ('Supported', ['maint@test.com']) + self.assertEqual('Active', database.get_status('test1')) + + # Test 'Orphan' status + database.database['orphan'] = ('Orphan', ['maint@test.com']) + self.assertEqual('Orphan', database.get_status('orphan')) + + # Test unknown status + database.database['test2'] = ('Unknown Status', ['maint@test.com']) + self.assertEqual('-', database.get_status('test2')) + self.assertIn("unknown status for 'test2'", database.warnings[-1]) + + def test_expr_term_str(self): + """Test Expr and Term __str__() methods""" + expr = boards.Expr('arm.*') + self.assertEqual('arm.*', str(expr)) + + term = boards.Term() + term.add_expr('arm') + term.add_expr('cortex') + self.assertEqual('arm&cortex', str(term)) + + def test_kconfig_scanner_warnings(self): + """Test KconfigScanner.scan() TARGET_xxx warnings""" + src = self._git_dir + kc_file = os.path.join(src, 'Kconfig') + orig_kc_data = tools.read_file(kc_file) + + # Test missing TARGET_xxx warning + defconfig = os.path.join(src, 'configs', 'no_target_defconfig') + tools.write_file(defconfig, 'CONFIG_SYS_ARCH="arm"\n', False) + try: + scanner = boards.KconfigScanner(src) + res, warnings = scanner.scan(defconfig, warn_targets=True) + self.assertEqual(1, len(warnings)) + self.assertIn('No TARGET_NO_TARGET enabled', warnings[0]) + finally: + if os.path.exists(defconfig): + os.remove(defconfig) + + # Test duplicate TARGET_xxx warning + extra = b''' +config TARGET_BOARD0_DUP +\tbool "Duplicate target" +\tdefault y if TARGET_BOARD0 +''' + tools.write_file(kc_file, orig_kc_data + extra) + try: + scanner = boards.KconfigScanner(src) + defconfig = os.path.join(src, 'configs', 'board0_defconfig') + res, warnings = scanner.scan(defconfig, warn_targets=True) + self.assertEqual(1, len(warnings)) + self.assertIn('Duplicate TARGET_xxx', warnings[0]) + finally: + tools.write_file(kc_file, orig_kc_data) + + def test_scan_extended(self): + """Test scan_extended() for finding boards matching extended criteria""" + brds = boards.Boards() + + # Test with CONFIG-based selection (value=y) + ext = Extended( + name='test_ext', + desc='Test extended board', + fragments=['test_frag'], + targets=[['CONFIG_ARM', 'y']]) + + with mock.patch('qconfig.find_config') as mock_find, \ + mock.patch.object(tools, 'read_file', return_value='CONFIG_TEST=y'): + mock_find.return_value = {'board0', 'board1'} + result = brds.scan_extended(None, ext) + self.assertEqual({'board0', 'board1'}, result) + mock_find.assert_called_once_with(None, ['CONFIG_ARM']) + + # Test with CONFIG-based selection (value=n) + ext = Extended( + name='test_ext2', + desc='Test extended board 2', + fragments=['test_frag'], + targets=[['CONFIG_DEBUG', 'n']]) + + with mock.patch('qconfig.find_config') as mock_find, \ + mock.patch.object(tools, 'read_file', return_value=''): + mock_find.return_value = {'board2'} + result = brds.scan_extended(None, ext) + self.assertEqual({'board2'}, result) + mock_find.assert_called_once_with(None, ['~CONFIG_DEBUG']) + + # Test with CONFIG-based selection (specific value) + ext = Extended( + name='test_ext3', + desc='Test extended board 3', + fragments=['test_frag'], + targets=[['CONFIG_SYS_SOC', '"k3"']]) + + with mock.patch('qconfig.find_config') as mock_find, \ + mock.patch.object(tools, 'read_file', return_value=''): + mock_find.return_value = {'board4'} + result = brds.scan_extended(None, ext) + self.assertEqual({'board4'}, result) + mock_find.assert_called_once_with(None, ['CONFIG_SYS_SOC="k3"']) + + # Test with regex pattern - intersection of glob and find_config + ext = Extended( + name='test_ext4', + desc='Test extended board 4', + fragments=['test_frag'], + targets=[['regex', 'configs/board*_defconfig']]) + + with mock.patch('qconfig.find_config') as mock_find, \ + mock.patch.object(tools, 'read_file', return_value=''), \ + mock.patch('glob.glob') as mock_glob: + mock_glob.return_value = ['configs/board0_defconfig', + 'configs/board2_defconfig'] + mock_find.return_value = {'board0', 'board1', 'board2'} + result = brds.scan_extended(None, ext) + # Should be intersection: {board0, board2} & {board0, board1, board2} + self.assertEqual({'board0', 'board2'}, result) + + def test_parse_extended(self): + """Test parse_extended() for creating extended board entries""" + brds = boards.Boards() + for brd in BOARDS: + brds.add_board(board.Board(*brd)) + + # Create a .buildman file + buildman_file = os.path.join(self._base_dir, 'test.buildman') + content = '''name: test_acpi +desc: Test ACPI boards +fragment: acpi +targets: + CONFIG_ARM=y +''' + tools.write_file(buildman_file, content, binary=False) + + # Mock scan_extended to return specific boards + with mock.patch.object(brds, 'scan_extended') as mock_scan: + mock_scan.return_value = {'board0', 'board1'} + brds.parse_extended(None, buildman_file) + + # Check that new extended boards were added + board_list = brds.get_list() + # Original 4 boards + 2 extended boards + self.assertEqual(6, len(board_list)) + + # Find the extended boards + ext_boards = [b for b in board_list if ',' in b.target] + self.assertEqual(2, len(ext_boards)) + + # Check the extended board properties + ext_board = next(b for b in ext_boards if 'board0' in b.target) + self.assertEqual('test_acpi,board0', ext_board.target) + self.assertEqual('arm', ext_board.arch) + self.assertEqual('board0', ext_board.orig_target) + self.assertIsNotNone(ext_board.extended) + self.assertEqual('test_acpi', ext_board.extended.name) + + def test_try_remove_other_error(self): + """Test try_remove() re-raises non-ENOENT errors""" + with mock.patch('os.remove') as mock_remove: + # Simulate a permission error (not ENOENT) + err = OSError(errno.EACCES, 'Permission denied') + mock_remove.side_effect = err + with self.assertRaises(OSError) as exc: + boards.try_remove('/some/file') + self.assertEqual(errno.EACCES, exc.exception.errno) + + def test_output_is_new_other_error(self): + """Test output_is_new() re-raises non-ENOENT errors""" + with mock.patch('os.path.getctime') as mock_ctime: + err = OSError(errno.EACCES, 'Permission denied') + mock_ctime.side_effect = err + with self.assertRaises(OSError) as exc: + boards.output_is_new('/some/file', 'configs', '.') + self.assertEqual(errno.EACCES, exc.exception.errno) + + def test_output_is_new_hidden_files(self): + """Test output_is_new() skips hidden defconfig files""" + base = self._base_dir + src = self._git_dir + config_dir = os.path.join(src, 'configs') + + # Create boards.cfg + boards_cfg = os.path.join(base, 'boards_hidden.cfg') + content = b'''# +# List of boards +# Automatically generated by buildman/boards.py: don't edit +# +# Status, Arch, CPU, SoC, Vendor, Board, Target, Config, Maintainers + +Active arm armv7 - Tester Board board0 config0 maint +''' + tools.write_file(boards_cfg, content) + + # Create a hidden defconfig file (should be skipped) + hidden = os.path.join(config_dir, '.hidden_defconfig') + tools.write_file(hidden, b'# hidden') + + try: + # Touch boards.cfg to make it newer + time.sleep(0.02) + Path(boards_cfg).touch() + # Should return True (hidden file skipped) + self.assertTrue(boards.output_is_new(boards_cfg, config_dir, src)) + finally: + os.remove(hidden) + + def test_kconfig_scanner_destructor(self): + """Test KconfigScanner.__del__() cleans up leftover temp file""" + src = self._git_dir + scanner = boards.KconfigScanner(src) + + # Simulate a leftover temp file + tmpfile = os.path.join(self._base_dir, 'leftover.tmp') + tools.write_file(tmpfile, b'temp') + scanner._tmpfile = tmpfile + + # Delete the scanner - should clean up the temp file + del scanner + self.assertFalse(os.path.exists(tmpfile)) + + def test_kconfig_scanner_aarch64(self): + """Test KconfigScanner.scan() aarch64 fix-up""" + src = self._git_dir + kc_file = os.path.join(src, 'Kconfig') + orig_kc_data = tools.read_file(kc_file) + + # Add aarch64 board to Kconfig + aarch64_kconfig = orig_kc_data + b''' + +config TARGET_AARCH64_BOARD +\tbool "AArch64 Board" +\tdefault n + +if TARGET_AARCH64_BOARD +config SYS_ARCH +\tdefault "arm" + +config SYS_CPU +\tdefault "armv8" + +config SYS_VENDOR +\tdefault "Test" + +config SYS_BOARD +\tdefault "AArch64 Board" + +config SYS_CONFIG_NAME +\tdefault "aarch64_config" +endif +''' + tools.write_file(kc_file, aarch64_kconfig) + + try: + scanner = boards.KconfigScanner(src) + defconfig = os.path.join(src, 'aarch64_defconfig') + tools.write_file(defconfig, 'CONFIG_TARGET_AARCH64_BOARD=y\n', False) + res, warnings = scanner.scan(defconfig, False) + # Should be fixed up to aarch64 + self.assertEqual('aarch64', res['arch']) + finally: + tools.write_file(kc_file, orig_kc_data) + if os.path.exists(defconfig): + os.remove(defconfig) + + def test_read_boards_short_line(self): + """Test Boards.read_boards() pads short lines to 8 fields""" + boards_cfg = os.path.join(self._base_dir, 'boards_veryshort.cfg') + # Create a board with only 7 fields (missing maintainers) + content = '''Active arm armv7 soc Tester Board target +''' + tools.write_file(boards_cfg, content.encode('utf-8')) + + brds = boards.Boards() + brds.read_boards(boards_cfg) + board_list = brds.get_list() + self.assertEqual(1, len(board_list)) + # cfg_name should be empty string (padded) + self.assertEqual('', board_list[0].cfg_name) + + def test_ensure_board_list_up_to_date_message(self): + """Test ensure_board_list() shows up-to-date message""" + outfile = os.path.join(self._output_dir, 'test-boards-uptodate.cfg') + brds = boards.Boards() + + # First generate the file + with terminal.capture() as (stdout, stderr): + brds.ensure_board_list(outfile, jobs=1, force=False, quiet=False) + + # Run again - should say "up to date" + with terminal.capture() as (stdout, stderr): + result = brds.ensure_board_list(outfile, jobs=1, force=False, + quiet=False) + self.assertTrue(result) + self.assertIn('up to date', stdout.getvalue()) + + def test_ensure_board_list_warnings(self): + """Test ensure_board_list() prints warnings to stderr""" + outfile = os.path.join(self._output_dir, 'test-boards-warn.cfg') + brds = boards.Boards() + + # Mock build_board_list to return warnings + with mock.patch.object(brds, 'build_board_list') as mock_build: + mock_build.return_value = ([], ['WARNING: test warning']) + with terminal.capture() as (stdout, stderr): + result = brds.ensure_board_list(outfile, jobs=1, force=True, + quiet=False) + self.assertFalse(result) + self.assertIn('WARNING: test warning', stderr.getvalue()) + + def test_parse_all_extended(self): + """Test parse_all_extended() finds and parses .buildman files""" + brds = boards.Boards() + for brd in BOARDS: + brds.add_board(board.Board(*brd)) + + # Mock glob to return a .buildman file and parse_extended + with mock.patch('glob.glob') as mock_glob, \ + mock.patch.object(brds, 'parse_extended') as mock_parse: + mock_glob.return_value = ['configs/test.buildman'] + brds.parse_all_extended(None) + mock_glob.assert_called_once_with('configs/*.buildman') + mock_parse.assert_called_once_with(None, 'configs/test.buildman') + + def test_scan_extended_no_match_warning(self): + """Test scan_extended() warns when no configs match regex""" + brds = boards.Boards() + + ext = Extended( + name='test_ext', + desc='Test extended board', + fragments=['test_frag'], + targets=[['regex', 'nonexistent*_defconfig']]) + + with mock.patch('qconfig.find_config') as mock_find, \ + mock.patch.object(tools, 'read_file', return_value=''), \ + mock.patch('glob.glob') as mock_glob, \ + terminal.capture() as (stdout, stderr): + mock_glob.return_value = [] # No matches + mock_find.return_value = set() + result = brds.scan_extended(None, ext) + self.assertEqual(set(), result) + # Warning should be printed + self.assertIn('Warning', stdout.getvalue()) + + def test_kconfig_scanner_riscv_no_rv32i(self): + """Test KconfigScanner.scan() when ARCH_RV32I symbol doesn't exist""" + src = self._git_dir + kc_file = os.path.join(src, 'Kconfig') + orig_kc_data = tools.read_file(kc_file) + + # Add RISCV board WITHOUT defining ARCH_RV32I symbol + # This will cause syms.get('ARCH_RV32I') to return None, + # and accessing .str_value on None raises AttributeError + riscv_kconfig = orig_kc_data + b''' + +config RISCV +\tbool + +config TARGET_RISCV_TEST +\tbool "RISC-V Test Board" +\tdefault n + +if TARGET_RISCV_TEST +config SYS_ARCH +\tdefault "riscv" + +config SYS_CPU +\tdefault "generic" + +config SYS_VENDOR +\tdefault "Test" + +config SYS_BOARD +\tdefault "RISCV Test" + +config SYS_CONFIG_NAME +\tdefault "riscv_test" +endif +''' + tools.write_file(kc_file, riscv_kconfig) + defconfig = os.path.join(src, 'riscv_test_defconfig') + + try: + # Create defconfig that enables the riscv board + tools.write_file(defconfig, 'CONFIG_TARGET_RISCV_TEST=y\n', False) + + scanner = boards.KconfigScanner(src) + res, warnings = scanner.scan(defconfig, False) + + # Should default to riscv64 when ARCH_RV32I lookup fails + self.assertEqual('riscv64', res['arch']) + finally: + tools.write_file(kc_file, orig_kc_data) + if os.path.exists(defconfig): + os.remove(defconfig) + + def test_scan_defconfigs_for_multiprocess(self): + """Test scan_defconfigs_for_multiprocess() function directly""" + src = self._git_dir + config_dir = os.path.join(src, 'configs') + + # Get a list of defconfigs + defconfigs = [os.path.join(config_dir, 'board0_defconfig')] + + # Create a queue and call the function + queue = multiprocessing.Queue() + boards.Boards.scan_defconfigs_for_multiprocess(src, queue, defconfigs, + False) + + # Get the result from the queue + result = queue.get(timeout=5) + params, warnings = result + self.assertEqual('board0', params['target']) + self.assertEqual('arm', params['arch']) + + def test_scan_defconfigs_hidden_files(self): + """Test scan_defconfigs() skips hidden defconfig files""" + src = self._git_dir + config_dir = os.path.join(src, 'configs') + + # Create a hidden defconfig + hidden = os.path.join(config_dir, '.hidden_defconfig') + tools.write_file(hidden, b'CONFIG_TARGET_BOARD0=y') + + try: + brds = boards.Boards() + params_list, warnings = brds.scan_defconfigs(config_dir, src, 1) + + # Hidden file should not be in results + targets = [p['target'] for p in params_list] + self.assertNotIn('.hidden', targets) + # But regular boards should be there + self.assertIn('board0', targets) + finally: + os.remove(hidden) + + def test_maintainers_n_tag_non_configs_path(self): + """Test MaintainersDatabase N: tag skips non-configs paths""" + src = self._git_dir + + # Create a MAINTAINERS file with N: tag + maintainers_file = os.path.join(src, 'MAINTAINERS_TEST') + maintainers_content = '''BOARD0 +M: Test <test@test.com> +S: Active +N: .* +''' + tools.write_file(maintainers_file, maintainers_content, binary=False) + + # Mock os.walk to return a path that doesn't start with 'configs/' + # when walking the configs directory. This tests line 443. + def mock_walk(path): + # Return paths with 'configs/' prefix (normal) and without (edge case) + yield (os.path.join(src, 'configs'), [], ['board0_defconfig']) + # This path will have 'other/' prefix after srcdir removal + yield (os.path.join(src, 'other'), [], ['fred_defconfig']) + + try: + database = boards.MaintainersDatabase() + with mock.patch('os.walk', mock_walk): + database.parse_file(src, maintainers_file) + + # board0 should be found (path starts with configs/) + # fred should be skipped (path starts with other/, not configs/) + self.assertIn('board0', database.database) + self.assertNotIn('fred', database.database) + finally: + os.remove(maintainers_file) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/pickman/README.rst b/tools/pickman/README.rst index 8e309af8837..37c3aac038f 100644 --- a/tools/pickman/README.rst +++ b/tools/pickman/README.rst @@ -124,34 +124,66 @@ Pickman will add ``[skipped]`` to the MR title. Skipped MRs: - Don't block the ``step`` or ``poll`` commands from proceeding - Can be unskipped by commenting ``pickman unskip`` -CI Pipelines ------------- +Already-Applied Detection +------------------------- + +Sometimes commits have already been applied to the target branch through a +different path (e.g., directly merged or cherry-picked with different hashes). +Pickman's Claude agent detects this situation automatically. + +**How it works** + +When the first cherry-pick fails with conflicts, the agent checks if the +commits are already present in the target branch by searching for matching +commit subjects:: + + git log --oneline ci/master --grep="<subject>" -1 + +If all commits in the set are found (same subjects, different hashes), the +agent: -Pickman manages CI pipelines to avoid unnecessary runs while ensuring changes -are properly verified. +1. Aborts the cherry-pick +2. Writes a signal file (``.pickman-signal``) with status ``already_applied`` +3. Reports the situation -**Initial MR creation** +**What pickman does** + +When pickman detects the ``already_applied`` signal: + +1. Marks all commits as 'skipped' in the database +2. Updates the source position to advance past these commits +3. Creates an MR with ``[skipped]`` prefix to record the attempt +4. The MR description explains that commits were already applied + +This ensures: + +- There's a record of what was attempted +- The source position advances so the next ``poll`` iteration processes new + commits +- No manual intervention is required to continue + +CI Pipelines +------------ -When creating a new MR (via ``apply -p`` or ``step``), pickman pushes the -branch with ``-o ci.skip``. This skips the push pipeline because GitLab -automatically triggers an MR pipeline when the merge request is created. -Without this, two pipelines would run: one for the push and one for the MR. +Pickman manages CI pipelines to avoid unnecessary duplicate runs. GitLab +automatically triggers an MR pipeline whenever the source branch is updated, +so pickman skips the push pipeline to avoid running two pipelines. -**Review comment handling** +**How it works** -When pushing changes after addressing review comments (via ``review``, -``step``, or ``poll``), pickman does NOT skip the pipeline. A new pipeline -is needed to verify that the changes made in response to review feedback -are correct. +When pushing a branch (for new MRs or updates), pickman uses ``-o ci.skip`` +to skip the push pipeline. GitLab then triggers an MR pipeline when it +detects the branch update on the merge request. This ensures exactly one +pipeline runs for each push. **Summary** -=============================== ================ ========================= +=============================== ================ ============================== Action Pipeline Skipped Reason -=============================== ================ ========================= -Initial branch push for new MR Yes MR creation triggers one -Push after review changes No Need to verify changes -=============================== ================ ========================= +=============================== ================ ============================== +Initial branch push for new MR Yes MR creation triggers pipeline +Push after rebase/review Yes MR update triggers pipeline +=============================== ================ ============================== Usage ----- diff --git a/tools/pickman/agent.py b/tools/pickman/agent.py index f3cec2e43bd..4570312a97c 100644 --- a/tools/pickman/agent.py +++ b/tools/pickman/agent.py @@ -16,6 +16,14 @@ sys.path.insert(0, os.path.join(our_path, '..')) # pylint: disable=wrong-import-position,import-error from u_boot_pylib import tout +# Signal file for agent to communicate status back to pickman +SIGNAL_FILE = '.pickman-signal' + +# Signal status codes +SIGNAL_SUCCESS = 'success' +SIGNAL_ALREADY_APPLIED = 'already_applied' +SIGNAL_CONFLICT = 'conflict' + # Check if claude_agent_sdk is available try: from claude_agent_sdk import query, ClaudeAgentOptions @@ -55,12 +63,20 @@ async def run(commits, source, branch_name, repo_path=None): if repo_path is None: repo_path = os.getcwd() + # Remove any stale signal file from previous runs + signal_path = os.path.join(repo_path, SIGNAL_FILE) + if os.path.exists(signal_path): + os.remove(signal_path) + # Build commit list for the prompt commit_list = '\n'.join( f' - {short_hash}: {subject}' for _, short_hash, subject in commits ) + # Get full hash of last commit for signal file + last_commit_hash = commits[-1][0] + prompt = f"""Cherry-pick the following commits from {source} branch: {commit_list} @@ -94,6 +110,20 @@ Important: - Do not force push or modify history - If cherry-pick fails, run 'git cherry-pick --abort' - NEVER skip merge commits - always use --allow-empty to preserve them + +CRITICAL - Detecting Already-Applied Commits: +If the FIRST cherry-pick fails with conflicts, BEFORE aborting, check if the commits +are already present in ci/master with different hashes. Do this by searching for +commit subjects in ci/master: + git log --oneline ci/master --grep="<subject>" -1 +If ALL commits are already in ci/master (same subjects, different hashes), this means +the series was already applied via a different path. In this case: +1. Abort the cherry-pick: git cherry-pick --abort +2. Delete the branch: git branch -D {branch_name} +3. Write a signal file to indicate this status: + echo "already_applied" > {SIGNAL_FILE} + echo "{last_commit_hash}" >> {SIGNAL_FILE} +4. Report that all {len(commits)} commits are already present in ci/master """ options = ClaudeAgentOptions( @@ -119,6 +149,39 @@ Important: return False, '\n\n'.join(conversation_log) +def read_signal_file(repo_path=None): + """Read and remove the signal file if it exists + + Args: + repo_path (str): path to repository (defaults to current directory) + + Returns: + tuple: (status, last_commit) where status is the signal status code + (e.g., 'already_applied') and last_commit is the commit hash, + or (None, None) if no signal file exists + """ + if repo_path is None: + repo_path = os.getcwd() + + signal_path = os.path.join(repo_path, SIGNAL_FILE) + if not os.path.exists(signal_path): + return None, None + + try: + with open(signal_path, 'r', encoding='utf-8') as fhandle: + lines = fhandle.read().strip().split('\n') + status = lines[0] if lines else None + last_commit = lines[1] if len(lines) > 1 else None + + # Remove the signal file after reading + os.remove(signal_path) + + return status, last_commit + except (IOError, OSError) as exc: + tout.warning(f'Failed to read signal file: {exc}') + return None, None + + def cherry_pick_commits(commits, source, branch_name, repo_path=None): """Synchronous wrapper for running the cherry-pick agent @@ -234,8 +297,8 @@ Steps to follow: 4. Run 'buildman -L --board sandbox -w -o /tmp/pickman' to verify the build 5. Create a local branch with suffix '-v2' (or increment: -v3, -v4, etc.) 6. Force push to the ORIGINAL remote branch to update the MR: - ./tools/pickman/pickman push-branch {branch_name} -r {remote} -f --run-ci - (--run-ci triggers a pipeline to verify the rebased changes) + ./tools/pickman/pickman push-branch {branch_name} -r {remote} -f + (GitLab automatically triggers an MR pipeline when the branch is updated) 7. Report what was done and what reply should be posted to the MR Important: @@ -243,7 +306,7 @@ Important: - If a comment is unclear or cannot be addressed, note this in your report - Local branch: {branch_name}-v2 (or -v3, -v4 etc.) - Remote push: always to '{branch_name}' to update the existing MR -- Always use --run-ci when pushing rebases/updates to trigger verification +- Do NOT update the MR title - it should remain as originally set """ diff --git a/tools/pickman/control.py b/tools/pickman/control.py index c06b8abea17..b14c830f1d4 100644 --- a/tools/pickman/control.py +++ b/tools/pickman/control.py @@ -683,6 +683,72 @@ def prepare_apply(dbs, source, branch): return ApplyInfo(commits, branch_name, original_branch, merge_found), 0 +# pylint: disable=too-many-arguments +def handle_already_applied(dbs, source, commits, branch_name, conv_log, args, + signal_commit): + """Handle the case where commits are already applied to the target branch + + Creates an MR with [skip] prefix to record the attempt and updates the + source position in the database. + + Args: + dbs (Database): Database instance + source (str): Source branch name + commits (list): List of CommitInfo namedtuples + branch_name (str): Branch name that was attempted + conv_log (str): Conversation log from the agent + args (Namespace): Parsed arguments with 'push', 'remote', 'target' + signal_commit (str): Last commit hash from signal file + + Returns: + int: 0 on success, 1 on failure + """ + tout.info('Commits already applied to target branch - creating skip MR') + + # Mark commits as 'skipped' in database + for commit in commits: + dbs.commit_set_status(commit.hash, 'skipped') + dbs.commit() + + # Update source position to the last commit (or signal_commit if provided) + last_hash = signal_commit if signal_commit else commits[-1].hash + dbs.source_set(source, last_hash) + dbs.commit() + tout.info(f"Updated source '{source}' to {last_hash[:12]}") + + # Push and create MR with [skip] prefix if requested + if args.push: + remote = args.remote + target = args.target + + # Create a skip branch from ci/master (no changes) + try: + run_git(['checkout', '-b', branch_name, f'{remote}/{target}']) + except Exception: # pylint: disable=broad-except + # Branch may already exist from failed attempt + try: + run_git(['checkout', branch_name]) + except Exception: # pylint: disable=broad-except + tout.error(f'Could not create/checkout branch {branch_name}') + return 1 + + # Use merge commit subject as title with [skip] prefix + title = f'{SKIPPED_TAG} [pickman] {commits[-1].subject}' + summary = format_history_summary(source, commits, branch_name) + description = (f'{summary}\n\n' + f'**Status:** Commits already applied to {target} ' + f'with different hashes.\n\n' + f'### Conversation log\n{conv_log}') + + mr_url = gitlab_api.push_and_create_mr( + remote, branch_name, target, title, description + ) + if not mr_url: + return 1 + + return 0 + + def execute_apply(dbs, source, commits, branch_name, args): # pylint: disable=too-many-locals """Execute the apply operation: run agent, update database, push MR @@ -709,6 +775,13 @@ def execute_apply(dbs, source, commits, branch_name, args): # pylint: disable=t success, conv_log = agent.cherry_pick_commits(commit_tuples, source, branch_name) + # Check for signal file from agent + signal_status, signal_commit = agent.read_signal_file() + if signal_status == agent.SIGNAL_ALREADY_APPLIED: + ret = handle_already_applied(dbs, source, commits, branch_name, + conv_log, args, signal_commit) + return ret, False, conv_log + # Verify the branch actually exists - agent may have aborted and deleted it if success: try: diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index ce0f61241f7..9d075586c76 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -2736,6 +2736,88 @@ class TestExecuteApply(unittest.TestCase): self.assertEqual(commit_rec[6], 'conflict') dbs.close() + def test_execute_apply_already_applied(self): + """Test execute_apply when agent detects commits already applied.""" + with terminal.capture(): + dbs = database.Database(self.db_path) + dbs.start() + dbs.source_set('us/next', 'abc123') + dbs.commit() + + commits = [control.CommitInfo('ggg777', 'ggg777g', 'Test commit', + 'Author'), + control.CommitInfo('hhh888', 'hhh888h', 'Merge commit', + 'Author')] + args = argparse.Namespace(push=False) + + # Agent returns success but leaves signal file + with mock.patch.object(control.agent, 'cherry_pick_commits', + return_value=(True, 'already applied log')): + with mock.patch.object(control.agent, 'read_signal_file', + return_value=(agent.SIGNAL_ALREADY_APPLIED, + 'hhh888')): + ret, success, _ = control.execute_apply( + dbs, 'us/next', commits, 'cherry-branch', args) + + # Should return success (skip MR created), but success=False + self.assertEqual(ret, 0) + self.assertFalse(success) + + # Check commits are marked as skipped + commit_rec = dbs.commit_get('ggg777') + self.assertEqual(commit_rec[6], 'skipped') + commit_rec = dbs.commit_get('hhh888') + self.assertEqual(commit_rec[6], 'skipped') + + # Check source was updated + source_commit = dbs.source_get('us/next') + self.assertEqual(source_commit, 'hhh888') + dbs.close() + + +class TestSignalFile(unittest.TestCase): + """Tests for signal file handling.""" + + def setUp(self): + """Set up test fixtures.""" + self.test_dir = tempfile.mkdtemp() + self.signal_path = os.path.join(self.test_dir, agent.SIGNAL_FILE) + + def tearDown(self): + """Clean up test fixtures.""" + if os.path.exists(self.signal_path): + os.unlink(self.signal_path) + os.rmdir(self.test_dir) + + def test_read_signal_file_not_exists(self): + """Test read_signal_file when file doesn't exist.""" + status, commit = agent.read_signal_file(self.test_dir) + self.assertIsNone(status) + self.assertIsNone(commit) + + def test_read_signal_file_already_applied(self): + """Test read_signal_file with already_applied status.""" + with open(self.signal_path, 'w', encoding='utf-8') as fhandle: + fhandle.write('already_applied\nabc123def456\n') + + status, commit = agent.read_signal_file(self.test_dir) + self.assertEqual(status, 'already_applied') + self.assertEqual(commit, 'abc123def456') + + # File should be removed after reading + self.assertFalse(os.path.exists(self.signal_path)) + + def test_read_signal_file_status_only(self): + """Test read_signal_file with only status line.""" + with open(self.signal_path, 'w', encoding='utf-8') as fhandle: + fhandle.write('conflict\n') + + status, commit = agent.read_signal_file(self.test_dir) + self.assertEqual(status, 'conflict') + self.assertIsNone(commit) + + self.assertFalse(os.path.exists(self.signal_path)) + class TestGetNextCommitsEmptyLine(unittest.TestCase): """Tests for get_next_commits with empty lines.""" -- 2.43.0
participants (1)
-
Simon Glass