mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading
@ 2026-03-12 14:44 Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 02/16] bootm: split preparatory step from handler invocation Ahmad Fatoum
                   ` (15 more replies)
  0 siblings, 16 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

This adds a new "loadable" abstraction that allows boot components (kernel,
initrd, FDT) to be represented without immediately loading their data.
Metadata can be queried via get_info() and actual loading happens on
demand via extract(), extract_into_buf() or mmap().

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/loadable.h        | 275 ++++++++++++++++++++++++++
 lib/Kconfig               |  16 ++
 lib/Makefile              |   2 +
 lib/loadable-compressed.c | 243 +++++++++++++++++++++++
 lib/loadable-file.c       | 340 ++++++++++++++++++++++++++++++++
 lib/loadable-mem.c        | 127 ++++++++++++
 lib/loadable.c            | 402 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 1405 insertions(+)
 create mode 100644 include/loadable.h
 create mode 100644 lib/loadable-compressed.c
 create mode 100644 lib/loadable-file.c
 create mode 100644 lib/loadable-mem.c
 create mode 100644 lib/loadable.c

diff --git a/include/loadable.h b/include/loadable.h
new file mode 100644
index 000000000000..79b1579be291
--- /dev/null
+++ b/include/loadable.h
@@ -0,0 +1,275 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __LOADABLE_H
+#define __LOADABLE_H
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/limits.h>
+#include <linux/bits.h>
+#include <linux/fs.h>
+#include <filetype.h>
+
+struct loadable;
+
+/**
+ * enum loadable_type - type of boot component
+ * @LOADABLE_UNSPECIFIED: unspecified type
+ * @LOADABLE_KERNEL: kernel image
+ * @LOADABLE_INITRD: initial ramdisk
+ * @LOADABLE_FDT: flattened device tree
+ * @LOADABLE_TEE: trusted execution environment
+ */
+enum loadable_type {
+	LOADABLE_UNSPECIFIED,
+	LOADABLE_KERNEL,
+	LOADABLE_INITRD,
+	LOADABLE_FDT,
+	LOADABLE_TEE,
+};
+
+/**
+ * loadable_type_tostr - convert loadable type enum to string
+ * @type: loadable type to convert
+ *
+ * Return: string representation of the type, or NULL for unknown types
+ */
+const char *loadable_type_tostr(enum loadable_type type);
+
+#define LOADABLE_SIZE_UNKNOWN		SIZE_MAX
+
+/**
+ * struct loadable_info - metadata about a loadable (no data loaded yet)
+ * @final_size: final size in bytes, or LOADABLE_SIZE_UNKNOWN if unknown
+ */
+struct loadable_info {
+	size_t final_size;
+};
+
+#define LOADABLE_EXTRACT_PARTIAL	BIT(0)
+
+/**
+ * struct loadable_ops - operations for a loadable
+ */
+struct loadable_ops {
+	/**
+	 * get_info - obtain metadata without loading data
+	 *
+	 * Must not allocate large buffers or decompress. Should read only
+	 * headers/properties needed to determine size and addresses.
+	 * Result is cached in loadable->info.
+	 */
+	int (*get_info)(struct loadable *l, struct loadable_info *info);
+
+	/**
+	 * extract_into_buf - load/decompress to target address
+	 *
+	 * @l: loadable
+	 * @load_addr: final RAM address where data should reside
+	 * @size: size of buffer at load_addr
+	 * @offset: offset within the loadable to start loading from
+	 * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+	 *
+	 * This is where data transfer happens.
+	 * For compressed data: decompress to load_addr.
+	 * For uncompressed data: read/copy to load_addr.
+	 *
+	 * Behavior:
+	 *   - Must respect the provided load_addr
+	 *   - Must check if buffer is sufficient, return -ENOSPC if too small
+	 *     unless flags & LOADABLE_EXTRACT_PARTIAL.
+	 *
+	 * Returns: actual number of bytes written on success, negative errno on error
+	 */
+	ssize_t (*extract_into_buf)(struct loadable *l, void *load_addr,
+				    size_t size, loff_t offset, unsigned flags);
+
+	/**
+	 * extract - load/decompress into newly allocated buffer
+	 *
+	 * @l: loadable
+	 * @size: on successful return, *size is set to the number of bytes extracted
+	 *
+	 * Allocates a buffer and extracts loadable data into it. The buffer
+	 * must be freed with free().
+	 *
+	 * Returns: allocated buffer, NULL for zero-size, or error pointer on failure
+	 */
+	void *(*extract)(struct loadable *l, size_t *size);
+
+	/**
+	 * mmap - memory map loaded/decompressed buffer
+	 *
+	 * @l: loadable
+	 * @size: on successful return, *size is set to the size of the memory map
+	 *
+	 * Prepares a memory map of the loadable without copying data.
+	 *
+	 * Returns: pointer to mapped data, NULL for zero-size, or MAP_FAILED on error
+	 */
+	const void *(*mmap)(struct loadable *l, size_t *size);
+
+	/**
+	 * munmap - release mmap'ed region
+	 *
+	 * @l: loadable
+	 * @buf: pointer returned by mmap
+	 * @size: size returned by mmap
+	 */
+	void (*munmap)(struct loadable *l, const void *buf, size_t size);
+
+	/**
+	 * release - free resources associated with this loadable
+	 *
+	 * @l: loadable
+	 *
+	 * Called during cleanup to free implementation-specific resources.
+	 */
+	void (*release)(struct loadable *l);
+};
+
+/**
+ * struct loadable - lazy-loadable boot component
+ * @name: descriptive name for debugging
+ * @type: type of component (kernel, initrd, fdt, tee)
+ * @ops: operations for this loadable
+ * @priv: format-specific private data
+ * @info: cached metadata populated by get_info()
+ * @info_valid: whether @info cache is valid
+ * @mmap_active: whether an mmap is currently active
+ * @chained_loadables: list of additional loadables chained to this one
+ * @list: list node for chained_loadables
+ *
+ * Represents something that can be loaded to RAM (kernel, initrd, fdt, tee).
+ * Metadata can be queried without loading. Actual loading happens on extract
+ * or via mmap.
+ */
+struct loadable {
+	char *name;
+	enum loadable_type type;
+
+	const struct loadable_ops *ops;
+	void *priv;
+
+	struct loadable_info info;
+	bool info_valid;
+	bool mmap_active;
+
+	struct list_head chained_loadables;
+	struct list_head list;
+};
+
+/**
+ * loadable_init - initialize a loadable structure
+ * @loadable: loadable to initialize
+ *
+ * Initializes list heads for a newly allocated loadable.
+ */
+static inline void loadable_init(struct loadable *loadable)
+{
+	INIT_LIST_HEAD(&loadable->chained_loadables);
+	INIT_LIST_HEAD(&loadable->list);
+}
+
+void loadable_chain(struct loadable **main, struct loadable *new);
+
+/**
+ * loadable_count - count total loadables in chain
+ * @l: main loadable (may be NULL or error pointer)
+ *
+ * Returns: number of loadables including main and all chained loadables
+ */
+static inline size_t loadable_count(struct loadable *l)
+{
+	if (IS_ERR_OR_NULL(l))
+		return 0;
+	return 1 + list_count_nodes(&l->chained_loadables);
+}
+
+/**
+ * loadable_is_main - check if loadable is a main (non-chained) loadable
+ * @l: loadable to check
+ *
+ * Returns: true if loadable is main, false otherwise
+ */
+static inline bool loadable_is_main(struct loadable *l)
+{
+	if (IS_ERR_OR_NULL(l))
+		return false;
+	return list_empty(&l->list);
+}
+
+/* Core API */
+
+/**
+ * loadable_set_name - set loadable name with printf-style formatting
+ * @l: loadable
+ * @fmt: printf-style format string
+ * @...: format arguments
+ *
+ * Updates the name of the loadable. The old name is freed if present.
+ */
+void loadable_set_name(struct loadable *l, const char *fmt, ...) __printf(2, 3);
+int loadable_get_info(struct loadable *l, struct loadable_info *info);
+
+/**
+ * loadable_get_size - get final size of loadable
+ * @l: loadable
+ * @size: on success, set to final size or FILE_SIZE_STREAM if unknown
+ *
+ * Returns: 0 on success, negative errno on error
+ */
+static inline int loadable_get_size(struct loadable *l, loff_t *size)
+{
+	struct loadable_info info;
+	int ret = loadable_get_info(l, &info);
+	if (ret)
+		return ret;
+
+	if (info.final_size == LOADABLE_SIZE_UNKNOWN)
+		*size = FILE_SIZE_STREAM;
+	else
+		*size = info.final_size;
+
+	return 0;
+}
+
+ssize_t loadable_extract_into_buf(struct loadable *l, void *load_addr,
+				  size_t size, loff_t offset, unsigned flags);
+
+static inline ssize_t loadable_extract_into_buf_full(struct loadable *l, void *load_addr,
+						     size_t size)
+{
+	return loadable_extract_into_buf(l, load_addr, size, 0, 0);
+}
+
+ssize_t loadable_extract_into_fd(struct loadable *l, int fd);
+void *loadable_extract(struct loadable *l, size_t *size);
+const void *loadable_mmap(struct loadable *l, size_t *size);
+const void *loadable_view(struct loadable *l, size_t *size);
+void loadable_view_free(struct loadable *l, const void *buf, size_t size);
+void loadable_munmap(struct loadable *l, const void *, size_t size);
+struct resource *loadable_extract_into_sdram_all(struct loadable *l, unsigned long adr,
+						 unsigned long end);
+
+void loadable_release(struct loadable **l);
+
+__returns_nonnull struct loadable *
+loadable_from_mem(const void *mem, size_t size, enum loadable_type type);
+
+__returns_nonnull struct loadable *
+loadable_from_file(const char *path, enum loadable_type type);
+
+int loadables_from_files(struct loadable **l,
+			 const char *files, const char *delimiters,
+			 enum loadable_type type);
+
+#ifdef CONFIG_LOADABLE_DECOMPRESS
+struct loadable *loadable_decompress(struct loadable *l);
+#else
+static inline struct loadable *loadable_decompress(struct loadable *l)
+{
+	return l;  /* Pass-through when not configured */
+}
+#endif
+
+#endif /* __LOADABLE_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 1322ad9f4722..3e314c05d7ee 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -8,6 +8,22 @@ config UNCOMPRESS
 	bool
 	select FILETYPE
 
+config LOADABLE
+	bool "Lazy loadable resource infrastructure" if COMPILE_TEST
+	help
+	  Provides infrastructure for lazy-loadable resources that can be
+	  queried for metadata without loading data, and extracted to memory
+	  on demand.
+
+config LOADABLE_DECOMPRESS
+	bool "Decompression support for loadable resources" if COMPILE_TEST
+	depends on LOADABLE
+	depends on UNCOMPRESS
+	help
+	  Enables transparent decompression of loadable resources.
+	  Compressed loadables can be wrapped with loadable_decompress()
+	  to provide decompressed access on demand.
+
 config JSMN
 	bool "JSMN JSON Parser" if COMPILE_TEST
 	help
diff --git a/lib/Makefile b/lib/Makefile
index 57ccd9616348..d5cb5f97f9e9 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -74,6 +74,8 @@ obj-$(CONFIG_STMP_DEVICE) += stmp-device.o
 obj-y			+= ucs2_string.o wchar.o
 obj-$(CONFIG_FUZZ)	+= fuzz.o
 obj-y			+= libfile.o
+obj-$(CONFIG_LOADABLE)	+= loadable.o loadable-mem.o loadable-file.o
+obj-$(CONFIG_LOADABLE_DECOMPRESS) += loadable-compressed.o
 obj-y			+= bitmap.o
 obj-y			+= gcd.o
 obj-y			+= bsearch.o
diff --git a/lib/loadable-compressed.c b/lib/loadable-compressed.c
new file mode 100644
index 000000000000..e66b2044b807
--- /dev/null
+++ b/lib/loadable-compressed.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * Decompression wrapper for loadable resources
+ *
+ * This provides transparent decompression for loadables, allowing compressed
+ * images to be used without requiring temporary files.
+ */
+
+#include <common.h>
+#include <loadable.h>
+#include <malloc.h>
+#include <uncompress.h>
+#include <linux/minmax.h>
+
+/**
+ * struct decompress_loadable_priv - private data for decompressing loadable
+ * @inner: wrapped compressed loadable (owned by wrapper)
+ */
+struct decompress_loadable_priv {
+	struct loadable *inner;
+};
+
+/**
+ * struct decompress_ctx - context passed to fill/flush callbacks
+ * @inner: inner loadable to read compressed data from
+ * @compressed_pos: current read position in compressed stream
+ * @dest_buf: destination buffer for decompressed data
+ * @dest_size: size of destination buffer
+ * @skip_bytes: bytes to skip at start (for offset handling)
+ * @bytes_written: total bytes written to dest_buf so far
+ * @done: early termination flag (buffer full)
+ */
+struct decompress_ctx {
+	struct loadable *inner;
+	loff_t compressed_pos;
+
+	void *dest_buf;
+	size_t dest_size;
+	loff_t skip_bytes;
+	size_t bytes_written;
+	bool done;
+};
+
+static struct decompress_ctx *current_ctx;
+
+/**
+ * decompress_fill() - callback to read compressed data from inner loadable
+ * @buf: buffer to fill with compressed data
+ * @len: maximum bytes to read
+ *
+ * Return: number of bytes read, or negative errno on error
+ */
+static long decompress_fill(void *buf, unsigned long len)
+{
+	struct decompress_ctx *ctx = current_ctx;
+	ssize_t ret;
+
+	ret = loadable_extract_into_buf(ctx->inner, buf, len,
+					ctx->compressed_pos,
+					LOADABLE_EXTRACT_PARTIAL);
+	if (ret < 0)
+		return ret;
+
+	ctx->compressed_pos += ret;
+	return ret;
+}
+
+/**
+ * decompress_flush() - callback to write decompressed data to output buffer
+ * @buf: buffer containing decompressed data
+ * @len: number of bytes in buffer
+ *
+ * Handles offset skipping and early termination when output buffer is full.
+ *
+ * Return: @len on success to continue, 0 to signal early termination
+ */
+static long decompress_flush(void *buf, unsigned long len)
+{
+	struct decompress_ctx *ctx = current_ctx;
+	size_t space_left, to_copy;
+
+	/* Already got enough data - signal early termination */
+	if (ctx->done || ctx->bytes_written >= ctx->dest_size) {
+		ctx->done = true;
+		return 0;  /* Returning != len signals abort to decompressors */
+	}
+
+	/* Skip bytes for offset handling */
+	if (ctx->skip_bytes > 0) {
+		if (len <= ctx->skip_bytes) {
+			ctx->skip_bytes -= len;
+			return len;  /* Continue decompressing */
+		}
+		buf = (char *)buf + ctx->skip_bytes;
+		len -= ctx->skip_bytes;
+		ctx->skip_bytes = 0;
+	}
+
+	/* Copy to destination buffer */
+	space_left = ctx->dest_size - ctx->bytes_written;
+	to_copy = min_t(size_t, len, space_left);
+
+	memcpy(ctx->dest_buf + ctx->bytes_written, buf, to_copy);
+	ctx->bytes_written += to_copy;
+
+	/* Check if we've filled the buffer */
+	if (ctx->bytes_written >= ctx->dest_size) {
+		ctx->done = true;
+		return 0;  /* Signal completion */
+	}
+
+	return len;
+}
+
+/**
+ * decompress_error() - error callback that suppresses early termination errors
+ *
+ * When the flush callback signals early termination via ctx->done,
+ * the decompressor may report a "write error". This is expected and not
+ * a real error, so suppress it
+ */
+static void decompress_error(char *msg)
+{
+	struct decompress_ctx *ctx = current_ctx;
+
+	if (ctx && ctx->done)
+		return;
+
+	uncompress_err_stdout(msg);
+}
+
+static int decompress_loadable_get_info(struct loadable *l,
+					struct loadable_info *info)
+{
+	/* Cannot know decompressed size without decompressing */
+	info->final_size = LOADABLE_SIZE_UNKNOWN;
+	return 0;
+}
+
+/**
+ * decompress_loadable_extract_into_buf() - extract decompressed data to buffer
+ * @l: loadable wrapper
+ * @load_addr: destination buffer
+ * @size: size of destination buffer
+ * @offset: offset within decompressed stream to start from
+ * @flags: extraction flags (LOADABLE_EXTRACT_PARTIAL)
+ *
+ * Decompresses data from the inner loadable to the provided buffer.
+ * When offset is zero, partial reads are allowed (streaming).
+ * When offset is non-zero, the full requested size must be available.
+ *
+ * Return: number of bytes written on success, negative errno on error
+ */
+static ssize_t decompress_loadable_extract_into_buf(struct loadable *l,
+						    void *load_addr,
+						    size_t size,
+						    loff_t offset,
+						    unsigned flags)
+{
+	struct decompress_loadable_priv *priv = l->priv;
+	int ret;
+
+	struct decompress_ctx ctx = {
+		.inner = priv->inner,
+		.compressed_pos = 0,
+		.dest_buf = load_addr,
+		.dest_size = size,
+		.skip_bytes = offset,
+		.bytes_written = 0,
+		.done = false,
+	};
+
+	current_ctx = &ctx;
+
+	ret = uncompress(NULL, 0, decompress_fill, decompress_flush,
+			 NULL, NULL, decompress_error);
+
+	current_ctx = NULL;
+
+	/* Early termination (ctx.done) is not an error */
+	if (ret != 0 && !ctx.done)
+		return ret;
+
+	/*
+	 * LOADABLE_EXTRACT_PARTIAL handling:
+	 * - If offset is zero, partial reads are always allowed (streaming)
+	 * - If offset is non-zero, partial reads require the flag
+	 */
+	if (offset != 0 && !(flags & LOADABLE_EXTRACT_PARTIAL) &&
+	    ctx.bytes_written < size && !ctx.done)
+		return -ENOSPC;
+
+	return ctx.bytes_written;
+}
+
+static void decompress_loadable_release(struct loadable *l)
+{
+	struct decompress_loadable_priv *priv = l->priv;
+
+	loadable_release(&priv->inner);
+	free(priv);
+}
+
+static const struct loadable_ops decompress_loadable_ops = {
+	.get_info = decompress_loadable_get_info,
+	.extract_into_buf = decompress_loadable_extract_into_buf,
+	.release = decompress_loadable_release,
+};
+
+/**
+ * loadable_decompress() - wrap a loadable with transparent decompression
+ * @l: compressed loadable (ownership transferred to wrapper)
+ *
+ * Creates a wrapper loadable that transparently decompresses data from
+ * the inner loadable. The wrapper takes ownership of the inner loadable
+ * and will release it when the wrapper is released.
+ *
+ * Supported compression formats depend on kernel configuration:
+ * gzip, bzip2, lzo, lz4, xz, zstd.
+ *
+ * Return: wrapper loadable on success, or the original loadable on error
+ */
+struct loadable *loadable_decompress(struct loadable *l)
+{
+	struct loadable *wrapper;
+	struct decompress_loadable_priv *priv;
+
+	if (IS_ERR_OR_NULL(l))
+		return l;
+
+	wrapper = xzalloc(sizeof(*wrapper));
+	priv = xzalloc(sizeof(*priv));
+
+	priv->inner = l;
+
+	wrapper->name = xasprintf("Decompress(%s)", l->name ?: "unnamed");
+	wrapper->type = l->type;
+	wrapper->ops = &decompress_loadable_ops;
+	wrapper->priv = priv;
+	loadable_init(wrapper);
+
+	return wrapper;
+}
diff --git a/lib/loadable-file.c b/lib/loadable-file.c
new file mode 100644
index 000000000000..a4e493c2df58
--- /dev/null
+++ b/lib/loadable-file.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <fs.h>
+#include <libfile.h>
+#include <linux/ctype.h>
+#include <linux/minmax.h>
+#include <linux/sprintf.h>
+#include <linux/stat.h>
+#include <loadable.h>
+#include <string.h>
+#include <xfuncs.h>
+
+struct file_loadable_priv {
+	char *path;
+	int fd;
+	int read_fd;
+};
+
+static int file_loadable_get_info(struct loadable *l,
+				  struct loadable_info *info)
+{
+	struct file_loadable_priv *priv = l->priv;
+	struct stat s;
+	int ret;
+
+	ret = stat(priv->path, &s);
+	if (ret)
+		return ret;
+
+	if (s.st_size == FILE_SIZE_STREAM)
+		s.st_size = LOADABLE_SIZE_UNKNOWN;
+	else if (FILE_SIZE_STREAM != LOADABLE_SIZE_UNKNOWN &&
+		 s.st_size > LOADABLE_SIZE_UNKNOWN)
+		return -EOVERFLOW;
+
+	info->final_size = s.st_size;
+
+	return 0;
+}
+
+static int file_loadable_open_and_lseek(struct file_loadable_priv *priv, loff_t pos)
+{
+	int ret, fd = priv->read_fd;
+
+	if (fd < 0) {
+		fd = open(priv->path, O_RDONLY);
+		if (fd < 0)
+			return fd;
+	}
+
+	if (lseek(fd, pos, SEEK_SET) != pos) {
+		ret = -errno;
+		priv->read_fd = -1;
+		close(fd);
+		return ret;
+	}
+
+	priv->read_fd = fd;
+	return fd;
+}
+
+/**
+ * file_loadable_extract_into_buf - load file data to target address
+ * @l: loadable representing a file
+ * @load_addr: virtual address to load data to
+ * @size: size of buffer at load_addr
+ * @offset: offset within the file to start reading from
+ * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+ *
+ * Extracts the file to the specified memory address.
+ *
+ * File is read as-is from the filesystem.
+ * The caller must provide a valid address range; this function does not allocate
+ * memory.
+ *
+ * Return: actual number of bytes read on success, negative errno on error
+ *         -ENOSPC if size is too small for the file (only when loading from start)
+ */
+static ssize_t file_loadable_extract_into_buf(struct loadable *l,
+					      void *load_addr, size_t size,
+					      loff_t offset, unsigned flags)
+{
+	struct file_loadable_priv *priv = l->priv;
+	char buf;
+	ssize_t ret;
+	int fd;
+
+	fd = file_loadable_open_and_lseek(priv, offset);
+	if (fd < 0)
+		return fd;
+
+	ret = __read_full_anywhere(fd, load_addr, size);
+	if (!(flags & LOADABLE_EXTRACT_PARTIAL) && ret == size &&
+	    __read_full_anywhere(fd, &buf, 1) > 0)
+		ret = -ENOSPC;
+
+	return ret;
+}
+
+/**
+ * file_loadable_extract - allocate buffer and load file data into it
+ * @l: loadable representing a file
+ * @size: on success, set to the number of bytes read
+ *
+ * Allocates a buffer and extracts the file data into it.
+ *
+ * No decompression is performed - the file is read as-is from the filesystem.
+ * The caller is responsible for freeing the returned buffer with free().
+ *
+ * Return: allocated buffer, NULL for zero-size, or error pointer on failure
+ */
+static void *file_loadable_extract(struct loadable *l, size_t *size)
+{
+	struct file_loadable_priv *priv = l->priv;
+	void *buf;
+	int ret;
+
+	ret = read_file_2(priv->path, size, &buf, FILESIZE_MAX);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (*size == 0) {
+		free(buf);
+		buf = NULL;
+	}
+
+	return buf;
+}
+
+/**
+ * file_loadable_mmap - memory map file data
+ * @l: loadable representing a file
+ * @size: on success, set to the size of the mapped region
+ *
+ * Memory maps the file without copying data into a separate buffer.
+ *
+ * No decompression is performed - the file is mapped as-is from the filesystem.
+ *
+ * Return: read-only pointer to mapped data, NULL for zero-size, or MAP_FAILED on error
+ */
+static const void *file_loadable_mmap(struct loadable *l, size_t *size)
+{
+	struct file_loadable_priv *priv = l->priv;
+	struct stat st;
+	void *buf;
+	int fd, ret;
+
+	if (unlikely(priv->fd >= 0))
+		return MAP_FAILED;
+
+	/* Reuse the read fd if already open, otherwise open a new one */
+	if (priv->read_fd >= 0) {
+		fd = priv->read_fd;
+		priv->read_fd = -1;
+	} else {
+		fd = open(priv->path, O_RDONLY);
+	}
+	if (fd < 0)
+		return MAP_FAILED;
+
+	ret = fstat(fd, &st);
+	if (ret || st.st_size == FILE_SIZE_STREAM)
+		goto err;
+	if (!st.st_size) {
+		close(fd);
+		*size = 0;
+		return NULL;
+	}
+
+	buf = memmap(fd, PROT_READ);
+	if (buf == MAP_FAILED)
+		goto err;
+
+	*size = min_t(u64, st.st_size, SIZE_MAX);
+	priv->fd = fd;
+	return buf;
+
+err:
+	close(fd);
+	return MAP_FAILED;
+}
+
+static void file_loadable_munmap(struct loadable *l, const void *buf,
+				 size_t size)
+{
+	struct file_loadable_priv *priv = l->priv;
+
+	if (priv->fd >= 0) {
+		close(priv->fd);
+		priv->fd = -1;
+	}
+}
+
+static void file_loadable_release(struct loadable *l)
+{
+	struct file_loadable_priv *priv = l->priv;
+
+	if (priv->read_fd >= 0)
+		close(priv->read_fd);
+	if (priv->fd >= 0)
+		close(priv->fd);
+	free(priv->path);
+	free(priv);
+}
+
+static const struct loadable_ops file_loadable_ops = {
+	.get_info = file_loadable_get_info,
+	.extract_into_buf = file_loadable_extract_into_buf,
+	.extract = file_loadable_extract,
+	.mmap = file_loadable_mmap,
+	.munmap = file_loadable_munmap,
+	.release = file_loadable_release,
+};
+
+/**
+ * loadable_from_file - create a loadable from filesystem file
+ * @path: filesystem path to the file
+ * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.)
+ *
+ * Creates a loadable structure that wraps access to a file in the filesystem.
+ * The file is read directly during extract or via mmap with no decompression
+ * - it is loaded as-is from the filesystem.
+ *
+ * The created loadable must be freed with loadable_release() when done.
+ * The file path is copied internally, so the caller's string can be freed.
+ *
+ * Return: pointer to allocated loadable on success, ERR_PTR() on error
+ */
+struct loadable *loadable_from_file(const char *path, enum loadable_type type)
+{
+	char *cpath;
+	struct loadable *l;
+	struct file_loadable_priv *priv;
+
+	cpath = canonicalize_path(AT_FDCWD, path);
+	if (!cpath) {
+		const char *typestr = loadable_type_tostr(type);
+		int ret = -errno;
+
+		pr_err("%s%simage \"%s\" not found: %m\n",
+		       typestr, typestr ? " ": "", path);
+
+		return ERR_PTR(ret);
+	}
+
+	l = xzalloc(sizeof(*l));
+	priv = xzalloc(sizeof(*priv));
+
+	priv->path = cpath;
+	priv->fd = -1;
+	priv->read_fd = -1;
+
+	l->name = xasprintf("File(%s)", path);
+	l->type = type;
+	l->ops = &file_loadable_ops;
+	l->priv = priv;
+	loadable_init(l);
+
+	return l;
+}
+
+/**
+ * loadables_from_files - create loadables from a list of file paths
+ * @l: pointer to loadable chain (updated on return)
+ * @files: delimited list of file paths
+ * @delimiters: each character on its own is a valid delimiter for @files
+ * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.)
+ *
+ * This helper splits up @files by any character in @delimiters and creates
+ * loadables for every file it extracts. File extraction is done as follows:
+ *
+ * - Multiple whitespace is treated as if it were a single whitespace.
+ * - Leading and trailing whitespace is ignored
+ * - When a file name is empty, because a non-whitespace delimiter was placed
+ *   at the beginning, the end or two delimiters were repeated, the original
+ *   loadable from @l is spliced into the chain at that location. If @l is NULL,
+ *   nothing happens
+ * - Regular non-empty file names have loadables created for them
+ *
+ * This is basically the algorithm by which PATH is interpreted in
+ * a POSIX-conformant shell (when delimiters=":" and the original is the current
+ * working directory).
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+int loadables_from_files(struct loadable **l,
+			 const char *files, const char *delimiters,
+			 enum loadable_type type)
+{
+	struct loadable *orig = *l;
+	struct loadable *main = NULL;
+	struct loadable *lf, *ltmp;
+	char *file, *buf, *origbuf = xstrdup(files);
+	LIST_HEAD(tmp);
+	bool orig_included = false;
+	char delim;
+
+	if (isempty(files))
+		goto out;
+
+	buf = origbuf;
+	while ((file = strsep_unescaped(&buf, delimiters, &delim))) {
+		if (*file == '\0') {
+			if (!isspace(delim) && !orig_included && orig) {
+				list_add_tail(&orig->list, &tmp);
+				orig_included = true;
+			}
+			continue;
+		}
+
+		lf = loadable_from_file(file, type);
+		if (IS_ERR(lf)) {
+			int ret = PTR_ERR(lf);
+
+			if (orig_included)
+				list_del_init(&orig->list);
+			list_for_each_entry_safe(lf, ltmp, &tmp, list)
+				loadable_release(&lf);
+			free(origbuf);
+			return ret;
+		}
+
+		list_add_tail(&lf->list, &tmp);
+	}
+
+	free(origbuf);
+
+	/* Build the final chain from collected entries */
+	list_for_each_entry_safe(lf, ltmp, &tmp, list) {
+		list_del_init(&lf->list);
+		loadable_chain(&main, lf);
+	}
+
+out:
+	if (!orig_included)
+		loadable_release(&orig);
+
+	*l = main;
+	return 0;
+}
diff --git a/lib/loadable-mem.c b/lib/loadable-mem.c
new file mode 100644
index 000000000000..4e697acc0bc4
--- /dev/null
+++ b/lib/loadable-mem.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <loadable.h>
+#include <memory.h>
+#include <zero_page.h>
+#include <xfuncs.h>
+
+struct mem_loadable_priv {
+	const void *start;
+	size_t size;
+};
+
+static int mem_loadable_get_info(struct loadable *l, struct loadable_info *info)
+{
+	struct mem_loadable_priv *priv = l->priv;
+
+	info->final_size = priv->size;
+
+	return 0;
+}
+
+/**
+ * mem_loadable_extract_into_buf - copy memory region to target address
+ * @l: loadable representing a memory region
+ * @load_addr: virtual address to copy data to
+ * @size: size of buffer at load_addr
+ * @offset: offset within the memory region to start copying from
+ * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+ *
+ * Copies data from the memory region to the specified address.
+ *
+ * No decompression is performed - the data is copied as-is.
+ * The caller must provide a valid address range; this function does not allocate
+ * memory.
+ *
+ * Return: actual number of bytes copied on success, negative errno on error
+ */
+static ssize_t mem_loadable_extract_into_buf(struct loadable *l,
+					     void *load_addr, size_t size,
+					     loff_t offset,
+					     unsigned flags)
+{
+	struct mem_loadable_priv *priv = l->priv;
+
+	if (offset >= priv->size)
+		return 0;
+	if (!(flags & LOADABLE_EXTRACT_PARTIAL) && priv->size - offset > size)
+		return -ENOSPC;
+
+	size = min_t(size_t, priv->size - offset, size);
+
+	if (unlikely(zero_page_contains((ulong)load_addr)))
+		zero_page_memcpy(load_addr, priv->start + offset, size);
+	else
+		memcpy(load_addr, priv->start + offset, size);
+
+	return size; /* Actual bytes read */
+}
+
+/**
+ * mem_loadable_mmap - return pointer to memory region
+ * @l: loadable representing a memory region
+ * @size: on success, set to the size of the memory region
+ *
+ * Returns a direct pointer to the memory region without copying.
+ *
+ * No decompression is performed - the data is accessed as-is.
+ *
+ * Return: read-only pointer to the memory region, or NULL for zero-size
+ */
+static const void *mem_loadable_mmap(struct loadable *l, size_t *size)
+{
+	struct mem_loadable_priv *priv = l->priv;
+
+	*size = priv->size;
+	return priv->start;
+}
+
+static void mem_loadable_release(struct loadable *l)
+{
+	struct mem_loadable_priv *priv = l->priv;
+
+	free(priv);
+}
+
+static const struct loadable_ops mem_loadable_ops = {
+	.get_info = mem_loadable_get_info,
+	.extract_into_buf = mem_loadable_extract_into_buf,
+	.mmap = mem_loadable_mmap,
+	.release = mem_loadable_release,
+};
+
+/**
+ * loadable_from_mem - create a loadable from a memory region
+ * @mem: pointer to the memory region
+ * @size: size of the memory region in bytes
+ * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.)
+ *
+ * Creates a loadable structure that wraps access to an existing memory region.
+ * The memory is accessed directly during extract or via mmap with no
+ * decompression - it is used as-is.
+ *
+ * The created loadable must be freed with loadable_release() when done.
+ * The memory region must remain valid for the lifetime of the loadable.
+ *
+ * Return: pointer to allocated loadable (never fails due to xzalloc)
+ */
+struct loadable *loadable_from_mem(const void *mem, size_t size,
+				   enum loadable_type type)
+{
+	struct loadable *l;
+	struct mem_loadable_priv *priv;
+
+	l = xzalloc(sizeof(*l));
+	priv = xzalloc(sizeof(*priv));
+
+	priv->start = size ? mem : NULL;
+	priv->size = size;
+
+	l->name = xasprintf("Mem(%p, %#zx)", mem, size);
+	l->type = type;
+	l->ops = &mem_loadable_ops;
+	l->priv = priv;
+	loadable_init(l);
+
+	return l;
+}
diff --git a/lib/loadable.c b/lib/loadable.c
new file mode 100644
index 000000000000..65121a7dbf90
--- /dev/null
+++ b/lib/loadable.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <loadable.h>
+#include <malloc.h>
+#include <memory.h>
+#include <libfile.h>
+#include <unistd.h>
+#include <linux/stat.h>
+#include <linux/fs.h>
+#include <linux/sizes.h>
+#include <zero_page.h>
+#include <fs.h>
+
+void loadable_set_name(struct loadable *l, const char *fmt, ...)
+{
+	va_list args;
+
+	free(l->name);
+
+	va_start(args, fmt);
+	l->name = xvasprintf(fmt, args);
+	va_end(args);
+}
+
+const char *loadable_type_tostr(enum loadable_type type)
+{
+	switch (type) {
+	case LOADABLE_KERNEL:
+		return "kernel";
+	case LOADABLE_INITRD:
+		return "initrd";
+	case LOADABLE_FDT:
+		return "fdt";
+	case LOADABLE_TEE:
+		return "tee";
+	default:
+		return NULL;
+	}
+}
+
+/**
+ * loadable_get_info - obtain metadata without loading data
+ * @l: loadable
+ * @info: loadable info populated on success
+ *
+ * This function reads the minimum necessary headers/properties
+ * needed to determine size and addresses.
+ * Result is cached in loadable->info.
+ *
+ * Return: 0 on success, or negative error code otherwise
+ */
+int loadable_get_info(struct loadable *l, struct loadable_info *info)
+{
+	int ret;
+
+	if (!l->info_valid) {
+		ret = l->ops->get_info(l, &l->info);
+		if (ret)
+			return ret;
+		l->info_valid = true;
+	}
+
+	*info = l->info;
+	return 0;
+}
+
+/**
+ * loadable_extract_into_buf - load fully to target address
+ * @l: loadable
+ * @load_addr: virtual address to load data to
+ * @size: size of buffer at load_addr
+ * @offset: offset within the loadable to start loading from
+ * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+ *
+ * Instructs the loadable implementation to populate the specified
+ * buffer with data, so it can be reused. This may involve decompression
+ * if the loadable was constructed by loadable_decompress for example.
+ *
+ * if !(@flags & LOADABLE_EXTRACT_PARTIAL), -ENOSPC will be returned if the
+ * size is too small.
+ *
+ * Return: actual number of bytes read on success, negative errno on error
+ */
+ssize_t loadable_extract_into_buf(struct loadable *l, void *load_addr,
+				  size_t size, loff_t offset, unsigned flags)
+{
+	return l->ops->extract_into_buf(l, load_addr, size, offset, flags);
+}
+
+#define EXTRACT_FD_BUF_SIZE	SZ_1M
+
+/**
+ * loadable_extract_into_fd - extract loadable data to a file descriptor
+ * @l: loadable
+ * @fd: file descriptor to write to
+ *
+ * Extracts data from the loadable and writes it to the specified file
+ * descriptor. Uses a 1MB buffer and streams data in chunks.
+ *
+ * Return: total bytes written on success, negative errno on error
+ */
+ssize_t loadable_extract_into_fd(struct loadable *l, int fd)
+{
+	void *buf;
+	loff_t offset = 0;
+	ssize_t total = 0;
+	ssize_t ret;
+
+	buf = malloc(EXTRACT_FD_BUF_SIZE);
+	if (!buf)
+		return -ENOMEM;
+
+	while (1) {
+		ret = loadable_extract_into_buf(l, buf, EXTRACT_FD_BUF_SIZE,
+						offset, LOADABLE_EXTRACT_PARTIAL);
+		if (ret < 0)
+			goto out;
+		if (ret == 0)
+			break;
+
+		ret = write_full(fd, buf, ret);
+		if (ret < 0)
+			goto out;
+
+		offset += ret;
+		total += ret;
+	}
+
+	ret = total;
+out:
+	free(buf);
+	return ret;
+}
+
+static ssize_t __loadable_extract_into_sdram(struct loadable *l,
+					     unsigned long *adr,
+					     unsigned long *size,
+					     unsigned long end)
+{
+	ssize_t nbyts = loadable_extract_into_buf_full(l, (void *)*adr, *size);
+	if (nbyts < 0)
+		return nbyts;
+
+	*adr += nbyts;
+	*size -= nbyts;
+
+	if (*adr - 1 > end)
+		return -EBUSY;
+
+	return nbyts;
+}
+
+/**
+ * loadable_extract_into_sdram_all - extract main and chained loadables into SDRAM
+ * @main: main loadable with optional chained loadables
+ * @adr: start address of the SDRAM region
+ * @end: end address of the SDRAM region (inclusive)
+ *
+ * Extracts the main loadable and all chained loadables into a contiguous SDRAM
+ * region. Registers the region with the appropriate memory type based on
+ * the loadable type.
+ *
+ * Return: SDRAM resource on success, or error pointer on failure
+ */
+struct resource *loadable_extract_into_sdram_all(struct loadable *main,
+						 unsigned long adr,
+						 unsigned long end)
+{
+	struct loadable *lc;
+	unsigned long adr_orig = adr;
+	unsigned long size = end - adr + 1;
+	struct resource *res;
+	unsigned memtype;
+	unsigned memattrs;
+	ssize_t ret;
+
+	/* FIXME: EFI payloads are started with MMU enabled, so for now
+	 * we keep attributes as RWX instead of remapping later on
+	 */
+	memattrs = IS_ENABLED(CONFIG_EFI_LOADER) ? MEMATTRS_RWX : MEMATTRS_RW;
+
+	if (main->type == LOADABLE_KERNEL)
+		memtype = MEMTYPE_LOADER_CODE;
+	else
+		memtype = MEMTYPE_LOADER_DATA;
+
+	res = request_sdram_region(loadable_type_tostr(main->type) ?: "image",
+				   adr, size, memtype, memattrs);
+	if (!res)
+		return ERR_PTR(-EBUSY);
+
+	ret = __loadable_extract_into_sdram(main, &adr, &size, end);
+	if (ret < 0)
+		goto err;
+
+	list_for_each_entry(lc, &main->chained_loadables, list) {
+		ret = __loadable_extract_into_sdram(lc, &adr, &size, end);
+		if (ret < 0)
+			goto err;
+	}
+
+	if (adr == adr_orig) {
+		release_region(res);
+		return NULL;
+	}
+
+	ret = resize_region(res, adr - adr_orig);
+	if (ret)
+		goto err;
+
+	return res;
+err:
+	release_sdram_region(res);
+	return ERR_PTR(ret);
+}
+
+/**
+ * loadable_mmap - memory map a buffer
+ * @l: loadable
+ * @size: size of memory map on success
+ *
+ * This is the most efficient way to access a loadable if supported.
+ * The returned pointer should be passed to loadable_munmap when no
+ * longer needed.
+ *
+ * Return: read-only pointer to the buffer, NULL for zero-size, or MAP_FAILED on error
+ */
+const void *loadable_mmap(struct loadable *l, size_t *size)
+{
+	return l->ops->mmap ? l->ops->mmap(l, size) : MAP_FAILED;
+}
+
+/**
+ * loadable_munmap - unmap a buffer
+ * @l: loadable
+ * @buf: buffer returned from loadable_mmap
+ * @size: size of memory map returned by loadable_mmap
+ */
+void loadable_munmap(struct loadable *l, const void *buf, size_t size)
+{
+	if (l->ops->munmap)
+		l->ops->munmap(l, buf, size);
+}
+
+/**
+ * loadable_extract - extract loadable into newly allocated buffer
+ * @l: loadable
+ * @size: on success, set to the number of bytes extracted
+ *
+ * Extracts the loadable data into a newly allocated buffer. The caller
+ * is responsible for freeing the returned buffer with free().
+ *
+ * Return: allocated buffer, NULL for zero-size, or error pointer on failure
+ */
+void *loadable_extract(struct loadable *l, size_t *size)
+{
+	struct loadable_info li;
+	ssize_t nbytes;
+	void *buf;
+	int ret;
+
+	ret = loadable_get_info(l, &li);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (li.final_size == LOADABLE_SIZE_UNKNOWN) {
+		if (l->ops->extract)
+			return l->ops->extract(l, size);
+		if (l->ops->mmap) {
+			const void *map = l->ops->mmap(l, size);
+			if (map != MAP_FAILED) {
+				void *dup = memdup(map, *size);
+				if (l->ops->munmap)
+					l->ops->munmap(l, map, *size);
+				return dup;
+			}
+		}
+
+		return ERR_PTR(-EFBIG);
+	}
+
+	/* We assume extract_into_buf to be more efficient as it
+	 * can allocate once instead of having to resize
+	 */
+	buf = malloc(li.final_size);
+	if (!buf)
+		return ERR_PTR(-ENOMEM);
+
+	/* We are reading for the full size of the file, so set
+	 * LOADABLE_EXTRACT_PARTIAL to save the underlying loadable
+	 * some work
+	 */
+	nbytes = loadable_extract_into_buf(l, buf, li.final_size, 0,
+					   LOADABLE_EXTRACT_PARTIAL);
+	if (nbytes < 0) {
+		free(buf);
+		return ERR_PTR(nbytes);
+	} else if (nbytes == 0) {
+		free(buf);
+		buf = NULL;
+	}
+
+	*size = nbytes;
+	return buf;
+}
+
+/**
+ * loadable_view - get read-only view of loadable
+ * @l: loadable
+ * @size: on success, set to the size of the view
+ *
+ * Use this when you want to non-destructively access the data.
+ * It tries to find the optimal way to provide the buffer:
+ * - If memory-mappable, return memory map without allocation
+ * - If size is known, allocate once and use extract_into_buf
+ * - If size is unknown, fall back to extract (assumed to be the slowest)
+ *
+ * Return: read-only pointer to the buffer, NULL for zero-size, or error pointer on failure
+ */
+const void *loadable_view(struct loadable *l, size_t *size)
+{
+	const void *mmap;
+
+	mmap = loadable_mmap(l, size);
+	if (mmap != MAP_FAILED) {
+		l->mmap_active = true;
+		return mmap;
+	}
+
+	l->mmap_active = false;
+	return loadable_extract(l, size);
+}
+
+/**
+ * loadable_view_free - free buffer returned by loadable_view()
+ * @l: loadable
+ * @buf: buffer returned by loadable_view()
+ * @size: size returned by loadable_view()
+ *
+ * Releases resources associated with a buffer obtained via loadable_view().
+ */
+void loadable_view_free(struct loadable *l, const void *buf, size_t size)
+{
+	if (IS_ERR_OR_NULL(buf))
+		return;
+
+	if (l->mmap_active)
+		loadable_munmap(l, buf, size);
+	else
+		free((void *)buf);
+}
+
+/**
+ * loadable_chain - chain a loadable to another loadable
+ * @main: pointer to the main loadable (may be NULL initially)
+ * @new: loadable to chain
+ *
+ * Links @new to @main. If @main is NULL, @new becomes the new main.
+ * Linked loadables are extracted together by loadable_extract_into_sdram_all().
+ */
+void loadable_chain(struct loadable **main, struct loadable *new)
+{
+	if (!new)
+		return;
+	if (!*main) {
+		*main = new;
+		return;
+	}
+
+	list_add_tail(&new->list, &(*main)->chained_loadables);
+	list_splice_tail_init(&new->chained_loadables,
+			      &(*main)->chained_loadables);
+}
+
+/**
+ * loadable_release - free resources associated with this loadable
+ * @lp: pointer to loadable (set to NULL on return)
+ *
+ * Release resources associated with a loadable and all chained loadables.
+ *
+ * This function is a no-op when passed NULL or error pointers.
+ */
+void loadable_release(struct loadable **lp)
+{
+	struct loadable *lc, *l, *tmp;
+
+	if (IS_ERR_OR_NULL(lp) || IS_ERR_OR_NULL(*lp))
+		return;
+
+	l = *lp;
+
+	list_for_each_entry_safe(lc, tmp, &l->chained_loadables, list)
+		loadable_release(&lc);
+
+	if (l->ops->release)
+		l->ops->release(l);
+
+	list_del(&l->list);
+	free(l->name);
+	free(l);
+	*lp = NULL;
+}
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 02/16] bootm: split preparatory step from handler invocation
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 03/16] boot: add bootm_boot wrapper that takes struct bootentry Ahmad Fatoum
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The override handling is currently embedded very deep inside the bootm
code, despite that configuring overrides is only possible from the
boot command. With the switch to a generic loadable abstraction, it will
be possible to switch out loadable objects and avoid this invasiveness,
so prepare for that by splitting the part that would register loadables
from the part that would actually load them.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm.c  | 44 +++++++++++++++++++++++++++++++++++---------
 include/bootm.h |  6 +++++-
 2 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/common/bootm.c b/common/bootm.c
index d43079bb81da..a10f3a4ea9dc 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -569,19 +569,15 @@ int file_read_and_detect_boot_image_type(const char *os_file, void **os_header)
 	return file_detect_boot_image_type(*os_header, PAGE_SIZE);
 }
 
-/*
- * bootm_boot - Boot an application image described by bootm_data
- */
-int bootm_boot(struct bootm_data *bootm_data)
+struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 {
 	struct image_data *data;
-	struct image_handler *handler;
 	int ret;
 	const char *image_type_str;
 
 	if (!bootm_data->os_file) {
 		pr_err("no image given\n");
-		return -ENOENT;
+		return ERR_PTR(-ENOENT);
 	}
 
 	data = xzalloc(sizeof(*data));
@@ -759,6 +755,17 @@ int bootm_boot(struct bootm_data *bootm_data)
 		free(hostname_bootarg);
 	}
 
+	return data;
+err_out:
+	bootm_boot_cleanup(data);
+	return ERR_PTR(ret);
+}
+
+int bootm_boot_handler(struct image_data *data)
+{
+	struct image_handler *handler;
+	int ret;
+
 	pr_info("\nLoading %s '%s'", file_type_to_string(data->kernel_type),
 		data->os_file);
 	if (data->kernel_type == filetype_uimage &&
@@ -777,8 +784,7 @@ int bootm_boot(struct bootm_data *bootm_data)
 		       file_type_to_string(data->kernel_type));
 		if (data->kernel_type == filetype_uimage)
 			pr_err("and OS type: %d\n", data->os_uimage->header.ih_os);
-		ret = -ENODEV;
-		goto err_out;
+		return -ENODEV;
 	}
 
 	if (bootm_verbose(data)) {
@@ -797,7 +803,11 @@ int bootm_boot(struct bootm_data *bootm_data)
 	if (data->dryrun)
 		pr_info("Dryrun. Aborted\n");
 
-err_out:
+	return ret;
+}
+
+void bootm_boot_cleanup(struct image_data *data)
+{
 	release_sdram_region(data->os_res);
 	if (data->initrd_res)
 		of_del_reserve_entry(data->initrd_res->start, data->initrd_res->end);
@@ -819,7 +829,23 @@ int bootm_boot(struct bootm_data *bootm_data)
 	free(data->initrd_file);
 	free(data->tee_file);
 	free(data);
+}
 
+/*
+ * bootm_boot - Boot an application image described by bootm_data
+ */
+int bootm_boot(const struct bootm_data *bootm_data)
+{
+	struct image_data *data;
+	int ret;
+
+	data = bootm_boot_prep(bootm_data);
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	ret = bootm_boot_handler(data);
+
+	bootm_boot_cleanup(data);
 	return ret;
 }
 
diff --git a/include/bootm.h b/include/bootm.h
index 21feb1ca98ae..da6cf7301709 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -52,7 +52,11 @@ struct bootm_data {
 	unsigned long os_entry;
 };
 
-int bootm_boot(struct bootm_data *data);
+int bootm_boot(const struct bootm_data *data);
+
+struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data);
+int bootm_boot_handler(struct image_data *data);
+void bootm_boot_cleanup(struct image_data *data);
 
 struct image_data {
 	/* simplest case. barebox has already loaded the os here */
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 03/16] boot: add bootm_boot wrapper that takes struct bootentry
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 02/16] bootm: split preparatory step from handler invocation Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 04/16] bootchooser: pass along " Ahmad Fatoum
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

struct bootentry holds the overrides given to the boot command, but it's
not plumbed through to the end leading to storage of the overrides in
global variables, which is error prone with respect to nested bootm
handlers.

In preparation for passing the overrides via arguments throughout,
introduce the bootm_entry and switch users over to it.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 arch/arm/boards/protonic-imx6/board.c |  2 +-
 common/blspec.c                       |  2 +-
 common/boot.c                         | 37 ++++++++++++++++++++++-----
 efi/loader/bootesp.c                  |  2 +-
 include/boot.h                        |  3 +++
 5 files changed, 37 insertions(+), 9 deletions(-)

diff --git a/arch/arm/boards/protonic-imx6/board.c b/arch/arm/boards/protonic-imx6/board.c
index c15a349c8a88..3bb2632693e5 100644
--- a/arch/arm/boards/protonic-imx6/board.c
+++ b/arch/arm/boards/protonic-imx6/board.c
@@ -406,7 +406,7 @@ static int prt_imx6_usb_boot(struct bootentry *entry, int verbose, int dryrun)
 	if (dryrun)
 		bootm_data.dryrun = dryrun;
 
-	ret = bootm_boot(&bootm_data);
+	ret = bootm_entry(entry, &bootm_data);
 	if (ret)
 		goto exit_usb_boot;
 
diff --git a/common/blspec.c b/common/blspec.c
index c07e3a2d672d..ef7490085c7f 100644
--- a/common/blspec.c
+++ b/common/blspec.c
@@ -156,7 +156,7 @@ static int blspec_boot(struct bootentry *be, int verbose, int dryrun)
 	firmware_set_searchpath(fws);
 	free(fws);
 
-	ret = bootm_boot(&data);
+	ret = bootm_entry(be, &data);
 	if (ret)
 		pr_err("Booting failed\n");
 
diff --git a/common/boot.c b/common/boot.c
index 0fa2022be1ac..b5a4205cd9e2 100644
--- a/common/boot.c
+++ b/common/boot.c
@@ -124,7 +124,7 @@ static int bootscript_boot(struct bootentry *entry, int verbose, int dryrun)
 	if (dryrun >= 2)
 		data.dryrun = dryrun - 1;
 
-	ret = bootm_boot(&data);
+	ret = bootm_entry(entry, &data);
 out:
 	bootm_data_restore_defaults(&backup);
 	return ret;
@@ -182,7 +182,6 @@ BAREBOX_MAGICVAR(global.boot.watchdog_timeout,
 
 int boot_entry(struct bootentry *be, int verbose, int dryrun)
 {
-	struct bootm_overrides old;
 	int ret;
 
 	pr_info("Booting entry '%s'\n", be->title);
@@ -197,14 +196,10 @@ int boot_entry(struct bootentry *be, int verbose, int dryrun)
 		}
 	}
 
-	old = bootm_save_overrides(be->overrides);
-
 	ret = be->boot(be, verbose, dryrun);
 	if (ret && ret != -ENOMEDIUM)
 		pr_err("Booting entry '%s' failed: %pe\n", be->title, ERR_PTR(ret));
 
-	bootm_restore_overrides(old);
-
 	globalvar_set_match("linux.bootargs.dyn.", "");
 
 	return ret;
@@ -555,3 +550,33 @@ void bootsources_list(struct bootentries *bootentries)
 }
 
 BAREBOX_MAGICVAR(global.boot.default, "default boot order");
+
+/**
+ * bootm_entry - invoke bootm while taking care of overrides
+ * @be: bootentry
+ * @bootm_data: prepopulated bootm data for bootm_boot()
+ *
+ * Compared to bootm_boot(), this takes an extra @be that allows
+ * applying boot entry specific info like overrides.
+ *
+ * Return: 0 on success, or negative error code otherwise
+ */
+int bootm_entry(struct bootentry *be, const struct bootm_data *bootm_data)
+{
+	struct bootm_overrides old;
+	struct image_data *data;
+	int ret;
+
+	data = bootm_boot_prep(bootm_data);
+	if (IS_ERR(data))
+		return PTR_ERR(data);
+
+	old = bootm_save_overrides(be->overrides);
+
+	ret = bootm_boot_handler(data);
+
+	bootm_restore_overrides(old);
+
+	bootm_boot_cleanup(data);
+	return ret;
+}
diff --git a/efi/loader/bootesp.c b/efi/loader/bootesp.c
index afce5118aa68..85de1e7e6350 100644
--- a/efi/loader/bootesp.c
+++ b/efi/loader/bootesp.c
@@ -55,7 +55,7 @@ static int esp_boot(struct bootentry *be, int verbose, int dryrun)
 	 * 2) implement device tree overlay patching protocol using it?
 	 */
 
-	ret = bootm_boot(&data);
+	ret = bootm_entry(be, &data);
 	if (ret)
 		pr_err("Booting failed\n");
 
diff --git a/include/boot.h b/include/boot.h
index 836a180a0c7b..57f9cb409bf3 100644
--- a/include/boot.h
+++ b/include/boot.h
@@ -55,4 +55,7 @@ void bootsources_menu(struct bootentries *bootentries, unsigned default_entry, i
 void bootsources_list(struct bootentries *bootentries);
 int boot_entry(struct bootentry *be, int verbose, int dryrun);
 
+struct bootm_data;
+int bootm_entry(struct bootentry *be, const struct bootm_data *data);
+
 #endif /* __BOOT_H */
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 04/16] bootchooser: pass along struct bootentry
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 02/16] bootm: split preparatory step from handler invocation Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 03/16] boot: add bootm_boot wrapper that takes struct bootentry Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 05/16] bootm: switch plain file names case to loadable API Ahmad Fatoum
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

By plumbing through the parent bootentry, we can merge the overrides
applied for the bootchooser targets onto the actual children if boot
override support is enabled.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootchooser.c  | 13 +++++++++----
 include/bootchooser.h |  2 +-
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/common/bootchooser.c b/common/bootchooser.c
index 07cbdacfeb0a..2a75dfdcc88e 100644
--- a/common/bootchooser.c
+++ b/common/bootchooser.c
@@ -839,7 +839,8 @@ void bootchooser_lock_attempts(bool locked)
 	attempts_locked = locked;
 }
 
-static int bootchooser_boot_one(struct bootchooser *bc, int *tryagain)
+static int bootchooser_boot_one(struct bootchooser *bc, int *tryagain,
+				struct bootentry *parent_entry)
 {
 	char *system;
 	struct bootentries *entries;
@@ -873,6 +874,10 @@ static int bootchooser_boot_one(struct bootchooser *bc, int *tryagain)
 	ret = -ENOENT;
 
 	bootentries_for_each_entry(entries, entry) {
+		if (parent_entry)
+			bootm_merge_overrides(&entry->overrides,
+					      &parent_entry->overrides);
+
 		ret = boot_entry(entry, bc->verbose, bc->dryrun);
 		if (!ret) {
 			*tryagain = 0;
@@ -889,12 +894,12 @@ static int bootchooser_boot_one(struct bootchooser *bc, int *tryagain)
 	return ret;
 }
 
-int bootchooser_boot(struct bootchooser *bc)
+int bootchooser_boot(struct bootchooser *bc, struct bootentry *entry)
 {
 	int ret, tryagain;
 
 	do {
-		ret = bootchooser_boot_one(bc, &tryagain);
+		ret = bootchooser_boot_one(bc, &tryagain, entry);
 
 		if (!retry)
 			break;
@@ -915,7 +920,7 @@ int bootchooser_entry_boot(struct bootentry *entry, int verbose, int dryrun)
 	bc->verbose = verbose;
 	bc->dryrun = dryrun;
 
-	ret = bootchooser_boot(bc);
+	ret = bootchooser_boot(bc, entry);
 
 	bootchooser_put(bc);
 
diff --git a/include/bootchooser.h b/include/bootchooser.h
index 36cbd0dd0a01..da3b974ef6f6 100644
--- a/include/bootchooser.h
+++ b/include/bootchooser.h
@@ -14,7 +14,7 @@ int bootchooser_put(struct bootchooser *bootchooser);
 
 void bootchooser_info(struct bootchooser *bootchooser);
 
-int bootchooser_boot(struct bootchooser *bc);
+int bootchooser_boot(struct bootchooser *bc, struct bootentry *entry);
 
 struct bootchooser_target *bootchooser_get_last_chosen(struct bootchooser *bootchooser);
 void bootchooser_lock_attempts(bool locked);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 05/16] bootm: switch plain file names case to loadable API
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (2 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 04/16] bootchooser: pass along " Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 06/16] uimage: add offset parameter to uimage_load Ahmad Fatoum
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The current bootm code has a number of downsides:

- uImage/FIT image support are deeply embedded in the code and every
  bootm_load_* function needs to take care of them in addition to
  the plain file load case

- Override handling is very invasive due to the above and needs to be
  repeated at every bootm_load_*

- Once a suitable starting region is determined, there is sometimes
  no bound checks to verify that the region is not exceeded

- Decompression necessarily needs a memory copy as only after a separate
  bootm handle for the compressed binary runs, the inside decompressed
  binary can actually have its bootm handler invoked

While trying to add support for multiple initrds, it became apparent, that
this further increase in complexity is not a good way forward.

As the current code is very intertwined and piece-wise migration would lead
to intermittent breakage, rework now everything except the uImage and
FIT image handling that will follow later.

This already has a number of benefits:

- override handling moved out of bootm code
- No separate compressed image bootm handler, decompression can be done
  directly to the final load address (in theory, the uncompress
  implementation still use an intermediate buffer for now).

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 arch/arm/lib32/bootm.c    |  41 +++--
 common/Kconfig            |   8 +
 common/Makefile           |   1 +
 common/boot.c             |   8 +-
 common/bootm-overrides.c  |  59 +++++++
 common/bootm.c            | 313 +++++++++++++++-----------------------
 include/bootm-overrides.h |  30 ++--
 include/bootm.h           |  21 ++-
 lib/Kconfig               |   1 +
 9 files changed, 254 insertions(+), 228 deletions(-)
 create mode 100644 common/bootm-overrides.c

diff --git a/arch/arm/lib32/bootm.c b/arch/arm/lib32/bootm.c
index 22364beadcd5..7ad2a26fdc2f 100644
--- a/arch/arm/lib32/bootm.c
+++ b/arch/arm/lib32/bootm.c
@@ -194,41 +194,34 @@ static int bootm_load_tee_from_fit(struct image_data *data)
 out:
 	return ret;
 }
-static int bootm_load_tee_from_file(struct image_data *data)
+static int bootm_load_tee(struct image_data *data)
 {
-	int fd, ret;
+	int ret;
 	struct optee_header hdr;
 
-	fd = open(data->tee_file, O_RDONLY);
-	if (fd < 0) {
-		pr_err("%m\n");
-		return -errno;
-	}
+	if (!data->tee)
+		return 0;
 
-	if (read_full(fd, &hdr, sizeof(hdr)) < 0) {
-		pr_err("%m\n");
-		ret = -errno;
-		goto out;
-	}
+	ret = loadable_extract_into_buf(data->tee, &hdr, sizeof(hdr), 0,
+					LOADABLE_EXTRACT_PARTIAL);
+	if (ret < 0)
+		return ret;
 
 	ret = optee_verify_header_request_region(data, &hdr);
 	if (ret < 0)
-		goto out;
+		return ret;
 
-	if (read_full(fd, (void *)data->tee_res->start, hdr.init_size) < 0) {
-		pr_err("%m\n");
-		ret = -errno;
+	ret = loadable_extract_into_buf(data->tee, (void *)data->tee_res->start,
+					hdr.init_size, sizeof(hdr), 0);
+	if (ret < 0) {
 		release_region(data->tee_res);
-		goto out;
+		return ret;
 	}
 
-	printf("Read optee file to %pa, size 0x%08x\n", (void *)data->tee_res->start, hdr.init_size);
+	printf("Loaded TEE image to %pa, size 0x%08x\n",
+	       (void *)data->tee_res->start, hdr.init_size);
 
-	ret = 0;
-out:
-	close(fd);
-
-	return ret;
+	return 0;
 }
 
 static int __do_bootm_linux(struct image_data *data, unsigned long free_mem,
@@ -295,7 +288,7 @@ static int __do_bootm_linux(struct image_data *data, unsigned long free_mem,
 
 	if (IS_ENABLED(CONFIG_BOOTM_OPTEE)) {
 		if (data->tee_file && !bootm_signed_images_are_forced()) {
-			ret = bootm_load_tee_from_file(data);
+			ret = bootm_load_tee(data);
 			if (ret)
 				return ret;
 		} else if (IS_ENABLED(CONFIG_FITIMAGE)) {
diff --git a/common/Kconfig b/common/Kconfig
index cd002865f736..fd422714d560 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -572,6 +572,14 @@ config TIMESTAMP
 menuconfig BOOTM
 	default y if COMMAND_SUPPORT
 	bool "bootm support"
+	select LOADABLE
+
+config BOOTM_COMPRESSED
+	bool "bootm support for compressed images"
+	default y
+	depends on BOOTM
+	depends on UNCOMPRESS
+	select LOADABLE_DECOMPRESS
 
 config BOOT_OVERRIDE
 	bool "Support partial override of boot entries"
diff --git a/common/Makefile b/common/Makefile
index ac39ee4e3ed5..27c5dea16860 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_BLOCK)		+= block.o
 obj-$(CONFIG_BLSPEC)		+= blspec.o
 obj-$(CONFIG_BOOTM)		+= bootm.o booti.o
 obj-$(CONFIG_BOOTM_AIMAGE)	+= bootm-android-image.o
+obj-$(CONFIG_BOOT_OVERRIDE)	+= bootm-overrides.o
 obj-$(CONFIG_CMD_LOADS)		+= s_record.o
 obj-$(CONFIG_MEMTEST)		+= memtest.o
 obj-$(CONFIG_COMMAND_SUPPORT)	+= command.o
diff --git a/common/boot.c b/common/boot.c
index b5a4205cd9e2..dc1441d0dc91 100644
--- a/common/boot.c
+++ b/common/boot.c
@@ -563,7 +563,6 @@ BAREBOX_MAGICVAR(global.boot.default, "default boot order");
  */
 int bootm_entry(struct bootentry *be, const struct bootm_data *bootm_data)
 {
-	struct bootm_overrides old;
 	struct image_data *data;
 	int ret;
 
@@ -571,12 +570,13 @@ int bootm_entry(struct bootentry *be, const struct bootm_data *bootm_data)
 	if (IS_ERR(data))
 		return PTR_ERR(data);
 
-	old = bootm_save_overrides(be->overrides);
+	ret = bootm_apply_overrides(data, &be->overrides);
+	if (ret)
+		goto out;
 
 	ret = bootm_boot_handler(data);
 
-	bootm_restore_overrides(old);
-
+out:
 	bootm_boot_cleanup(data);
 	return ret;
 }
diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
new file mode 100644
index 000000000000..e1cba21e7711
--- /dev/null
+++ b/common/bootm-overrides.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <bootm.h>
+#include <bootm-overrides.h>
+
+/**
+ * bootm_apply_overrides - apply overrides
+ * @data: image data context
+ * @overrides: overrides to apply
+ *
+ * Applies bootm.initrd and bootm.oftree overrides by translating
+ * them into file-based loadables.
+ *
+ * Context: Called during boot preparation
+ * Return: 0 on success, negative error code otherwise
+ */
+int bootm_apply_overrides(struct image_data *data,
+			  const struct bootm_overrides *overrides)
+{
+	if (bootm_signed_images_are_forced())
+		return 0;
+
+	/* TODO: As we haven't switched over everything to loadables yet,
+	 * we need a special marker to mean override to empty.
+	 * We do this via a 0-byte file (/dev/null) for now..
+	 */
+
+	if (overrides->initrd_file) {
+		loadable_release(&data->initrd);
+
+		/* Empty string means to mask the original initrd */
+		if (nonempty(overrides->initrd_file))
+			data->initrd = loadable_from_file(overrides->initrd_file,
+							   LOADABLE_INITRD);
+		else
+			data->initrd = loadable_from_file("/dev/null",
+							   LOADABLE_INITRD);
+		if (IS_ERR(data->initrd))
+			return PTR_ERR(data->initrd);
+		data->is_override.initrd = true;
+	}
+
+	if (overrides->oftree_file) {
+		loadable_release(&data->oftree);
+
+		/* Empty string means to mask the original FDT */
+		if (nonempty(overrides->oftree_file))
+			data->oftree = loadable_from_file(overrides->oftree_file,
+							   LOADABLE_FDT);
+		else
+			data->oftree = loadable_from_file("/dev/null",
+							   LOADABLE_FDT);
+		if (IS_ERR(data->oftree))
+			return PTR_ERR(data->oftree);
+		data->is_override.oftree = true;
+	}
+
+	return 0;
+}
diff --git a/common/bootm.c b/common/bootm.c
index a10f3a4ea9dc..74caa3b13d86 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -3,7 +3,6 @@
 #include <common.h>
 #include <bootargs.h>
 #include <bootm.h>
-#include <bootm-overrides.h>
 #include <fs.h>
 #include <fcntl.h>
 #include <efi/mode.h>
@@ -19,15 +18,12 @@
 #include <environment.h>
 #include <linux/stat.h>
 #include <magicvar.h>
-#include <uncompress.h>
 #include <zero_page.h>
 #include <security/config.h>
 
 static LIST_HEAD(handler_list);
 static struct sconfig_notifier_block sconfig_notifier;
 
-static __maybe_unused struct bootm_overrides bootm_overrides;
-
 static bool uimage_check(struct image_handler *handler,
 			 struct image_data *data,
 			 enum filetype detected_filetype)
@@ -211,23 +207,6 @@ static inline bool image_is_uimage(struct image_data *data)
 	return IS_ENABLED(CONFIG_BOOTM_UIMAGE) && data->os_uimage;
 }
 
-static bool bootm_get_override(char **oldpath, const char *newpath)
-{
-	if (!IS_ENABLED(CONFIG_BOOT_OVERRIDE))
-		return false;
-	if (bootm_signed_images_are_forced())
-		return false;
-	if (!newpath)
-		return false;
-
-	if (oldpath && !streq_ptr(*oldpath, newpath)) {
-		free(*oldpath);
-		*oldpath = *newpath ? xstrdup(newpath) : NULL;
-	}
-
-	return true;
-}
-
 /**
  * bootm_load_os() - load OS to RAM
  * @data:		image data context
@@ -243,6 +222,7 @@ static bool bootm_get_override(char **oldpath, const char *newpath)
 const struct resource *bootm_load_os(struct image_data *data,
 		ulong load_address, ulong end_address)
 {
+	struct resource *res;
 	int err;
 
 	if (data->os_res)
@@ -253,17 +233,22 @@ const struct resource *bootm_load_os(struct image_data *data,
 	if (end_address <= load_address)
 		return ERR_PTR(-EINVAL);
 
-	if (data->os_fit) {
-		err = bootm_load_fit_os(data, load_address);
-	} else if (image_is_uimage(data)) {
-		err = bootm_load_uimage_os(data, load_address);
-	} else if (data->os_file) {
-		data->os_res = file_to_sdram(data->os_file, load_address, MEMTYPE_LOADER_CODE);
-		err = data->os_res ? 0 : -EBUSY;
-	} else {
-		err = -EINVAL;
+	if (data->os) {
+		res = loadable_extract_into_sdram_all(data->os, load_address, end_address);
+		if (!IS_ERR(res))
+			data->os_res = res;
+		return res;
 	}
 
+	/* TODO: eliminate below special cases */
+
+	if (data->os_fit)
+		err = bootm_load_fit_os(data, load_address);
+	else if (image_is_uimage(data))
+		err = bootm_load_uimage_os(data, load_address);
+	else
+		err = -EINVAL;
+
 	if (err)
 		return ERR_PTR(err);
 
@@ -308,7 +293,12 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
 	if (end_address <= load_address)
 		return ERR_PTR(-EINVAL);
 
-	bootm_get_override(&data->initrd_file, bootm_overrides.initrd_file);
+	if (data->initrd) {
+		res = loadable_extract_into_sdram_all(data->initrd, load_address, end_address);
+		if (!IS_ERR(res))
+			data->initrd_res = res;
+		return res;
+	}
 
 	initrd = data->initrd_file;
 	if (initrd) {
@@ -324,11 +314,6 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
 		res = bootm_load_uimage_initrd(data, load_address);
 		if (data->initrd_uimage->header.ih_type == IH_TYPE_MULTI)
 			initrd_part = data->initrd_part;
-
-	} else if (initrd) {
-		res = file_to_sdram(initrd, load_address, MEMTYPE_LOADER_DATA)
-			?: ERR_PTR(-EBUSY);
-
 	} else if (data->os_fit) {
 		res = bootm_load_fit_initrd(data, load_address);
 		type = filetype_fit;
@@ -368,21 +353,40 @@ void *bootm_get_devicetree(struct image_data *data)
 {
 	enum filetype type;
 	struct fdt_header *oftree;
-	bool from_fit = false;
 	int ret;
 
 	if (!IS_ENABLED(CONFIG_OFTREE))
 		return ERR_PTR(-ENOSYS);
 
-	from_fit = bootm_fit_has_fdt(data);
-	if (bootm_get_override(&data->oftree_file, bootm_overrides.oftree_file))
-		from_fit = false;
+	if (data->oftree) {
+		const struct fdt_header *oftree_view;
+		size_t size;
 
-	if (from_fit) {
+		oftree_view = loadable_view(data->oftree, &size);
+		if (IS_ERR(oftree_view))
+			pr_err("could not open device tree \"%s\": %pe\n",
+			       data->oftree_file, oftree_view);
+		if (IS_ERR_OR_NULL(oftree_view))
+			return ERR_CAST(oftree_view);
+
+		data->of_root_node = of_unflatten_dtb(oftree_view, size);
+		loadable_view_free(data->oftree, oftree_view, size);
+
+		if (IS_ERR(data->of_root_node)) {
+			data->of_root_node = NULL;
+			pr_err("unable to unflatten devicetree\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+	} else if (bootm_fit_has_fdt(data)) {
 		data->of_root_node = bootm_get_fit_devicetree(data);
 	} else if (data->oftree_file) {
 		size_t size;
 
+		/* TODO: There's some duplication here, but that will go away
+		 * once we switch this over to the loadable API
+		 */
+
 		ret = file_name_detect_type(data->oftree_file, &type);
 		if (ret) {
 			pr_err("could not open device tree \"%s\": %pe\n", data->oftree_file,
@@ -390,21 +394,10 @@ void *bootm_get_devicetree(struct image_data *data)
 			return ERR_PTR(ret);
 		}
 
-		switch (type) {
-		case filetype_uimage:
-			ret = bootm_open_oftree_uimage(data, &size, &oftree);
-			break;
-		case filetype_oftree:
-			pr_info("Loading devicetree from '%s'\n", data->oftree_file);
-			ret = read_file_2(data->oftree_file, &size, (void *)&oftree,
-					  FILESIZE_MAX);
-			break;
-		case filetype_empty:
-			return NULL;
-		default:
+		if (type != filetype_uimage)
 			return ERR_PTR(-EINVAL);
-		}
 
+		ret = bootm_open_oftree_uimage(data, &size, &oftree);
 		if (ret)
 			return ERR_PTR(ret);
 
@@ -488,25 +481,18 @@ bootm_load_devicetree(struct image_data *data, void *fdt,
 	return data->oftree_res;
 }
 
-int bootm_get_os_size(struct image_data *data)
+loff_t bootm_get_os_size(struct image_data *data)
 {
-	const char *os_file;
-	struct stat s;
-	int ret;
+	loff_t size;
 
+	if (data->os)
+		return loadable_get_size(data->os, &size) ?: size;
 	if (image_is_uimage(data))
 		return uimage_get_size(data->os_uimage, uimage_part_num(data->os_part));
 	if (data->os_fit)
 		return data->fit_kernel_size;
-	if (!data->os_file)
-		return -EINVAL;
-	os_file = data->os_file;
 
-	ret = stat(os_file, &s);
-	if (ret)
-		return ret;
-
-	return s.st_size;
+	return -EINVAL;
 }
 
 static void bootm_print_info(struct image_data *data)
@@ -569,6 +555,63 @@ int file_read_and_detect_boot_image_type(const char *os_file, void **os_header)
 	return file_detect_boot_image_type(*os_header, PAGE_SIZE);
 }
 
+static int bootm_open_files(struct image_data *data)
+{
+	data->os = loadable_from_file(data->os_file, LOADABLE_KERNEL);
+	if (IS_ERR(data->os))
+		return PTR_ERR(data->os);
+
+	if (data->oftree_file) {
+		data->oftree = loadable_from_file(data->oftree_file, LOADABLE_FDT);
+		if (IS_ERR(data->oftree))
+			return PTR_ERR(data->oftree);
+	}
+
+	if (data->initrd_file) {
+		data->initrd = loadable_from_file(data->initrd_file, LOADABLE_INITRD);
+		if (IS_ERR(data->initrd))
+			return PTR_ERR(data->initrd);
+	}
+
+	if (data->tee_file) {
+		data->tee = loadable_from_file(data->tee_file, LOADABLE_TEE);
+		if (IS_ERR(data->tee))
+			return PTR_ERR(data->tee);
+	}
+
+	return 0;
+}
+
+int bootm_open_os_compressed(struct image_data *data)
+{
+	void *header;
+	ssize_t ret;
+
+	if (!IS_ENABLED(CONFIG_BOOTM_COMPRESSED))
+		return -ENOSYS;
+
+	/* Wrap the OS loadable with transparent decompression */
+	data->os = loadable_decompress(data->os);
+
+	/* Read decompressed header to detect actual kernel type */
+	header = xzalloc(PAGE_SIZE);
+	ret = loadable_extract_into_buf(data->os, header, PAGE_SIZE, 0,
+					LOADABLE_EXTRACT_PARTIAL);
+	if (ret < 0) {
+		free(header);
+		return ret;
+	}
+
+	/* Detect actual image type from decompressed content */
+	data->kernel_type = file_detect_boot_image_type(header, ret);
+
+	/* Replace the os_header with the decompressed header */
+	free(data->os_header);
+	data->os_header = header;
+
+	return 0;
+}
+
 struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 {
 	struct image_data *data;
@@ -636,8 +679,20 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 	case filetype_uimage:
 		ret = bootm_open_uimage(data);
 		break;
+	case filetype_gzip:
+	case filetype_bzip2:
+	case filetype_lzo_compressed:
+	case filetype_lz4_compressed:
+	case filetype_xz_compressed:
+	case filetype_zstd_compressed:
+		ret = bootm_open_files(data);
+		if (!ret)
+			ret = bootm_open_os_compressed(data);
+		image_type_str = "compressed boot";
+		break;
 	default:
-		ret = 0;
+		ret = bootm_open_files(data);
+		image_type_str = "boot";
 		break;
 	}
 
@@ -792,13 +847,6 @@ int bootm_boot_handler(struct image_data *data)
 		printf("Passing control to %s handler\n", handler->name);
 	}
 
-	bootm_get_override(&data->oftree_file, bootm_overrides.oftree_file);
-
-	if (bootm_get_override(&data->initrd_file, bootm_overrides.initrd_file)) {
-		release_sdram_region(data->initrd_res);
-		data->initrd_res = NULL;
-	}
-
 	ret = handler->bootm(data);
 	if (data->dryrun)
 		pr_info("Dryrun. Aborted\n");
@@ -818,6 +866,10 @@ void bootm_boot_cleanup(struct image_data *data)
 		bootm_close_uimage(data);
 	if (data->os_fit)
 		bootm_close_fit(data);
+	loadable_release(&data->oftree);
+	loadable_release(&data->initrd);
+	loadable_release(&data->os);
+	loadable_release(&data->tee);
 	if (data->of_root_node)
 		of_delete_node(data->of_root_node);
 
@@ -849,20 +901,6 @@ int bootm_boot(const struct bootm_data *bootm_data)
 	return ret;
 }
 
-#ifdef CONFIG_BOOT_OVERRIDE
-struct bootm_overrides bootm_save_overrides(const struct bootm_overrides overrides)
-{
-	struct bootm_overrides old = bootm_overrides;
-	/* bootm_merge_overrides copies only actual (non-NULL) overrides */
-	bootm_merge_overrides(&bootm_overrides, &overrides);
-	return old;
-}
-void bootm_restore_overrides(const struct bootm_overrides overrides)
-{
-	bootm_overrides = overrides;
-}
-#endif
-
 bool bootm_efi_check_image(struct image_handler *handler,
 			   struct image_data *data,
 			   enum filetype detected_filetype)
@@ -887,92 +925,6 @@ bool bootm_efi_check_image(struct image_handler *handler,
 	return detected_filetype == handler->filetype;
 }
 
-static int do_bootm_compressed(struct image_data *img_data)
-{
-	struct bootm_data bootm_data = {
-		.oftree_file = img_data->oftree_file,
-		.initrd_file = img_data->initrd_file,
-		.tee_file = img_data->tee_file,
-		.verbose = img_data->verbose,
-		.verify = img_data->verify,
-		.force = img_data->force,
-		.dryrun = img_data->dryrun,
-		.initrd_address = img_data->initrd_address,
-		.os_address = img_data->os_address,
-		.os_entry = img_data->os_entry,
-	};
-	int from, to, ret;
-	char *dstpath;
-
-	from = open(img_data->os_file, O_RDONLY);
-	if (from < 0)
-		return -ENODEV;
-
-	dstpath = make_temp("bootm-compressed");
-	if (!dstpath) {
-		ret = -ENOMEM;
-		goto fail_from;
-	}
-
-	to = open(dstpath, O_CREAT | O_WRONLY);
-	if (to < 0) {
-		ret = -ENODEV;
-		goto fail_make_temp;
-	}
-
-	ret = uncompress_fd_to_fd(from, to, uncompress_err_stdout);
-	if (ret)
-		goto fail_to;
-
-	bootm_data.os_file = dstpath;
-	ret = bootm_boot(&bootm_data);
-
-fail_to:
-	close(to);
-	unlink(dstpath);
-fail_make_temp:
-	free(dstpath);
-fail_from:
-	close(from);
-	return ret;
-}
-
-static struct image_handler bzip2_bootm_handler = {
-	.name = "BZIP2 compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_bzip2,
-};
-
-static struct image_handler gzip_bootm_handler = {
-	.name = "GZIP compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_gzip,
-};
-
-static struct image_handler lzo_bootm_handler = {
-	.name = "LZO compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_lzo_compressed,
-};
-
-static struct image_handler lz4_bootm_handler = {
-	.name = "LZ4 compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_lz4_compressed,
-};
-
-static struct image_handler xz_bootm_handler = {
-	.name = "XZ compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_xz_compressed,
-};
-
-static struct image_handler zstd_bootm_handler = {
-	.name = "ZSTD compressed file",
-	.bootm = do_bootm_compressed,
-	.filetype = filetype_zstd_compressed,
-};
-
 int linux_rootwait_secs = 10;
 
 static const char * const bootm_efi_loader_mode_names[] = {
@@ -1020,19 +972,6 @@ static int bootm_init(void)
 					  bootm_efi_loader_mode_names,
 					  ARRAY_SIZE(bootm_efi_loader_mode_names));
 
-	if (IS_ENABLED(CONFIG_BZLIB))
-		register_image_handler(&bzip2_bootm_handler);
-	if (IS_ENABLED(CONFIG_ZLIB))
-		register_image_handler(&gzip_bootm_handler);
-	if (IS_ENABLED(CONFIG_LZO_DECOMPRESS))
-		register_image_handler(&lzo_bootm_handler);
-	if (IS_ENABLED(CONFIG_LZ4_DECOMPRESS))
-		register_image_handler(&lz4_bootm_handler);
-	if (IS_ENABLED(CONFIG_XZ_DECOMPRESS))
-		register_image_handler(&xz_bootm_handler);
-	if (IS_ENABLED(CONFIG_ZSTD_DECOMPRESS))
-		register_image_handler(&zstd_bootm_handler);
-
 	return 0;
 }
 late_initcall(bootm_init);
diff --git a/include/bootm-overrides.h b/include/bootm-overrides.h
index b807e5be310a..a52a498b97a2 100644
--- a/include/bootm-overrides.h
+++ b/include/bootm-overrides.h
@@ -7,26 +7,32 @@ struct bootm_overrides {
 	const char *initrd_file;
 };
 
-#ifdef CONFIG_BOOT_OVERRIDE
-struct bootm_overrides bootm_save_overrides(const struct bootm_overrides overrides);
-void bootm_restore_overrides(const struct bootm_overrides overrides);
-#else
-static inline struct bootm_overrides bootm_save_overrides(const struct bootm_overrides overrides)
-{
-	return (struct bootm_overrides) {};
-}
-static inline void bootm_restore_overrides(const struct bootm_overrides overrides) {}
-#endif
+struct image_data;
 
+#ifdef CONFIG_BOOT_OVERRIDE
 static inline void bootm_merge_overrides(struct bootm_overrides *dst,
 					 const struct bootm_overrides *src)
 {
-	if (!IS_ENABLED(CONFIG_BOOT_OVERRIDE))
-		return;
 	if (src->oftree_file)
 		dst->oftree_file = src->oftree_file;
 	if (src->initrd_file)
 		dst->initrd_file = src->initrd_file;
 }
 
+int bootm_apply_overrides(struct image_data *data,
+			  const struct bootm_overrides *overrides);
+#else
+
+static inline void bootm_merge_overrides(struct bootm_overrides *dst,
+					 const struct bootm_overrides *src)
+{
+}
+
+static inline int bootm_apply_overrides(struct image_data *data,
+					const struct bootm_overrides *overrides)
+{
+	return 0;
+}
+#endif
+
 #endif
diff --git a/include/bootm.h b/include/bootm.h
index da6cf7301709..1c3e06f20f47 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -5,6 +5,7 @@
 #include <image.h>
 #include <filetype.h>
 #include <linux/list.h>
+#include <loadable.h>
 
 enum bootm_verify {
 	BOOTM_VERIFY_NONE,
@@ -55,6 +56,7 @@ struct bootm_data {
 int bootm_boot(const struct bootm_data *data);
 
 struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data);
+int bootm_open_os_compressed(struct image_data *data);
 int bootm_boot_handler(struct image_data *data);
 void bootm_boot_cleanup(struct image_data *data);
 
@@ -62,6 +64,9 @@ struct image_data {
 	/* simplest case. barebox has already loaded the os here */
 	struct resource *os_res;
 
+	/* Future default case: A generic loadable object */
+	struct loadable *os;
+
 	/* if os is an uImage this will be provided */
 	struct uimage_handle *os_uimage;
 
@@ -87,6 +92,9 @@ struct image_data {
 	/* if initrd is already loaded this resource will be !NULL */
 	struct resource *initrd_res;
 
+	/* Future default case: A generic loadable object */
+	struct loadable *initrd;
+
 	/* if initrd is an uImage this will be provided */
 	struct uimage_handle *initrd_uimage;
 	char *initrd_part;
@@ -106,6 +114,9 @@ struct image_data {
 	struct device_node *of_root_node;
 	struct resource *oftree_res;
 
+	/* Future default case: A generic loadable object */
+	struct loadable *oftree;
+
 	/*
 	 * The first PAGE_SIZE bytes of the OS image. Can be used by the image
 	 * handlers to analyze the OS image before actually loading the bulk of
@@ -115,6 +126,9 @@ struct image_data {
 	char *tee_file;
 	struct resource *tee_res;
 
+	/* Future default case: A generic loadable object */
+	struct loadable *tee;
+
 	/* Type of OS image, e.g. filetype_fit or the same as kernel_type */
 	enum filetype image_type;
 	/* Type of kernel image that's going to be booted */
@@ -124,6 +138,11 @@ struct image_data {
 	int verbose;
 	int force;
 	int dryrun;
+	struct {
+		u8 os:1;
+		u8 initrd:1;
+		u8 oftree:1;
+	} is_override;
 	enum bootm_efi_mode efi_boot;
 };
 
@@ -171,7 +190,7 @@ void *bootm_get_devicetree(struct image_data *data);
 const struct resource *
 bootm_load_devicetree(struct image_data *data, void *fdt,
 		      ulong load_address, ulong end_address);
-int bootm_get_os_size(struct image_data *data);
+loff_t bootm_get_os_size(struct image_data *data);
 
 enum bootm_verify bootm_get_verify_mode(void);
 void bootm_set_verify_mode(enum bootm_verify mode);
diff --git a/lib/Kconfig b/lib/Kconfig
index 3e314c05d7ee..5f5771ff2bdf 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -14,6 +14,7 @@ config LOADABLE
 	  Provides infrastructure for lazy-loadable resources that can be
 	  queried for metadata without loading data, and extracted to memory
 	  on demand.
+	  Used mainly by bootm for managing kernel/initrd/fdt loading.
 
 config LOADABLE_DECOMPRESS
 	bool "Decompression support for loadable resources" if COMPILE_TEST
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 06/16] uimage: add offset parameter to uimage_load
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (3 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 05/16] bootm: switch plain file names case to loadable API Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 07/16] bootm: uimage: switch to loadable API Ahmad Fatoum
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The loadable API allows users to specify an offset at which reading
should start. In preparation for wrapping the uImage loading as
loadable, extend uimage_load to allow specifying a start offset.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm-uimage.c |  4 ++--
 common/uimage.c       | 39 ++++++++++++++++++++++++++++++++++-----
 include/image.h       |  2 +-
 3 files changed, 37 insertions(+), 8 deletions(-)

diff --git a/common/bootm-uimage.c b/common/bootm-uimage.c
index b32ed5b0e95f..a1cb39d3ecd8 100644
--- a/common/bootm-uimage.c
+++ b/common/bootm-uimage.c
@@ -30,7 +30,7 @@ int bootm_load_uimage_os(struct image_data *data, unsigned long load_address)
 	num = uimage_part_num(data->os_part);
 
 	data->os_res = uimage_load_to_sdram(data->os_uimage,
-		num, load_address);
+		num, load_address, 0);
 	if (!data->os_res)
 		return -ENOMEM;
 
@@ -91,7 +91,7 @@ bootm_load_uimage_initrd(struct image_data *data, unsigned long load_address)
 	num = uimage_part_num(data->initrd_part);
 
 	res = uimage_load_to_sdram(data->initrd_uimage,
-		num, load_address);
+		num, load_address, 0);
 	if (!res)
 		return ERR_PTR(-ENOMEM);
 
diff --git a/common/uimage.c b/common/uimage.c
index d34205572510..510f91a26bee 100644
--- a/common/uimage.c
+++ b/common/uimage.c
@@ -338,10 +338,24 @@ EXPORT_SYMBOL(uimage_load);
 
 static void *uimage_buf;
 static size_t uimage_size;
+static size_t uimage_skip;
 static struct resource *uimage_resource;
 
 static long uimage_sdram_flush(void *buf, unsigned long len)
 {
+	unsigned long skip_now = 0;
+
+	/* Skip the first uimage_skip bytes of decompressed data */
+	if (uimage_skip > 0) {
+		skip_now = min_t(unsigned long, uimage_skip, len);
+		buf += skip_now;
+		len -= skip_now;
+		uimage_skip -= skip_now;
+
+		if (len == 0)
+			return skip_now;
+	}
+
 	if (uimage_size + len > resource_size(uimage_resource)) {
 		resource_size_t start = uimage_resource->start;
 		resource_size_t size = resource_size(uimage_resource) + len;
@@ -362,27 +376,42 @@ static long uimage_sdram_flush(void *buf, unsigned long len)
 
 	uimage_size += len;
 
-	return len;
+	return len + skip_now;
 }
 
 /*
  * Load an uImage to a dynamically allocated sdram resource.
  * the resource must be freed afterwards with release_sdram_region
+ *
+ * @handle: uImage handle
+ * @image_no: image number within the uImage
+ * @load_address: address to load the image to
+ * @offset: offset in bytes to skip from the beginning of the decompressed data
+ *
+ * Returns: SDRAM resource on success, NULL on error
  */
 struct resource *uimage_load_to_sdram(struct uimage_handle *handle,
-		int image_no, unsigned long load_address)
+		int image_no, unsigned long load_address, loff_t offset)
 {
 	int ret;
-	ssize_t size;
+	ssize_t total_size;
+	size_t size;
 	resource_size_t start = (resource_size_t)load_address;
 
 	uimage_buf = (void *)load_address;
 	uimage_size = 0;
+	uimage_skip = offset;
 
-	size = uimage_get_size(handle, image_no);
-	if (size < 0)
+	total_size = uimage_get_size(handle, image_no);
+	if (total_size < 0)
 		return NULL;
 
+	if (offset > total_size)
+		return NULL;
+
+	/* Allocate for the data after offset: size = total_size - offset */
+	size = total_size - offset;
+
 	uimage_resource = request_sdram_region("uimage",
 				start, size, MEMTYPE_LOADER_CODE,
 				MEMATTRS_RWX);
diff --git a/include/image.h b/include/image.h
index 769523d6fcaf..b37e04f54bae 100644
--- a/include/image.h
+++ b/include/image.h
@@ -304,7 +304,7 @@ int uimage_load(struct uimage_handle *handle, unsigned int image_no,
 void uimage_print_contents(struct uimage_handle *handle);
 ssize_t uimage_get_size(struct uimage_handle *handle, unsigned int image_no);
 struct resource *uimage_load_to_sdram(struct uimage_handle *handle,
-		int image_no, unsigned long load_address);
+		int image_no, unsigned long load_address, loff_t offset);
 void *uimage_load_to_buf(struct uimage_handle *handle, int image_no,
 		size_t *size);
 #define MAX_MULTI_IMAGE_COUNT 16
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 07/16] bootm: uimage: switch to loadable API
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (4 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 06/16] uimage: add offset parameter to uimage_load Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 08/16] bootm: fit: switch to new " Ahmad Fatoum
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Optimally, common/bootm.c should not contain uImage specific code at
all. Leverage the new loadable support, so loadables for the boot
artifacts are created once and then we can remove much of the uImage
specific code that happens later in bootm.c

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm-uimage.c  | 340 +++++++++++++++++++++++++++++++----------
 common/bootm.c         |  76 +--------
 common/uimage.c        |  60 +++-----
 include/bootm-uimage.h |  31 +---
 include/bootm.h        |   3 +
 include/image.h        |   5 +-
 6 files changed, 292 insertions(+), 223 deletions(-)

diff --git a/common/bootm-uimage.c b/common/bootm-uimage.c
index a1cb39d3ecd8..f2d8d7b22db9 100644
--- a/common/bootm-uimage.c
+++ b/common/bootm-uimage.c
@@ -11,37 +11,56 @@ static int uimage_part_num(const char *partname)
 	return simple_strtoul(partname, NULL, 0);
 }
 
-/*
- * bootm_load_uimage_os() - load uImage OS to RAM
- *
- * @data:		image data context
- * @load_address:	The address where the OS should be loaded to
- *
- * This loads the OS to a RAM location. load_address must be a valid
- * address. If the image_data doesn't have a OS specified it's considered
- * an error.
- *
- * Return: 0 on success, negative error code otherwise
- */
-int bootm_load_uimage_os(struct image_data *data, unsigned long load_address)
+static struct loadable *loadable_from_uimage(struct uimage_handle *uimage,
+					     int part_num,
+					     enum loadable_type type,
+					     bool exclusive);
+
+static void loadable_from_uimage_os(struct image_data *data)
 {
 	int num;
 
 	num = uimage_part_num(data->os_part);
 
-	data->os_res = uimage_load_to_sdram(data->os_uimage,
-		num, load_address, 0);
-	if (!data->os_res)
-		return -ENOMEM;
-
-	return 0;
+	loadable_release(&data->os);
+	data->os = loadable_from_uimage(data->os_uimage, num, LOADABLE_KERNEL, true);
 }
 
-static int bootm_open_initrd_uimage(struct image_data *data)
+static int file_name_detect_type_is_uimage(const char *filename, const char *desc)
 {
+	enum filetype type;
 	int ret;
 
-	if (strcmp(data->os_file, data->initrd_file)) {
+	ret = file_name_detect_type(filename, &type);
+	if (ret < 0) {
+		pr_err("could not open %s \"%s\": %pe\n",
+		       desc, filename, ERR_PTR(ret));
+		return ret;
+	}
+
+	return type == filetype_uimage;
+}
+
+static int loadable_from_uimage_initrd(struct image_data *data)
+{
+	int ret, num;
+	bool release = false;
+
+	if (streq_ptr(data->os_file, data->initrd_file)) {
+		data->initrd_uimage = data->os_uimage;
+	} else {
+		int is_uimage;
+
+		is_uimage = file_name_detect_type_is_uimage(data->initrd_file, "initrd");
+		if (is_uimage < 0)
+			return is_uimage;
+		if (!is_uimage) {
+			loadable_release(&data->initrd);
+			data->initrd = loadable_from_file(data->initrd_file,
+							  LOADABLE_INITRD);
+			return PTR_ERR_OR_ZERO(data->initrd);
+		}
+
 		data->initrd_uimage = uimage_open(data->initrd_file);
 		if (!data->initrd_uimage)
 			return -EINVAL;
@@ -51,88 +70,62 @@ static int bootm_open_initrd_uimage(struct image_data *data)
 			if (ret) {
 				pr_err("Checking data crc failed with %pe\n",
 					ERR_PTR(ret));
+				uimage_close(data->initrd_uimage);
+				data->initrd_uimage = NULL;
 				return ret;
 			}
 		}
 		uimage_print_contents(data->initrd_uimage);
-	} else {
-		data->initrd_uimage = data->os_uimage;
-	}
-
-	return 0;
-}
-
-/*
- * bootm_load_uimage_initrd() - load initrd from uimage to RAM
- *
- * @data:		image data context
- * @load_address:	The address where the initrd should be loaded to
- *
- * This loads the initrd to a RAM location. load_address must be a valid
- * address. If the image_data doesn't have a initrd specified this function
- * still returns successful as an initrd is optional.
- *
- * Return: initrd resource on success, NULL if no initrd is present or
- *         an error pointer if an error occurred.
- */
-struct resource *
-bootm_load_uimage_initrd(struct image_data *data, unsigned long load_address)
-{
-	struct resource *res;
-	int ret;
-
-	int num;
-	ret = bootm_open_initrd_uimage(data);
-	if (ret) {
-		pr_err("loading initrd failed with %pe\n", ERR_PTR(ret));
-		return ERR_PTR(ret);
+		release = true;
 	}
 
 	num = uimage_part_num(data->initrd_part);
 
-	res = uimage_load_to_sdram(data->initrd_uimage,
-		num, load_address, 0);
-	if (!res)
-		return ERR_PTR(-ENOMEM);
+	loadable_release(&data->initrd);
+	data->initrd = loadable_from_uimage(data->initrd_uimage, num,
+					    LOADABLE_INITRD, release);
 
-	return res;
+	return 0;
 }
 
-int bootm_open_oftree_uimage(struct image_data *data, size_t *size,
-			     struct fdt_header **fdt)
+static int loadable_from_uimage_oftree(struct image_data *data)
 {
-	enum filetype ft;
 	const char *oftree = data->oftree_file;
-	int num = uimage_part_num(data->oftree_part);
+	int num;
 	struct uimage_handle *of_handle;
-	int release = 0;
+	bool release = false;
 
-	pr_info("Loading devicetree from '%s'@%d\n", oftree, num);
-
-	if (!strcmp(data->os_file, oftree)) {
+	if (data->os_uimage && streq_ptr(data->os_file, oftree)) {
 		of_handle = data->os_uimage;
-	} else if (!strcmp(data->initrd_file, oftree)) {
+	} else if (data->initrd_uimage && streq_ptr(data->initrd_file, oftree)) {
 		of_handle = data->initrd_uimage;
 	} else {
+		int is_uimage;
+
+		is_uimage = file_name_detect_type_is_uimage(data->oftree_file, "device tree");
+		if (is_uimage < 0)
+			return is_uimage;
+		if (!is_uimage) {
+			loadable_release(&data->oftree);
+			data->oftree = loadable_from_file(data->oftree_file,
+							  LOADABLE_FDT);
+			return PTR_ERR_OR_ZERO(data->oftree);
+		}
+
 		of_handle = uimage_open(oftree);
 		if (!of_handle)
 			return -ENODEV;
 		uimage_print_contents(of_handle);
-		release = 1;
+		release = true;
 	}
 
-	*fdt = uimage_load_to_buf(of_handle, num, size);
+	num = uimage_part_num(data->oftree_part);
+	pr_info("Loading devicetree from '%s'@%d\n", oftree, num);
 
-	if (release)
-		uimage_close(of_handle);
+	data->oftree_uimage = of_handle;
 
-	ft = file_detect_type(*fdt, *size);
-	if (ft != filetype_oftree) {
-		pr_err("%s is not an oftree but %s\n",
-			data->oftree_file, file_type_to_string(ft));
-		free(*fdt);
-		return -EINVAL;
-	}
+	loadable_release(&data->oftree);
+	data->oftree = loadable_from_uimage(data->oftree_uimage, num, LOADABLE_FDT, release);
 
 	return 0;
 }
@@ -150,7 +143,7 @@ int bootm_open_uimage(struct image_data *data)
 		if (ret) {
 			pr_err("Checking data crc failed with %pe\n",
 					ERR_PTR(ret));
-			return ret;
+			goto err_close;
 		}
 	}
 
@@ -159,18 +152,199 @@ int bootm_open_uimage(struct image_data *data)
 	if (IH_ARCH == IH_ARCH_INVALID || data->os_uimage->header.ih_arch != IH_ARCH) {
 		pr_err("Unsupported Architecture 0x%x\n",
 		       data->os_uimage->header.ih_arch);
-		return -EINVAL;
+		ret = -EINVAL;
+		goto err_close;
 	}
 
 	if (data->os_address == UIMAGE_SOME_ADDRESS)
 		data->os_address = data->os_uimage->header.ih_load;
 
 	return 0;
+
+err_close:
+	uimage_close(data->os_uimage);
+	data->os_uimage = NULL;
+	return ret;
 }
 
-void bootm_close_uimage(struct image_data *data)
+/* === Loadable implementation for uImage === */
+
+struct uimage_loadable_priv {
+	struct uimage_handle *handle;
+	int part_num;
+	bool release_handle;
+};
+
+static int uimage_loadable_get_info(struct loadable *l, struct loadable_info *info)
 {
-	if (data->initrd_uimage && data->initrd_uimage != data->os_uimage)
-		uimage_close(data->initrd_uimage);
-	uimage_close(data->os_uimage);
+	struct uimage_loadable_priv *priv = l->priv;
+	struct uimage_handle *handle = priv->handle;
+	ssize_t size;
+
+	/* Get size from uImage header */
+	size = uimage_get_size(handle, priv->part_num);
+	if (size <= 0)
+		return size < 0 ? size : -EINVAL;
+
+	/*
+	 * uimage_get_size() returns the compressed (stored) size.
+	 * For compressed uImages, the decompressed size is unknown,
+	 * so report LOADABLE_SIZE_UNKNOWN to let loadable_extract()
+	 * fall through to the extract op which handles decompression.
+	 */
+	if (handle->header.ih_comp != IH_COMP_NONE)
+		info->final_size = LOADABLE_SIZE_UNKNOWN;
+	else
+		info->final_size = size;
+
+	return 0;
+}
+
+/**
+ * uimage_loadable_extract_into_buf - load uImage data to target address
+ * @l: loadable representing uImage component
+ * @load_addr: physical address to load data to
+ * @buf_size: size of buffer at load_addr (0 = no limit check)
+ * @offset: how many bytes to skip at the start of the uncompressed input
+ * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+ *
+ * Commits the uImage component to the specified memory address. This involves:
+ * 1. Getting size information from loadable
+ * 2. Checking buffer size if buf_size > 0
+ * 3. Calling uimage_load_to_sdram() to decompress and load data
+ * 4. Returning actual bytes written
+ *
+ * The uimage_load_to_sdram() function handles decompression (if needed),
+ * memory allocation with request_sdram_region(), and copying data to the
+ * target address.
+ *
+ * Return: actual number of bytes written on success, negative errno on error
+ *         -ENOSPC if buf_size is specified and too small
+ *         -ENOMEM if failed to load to SDRAM
+ */
+static ssize_t uimage_loadable_extract_into_buf(struct loadable *l, void *load_addr,
+						size_t buf_size, loff_t offset,
+						unsigned flags)
+{
+	struct uimage_loadable_priv *priv = l->priv;
+	return uimage_load_into_fixed_buf(priv->handle, priv->part_num,
+					  load_addr, buf_size, offset,
+					  flags & LOADABLE_EXTRACT_PARTIAL);
+}
+
+static void *uimage_loadable_extract(struct loadable *l, size_t *size)
+{
+	struct uimage_loadable_priv *priv = l->priv;
+
+	return uimage_load_to_buf(priv->handle, priv->part_num, size);
+}
+
+static void uimage_loadable_release(struct loadable *l)
+{
+	struct uimage_loadable_priv *priv = l->priv;
+
+	if (priv->release_handle)
+		uimage_close(priv->handle);
+	free(priv);
+}
+
+static const struct loadable_ops uimage_loadable_ops = {
+	.get_info = uimage_loadable_get_info,
+	.extract_into_buf = uimage_loadable_extract_into_buf,
+	.extract = uimage_loadable_extract,
+	/* .mmap is implementable for uncompressed content if anyone cares enough */
+	.release = uimage_loadable_release,
+};
+
+/**
+ * loadable_from_uimage - create a loadable from uImage component
+ * @uimage: opened uImage handle
+ * @part_num: partition/part number within uImage (0 for single-part)
+ * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.)
+ * @exclusive: whether the uimage will be owned (and released) by the loadable
+ *
+ * Creates a loadable structure that wraps access to a component within a
+ * uImage. For multi-part uImages, part_num selects which part to load.
+ * The loadable uses the uImage handle to access and potentially decompress
+ * data on demand during commit.
+ *
+ * The created loadable must be freed with loadable_release() when done.
+ * The uImage handle itself is managed by the caller and must remain valid
+ * until the loadable is released.
+ *
+ * Return: pointer to allocated loadable on success, ERR_PTR() on error
+ */
+static struct loadable *loadable_from_uimage(struct uimage_handle *uimage,
+					     int part_num,
+					     enum loadable_type type,
+					     bool exclusive)
+{
+	struct loadable *l;
+	struct uimage_loadable_priv *priv;
+
+	l = xzalloc(sizeof(*l));
+	priv = xzalloc(sizeof(*priv));
+
+	priv->handle = uimage;
+	priv->release_handle = exclusive;
+	priv->part_num = part_num;
+
+	/* Create descriptive name */
+	if (part_num > 0)
+		l->name = xasprintf("uImage(%s, %d)", uimage->filename, part_num);
+	else
+		l->name = xasprintf("uImage(%s)", uimage->filename);
+
+	l->type = type;
+	l->ops = &uimage_loadable_ops;
+	l->priv = priv;
+	loadable_init(l);
+
+	return l;
+}
+
+/**
+ * bootm_collect_uimage_loadables - create loadables from opened uImage
+ * @data: image data context with opened uImage handle
+ *
+ * Creates loadable structures for boot components from opened uImage handles.
+ * This includes:
+ * * Kernel from data->os_uimage (using data->os_part for multi-part selection)
+ * * Initrd from data->initrd_files uImage if specified (opens it if needed)
+ *
+ * For initrd handling:
+ * * If initrd_files matches os_file: uses same uImage handle (multi-part)
+ * * Otherwise: opens separate uImage for initrd and verifies it
+ *
+ * Each loadable is added to data->loadables list and appropriate shortcuts
+ * (data->kernel) are set. The loadables are not yet committed to memory - that
+ * happens later during bootm_load_os/bootm_load_initrd.
+ *
+ * Note: FDT and TEE are not commonly used in uImage format and are not
+ * collected here.
+ *
+ * Requires: data->os_uimage must be already opened by bootm_open_uimage()
+ * Context: Called during boot preparation for uImage boots
+ */
+int bootm_collect_uimage_loadables(struct image_data *data)
+{
+	int ret;
+
+	/* Create kernel loadable from opened uImage */
+	if (data->os_uimage)
+		loadable_from_uimage_os(data);
+
+	if (IS_ENABLED(CONFIG_BOOTM_INITRD) && data->initrd_file) {
+		ret = loadable_from_uimage_initrd(data);
+		if (ret)
+			return ret;
+	}
+
+	if (IS_ENABLED(CONFIG_BOOTM_OFTREE_UIMAGE) && data->oftree_file) {
+		ret = loadable_from_uimage_oftree(data);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
diff --git a/common/bootm.c b/common/bootm.c
index 74caa3b13d86..08a8fbbfdcf5 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -202,11 +202,6 @@ static int uimage_part_num(const char *partname)
 	return simple_strtoul(partname, NULL, 0);
 }
 
-static inline bool image_is_uimage(struct image_data *data)
-{
-	return IS_ENABLED(CONFIG_BOOTM_UIMAGE) && data->os_uimage;
-}
-
 /**
  * bootm_load_os() - load OS to RAM
  * @data:		image data context
@@ -244,8 +239,6 @@ const struct resource *bootm_load_os(struct image_data *data,
 
 	if (data->os_fit)
 		err = bootm_load_fit_os(data, load_address);
-	else if (image_is_uimage(data))
-		err = bootm_load_uimage_os(data, load_address);
 	else
 		err = -EINVAL;
 
@@ -278,9 +271,6 @@ const struct resource *
 bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address)
 {
 	struct resource *res = NULL;
-	const char *initrd, *initrd_part = NULL;
-	enum filetype type = filetype_unknown;
-	int ret;
 
 	if (!IS_ENABLED(CONFIG_BOOTM_INITRD))
 		return NULL;
@@ -300,24 +290,8 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
 		return res;
 	}
 
-	initrd = data->initrd_file;
-	if (initrd) {
-		ret = file_name_detect_type(initrd, &type);
-		if (ret) {
-			pr_err("could not open initrd \"%s\": %pe\n",
-			       initrd, ERR_PTR(ret));
-			return ERR_PTR(ret);
-		}
-	}
-
-	if (type == filetype_uimage) {
-		res = bootm_load_uimage_initrd(data, load_address);
-		if (data->initrd_uimage->header.ih_type == IH_TYPE_MULTI)
-			initrd_part = data->initrd_part;
-	} else if (data->os_fit) {
+	if (data->os_fit)
 		res = bootm_load_fit_initrd(data, load_address);
-		type = filetype_fit;
-	}
 
 	if (IS_ERR_OR_NULL(res))
 		return res;
@@ -328,11 +302,6 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
 	if (WARN_ON(res->end > end_address))
 		return ERR_PTR(-ENOSPC);
 
-	pr_info("Loaded initrd from %s %s%s%s to %pa-%pa\n",
-		file_type_to_string(type), initrd ?: "",
-		initrd_part ? "@" : "", initrd_part ?: "",
-		&res->start, &res->end);
-
 	data->initrd_res = res;
 	return data->initrd_res;
 }
@@ -351,9 +320,7 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
  */
 void *bootm_get_devicetree(struct image_data *data)
 {
-	enum filetype type;
 	struct fdt_header *oftree;
-	int ret;
 
 	if (!IS_ENABLED(CONFIG_OFTREE))
 		return ERR_PTR(-ENOSYS);
@@ -380,37 +347,6 @@ void *bootm_get_devicetree(struct image_data *data)
 
 	} else if (bootm_fit_has_fdt(data)) {
 		data->of_root_node = bootm_get_fit_devicetree(data);
-	} else if (data->oftree_file) {
-		size_t size;
-
-		/* TODO: There's some duplication here, but that will go away
-		 * once we switch this over to the loadable API
-		 */
-
-		ret = file_name_detect_type(data->oftree_file, &type);
-		if (ret) {
-			pr_err("could not open device tree \"%s\": %pe\n", data->oftree_file,
-			       ERR_PTR(ret));
-			return ERR_PTR(ret);
-		}
-
-		if (type != filetype_uimage)
-			return ERR_PTR(-EINVAL);
-
-		ret = bootm_open_oftree_uimage(data, &size, &oftree);
-		if (ret)
-			return ERR_PTR(ret);
-
-		data->of_root_node = of_unflatten_dtb(oftree, size);
-
-		free(oftree);
-
-		if (IS_ERR(data->of_root_node)) {
-			data->of_root_node = NULL;
-			pr_err("unable to unflatten devicetree\n");
-			return ERR_PTR(-EINVAL);
-		}
-
 	} else {
 		data->of_root_node = of_dup_root_node_for_boot();
 		if (!data->of_root_node)
@@ -487,8 +423,6 @@ loff_t bootm_get_os_size(struct image_data *data)
 
 	if (data->os)
 		return loadable_get_size(data->os, &size) ?: size;
-	if (image_is_uimage(data))
-		return uimage_get_size(data->os_uimage, uimage_part_num(data->os_part));
 	if (data->os_fit)
 		return data->fit_kernel_size;
 
@@ -701,6 +635,12 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 		goto err_out;
 	}
 
+	if (IS_ENABLED(CONFIG_BOOTM_UIMAGE)) {
+		ret = bootm_collect_uimage_loadables(data);
+		if (ret)
+			goto err_out;
+	}
+
 	if (bootm_data->appendroot) {
 		const char *root = NULL;
 		const char *rootopts = NULL;
@@ -862,8 +802,6 @@ void bootm_boot_cleanup(struct image_data *data)
 	release_sdram_region(data->initrd_res);
 	release_sdram_region(data->oftree_res);
 	release_sdram_region(data->tee_res);
-	if (image_is_uimage(data))
-		bootm_close_uimage(data);
 	if (data->os_fit)
 		bootm_close_fit(data);
 	loadable_release(&data->oftree);
diff --git a/common/uimage.c b/common/uimage.c
index 510f91a26bee..cfa9d600c144 100644
--- a/common/uimage.c
+++ b/common/uimage.c
@@ -339,7 +339,8 @@ EXPORT_SYMBOL(uimage_load);
 static void *uimage_buf;
 static size_t uimage_size;
 static size_t uimage_skip;
-static struct resource *uimage_resource;
+static size_t uimage_maxsize;
+static bool uimage_extract_partial;
 
 static long uimage_sdram_flush(void *buf, unsigned long len)
 {
@@ -356,18 +357,14 @@ static long uimage_sdram_flush(void *buf, unsigned long len)
 			return skip_now;
 	}
 
-	if (uimage_size + len > resource_size(uimage_resource)) {
-		resource_size_t start = uimage_resource->start;
-		resource_size_t size = resource_size(uimage_resource) + len;
+	if (!uimage_extract_partial && uimage_size + len > uimage_maxsize)
+		return -ENOSPC;
 
-		release_sdram_region(uimage_resource);
+	/* Buffer full: discard excess data in partial mode */
+	if (uimage_size >= uimage_maxsize)
+		return len + skip_now;
 
-		uimage_resource = request_sdram_region("uimage",
-				start, size, MEMTYPE_LOADER_CODE,
-				MEMATTRS_RWX);
-		if (!uimage_resource)
-			return -ENOMEM;
-	}
+	len = min_t(unsigned long, len, uimage_maxsize - uimage_size);
 
 	if (zero_page_contains((unsigned long)uimage_buf + uimage_size))
 		zero_page_memcpy(uimage_buf + uimage_size, buf, len);
@@ -380,53 +377,34 @@ static long uimage_sdram_flush(void *buf, unsigned long len)
 }
 
 /*
- * Load an uImage to a dynamically allocated sdram resource.
- * the resource must be freed afterwards with release_sdram_region
+ * Load an uImage to a fixed buffer
  *
  * @handle: uImage handle
  * @image_no: image number within the uImage
  * @load_address: address to load the image to
  * @offset: offset in bytes to skip from the beginning of the decompressed data
  *
- * Returns: SDRAM resource on success, NULL on error
+ * Return: number of bytes written on success or negative error code
  */
-struct resource *uimage_load_to_sdram(struct uimage_handle *handle,
-		int image_no, unsigned long load_address, loff_t offset)
+int uimage_load_into_fixed_buf(struct uimage_handle *handle, int image_no,
+			       void *load_address, size_t size,
+			       loff_t offset, bool partial)
 {
 	int ret;
-	ssize_t total_size;
-	size_t size;
-	resource_size_t start = (resource_size_t)load_address;
 
 	uimage_buf = (void *)load_address;
+	uimage_maxsize = size;
 	uimage_size = 0;
 	uimage_skip = offset;
-
-	total_size = uimage_get_size(handle, image_no);
-	if (total_size < 0)
-		return NULL;
-
-	if (offset > total_size)
-		return NULL;
-
-	/* Allocate for the data after offset: size = total_size - offset */
-	size = total_size - offset;
-
-	uimage_resource = request_sdram_region("uimage",
-				start, size, MEMTYPE_LOADER_CODE,
-				MEMATTRS_RWX);
-	if (!uimage_resource)
-		return NULL;
+	uimage_extract_partial = partial;
 
 	ret = uimage_load(handle, image_no, uimage_sdram_flush);
-	if (ret) {
-		release_sdram_region(uimage_resource);
-		return NULL;
-	}
+	if (ret)
+		return ret;
 
-	return uimage_resource;
+	return uimage_size;
 }
-EXPORT_SYMBOL(uimage_load_to_sdram);
+EXPORT_SYMBOL(uimage_load_into_fixed_buf);
 
 void *uimage_load_to_buf(struct uimage_handle *handle, int image_no,
 		size_t *outsize)
diff --git a/include/bootm-uimage.h b/include/bootm-uimage.h
index aac2beb35e2a..1676f4fc60cf 100644
--- a/include/bootm-uimage.h
+++ b/include/bootm-uimage.h
@@ -11,45 +11,20 @@ struct resource;
 
 #ifdef CONFIG_BOOTM_UIMAGE
 
-int bootm_load_uimage_os(struct image_data *data, unsigned long load_address);
-
-struct resource *bootm_load_uimage_initrd(struct image_data *data,
-					  unsigned long load_address);
-
-int bootm_open_oftree_uimage(struct image_data *data, size_t *size,
-			     struct fdt_header **fdt);
 int bootm_open_uimage(struct image_data *data);
 
-void bootm_close_uimage(struct image_data *data);
+int bootm_collect_uimage_loadables(struct image_data *data);
 
 #else
 
-static inline int bootm_load_uimage_os(struct image_data *data,
-				       unsigned long load_address)
-{
-	return -ENOSYS;
-}
-
-static inline struct resource *
-bootm_load_uimage_initrd(struct image_data *data, unsigned long load_address)
-{
-	return ERR_PTR(-ENOSYS);
-}
-
-static inline int bootm_open_oftree_uimage(struct image_data *data,
-					   size_t *size,
-					   struct fdt_header **fdt)
-{
-	return -ENOSYS;
-}
-
 static inline int bootm_open_uimage(struct image_data *data)
 {
 	return -ENOSYS;
 }
 
-static inline void bootm_close_uimage(struct image_data *data)
+static inline int bootm_collect_uimage_loadables(struct image_data *data)
 {
+	return 0;
 }
 
 #endif
diff --git a/include/bootm.h b/include/bootm.h
index 1c3e06f20f47..3ba8402217e3 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -107,6 +107,9 @@ struct image_data {
 	char *oftree_file;
 	char *oftree_part;
 
+	/* if oftree is an uImage this will be provided */
+	struct uimage_handle *oftree_uimage;
+
 	const void *fit_kernel;
 	unsigned long fit_kernel_size;
 	void *fit_config;
diff --git a/include/image.h b/include/image.h
index b37e04f54bae..fa751de74108 100644
--- a/include/image.h
+++ b/include/image.h
@@ -303,8 +303,9 @@ int uimage_load(struct uimage_handle *handle, unsigned int image_no,
 		long(*flush)(void*, unsigned long));
 void uimage_print_contents(struct uimage_handle *handle);
 ssize_t uimage_get_size(struct uimage_handle *handle, unsigned int image_no);
-struct resource *uimage_load_to_sdram(struct uimage_handle *handle,
-		int image_no, unsigned long load_address, loff_t offset);
+int uimage_load_into_fixed_buf(struct uimage_handle *handle, int image_no,
+			       void *load_address, size_t size, loff_t offset,
+			       bool partial);
 void *uimage_load_to_buf(struct uimage_handle *handle, int image_no,
 		size_t *size);
 #define MAX_MULTI_IMAGE_COUNT 16
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 08/16] bootm: fit: switch to new loadable API
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (5 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 07/16] bootm: uimage: switch to loadable API Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 09/16] bootm: stash initial OS address/entry in image_data Ahmad Fatoum
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

The new loadable API allows us to remove FIT specific code from
common/bootm.c as can be seen by this commit having a break-even
diff line count.

The work is not over yet though: A lot of architecture specific bootm
handlers access os_file directly and they would need to be adapted to
use loadables, but that's for another day..

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 arch/arm/lib32/bootm.c   |  42 +----
 arch/kvx/lib/bootm.c     |  25 ++-
 common/bootm-fit.c       | 370 +++++++++++++++++++++++++++++----------
 common/bootm-overrides.c |  25 +--
 common/bootm.c           |  75 ++------
 common/image-fit.c       |   6 +
 efi/payload/bootm.c      | 164 +++++------------
 include/bootm-fit.h      |  50 +-----
 include/bootm.h          |  13 +-
 include/image-fit.h      |   8 +
 10 files changed, 389 insertions(+), 389 deletions(-)

diff --git a/arch/arm/lib32/bootm.c b/arch/arm/lib32/bootm.c
index 7ad2a26fdc2f..4bca29c3fbf8 100644
--- a/arch/arm/lib32/bootm.c
+++ b/arch/arm/lib32/bootm.c
@@ -22,7 +22,6 @@
 #include <restart.h>
 #include <globalvar.h>
 #include <tee/optee.h>
-#include <image-fit.h>
 #include <asm/byteorder.h>
 #include <asm/setup.h>
 #include <asm/barebox-arm.h>
@@ -168,32 +167,6 @@ static int optee_verify_header_request_region(struct image_data *data, struct op
 	return 0;
 }
 
-static int bootm_load_tee_from_fit(struct image_data *data)
-{
-	int ret = 0;
-	struct optee_header hdr;
-
-	if (data->os_fit &&
-	    fit_has_image(data->os_fit, data->fit_config, "tee")) {
-		const void *tee;
-		unsigned long tee_size;
-
-		ret = fit_open_image(data->os_fit, data->fit_config, "tee", 0,
-				     &tee, &tee_size);
-		if (ret) {
-			pr_err("Error opening tee fit image: %pe\n", ERR_PTR(ret));
-			return ret;
-		}
-		memcpy(&hdr, tee, sizeof(hdr));
-		ret = optee_verify_header_request_region(data, &hdr);
-		if (ret < 0)
-			goto out;
-		memcpy((void *)data->tee_res->start, tee + sizeof(hdr), hdr.init_size);
-		printf("Read optee image to %pa, size 0x%08x\n", (void *)data->tee_res->start, hdr.init_size);
-	}
-out:
-	return ret;
-}
 static int bootm_load_tee(struct image_data *data)
 {
 	int ret;
@@ -287,15 +260,9 @@ static int __do_bootm_linux(struct image_data *data, unsigned long free_mem,
 	}
 
 	if (IS_ENABLED(CONFIG_BOOTM_OPTEE)) {
-		if (data->tee_file && !bootm_signed_images_are_forced()) {
-			ret = bootm_load_tee(data);
-			if (ret)
-				return ret;
-		} else if (IS_ENABLED(CONFIG_FITIMAGE)) {
-			ret = bootm_load_tee_from_fit(data);
-			if (ret)
-				return ret;
-		}
+		ret = bootm_load_tee(data);
+		if (ret)
+			return ret;
 	}
 
 
@@ -459,7 +426,8 @@ static int do_bootz_linux(struct image_data *data)
 	unsigned long mem_free;
 	void *fdt = NULL;
 
-	if (data->os_fit)
+	/* FIXME: whole function should be switched to loadables */
+	if (data->image_type == filetype_fit)
 		return do_bootm_linux(data);
 
 	fd = open(data->os_file, O_RDONLY);
diff --git a/arch/kvx/lib/bootm.c b/arch/kvx/lib/bootm.c
index c464b5006fa2..1bd457f2e7d4 100644
--- a/arch/kvx/lib/bootm.c
+++ b/arch/kvx/lib/bootm.c
@@ -119,20 +119,33 @@ static int do_boot_elf(struct image_data *data, struct elf_image *elf)
 static int do_bootm_elf(struct image_data *data)
 {
 	struct elf_image *elf;
+	const void *view = NULL;
+	size_t size;
 	int ret;
 
-	if (data->fit_kernel)
-		elf = elf_open_binary((void *) data->fit_kernel);
-	else
-		elf = elf_open(data->os_file);
+	/* FIXME: whole function should be switched to loadables */
+	if (data->image_type == filetype_fit) {
+		view = loadable_view(data->os, &size) ?: ERR_PTR(-ENODATA);
+		if (IS_ERR(view))
+			return PTR_ERR(view);
 
-	if (IS_ERR(elf))
-		return PTR_ERR(elf);
+		elf = elf_open_binary((void *)view);
+	} else {
+		elf = elf_open(data->os_file);
+	}
+
+	if (IS_ERR(elf)) {
+		ret = PTR_ERR(elf);
+		goto out_view_free;
+	}
 
 	ret = do_boot_elf(data, elf);
 
 	elf_close(elf);
 
+out_view_free:
+	loadable_view_free(data->os, view, size);
+
 	return ret;
 }
 
diff --git a/common/bootm-fit.c b/common/bootm-fit.c
index 70d6ba8edff2..089f376fd7f1 100644
--- a/common/bootm-fit.c
+++ b/common/bootm-fit.c
@@ -6,103 +6,103 @@
 #include <memory.h>
 #include <zero_page.h>
 #include <filetype.h>
+#include <fs.h>
+
+static struct loadable *loadable_from_fit(struct fit_handle *fit,
+					  void *config,
+					  const char *image_name,
+					  int index,
+					  enum loadable_type type);
 
 /*
- * bootm_load_fit_os() - load OS from FIT to RAM
- *
+ * loadable_from_fit_os() - create OS loadable from FIT
  * @data:		image data context
- * @load_address:	The address where the OS should be loaded to
+ * @fit:		handle of FIT image
+ * @config:		config to look up kernel in
  *
- * This loads the OS to a RAM location. load_address must be a valid
- * address. If the image_data doesn't have a OS specified it's considered
- * an error.
- *
- * Return: 0 on success, negative error code otherwise
+ * This creates a loadable for the OS.
  */
-int bootm_load_fit_os(struct image_data *data, unsigned long load_address)
+static void loadable_from_fit_os(struct image_data *data,
+				 struct fit_handle *fit,
+				 void *config)
 {
-	const void *kernel = data->fit_kernel;
-	unsigned long kernel_size = data->fit_kernel_size;
-
-	data->os_res = request_sdram_region("kernel",
-			load_address, kernel_size,
-			MEMTYPE_LOADER_CODE, MEMATTRS_RWX);
-	if (!data->os_res)
-		return -ENOMEM;
-
-	zero_page_memcpy((void *)load_address, kernel, kernel_size);
-	return 0;
-}
-
-static bool fitconfig_has_ramdisk(struct image_data *data)
-{
-	return fit_has_image(data->os_fit, data->fit_config, "ramdisk");
+	loadable_release(&data->os);
+	data->os = loadable_from_fit(fit, config, "kernel", 0, LOADABLE_KERNEL);
 }
 
 /*
- * bootm_load_fit_initrd() - load initrd from FIT to RAM
- *
+ * loadable_from_fit_initrd() - create initrd loadable from FIT
  * @data:		image data context
- * @load_address:	The address where the initrd should be loaded to
+ * @fit:		handle of FIT image
+ * @config:		config to look up kernel in
  *
- * This loads the initrd to a RAM location. load_address must be a valid
- * address. If the image_data doesn't have a initrd specified this function
- * still returns successful as an initrd is optional.
+ * This creates a loadable for the first initial ram disk in the config.
  *
- * Return: initrd resource on success, NULL if no initrd is present or
- *         an error pointer if an error occurred.
+ * Return: true if initrd booting is supported and a ramdisk exists or
+ *         false otherwise.
  */
-struct resource *bootm_load_fit_initrd(struct image_data *data, unsigned long load_address)
+static bool loadable_from_fit_initrd(struct image_data *data,
+				struct fit_handle *fit,
+				void *config)
 {
-	struct resource *res;
-	const void *initrd;
-	unsigned long initrd_size;
-	int ret;
+	if (!IS_ENABLED(CONFIG_BOOTM_INITRD))
+		return false;
 
-	if (!fitconfig_has_ramdisk(data))
-		return NULL;
+	if (!fit_has_image(fit, config, "ramdisk"))
+		return false;
 
-	ret = fit_open_image(data->os_fit, data->fit_config, "ramdisk", 0,
-			     &initrd, &initrd_size);
-	if (ret) {
-		pr_err("Cannot open ramdisk image in FIT image: %pe\n",
-				ERR_PTR(ret));
-		return ERR_PTR(ret);
-	}
-	res = request_sdram_region("initrd",
-				   load_address, initrd_size,
-				   MEMTYPE_LOADER_DATA, MEMATTRS_RW);
-	if (!res)
-		return ERR_PTR(-ENOMEM);
+	loadable_release(&data->initrd);
 
-	memcpy((void *)load_address, initrd, initrd_size);
-	return res;
+	data->initrd = loadable_from_fit(fit, config, "ramdisk", 0, LOADABLE_INITRD);
+
+	return true;
 }
 
 /*
- * bootm_get_fit_devicetree() - get devicetree
- *
+ * loadable_from_fit_oftree() - create devicetree loadable from FIT
  * @data:		image data context
+ * @fit:		handle of FIT image
+ * @config:		config to look up kernel in
  *
- * This gets the fixed devicetree from the various image sources or the internal
- * devicetree. It returns a pointer to the allocated devicetree which must be
- * freed after use.
+ * This creates a loadable for the first fdt in the config.
  *
- * Return: pointer to the fixed devicetree, NULL if image_data has an empty DT
- *         or a ERR_PTR() on failure.
+ * Return: true if a FDT exists or
+ *         false otherwise.
  */
-void *bootm_get_fit_devicetree(struct image_data *data)
+static bool loadable_from_fit_oftree(struct image_data *data,
+				struct fit_handle *fit,
+				void *config)
 {
-	int ret;
-	const void *of_tree;
-	unsigned long of_size;
+	if (!fit_has_image(fit, config, "fdt"))
+		return false;
 
-	ret = fit_open_image(data->os_fit, data->fit_config, "fdt", 0,
-			     &of_tree, &of_size);
-	if (ret)
-		return ERR_PTR(ret);
+	loadable_release(&data->oftree);
+	data->oftree = loadable_from_fit(fit, config, "fdt", 0, LOADABLE_FDT);
+	return true;
+}
 
-	return of_unflatten_dtb(of_tree, of_size);
+/*
+ * loadable_from_fit_tee() - create tee loadable from FIT
+ * @data:		image data context
+ * @fit:		handle of FIT image
+ * @config:		config to look up kernel in
+ *
+ * This creates a loadable for the first trusted execution environment
+ * in the config.
+ *
+ * Return: true if a TEE exists or
+ *         false otherwise.
+ */
+static bool loadable_from_fit_tee(struct image_data *data,
+				struct fit_handle *fit,
+				void *config)
+{
+	if (!fit_has_image(fit, config, "tee"))
+		return false;
+
+	loadable_release(&data->tee);
+	data->tee = loadable_from_fit(fit, config, "tee", 0, LOADABLE_TEE);
+	return true;
 }
 
 static bool bootm_fit_config_valid(struct fit_handle *fit,
@@ -117,19 +117,31 @@ static bool bootm_fit_config_valid(struct fit_handle *fit,
 
 static enum filetype bootm_fit_update_os_header(struct image_data *data)
 {
-	if (data->fit_kernel_size < PAGE_SIZE)
+	size_t size;
+	const void *header;
+	enum filetype os_type;
+
+	header = loadable_view(data->os, &size);
+	if (IS_ERR(header))
 		return filetype_unknown;
 
-	free(data->os_header);
-	data->os_header = xmemdup(data->fit_kernel, PAGE_SIZE);
+	if (size >= PAGE_SIZE)
+		os_type = file_detect_type(header, size);
+	else
+		os_type = filetype_unknown;
 
-	return file_detect_type(data->os_header, PAGE_SIZE);
+	free(data->os_header);
+	data->os_header = xmemdup(header, min_t(size_t, size, PAGE_SIZE));
+
+	loadable_view_free(data->os, header, size);
+
+	return os_type;
 }
 
 int bootm_open_fit(struct image_data *data)
 {
 	struct fit_handle *fit;
-	static const char *kernel_img = "kernel";
+	void *fit_config;
 	int ret;
 
 	fit = fit_open(data->os_file, data->verbose, data->verify);
@@ -138,47 +150,215 @@ int bootm_open_fit(struct image_data *data)
 		return PTR_ERR(fit);
 	}
 
-	data->os_fit = fit;
-
-	data->fit_config = fit_open_configuration(data->os_fit,
-						  data->os_part,
-						  bootm_fit_config_valid);
-	if (IS_ERR(data->fit_config)) {
+	fit_config = fit_open_configuration(fit, data->os_part, bootm_fit_config_valid);
+	if (IS_ERR(fit_config)) {
 		pr_err("Cannot open FIT image configuration '%s'\n",
-		       data->os_part ? data->os_part : "default");
-		return PTR_ERR(data->fit_config);
+		       data->os_part ?: "default");
+		ret = PTR_ERR(fit_config);
+		goto err;
 	}
 
-	ret = fit_open_image(data->os_fit, data->fit_config, kernel_img, 0,
-			     &data->fit_kernel, &data->fit_kernel_size);
-	if (ret)
-		return ret;
+	loadable_from_fit_os(data, fit, fit_config);
+	loadable_from_fit_initrd(data, fit, fit_config);
+	loadable_from_fit_oftree(data, fit, fit_config);
+	loadable_from_fit_tee(data, fit, fit_config);
 
 	data->kernel_type = bootm_fit_update_os_header(data);
 
 	if (data->os_address == UIMAGE_SOME_ADDRESS) {
-		ret = fit_get_image_address(data->os_fit,
-					    data->fit_config,
-					    kernel_img,
+		ret = fit_get_image_address(fit, fit_config, "kernel",
 					    "load", &data->os_address);
 		if (!ret)
 			pr_info("Load address from FIT '%s': 0x%lx\n",
-				kernel_img, data->os_address);
+				"kernel", data->os_address);
 		/* Note: Error case uses default value. */
 	}
 	if (data->os_entry == UIMAGE_SOME_ADDRESS) {
 		unsigned long entry;
-		ret = fit_get_image_address(data->os_fit,
-					    data->fit_config,
-					    kernel_img,
+		ret = fit_get_image_address(fit, fit_config, "kernel",
 					    "entry", &entry);
 		if (!ret) {
 			data->os_entry = entry - data->os_address;
 			pr_info("Entry address from FIT '%s': 0x%lx\n",
-				kernel_img, entry);
+				"kernel", entry);
 		}
 		/* Note: Error case uses default value. */
 	}
 
+	/* Each loadable now holds a reference to the FIT, so close our original
+	 * reference, so the FIT is completely reclaimed if bootm fails.
+	 */
+	fit_close(fit);
+
+	return 0;
+err:
+	fit_close(fit);
+	return ret;
+}
+
+/* === Loadable implementation for FIT images === */
+
+struct fit_loadable_priv {
+	struct fit_handle *fit;
+	struct device_node *config;
+	const char *image_name;
+	int index;
+};
+
+static int fit_loadable_get_info(struct loadable *l, struct loadable_info *info)
+{
+	struct fit_loadable_priv *priv = l->priv;
+	const void *data;
+	unsigned long size;
+	int ret;
+
+	/* Open image to get size */
+	ret = fit_open_image(priv->fit, priv->config, priv->image_name,
+			     priv->index, &data, &size);
+	if (ret)
+		return ret;
+
+	/* TODO: This will trigger an uncompression currently.. */
+	info->final_size = size;
+
 	return 0;
 }
+
+static const void *fit_loadable_mmap(struct loadable *l, size_t *size)
+{
+	struct fit_loadable_priv *priv = l->priv;
+	const void *data;
+	unsigned long image_size;
+	int ret;
+
+	ret = fit_open_image(priv->fit, priv->config, priv->image_name,
+			     priv->index, &data, &image_size);
+	if (ret)
+		return MAP_FAILED;
+
+	*size = image_size;
+	return data;
+}
+
+/**
+ * fit_loadable_extract_into_buf - load FIT image data to target address
+ * @l: loadable representing FIT image component
+ * @load_addr: physical address to load data to
+ * @buf_size: size of buffer at load_addr (0 = no limit check)
+ * @offset: how many bytes to skip at the start of the uncompressed input
+ * @flags: A bitmask of OR-ed LOADABLE_EXTRACT_ flags
+ *
+ * Commits the FIT image component to the specified memory address. This
+ * involves:
+ * 1. Opening the FIT image to get decompressed data
+ * 2. Checking buffer size
+ * 3. Copying data to target address
+ *
+ * The FIT data is already decompressed by fit_open_image(), so this just
+ * performs a memcpy to the target address.
+ *
+ * Return: actual number of bytes written on success, negative errno on error
+ *         -ENOSPC if buf_size is specified and too small
+ *         -ENOMEM if failed to register SDRAM region
+ */
+static ssize_t fit_loadable_extract_into_buf(struct loadable *l, void *load_addr,
+					     size_t buf_size, loff_t offset,
+					     unsigned flags)
+{
+	struct fit_loadable_priv *priv = l->priv;
+	const void *data;
+	unsigned long size;
+	int ret;
+
+	/* TODO: optimize, so it decompresses directly to load address */
+
+	/* Open image to get data */
+	ret = fit_open_image(priv->fit, priv->config, priv->image_name,
+			     priv->index, &data, &size);
+	if (ret)
+		return ret;
+
+	/* Check if buffer is large enough */
+	if (offset > size)
+		return 0;
+
+	if (!(flags & LOADABLE_EXTRACT_PARTIAL) && buf_size < size - offset)
+		return -ENOSPC;
+
+	size = min_t(size_t, size - offset, buf_size);
+
+	if (unlikely(zero_page_contains((ulong)load_addr)))
+		zero_page_memcpy(load_addr, data + offset, size);
+	else
+		memcpy(load_addr, data + offset, size);
+
+	return size; /* Return actual bytes written */
+}
+
+static void fit_loadable_release(struct loadable *l)
+{
+	struct fit_loadable_priv *priv = l->priv;
+
+	fit_close(priv->fit);
+	free_const(priv->image_name);
+	free(priv);
+}
+
+static const struct loadable_ops fit_loadable_ops = {
+	.get_info = fit_loadable_get_info,
+	.extract_into_buf = fit_loadable_extract_into_buf,
+	.mmap = fit_loadable_mmap,
+	.release = fit_loadable_release,
+};
+
+/**
+ * loadable_from_fit - create a loadable from FIT image component
+ * @fit: opened FIT image handle
+ * @config: FIT configuration device node
+ * @image_name: name of image in FIT (e.g., "kernel", "ramdisk", "fdt")
+ * @index: index for multi-image types (e.g., ramdisk-0, ramdisk-1)
+ * @type: type of loadable (LOADABLE_KERNEL, LOADABLE_INITRD, etc.)
+ *
+ * Creates a loadable structure that wraps access to a component within a
+ * FIT image. The loadable uses the FIT handle to access decompressed image
+ * data on demand during commit.
+ *
+ * The created loadable must be freed with loadable_release() when done.
+ * The FIT handle itself is managed by the caller and must remain valid
+ * until the loadable is released.
+ *
+ * Return: pointer to allocated loadable on success. Function never fails.
+ */
+static struct loadable *loadable_from_fit(struct fit_handle *fit,
+					  void *config,
+					  const char *image_name,
+					  int index,
+					  enum loadable_type type)
+{
+	struct loadable *l;
+	struct fit_loadable_priv *priv;
+
+	l = xzalloc(sizeof(*l));
+	priv = xzalloc(sizeof(*priv));
+
+	priv->fit = fit_open_handle(fit);
+	priv->config = config;
+	priv->image_name = xstrdup_const(image_name);
+	priv->index = index;
+
+	/* Create descriptive name */
+	if (index)
+		l->name = xasprintf("FIT(%s, %s/%s, %d)", fit->filename,
+				    fit_config_get_name(fit, config),
+				    image_name, index);
+	else
+		l->name = xasprintf("FIT(%s, %s/%s)", fit->filename,
+				    fit_config_get_name(fit, config),
+				    image_name);
+	l->ops = &fit_loadable_ops;
+	l->type = type;
+	l->priv = priv;
+	loadable_init(l);
+
+	return l;
+}
diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
index e1cba21e7711..c1f3ee7cade8 100644
--- a/common/bootm-overrides.c
+++ b/common/bootm-overrides.c
@@ -20,23 +20,16 @@ int bootm_apply_overrides(struct image_data *data,
 	if (bootm_signed_images_are_forced())
 		return 0;
 
-	/* TODO: As we haven't switched over everything to loadables yet,
-	 * we need a special marker to mean override to empty.
-	 * We do this via a 0-byte file (/dev/null) for now..
-	 */
-
 	if (overrides->initrd_file) {
 		loadable_release(&data->initrd);
 
 		/* Empty string means to mask the original initrd */
-		if (nonempty(overrides->initrd_file))
+		if (nonempty(overrides->initrd_file)) {
 			data->initrd = loadable_from_file(overrides->initrd_file,
 							   LOADABLE_INITRD);
-		else
-			data->initrd = loadable_from_file("/dev/null",
-							   LOADABLE_INITRD);
-		if (IS_ERR(data->initrd))
-			return PTR_ERR(data->initrd);
+			if (IS_ERR(data->initrd))
+				return PTR_ERR(data->initrd);
+		}
 		data->is_override.initrd = true;
 	}
 
@@ -44,14 +37,12 @@ int bootm_apply_overrides(struct image_data *data,
 		loadable_release(&data->oftree);
 
 		/* Empty string means to mask the original FDT */
-		if (nonempty(overrides->oftree_file))
+		if (nonempty(overrides->oftree_file)) {
 			data->oftree = loadable_from_file(overrides->oftree_file,
 							   LOADABLE_FDT);
-		else
-			data->oftree = loadable_from_file("/dev/null",
-							   LOADABLE_FDT);
-		if (IS_ERR(data->oftree))
-			return PTR_ERR(data->oftree);
+			if (IS_ERR(data->oftree))
+				return PTR_ERR(data->oftree);
+		}
 		data->is_override.oftree = true;
 	}
 
diff --git a/common/bootm.c b/common/bootm.c
index 08a8fbbfdcf5..01d569dd37d4 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -11,7 +11,6 @@
 #include <block.h>
 #include <libfile.h>
 #include <bootm-fit.h>
-#include <image-fit.h>
 #include <bootm-uimage.h>
 #include <globalvar.h>
 #include <init.h>
@@ -218,40 +217,19 @@ const struct resource *bootm_load_os(struct image_data *data,
 		ulong load_address, ulong end_address)
 {
 	struct resource *res;
-	int err;
 
 	if (data->os_res)
 		return data->os_res;
 
-	if (load_address == UIMAGE_INVALID_ADDRESS)
-		return ERR_PTR(-EINVAL);
-	if (end_address <= load_address)
+	if (load_address == UIMAGE_INVALID_ADDRESS ||
+	    end_address <= load_address || !data->os)
 		return ERR_PTR(-EINVAL);
 
-	if (data->os) {
-		res = loadable_extract_into_sdram_all(data->os, load_address, end_address);
-		if (!IS_ERR(res))
-			data->os_res = res;
-		return res;
-	}
+	res = loadable_extract_into_sdram_all(data->os, load_address, end_address);
+	if (!IS_ERR(res))
+		data->os_res = res;
 
-	/* TODO: eliminate below special cases */
-
-	if (data->os_fit)
-		err = bootm_load_fit_os(data, load_address);
-	else
-		err = -EINVAL;
-
-	if (err)
-		return ERR_PTR(err);
-
-	/* FIXME: We need some more rework to be able to detect this overflow
-	 * before it happens, but for now, let's at least detect it.
-	 */
-	if (WARN_ON(data->os_res->end > end_address))
-		return ERR_PTR(-ENOSPC);
-
-	return data->os_res;
+	return res;
 }
 
 /**
@@ -280,30 +258,17 @@ bootm_load_initrd(struct image_data *data, ulong load_address, ulong end_address
 	 */
 	if (WARN_ON(data->initrd_res))
 		return data->initrd_res;
+
+	if (!data->initrd)
+		return NULL;
+
 	if (end_address <= load_address)
 		return ERR_PTR(-EINVAL);
 
-	if (data->initrd) {
-		res = loadable_extract_into_sdram_all(data->initrd, load_address, end_address);
-		if (!IS_ERR(res))
-			data->initrd_res = res;
-		return res;
-	}
-
-	if (data->os_fit)
-		res = bootm_load_fit_initrd(data, load_address);
-
-	if (IS_ERR_OR_NULL(res))
-		return res;
-
-	/* FIXME: We need some more rework to be able to detect this overflow
-	 * before it happens, but for now, let's at least detect it.
-	 */
-	if (WARN_ON(res->end > end_address))
-		return ERR_PTR(-ENOSPC);
-
-	data->initrd_res = res;
-	return data->initrd_res;
+	res = loadable_extract_into_sdram_all(data->initrd, load_address, end_address);
+	if (!IS_ERR(res))
+		data->initrd_res = res;
+	return res;
 }
 
 /*
@@ -345,8 +310,6 @@ void *bootm_get_devicetree(struct image_data *data)
 			return ERR_PTR(-EINVAL);
 		}
 
-	} else if (bootm_fit_has_fdt(data)) {
-		data->of_root_node = bootm_get_fit_devicetree(data);
 	} else {
 		data->of_root_node = of_dup_root_node_for_boot();
 		if (!data->of_root_node)
@@ -421,12 +384,10 @@ loff_t bootm_get_os_size(struct image_data *data)
 {
 	loff_t size;
 
-	if (data->os)
-		return loadable_get_size(data->os, &size) ?: size;
-	if (data->os_fit)
-		return data->fit_kernel_size;
+	if (!data->os)
+		return -EINVAL;
 
-	return -EINVAL;
+	return loadable_get_size(data->os, &size) ?: size;
 }
 
 static void bootm_print_info(struct image_data *data)
@@ -802,8 +763,6 @@ void bootm_boot_cleanup(struct image_data *data)
 	release_sdram_region(data->initrd_res);
 	release_sdram_region(data->oftree_res);
 	release_sdram_region(data->tee_res);
-	if (data->os_fit)
-		bootm_close_fit(data);
 	loadable_release(&data->oftree);
 	loadable_release(&data->initrd);
 	loadable_release(&data->os);
diff --git a/common/image-fit.c b/common/image-fit.c
index 27e5ec9062c5..40aeee42cd09 100644
--- a/common/image-fit.c
+++ b/common/image-fit.c
@@ -917,6 +917,12 @@ void *fit_open_configuration(struct fit_handle *handle, const char *name,
 	return conf_node;
 }
 
+const char *fit_config_get_name(struct fit_handle *handle, void *config)
+{
+	struct device_node *node = config;
+	return node->name;
+}
+
 static struct fit_handle *fit_get_handle(const char *filename)
 {
 	struct fit_handle *handle;
diff --git a/efi/payload/bootm.c b/efi/payload/bootm.c
index aba643b2c59d..b8e555dab5fa 100644
--- a/efi/payload/bootm.c
+++ b/efi/payload/bootm.c
@@ -27,7 +27,6 @@
 #include <libfile.h>
 #include <binfmt.h>
 #include <wchar.h>
-#include <image-fit.h>
 #include <efi/payload.h>
 #include <efi/payload/driver.h>
 #include <efi/error.h>
@@ -35,73 +34,26 @@
 
 #include "image.h"
 
-static bool ramdisk_is_fit(struct image_data *data)
-{
-	struct stat st;
-
-	if (!IS_ENABLED(CONFIG_BOOTM_FITIMAGE))
-		return false;
-
-	if (bootm_signed_images_are_forced())
-		return true;
-
-	if (data->initrd_file) {
-		if (!stat(data->initrd_file, &st) && st.st_size > 0)
-			return false;
-	}
-
-	return data->os_fit ? fit_has_image(data->os_fit,
-			data->fit_config, "ramdisk") > 0 : false;
-}
-
-static bool fdt_is_fit(struct image_data *data)
-{
-	struct stat st;
-
-	if (!IS_ENABLED(CONFIG_BOOTM_FITIMAGE))
-		return false;
-
-	if (bootm_signed_images_are_forced())
-		return true;
-
-	if (data->oftree_file) {
-		if (!stat(data->oftree_file, &st) && st.st_size > 0)
-			return false;
-	}
-
-	return data->os_fit ? fit_has_image(data->os_fit,
-			data->fit_config, "fdt") > 0 : false;
-}
-
-static bool os_is_fit(struct image_data *data)
-{
-	if (!IS_ENABLED(CONFIG_BOOTM_FITIMAGE))
-		return false;
-
-	if (bootm_signed_images_are_forced())
-		return true;
-
-	return data->os_fit;
-}
-
 static int efi_load_os(struct image_data *data,
 		       struct efi_loaded_image **loaded_image,
 		       efi_handle_t *handle)
 {
 	efi_status_t efiret;
 	efi_handle_t h;
+	const void *view;
+	size_t size;
 
-	if (!os_is_fit(data))
-		return efi_load_image(data->os_file, loaded_image, handle);
-
-	if (!data->fit_kernel)
-		return -ENOENT;
+	view = loadable_view(data->os, &size) ?: ERR_PTR(-ENODATA);
+	if (IS_ERR(view)) {
+		pr_err("could not view kernel: %pe\n", view);
+		return PTR_ERR(view);
+	}
 
 	efiret = BS->load_image(false, efi_parent_image, efi_device_path,
-				(void *)data->fit_kernel, data->fit_kernel_size, &h);
+				(void *)view, size, &h);
 	if (EFI_ERROR(efiret)) {
 		pr_err("failed to LoadImage: %s\n", efi_strerror(efiret));
-		goto out_mem;
+		goto out_view_free;
 	};
 
 	efiret = BS->open_protocol(h, &efi_loaded_image_protocol_guid,
@@ -114,43 +66,33 @@ static int efi_load_os(struct image_data *data,
 
 	*handle = h;
 
+	loadable_view_free(data->os, view, size);
 	return 0;
 
 out_unload:
 	BS->unload_image(h);
-out_mem:
+out_view_free:
+	loadable_view_free(data->os, view, size);
 	return -efi_errno(efiret);
 }
 
-static int efi_load_ramdisk(struct image_data *data, void **initrd)
+static int efi_load_ramdisk(struct image_data *data,
+			    const void **initrd,
+			    size_t *initrd_size)
 {
-	unsigned long initrd_size;
-	void *initrd_mem;
+	const void *initrd_mem;
 	int ret;
 
-	if (ramdisk_is_fit(data)) {
-		ret = fit_open_image(data->os_fit, data->fit_config, "ramdisk", 0,
-				     (const void **)&initrd_mem, &initrd_size);
-		if (ret) {
-			pr_err("Cannot open ramdisk image in FIT image: %m\n");
-			return ret;
-		}
-	} else {
-		if (!data->initrd_file)
-			return 0;
+	if (!data->initrd)
+		return 0;
 
-		pr_info("Loading ramdisk from '%s'\n", data->initrd_file);
-
-		initrd_mem = read_file(data->initrd_file, &initrd_size);
-		if (!initrd_mem) {
-			ret = -errno;
-			pr_err("Failed to read initrd from file '%s': %m\n",
-			       data->initrd_file);
-			return ret;
-		}
+	initrd_mem = loadable_view(data->initrd, initrd_size);
+	if (IS_ERR(initrd_mem)) {
+		pr_err("Cannot open ramdisk image: %pe\n", initrd_mem);
+		return PTR_ERR(initrd_mem);
 	}
 
-	ret = efi_initrd_register(initrd_mem, initrd_size);
+	ret = efi_initrd_register(initrd_mem, *initrd_size);
 	if (ret) {
 		pr_err("Failed to register initrd: %pe\n", ERR_PTR(ret));
 		goto free_mem;
@@ -161,7 +103,7 @@ static int efi_load_ramdisk(struct image_data *data, void **initrd)
 	return 0;
 
 free_mem:
-	free(initrd_mem);
+	loadable_view_free(data->initrd, initrd_mem, *initrd_size);
 
 	return ret;
 }
@@ -170,45 +112,31 @@ static int efi_load_fdt(struct image_data *data, void **fdt)
 {
 	efi_physical_addr_t mem;
 	efi_status_t efiret;
-	void *of_tree, *vmem;
-	unsigned long of_size;
-	int ret;
+	void *vmem;
+	size_t bufsize = DIV_ROUND_UP(SZ_2M, EFI_PAGE_SIZE);
+	ssize_t ret;
 
-	if (fdt_is_fit(data)) {
-		ret = fit_open_image(data->os_fit, data->fit_config, "fdt", 0,
-				     (const void **)&of_tree, &of_size);
-		if (ret) {
-			pr_err("Cannot open FDT image in FIT image: %m\n");
-			return ret;
-		}
-	} else {
-		if (!data->oftree_file)
-			return 0;
+	if (!data->oftree)
+		return 0;
 
-		pr_info("Loading devicetree from '%s'\n", data->oftree_file);
-
-		of_tree = read_file(data->oftree_file, &of_size);
-		if (!of_tree) {
-			ret = -errno;
-			pr_err("Failed to read oftree: %m\n");
-			return ret;
-		}
-	}
-
-	efiret = BS->allocate_pages(EFI_ALLOCATE_ANY_PAGES,
-				    EFI_ACPI_RECLAIM_MEMORY,
-				    DIV_ROUND_UP(SZ_2M, EFI_PAGE_SIZE), &mem);
+	efiret = BS->allocate_pages(EFI_ALLOCATE_ANY_PAGES, EFI_ACPI_RECLAIM_MEMORY,
+				    bufsize, &mem);
 	if (EFI_ERROR(efiret)) {
 		pr_err("Failed to allocate pages for FDT: %s\n", efi_strerror(efiret));
-		goto free_mem;
+		return -efi_errno(efiret);
 	}
 
 	vmem = efi_phys_to_virt(mem);
-	memcpy(vmem, of_tree, of_size);
+
+	ret = loadable_extract_into_buf_full(data->oftree, vmem,
+					     bufsize * EFI_PAGE_SIZE);
+	if (ret < 0)
+		goto free_efi_mem;
 
 	efiret = BS->install_configuration_table(&efi_fdt_guid, vmem);
 	if (EFI_ERROR(efiret)) {
 		pr_err("Failed to install FDT: %s\n", efi_strerror(efiret));
+		ret = -efi_errno(efiret);
 		goto free_efi_mem;
 	}
 
@@ -216,10 +144,8 @@ static int efi_load_fdt(struct image_data *data, void **fdt)
 	return 0;
 
 free_efi_mem:
-	BS->free_pages(mem, DIV_ROUND_UP(SZ_2M, EFI_PAGE_SIZE));
-free_mem:
-	free(of_tree);
-	return -efi_errno(efiret);
+	BS->free_pages(mem, bufsize);
+	return ret;
 }
 
 static void efi_unload_fdt(void *fdt)
@@ -234,8 +160,10 @@ static void efi_unload_fdt(void *fdt)
 static int do_bootm_efi_stub(struct image_data *data)
 {
 	struct efi_loaded_image *loaded_image;
-	void *fdt = NULL, *initrd = NULL;
-	efi_handle_t handle;
+	void *fdt = NULL;
+	const void *initrd = NULL;
+	size_t initrd_size;
+	efi_handle_t handle = NULL; /* silence compiler warning */
 	enum filetype type;
 	int ret;
 
@@ -247,7 +175,7 @@ static int do_bootm_efi_stub(struct image_data *data)
 	if (ret)
 		goto unload_os;
 
-	ret = efi_load_ramdisk(data, &initrd);
+	ret = efi_load_ramdisk(data, &initrd, &initrd_size);
 	if (ret)
 		goto unload_oftree;
 
@@ -260,7 +188,7 @@ static int do_bootm_efi_stub(struct image_data *data)
 unload_ramdisk:
 	if (initrd) {
 		efi_initrd_unregister();
-		free(initrd);
+		loadable_view_free(data->initrd, initrd, initrd_size);
 	}
 unload_oftree:
 	efi_unload_fdt(fdt);
diff --git a/include/bootm-fit.h b/include/bootm-fit.h
index 8deddd62e328..05f4f5acfe9a 100644
--- a/include/bootm-fit.h
+++ b/include/bootm-fit.h
@@ -4,67 +4,21 @@
 
 #include <linux/types.h>
 #include <image-fit.h>
-#include <bootm.h>
+#include <linux/errno.h>
 
-struct resource;
+struct image_data;
 
 #ifdef CONFIG_BOOTM_FITIMAGE
 
-int bootm_load_fit_os(struct image_data *data, unsigned long load_address);
-
-struct resource *bootm_load_fit_initrd(struct image_data *data,
-				       unsigned long load_address);
-
-void *bootm_get_fit_devicetree(struct image_data *data);
-
 int bootm_open_fit(struct image_data *data);
 
-static inline void bootm_close_fit(struct image_data *data)
-{
-	fit_close(data->os_fit);
-}
-
-static inline bool bootm_fit_has_fdt(struct image_data *data)
-{
-	if (!data->os_fit)
-		return false;
-
-	return fit_has_image(data->os_fit, data->fit_config, "fdt");
-}
-
 #else
 
-static inline int bootm_load_fit_os(struct image_data *data,
-				    unsigned long load_address)
-{
-	return -ENOSYS;
-}
-
-static inline struct resource *bootm_load_fit_initrd(struct image_data *data,
-						     unsigned long load_address)
-{
-	return ERR_PTR(-ENOSYS);
-}
-
-static inline void *bootm_get_fit_devicetree(struct image_data *data)
-{
-	return ERR_PTR(-ENOSYS);
-}
-
 static inline int bootm_open_fit(struct image_data *data)
 {
 	return -ENOSYS;
 }
 
-static inline void bootm_close_fit(struct image_data *data)
-{
-}
-
-static inline bool bootm_fit_has_fdt(struct image_data *data)
-{
-	return false;
-}
-
 #endif
 
 #endif
diff --git a/include/bootm.h b/include/bootm.h
index 3ba8402217e3..d3874a0e8ef2 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -64,15 +64,12 @@ struct image_data {
 	/* simplest case. barebox has already loaded the os here */
 	struct resource *os_res;
 
-	/* Future default case: A generic loadable object */
+	/* Generic loadable object for OS image */
 	struct loadable *os;
 
 	/* if os is an uImage this will be provided */
 	struct uimage_handle *os_uimage;
 
-	/* if os is a FIT image this will be provided */
-	struct fit_handle *os_fit;
-
 	char *os_part;
 
 	/* otherwise only the filename will be provided */
@@ -92,7 +89,7 @@ struct image_data {
 	/* if initrd is already loaded this resource will be !NULL */
 	struct resource *initrd_res;
 
-	/* Future default case: A generic loadable object */
+	/* Generic loadable object for initrd */
 	struct loadable *initrd;
 
 	/* if initrd is an uImage this will be provided */
@@ -110,14 +107,10 @@ struct image_data {
 	/* if oftree is an uImage this will be provided */
 	struct uimage_handle *oftree_uimage;
 
-	const void *fit_kernel;
-	unsigned long fit_kernel_size;
-	void *fit_config;
-
 	struct device_node *of_root_node;
 	struct resource *oftree_res;
 
-	/* Future default case: A generic loadable object */
+	/* Generic loadable object for oftree */
 	struct loadable *oftree;
 
 	/*
diff --git a/include/image-fit.h b/include/image-fit.h
index ede43beab12e..1de3468c401d 100644
--- a/include/image-fit.h
+++ b/include/image-fit.h
@@ -27,6 +27,13 @@ struct fit_handle {
 	struct device_node *configurations;
 };
 
+static inline struct fit_handle *fit_open_handle(struct fit_handle *handle)
+{
+	if (handle)
+		refcount_inc(&handle->users);
+	return handle;
+}
+
 struct fit_handle *fit_open(const char *filename, bool verbose,
 			    enum bootm_verify verify);
 struct fit_handle *fit_open_buf(const void *buf, size_t len, bool verbose,
@@ -66,6 +73,7 @@ int fit_get_image_address(struct fit_handle *handle, void *configuration,
 			  const char *name, const char *property,
 			  unsigned long *address);
 int fit_config_verify_signature(struct fit_handle *handle, struct device_node *conf_node);
+const char *fit_config_get_name(struct fit_handle *handle, void *config);
 
 void fit_close(struct fit_handle *handle);
 
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 09/16] bootm: stash initial OS address/entry in image_data
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (6 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 08/16] bootm: fit: switch to new " Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 10/16] bootm: support multiple entries for bootm.initrd Ahmad Fatoum
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Save the initial os_address and os_entry values that were passed to
bootm_boot() in new os_address_hint and os_entry_hint fields. These
preserve the originally requested values even if os_address/os_entry
are modified during the boot process.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm.c  | 4 ++--
 include/bootm.h | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/common/bootm.c b/common/bootm.c
index 01d569dd37d4..25cbce6ccb3f 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -530,8 +530,8 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 	data->force = bootm_data->force;
 	data->dryrun = bootm_data->dryrun;
 	data->initrd_address = bootm_data->initrd_address;
-	data->os_address = bootm_data->os_address;
-	data->os_entry = bootm_data->os_entry;
+	data->os_address = data->os_address_hint = bootm_data->os_address;
+	data->os_entry = data->os_entry_hint = bootm_data->os_entry;
 	data->efi_boot = bootm_data->efi_boot;
 
 	ret = file_read_and_detect_boot_image_type(data->os_file, &data->os_header);
diff --git a/include/bootm.h b/include/bootm.h
index d3874a0e8ef2..570ac4d289f2 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -86,6 +86,10 @@ struct image_data {
 	/* entry point to the os. relative to the start of the image */
 	unsigned long os_entry;
 
+	/* initial os_address/os_entry supplied at entry to bootm_boot */
+	unsigned long os_address_hint;
+	unsigned long os_entry_hint;
+
 	/* if initrd is already loaded this resource will be !NULL */
 	struct resource *initrd_res;
 
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 10/16] bootm: support multiple entries for bootm.initrd
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (7 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 09/16] bootm: stash initial OS address/entry in image_data Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 11/16] bootm: implement plain and FIT bootm.image override Ahmad Fatoum
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Linux can extract transparently any number of concatenated CPIOs, even
if individually compressed. This can be useful if we have two CPIOs from
different sources, e.g. an rdinit and a CPIO with the kernel modules.

Teach bootm how to collect multiple initrd entries by leveraging the
loadable chaining support.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm-fit.c       | 13 +++++++++----
 common/bootm-overrides.c | 17 +++++++----------
 common/bootm.c           |  7 ++++---
 3 files changed, 20 insertions(+), 17 deletions(-)

diff --git a/common/bootm-fit.c b/common/bootm-fit.c
index 089f376fd7f1..5b110ad2f19e 100644
--- a/common/bootm-fit.c
+++ b/common/bootm-fit.c
@@ -36,7 +36,7 @@ static void loadable_from_fit_os(struct image_data *data,
  * @fit:		handle of FIT image
  * @config:		config to look up kernel in
  *
- * This creates a loadable for the first initial ram disk in the config.
+ * This creates loadables for all initial ram disks in the config and chains them.
  *
  * Return: true if initrd booting is supported and a ramdisk exists or
  *         false otherwise.
@@ -45,17 +45,22 @@ static bool loadable_from_fit_initrd(struct image_data *data,
 				struct fit_handle *fit,
 				void *config)
 {
+	int nramdisks;
+
 	if (!IS_ENABLED(CONFIG_BOOTM_INITRD))
 		return false;
 
-	if (!fit_has_image(fit, config, "ramdisk"))
+	nramdisks = fit_count_images(fit, config, "ramdisk");
+	if (nramdisks < 0)
 		return false;
 
 	loadable_release(&data->initrd);
 
-	data->initrd = loadable_from_fit(fit, config, "ramdisk", 0, LOADABLE_INITRD);
+	for (int i = 0; i < nramdisks; i++)
+		loadable_chain(&data->initrd, loadable_from_fit(fit, config, "ramdisk",
+								i, LOADABLE_INITRD));
 
-	return true;
+	return nramdisks > 0;
 }
 
 /*
diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
index c1f3ee7cade8..59ca90a58684 100644
--- a/common/bootm-overrides.c
+++ b/common/bootm-overrides.c
@@ -20,16 +20,12 @@ int bootm_apply_overrides(struct image_data *data,
 	if (bootm_signed_images_are_forced())
 		return 0;
 
-	if (overrides->initrd_file) {
-		loadable_release(&data->initrd);
-
-		/* Empty string means to mask the original initrd */
-		if (nonempty(overrides->initrd_file)) {
-			data->initrd = loadable_from_file(overrides->initrd_file,
-							   LOADABLE_INITRD);
-			if (IS_ERR(data->initrd))
-				return PTR_ERR(data->initrd);
-		}
+	if (IS_ENABLED(CONFIG_BOOTM_INITRD) && overrides->initrd_file) {
+		/* loadables_from_files() will set data->initrd on empty initrd_file */
+		int ret = loadables_from_files(&data->initrd, overrides->initrd_file, ":",
+					       LOADABLE_INITRD);
+		if (ret)
+			return ret;
 		data->is_override.initrd = true;
 	}
 
@@ -48,3 +44,4 @@ int bootm_apply_overrides(struct image_data *data,
 
 	return 0;
 }
+
diff --git a/common/bootm.c b/common/bootm.c
index 25cbce6ccb3f..ddc656fb34c8 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -463,9 +463,10 @@ static int bootm_open_files(struct image_data *data)
 	}
 
 	if (data->initrd_file) {
-		data->initrd = loadable_from_file(data->initrd_file, LOADABLE_INITRD);
-		if (IS_ERR(data->initrd))
-			return PTR_ERR(data->initrd);
+		int ret = loadables_from_files(&data->initrd, data->initrd_file, ":",
+					       LOADABLE_INITRD);
+		if (ret)
+			return ret;
 	}
 
 	if (data->tee_file) {
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 11/16] bootm: implement plain and FIT bootm.image override
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (8 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 10/16] bootm: support multiple entries for bootm.initrd Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-18  9:01   ` Sascha Hauer
  2026-03-12 14:44 ` [PATCH 12/16] bootm: overrides: add support for overlays Ahmad Fatoum
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum, Claude Opus 4.5

Add support for overriding the boot image via bootm.image when using
either plain files or FIT images. The override behavior depends on the
file types involved:

- If the override is not a FIT image, it replaces only the OS loadable,
  regardless of whether the original was a FIT image or not.

- If the override is a FIT image, it replaces the current OS, whether
  it's a FIT or not.

This allows developers to use devboot scripts to redirect FIT image
loading to alternate locations (e.g., network) while preserving the
configuration selection from the original boot entry.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 commands/boot.c           |   2 +-
 common/bootm-fit.c        |  11 +++-
 common/bootm-overrides.c  | 119 +++++++++++++++++++++++++++++++++++++-
 common/bootm.c            |   4 +-
 include/bootm-fit.h       |   4 +-
 include/bootm-overrides.h |   3 +
 include/bootm.h           |   2 +
 7 files changed, 134 insertions(+), 11 deletions(-)

diff --git a/commands/boot.c b/commands/boot.c
index 555667ee098c..04bb9eb9186f 100644
--- a/commands/boot.c
+++ b/commands/boot.c
@@ -44,7 +44,7 @@ static int boot_add_override(struct bootm_overrides *overrides, char *var)
 	if (!strcmp(var, "bootm.image")) {
 		if (isempty(val))
 			return -EINVAL;
-		return -ENOSYS;
+		overrides->os_file = val;
 	} else if (!strcmp(var, "bootm.oftree")) {
 		overrides->oftree_file = val;
 	} else if (!strcmp(var, "bootm.initrd")) {
diff --git a/common/bootm-fit.c b/common/bootm-fit.c
index 5b110ad2f19e..995bd7181223 100644
--- a/common/bootm-fit.c
+++ b/common/bootm-fit.c
@@ -4,6 +4,7 @@
 #include <image-fit.h>
 #include <bootm-fit.h>
 #include <memory.h>
+#include <string.h>
 #include <zero_page.h>
 #include <filetype.h>
 #include <fs.h>
@@ -143,7 +144,7 @@ static enum filetype bootm_fit_update_os_header(struct image_data *data)
 	return os_type;
 }
 
-int bootm_open_fit(struct image_data *data)
+int bootm_open_fit(struct image_data *data, bool override)
 {
 	struct fit_handle *fit;
 	void *fit_config;
@@ -164,8 +165,12 @@ int bootm_open_fit(struct image_data *data)
 	}
 
 	loadable_from_fit_os(data, fit, fit_config);
-	loadable_from_fit_initrd(data, fit, fit_config);
-	loadable_from_fit_oftree(data, fit, fit_config);
+	if (override)
+		data->is_override.os = true;
+	if (loadable_from_fit_initrd(data, fit, fit_config) && override)
+		data->is_override.initrd = true;
+	if (loadable_from_fit_oftree(data, fit, fit_config) && override)
+		data->is_override.oftree = true;
 	loadable_from_fit_tee(data, fit, fit_config);
 
 	data->kernel_type = bootm_fit_update_os_header(data);
diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
index 59ca90a58684..8c71ceb5f80d 100644
--- a/common/bootm-overrides.c
+++ b/common/bootm-overrides.c
@@ -2,14 +2,118 @@
 
 #include <bootm.h>
 #include <bootm-overrides.h>
+#include <bootm-fit.h>
+#include <libfile.h>
+#include <linux/pagemap.h>
+
+/**
+ * bootm_apply_image_override - apply bootm.image override
+ * @data: image data context
+ * @override: override path (may include @config suffix for FIT)
+ *
+ * Applies a bootm.image override according to these rules:
+ * - If the override is not a FIT image, it replaces only data->os.
+ * - If the override is a FIT image, it is re-opened via bootm_open_fit()
+ *   using the default configuration or the @config suffix if given.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int bootm_apply_image_override(struct image_data *data,
+				      const char *override)
+{
+	char *override_file, *override_part;
+	void *override_header = NULL;
+	int ret;
+
+	ret = bootm_image_name_and_part(override, &override_file, &override_part);
+	if (ret)
+		return ret;
+
+	/* Read header to detect file type */
+	ret = file_read_and_detect_boot_image_type(override_file, &override_header);
+	if (ret < 0) {
+		free(override_file);
+		return ret;
+	}
+
+	data->image_type = ret;
+
+	/*
+	 * bootm_image_name_and_part() returns os_part pointing inside the
+	 * os_file allocation, so we must NULLify os_part before freeing
+	 * os_file to avoid a use-after-free.
+	 */
+	data->os_part = NULL;
+	free(data->os_file);
+	data->os_file = override_file;
+	if (override_part)
+		data->os_part = override_part;
+
+	/* Update the detected type for the handler */
+	free(data->os_header);
+	data->os_header = override_header;
+
+	switch (data->image_type) {
+	case filetype_fit:
+		/* Restore, so bootm_open_fit looks for them in the new FIT */
+		data->os_address = data->os_address_hint;
+		data->os_entry = data->os_entry_hint;
+
+		/* Re-open configuration and collect loadables */
+		return bootm_open_fit(data, true);
+	default:
+		loadable_release(&data->os);
+		data->os = loadable_from_file(override_file, LOADABLE_KERNEL);
+		if (IS_ERR(data->os))
+			return PTR_ERR(data->os);
+
+		data->kernel_type = data->image_type;
+		data->is_override.os = true;
+
+		if (file_is_compressed_file(data->kernel_type))
+			return bootm_open_os_compressed(data);
+
+		return 0;
+	}
+}
+
+static void report_active_override(const char *type_str, struct loadable *l)
+{
+	struct loadable *lc;
+	char *buf = NULL;
+
+	if (!l) {
+		pr_notice("[bootm override active] removed %s\n", type_str);
+		return;
+	}
+
+	buf = xrasprintf(buf, "[bootm override active] replacement %s: %s",
+		      type_str, l->name);
+
+	list_for_each_entry(lc, &l->chained_loadables, list)
+		buf = xrasprintf(buf, ", %s", lc->name);
+
+	pr_notice("%s\n", buf);
+	free(buf);
+}
+
+static void report_active_overrides(const struct image_data *data)
+{
+	if (data->is_override.os)
+		report_active_override("OS", data->os);
+	if (data->is_override.initrd)
+		report_active_override("initrd", data->initrd);
+	if (data->is_override.oftree)
+		report_active_override("FDT", data->oftree);
+}
 
 /**
  * bootm_apply_overrides - apply overrides
  * @data: image data context
  * @overrides: overrides to apply
  *
- * Applies bootm.initrd and bootm.oftree overrides by translating
- * them into file-based loadables.
+ * Applies bootm.image, bootm.initrd, and bootm.oftree overrides by translating
+ * them into file-based loadables or FIT image replacements.
  *
  * Context: Called during boot preparation
  * Return: 0 on success, negative error code otherwise
@@ -17,9 +121,17 @@
 int bootm_apply_overrides(struct image_data *data,
 			  const struct bootm_overrides *overrides)
 {
+	int ret;
+
 	if (bootm_signed_images_are_forced())
 		return 0;
 
+	if (nonempty(overrides->os_file)) {
+		ret = bootm_apply_image_override(data, overrides->os_file);
+		if (ret)
+			return ret;
+	}
+
 	if (IS_ENABLED(CONFIG_BOOTM_INITRD) && overrides->initrd_file) {
 		/* loadables_from_files() will set data->initrd on empty initrd_file */
 		int ret = loadables_from_files(&data->initrd, overrides->initrd_file, ":",
@@ -42,6 +154,7 @@ int bootm_apply_overrides(struct image_data *data,
 		data->is_override.oftree = true;
 	}
 
+	report_active_overrides(data);
+
 	return 0;
 }
-
diff --git a/common/bootm.c b/common/bootm.c
index ddc656fb34c8..ed79c20c0972 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -400,7 +400,7 @@ static void bootm_print_info(struct image_data *data)
 		printf("OS image not yet relocated\n");
 }
 
-static int bootm_image_name_and_part(const char *name, char **filename, char **part)
+int bootm_image_name_and_part(const char *name, char **filename, char **part)
 {
 	char *at, *ret;
 
@@ -570,7 +570,7 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
 
 	switch (data->image_type) {
 	case filetype_fit:
-		ret = bootm_open_fit(data);
+		ret = bootm_open_fit(data, false);
 		break;
 	case filetype_uimage:
 		ret = bootm_open_uimage(data);
diff --git a/include/bootm-fit.h b/include/bootm-fit.h
index 05f4f5acfe9a..972d9e4fac8e 100644
--- a/include/bootm-fit.h
+++ b/include/bootm-fit.h
@@ -10,11 +10,11 @@ struct image_data;
 
 #ifdef CONFIG_BOOTM_FITIMAGE
 
-int bootm_open_fit(struct image_data *data);
+int bootm_open_fit(struct image_data *data, bool override);
 
 #else
 
-static inline int bootm_open_fit(struct image_data *data)
+static inline int bootm_open_fit(struct image_data *data, bool override)
 {
 	return -ENOSYS;
 }
diff --git a/include/bootm-overrides.h b/include/bootm-overrides.h
index a52a498b97a2..9b133928a923 100644
--- a/include/bootm-overrides.h
+++ b/include/bootm-overrides.h
@@ -3,6 +3,7 @@
 #define __BOOTM_OVERRIDES_H
 
 struct bootm_overrides {
+	const char *os_file;
 	const char *oftree_file;
 	const char *initrd_file;
 };
@@ -13,6 +14,8 @@ struct image_data;
 static inline void bootm_merge_overrides(struct bootm_overrides *dst,
 					 const struct bootm_overrides *src)
 {
+	if (src->os_file)
+		dst->os_file = src->os_file;
 	if (src->oftree_file)
 		dst->oftree_file = src->oftree_file;
 	if (src->initrd_file)
diff --git a/include/bootm.h b/include/bootm.h
index 570ac4d289f2..85199fc702b4 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -53,6 +53,8 @@ struct bootm_data {
 	unsigned long os_entry;
 };
 
+int bootm_image_name_and_part(const char *name, char **filename, char **part);
+
 int bootm_boot(const struct bootm_data *data);
 
 struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data);
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 12/16] bootm: overrides: add support for overlays
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (9 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 11/16] bootm: implement plain and FIT bootm.image override Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 13/16] test: py: add test for initrd concatenation Ahmad Fatoum
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum, Claude Opus 4.5

Use loadables_from_files() for the oftree override, matching the initrd
pattern, to support colon-separated multi-file syntax. The first entry
is the base DT and subsequent entries are applied as overlays:

  bootm.oftree=a       use a as DT (unchanged behavior)
  bootm.oftree=a:b     use a as DT, apply b as overlay
  bootm.oftree=:b      keep existing DT, apply b as overlay
  bootm.oftree=a:b:c   use a as DT, apply b and c as overlays

Override overlays are applied during of_fix_tree() via a fixup registered
at of_populate_initcall level (13), placing them after global overlays
(device_initcall, 11) but before bootargs fixup (late_initcall, 14).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 common/bootm-overrides.c  | 65 +++++++++++++++++++++++++++++++++------
 common/bootm.c            |  3 ++
 include/bootm-overrides.h |  6 ++++
 3 files changed, 65 insertions(+), 9 deletions(-)

diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
index 8c71ceb5f80d..720d5636f331 100644
--- a/common/bootm-overrides.c
+++ b/common/bootm-overrides.c
@@ -3,7 +3,9 @@
 #include <bootm.h>
 #include <bootm-overrides.h>
 #include <bootm-fit.h>
+#include <init.h>
 #include <libfile.h>
+#include <of.h>
 #include <linux/pagemap.h>
 
 /**
@@ -142,15 +144,11 @@ int bootm_apply_overrides(struct image_data *data,
 	}
 
 	if (overrides->oftree_file) {
-		loadable_release(&data->oftree);
-
-		/* Empty string means to mask the original FDT */
-		if (nonempty(overrides->oftree_file)) {
-			data->oftree = loadable_from_file(overrides->oftree_file,
-							   LOADABLE_FDT);
-			if (IS_ERR(data->oftree))
-				return PTR_ERR(data->oftree);
-		}
+		int ret = loadables_from_files(&data->oftree,
+					       overrides->oftree_file, ":",
+					       LOADABLE_FDT);
+		if (ret)
+			return ret;
 		data->is_override.oftree = true;
 	}
 
@@ -158,3 +156,52 @@ int bootm_apply_overrides(struct image_data *data,
 
 	return 0;
 }
+
+static struct loadable *pending_oftree_overlays;
+
+void bootm_set_pending_oftree_overlays(struct loadable *oftree)
+{
+	pending_oftree_overlays = oftree;
+}
+
+void bootm_clear_pending_oftree_overlays(void)
+{
+	pending_oftree_overlays = NULL;
+}
+
+static int bootm_override_overlay_fixup(struct device_node *root, void *ctx)
+{
+	struct loadable *ovl;
+
+	if (!pending_oftree_overlays)
+		return 0;
+
+	list_for_each_entry(ovl, &pending_oftree_overlays->chained_loadables, list) {
+		const void *dtbo;
+		size_t size;
+		int ret;
+
+		dtbo = loadable_view(ovl, &size);
+		if (IS_ERR(dtbo)) {
+			pr_err("could not load overlay \"%s\": %pe\n",
+			       ovl->name, dtbo);
+			continue;
+		}
+
+		ret = of_overlay_apply_dtbo(root, dtbo);
+		if (ret)
+			pr_err("failed to apply overlay \"%s\": %pe\n",
+			       ovl->name, ERR_PTR(ret));
+		loadable_view_free(ovl, dtbo, size);
+	}
+
+	return 0;
+}
+
+static int bootm_register_override_overlay_fixup(void)
+{
+	if (!IS_ENABLED(CONFIG_OF_OVERLAY))
+		return 0;
+	return of_register_fixup(bootm_override_overlay_fixup, NULL);
+}
+of_populate_initcall(bootm_register_override_overlay_fixup);
diff --git a/common/bootm.c b/common/bootm.c
index ed79c20c0972..d4a3a232743f 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -11,6 +11,7 @@
 #include <block.h>
 #include <libfile.h>
 #include <bootm-fit.h>
+#include <bootm-overrides.h>
 #include <bootm-uimage.h>
 #include <globalvar.h>
 #include <init.h>
@@ -325,7 +326,9 @@ void *bootm_get_devicetree(struct image_data *data)
 		of_add_reserve_entry(data->initrd_res->start, data->initrd_res->end);
 	}
 
+	bootm_set_pending_oftree_overlays(data->oftree);
 	of_fix_tree(data->of_root_node);
+	bootm_clear_pending_oftree_overlays();
 
 	oftree = of_flatten_dtb(data->of_root_node);
 	if (!oftree)
diff --git a/include/bootm-overrides.h b/include/bootm-overrides.h
index 9b133928a923..0dbf6e7fa823 100644
--- a/include/bootm-overrides.h
+++ b/include/bootm-overrides.h
@@ -9,6 +9,7 @@ struct bootm_overrides {
 };
 
 struct image_data;
+struct loadable;
 
 #ifdef CONFIG_BOOT_OVERRIDE
 static inline void bootm_merge_overrides(struct bootm_overrides *dst,
@@ -24,6 +25,8 @@ static inline void bootm_merge_overrides(struct bootm_overrides *dst,
 
 int bootm_apply_overrides(struct image_data *data,
 			  const struct bootm_overrides *overrides);
+void bootm_set_pending_oftree_overlays(struct loadable *oftree);
+void bootm_clear_pending_oftree_overlays(void);
 #else
 
 static inline void bootm_merge_overrides(struct bootm_overrides *dst,
@@ -36,6 +39,9 @@ static inline int bootm_apply_overrides(struct image_data *data,
 {
 	return 0;
 }
+
+static inline void bootm_set_pending_oftree_overlays(struct loadable *o) {}
+static inline void bootm_clear_pending_oftree_overlays(void) {}
 #endif
 
 #endif
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 13/16] test: py: add test for initrd concatenation
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (10 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 12/16] bootm: overrides: add support for overlays Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 14/16] defaultenv: base: add new devboot script Ahmad Fatoum
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

Now that the FIT code supports processing multiple entries in the
ramdisk property, add a test to ensure this doesn't regress.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 test/py/test_fit.py                          | 38 +++++++++++++-------
 test/py/testfs.py                            | 23 ++++++++++++
 test/testdata/multi_v7_defconfig-gzipped.its | 28 +++++++++++++--
 test/testdata/multi_v8_defconfig-gzipped.its | 28 +++++++++++++--
 4 files changed, 98 insertions(+), 19 deletions(-)
 create mode 100644 test/py/testfs.py

diff --git a/test/py/test_fit.py b/test/py/test_fit.py
index 8bcee6a75f23..0fd5a3c5dcc5 100644
--- a/test/py/test_fit.py
+++ b/test/py/test_fit.py
@@ -7,6 +7,7 @@ import shutil
 import subprocess
 from pathlib import Path
 from .helper import of_get_property, filter_errors
+from .testfs import mkcpio
 
 
 def generate_bootscript(barebox, image, name="test"):
@@ -37,16 +38,9 @@ def fit_testdata(barebox_config, testfs):
             input=(builddir / "images" / "barebox-dt-2nd.img").read_bytes(),
             stdout=open(builddir / "barebox-dt-2nd.img.gz", "wb"))
 
-        find = subprocess.Popen(["find", "COPYING", "LICENSES/"],
-                                stdout=subprocess.PIPE)
-        cpio = subprocess.Popen(["cpio", "-o", "-H", "newc"],
-                                stdin=find.stdout, stdout=subprocess.PIPE)
-        gzip = subprocess.Popen(["gzip"], stdin=cpio.stdout,
-                                stdout=open(builddir / "ramdisk.cpio.gz", "wb"))
-
-        find.wait()
-        cpio.wait()
-        gzip.wait()
+        mkcpio("COPYING", outdir / "ramdisk1.cpio.gz")
+        mkcpio("LICENSES/exceptions/", outdir / "ramdisk2.cpio")
+        mkcpio("LICENSES/preferred/", outdir / "ramdisk3.cpio.gz")
 
         run(["mkimage", "-G", "test/self/development_rsa2048.pem", "-r", "-f",
              str(builddir / its_name), str(outfile)])
@@ -105,6 +99,15 @@ def test_fit(barebox, strategy, fitimage):
 
     barebox.run_check("global linux.bootargs.testarg=barebox.chainloaded")
 
+    barebox.run_check("cat -o /tmp/ramdisks.cpio /mnt/9p/testfs/ramdisk*.cpio*")
+    [hashsum1] = barebox.run_check("md5sum /tmp/ramdisks.cpio")
+
+    hashsum1 = re.split("/tmp/ramdisks.cpio", hashsum1, maxsplit=1)[0]
+    # Get the actual size of the concatenated ramdisks file
+    [fileinfo] = barebox.run_check("ls -l /tmp/ramdisks.cpio")
+    # Parse the size from ls -l output (format: permissions links user group size ...)
+    actual_size = int(fileinfo.split()[1])
+
     boottarget = generate_bootscript(barebox, fitimage)
 
     with strategy.boot_barebox(boottarget) as barebox:
@@ -118,7 +121,16 @@ def test_fit(barebox, strategy, fitimage):
         bootargs = of_get_property(barebox, "/chosen/bootargs")
         assert "barebox.chainloaded" in bootargs
 
-        initrd_start = of_get_property(barebox, "/chosen/linux,initrd-start", 0)
-        initrd_end = of_get_property(barebox, "/chosen/linux,initrd-end", 0)
+        initrd_start = of_get_property(barebox, "/chosen/linux,initrd-start", ncells=0)
+        initrd_end = of_get_property(barebox, "/chosen/linux,initrd-end", ncells=0)
+        initrd_size = initrd_end - initrd_start
 
-        assert initrd_start < initrd_end
+        # Verify the DT-reported size matches the actual file size
+        assert initrd_size == actual_size, \
+            f"Initrd size mismatch: DT says {initrd_size}, file is {actual_size}"
+
+        [hashsum2] = barebox.run_check(f"md5sum {initrd_start}+{initrd_size}")
+
+        hashsum2 = re.split("/dev/mem", hashsum2, maxsplit=1)[0]
+
+        assert hashsum1 == hashsum2
diff --git a/test/py/testfs.py b/test/py/testfs.py
new file mode 100644
index 000000000000..3ead67c0621b
--- /dev/null
+++ b/test/py/testfs.py
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import subprocess
+
+
+def mkcpio(inpath, outname):
+    compress = outname.suffix == ".gz"
+
+    find = subprocess.Popen(["find", inpath], stdout=subprocess.PIPE)
+
+    with open(outname, "wb") as outfile:
+        cpio = subprocess.Popen(["cpio", "-o", "-H", "newc"], stdin=find.stdout,
+                                stdout=(subprocess.PIPE if compress else outfile))
+
+        find.stdout.close()
+
+        if compress:
+            gzip = subprocess.Popen(["gzip"], stdin=cpio.stdout, stdout=outfile)
+            cpio.stdout.close()
+            gzip.wait()
+
+        cpio.wait()
+        find.wait()
diff --git a/test/testdata/multi_v7_defconfig-gzipped.its b/test/testdata/multi_v7_defconfig-gzipped.its
index 11595c39baa1..1cb84fd41564 100644
--- a/test/testdata/multi_v7_defconfig-gzipped.its
+++ b/test/testdata/multi_v7_defconfig-gzipped.its
@@ -26,8 +26,30 @@
             };
         };
         ramdisk-1 {
-            description = "initramfs";
-            data = /incbin/("ramdisk.cpio.gz");
+            description = "initramfs 1 (compressed)";
+            data = /incbin/("testfs/ramdisk1.cpio.gz");
+            type = "ramdisk";
+            arch = "arm";
+            os = "linux";
+            compression = "none";
+            hash-1 {
+                algo = "sha256";
+            };
+        };
+        ramdisk-2 {
+            description = "initramfs 2 (uncompressed)";
+            data = /incbin/("testfs/ramdisk2.cpio");
+            type = "ramdisk";
+            arch = "arm";
+            os = "linux";
+            compression = "none";
+            hash-1 {
+                algo = "sha256";
+            };
+        };
+        ramdisk-3 {
+            description = "initramfs 3 (compressed)";
+            data = /incbin/("testfs/ramdisk3.cpio.gz");
             type = "ramdisk";
             arch = "arm";
             os = "linux";
@@ -44,7 +66,7 @@
             description = "Qemu Virt32";
             kernel = "kernel-1";
             fdt = "fdt-qemu-virt32.dtb";
-            ramdisk = "ramdisk-1";
+            ramdisk = "ramdisk-1", "ramdisk-2", "ramdisk-3";
             compatible = "linux,dummy-virt";
             signature-1 {
                 algo = "sha256,rsa2048";
diff --git a/test/testdata/multi_v8_defconfig-gzipped.its b/test/testdata/multi_v8_defconfig-gzipped.its
index 5ce678eec9a8..3786d0f12187 100644
--- a/test/testdata/multi_v8_defconfig-gzipped.its
+++ b/test/testdata/multi_v8_defconfig-gzipped.its
@@ -26,8 +26,30 @@
             };
         };
         ramdisk-1 {
-            description = "initramfs";
-            data = /incbin/("ramdisk.cpio.gz");
+            description = "initramfs 1 (compressed)";
+            data = /incbin/("testfs/ramdisk1.cpio.gz");
+            type = "ramdisk";
+            arch = "arm64";
+            os = "linux";
+            compression = "none";
+            hash-1 {
+                algo = "sha256";
+            };
+        };
+        ramdisk-2 {
+            description = "initramfs 2 (uncompressed)";
+            data = /incbin/("testfs/ramdisk2.cpio");
+            type = "ramdisk";
+            arch = "arm64";
+            os = "linux";
+            compression = "none";
+            hash-1 {
+                algo = "sha256";
+            };
+        };
+        ramdisk-3 {
+            description = "initramfs 3 (compressed)";
+            data = /incbin/("testfs/ramdisk3.cpio.gz");
             type = "ramdisk";
             arch = "arm64";
             os = "linux";
@@ -44,7 +66,7 @@
             description = "Qemu Virt64";
             kernel = "kernel-1";
             fdt = "fdt-qemu-virt64.dtb";
-            ramdisk = "ramdisk-1";
+            ramdisk = "ramdisk-1", "ramdisk-2", "ramdisk-3";
             compatible = "linux,dummy-virt";
             signature-1 {
                 algo = "sha256,rsa2048";
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 14/16] defaultenv: base: add new devboot script
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (11 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 13/16] test: py: add test for initrd concatenation Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-18  9:50   ` Sascha Hauer
  2026-03-12 14:44 ` [PATCH 15/16] Documentation: user: devboot: add section on forwarding build dirs Ahmad Fatoum
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

devboot is a new command wrapper that makes it easier to use overrides
with network-provided artifacts:

- On the host side, add a *-devboot-* script that describes which
  overrides to use

- On the barebox side, ensure the script can be found at either
  ${fetchfs}/${global.user}-devboot-${global.hostname} or
  ${fetchfs}/${global.user}-devboot-${global.arch}

- barebox will source this script and pass the applicable overrides
  to the boot command in addition to whatever arguments devboot
  was given

Here's an example *-devboot-* script:

  devboot_image=afa-fit-rock3a
  devboot_oftree=                    # force use of barebox DT
  devboot_initrd=":afa-rsinit-arm64" # append specified CPIO to FIT's

  global linux.bootargs.dyn.rsinit=rsinit.bind=/lib/modules

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 Documentation/user/booting-linux.rst     |   5 +-
 Documentation/user/devboot.rst           | 204 +++++++++++++++++++++++
 Documentation/user/user-manual.rst       |   1 +
 defaultenv/defaultenv-2-base/bin/devboot |  48 ++++++
 defaultenv/defaultenv-2-base/boot/devel  |   3 +
 5 files changed, 260 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/user/devboot.rst
 create mode 100644 defaultenv/defaultenv-2-base/bin/devboot
 create mode 100755 defaultenv/defaultenv-2-base/boot/devel

diff --git a/Documentation/user/booting-linux.rst b/Documentation/user/booting-linux.rst
index ed1ec3f68a93..3972777f30cf 100644
--- a/Documentation/user/booting-linux.rst
+++ b/Documentation/user/booting-linux.rst
@@ -182,7 +182,10 @@ and configure the overrides as arguments to the ``boot`` command:
 
 .. code-block:: sh
 
-  boot -o bootm.image=/mnt/tftp/oftree mmc
+  boot -o bootm.image=/mnt/tftp/zImage mmc
+
+See :ref:`devboot` for the full override syntax and the ``devboot``
+convenience script for network-based development.
 
 Generic Boot Targets
 ^^^^^^^^^^^^^^^^^^^^
diff --git a/Documentation/user/devboot.rst b/Documentation/user/devboot.rst
new file mode 100644
index 000000000000..ad1b4c909ab0
--- /dev/null
+++ b/Documentation/user/devboot.rst
@@ -0,0 +1,204 @@
+.. _devboot:
+
+Devboot
+=======
+
+Devboot is a development convenience that builds on top of the
+:ref:`boot override <boot_overrides>` mechanism. It sources a per-user,
+per-board script from the network and translates it into ``boot -o``
+arguments, making it easy to redirect any combination of kernel, device tree
+and initrd to network-hosted files without touching the boot medium.
+
+.. _boot_overrides:
+
+Boot overrides
+--------------
+
+Enable ``CONFIG_BOOT_OVERRIDE`` to allow partial overrides of boot entries.
+This is already enabled in ``multi_v7_defconfig`` and ``multi_v8_defconfig``.
+
+The ``boot`` command then accepts one or more ``-o`` flags:
+
+.. code-block:: sh
+
+  boot -o bootm.image=/mnt/tftp/my-zImage mmc
+  boot -o bootm.oftree=/mnt/tftp/my-board.dtb mmc
+  boot -o bootm.initrd=/mnt/tftp/my-initrd mmc
+
+Multiple overrides can be combined in a single invocation:
+
+.. code-block:: sh
+
+  boot -o bootm.image=/mnt/tftp/zImage \
+       -o bootm.oftree=/mnt/tftp/board.dtb \
+       -o bootm.initrd=/mnt/tftp/initrd \
+       mmc
+
+Overrides are applied after the boot entry has been loaded, so the original
+entry still selects the boot handler, kernel arguments, etc. Only the
+specified components are replaced.
+
+Override rules
+^^^^^^^^^^^^^^
+
+``bootm.image``
+  The override file is probed for its type. If it is a FIT image, the
+  FIT configuration is re-opened and all components (kernel, initrd, device
+  tree) are taken from the override FIT. If it is a plain kernel image
+  (zImage, Image, uImage, ...), only the OS loadable is replaced.
+
+``bootm.oftree``
+  A colon-separated list of files. The first entry is the base device tree;
+  subsequent entries are applied as overlays:
+
+  .. code-block:: sh
+
+    # Use a as the device tree
+    boot -o bootm.oftree=a mmc
+
+    # Use a as the device tree, apply b as overlay
+    boot -o bootm.oftree=a:b mmc
+
+    # Keep the existing device tree, apply b as overlay
+    boot -o bootm.oftree=:b mmc
+
+    # Use a as the device tree, apply b and c as overlays
+    boot -o bootm.oftree=a:b:c mmc
+
+  Setting ``bootm.oftree`` to an empty string (``bootm.oftree=``) discards
+  whatever device tree the boot entry provides. If
+  ``CONFIG_BOOTM_OFTREE_FALLBACK`` is enabled (the default), the
+  barebox-internal device tree is passed to the kernel instead. If the
+  option is disabled, no device tree is passed at all.
+
+``bootm.initrd``
+  A colon-separated list of initrd/CPIO files. Linux can transparently
+  extract any number of concatenated CPIOs, even if individually compressed.
+  A leading colon appends to the initrd that the boot entry already provides:
+
+  .. code-block:: sh
+
+    # Replace with a single initrd
+    boot -o bootm.initrd=/mnt/tftp/initrd mmc
+
+    # Concatenate two initrds
+    boot -o bootm.initrd=initrd1:initrd2 mmc
+
+    # Append an extra CPIO to the existing initrd
+    boot -o bootm.initrd=:extra.cpio mmc
+
+  .. tip::
+
+     The Linux kernel ``make cpio-modules-pkg`` target builds a CPIO archive
+     containing all kernel modules. This is useful for supplementing an existing
+     initramfs with modules without modifying the root filesystem.
+     Combined with initrd concatenation, a devboot script
+     can append it to any boot entry's initrd::
+
+       devboot_initrd=":afa-modules-arm64"
+
+     To make use of the modules, the initramfs init will need to make initramfs
+     /modules available to the rootfs, e.g. via a bind mount:
+
+       mount -n --bind /lib/modules ${ROOT_MOUNT}/lib/modules
+
+.. note::
+
+   When secure boot is enforced (``bootm_signed_images_are_forced()``),
+   overrides are silently ignored.
+
+The devboot script
+------------------
+
+The ``devboot`` command (``/env/bin/devboot``) automates the override
+workflow for network-based development:
+
+1. Brings up the network (``ifup -a1``).
+2. Changes into the fetch directory
+   (:ref:`global.net.fetchdir <magicvar_global_net_fetchdir>`, default
+   ``/mnt/tftp``).
+3. Sources a configuration script named
+   ``${global.user}-devboot-${global.hostname}``. If that file does not
+   exist, it falls back to ``${global.user}-devboot-${global.arch}``.
+4. Translates the variables set by the script into ``boot -o`` arguments.
+5. Passes any extra arguments through to the ``boot`` command.
+
+.. note::
+
+   TFTP is not a real file system, e.g., listing of directories is not
+   possible. Keep that in mind if the devboot script does more than
+   just fetch files by name from TFTP.
+
+Usage
+^^^^^
+
+.. code-block:: sh
+
+  # Boot from MMC with devboot overrides
+  devboot mmc
+
+  # Boot from the default boot entry
+  devboot
+
+  # Make devboot the default for subsequent boots
+  nv.boot.default=devel
+
+Configuration script
+^^^^^^^^^^^^^^^^^^^^
+
+The configuration script lives in the fetch directory and sets up to three
+variables:
+
+``devboot_image``
+  Override for ``bootm.image``. Path is relative to the fetch directory.
+
+``devboot_oftree``
+  Override for ``bootm.oftree``. An empty string discards the boot
+  entry's device tree (see ``bootm.oftree`` rules above).
+
+``devboot_initrd``
+  Override for ``bootm.initrd``. A leading colon appends to the boot
+  entry's existing initrd.
+
+Variables that are not set (using ``[[ -v ... ]]``) are left alone,
+so unset variables do not generate override arguments.
+
+The script can also set arbitrary barebox variables, for example
+kernel command line fragments:
+
+.. code-block:: sh
+
+  # /tftpboot/afa-devboot-rock3a
+  devboot_image=afa-fit-rock3a
+  devboot_oftree=
+  devboot_initrd=":afa-rsinit-arm64"
+
+  global linux.bootargs.dyn.rsinit="rsinit.bind=/lib/modules"
+
+With this script in place and ``global.user=afa``, ``global.hostname=rock3a``,
+running ``devboot mmc`` on the board expands to::
+
+  boot -o bootm.image="afa-fit-rock3a" \
+       -o bootm.oftree="" \
+       -o bootm.initrd=":afa-rsinit-arm64" \
+       mmc
+
+This fetches the FIT image ``afa-fit-rock3a`` from the fetch directory,
+discards the FIT's device tree (falling back to the barebox-internal one
+when ``CONFIG_BOOTM_OFTREE_FALLBACK`` is enabled), and appends the
+``afa-rsinit-arm64`` CPIO archive to whatever initrd the FIT already
+contains.
+
+Setting up the variables
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``global.user`` and ``global.hostname`` variables control which
+script is loaded. Make them persistent with:
+
+.. code-block:: sh
+
+  nv user=afa
+  nv hostname=rock3a
+
+``global.hostname`` is typically derived from the device tree, but can be
+overridden.
diff --git a/Documentation/user/user-manual.rst b/Documentation/user/user-manual.rst
index cb01721863dd..aa55017c69d7 100644
--- a/Documentation/user/user-manual.rst
+++ b/Documentation/user/user-manual.rst
@@ -26,6 +26,7 @@ Contents:
    usb
    ubi
    booting-linux
+   devboot
    bootchooser
    remote-control
    security
diff --git a/defaultenv/defaultenv-2-base/bin/devboot b/defaultenv/defaultenv-2-base/bin/devboot
new file mode 100644
index 000000000000..124bfec01ba3
--- /dev/null
+++ b/defaultenv/defaultenv-2-base/bin/devboot
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+devboot_path="${global.net.fetchdir}"
+
+# global.net.server and global.hostname may be set by DHCP, so trigger it first
+ifup -a1
+
+cd "${devboot_path}" || exit 1
+saved_pwd="$OLDPWD"
+
+devboot_script="${global.user}-devboot-${global.hostname}"
+if [ ! -f "${devboot_script}" ]; then
+	devboot_script2="${global.user}-devboot-${global.arch}"
+	if [ ! -f "${devboot_script2}" ]; then
+		echo "Error: Neither ${devboot_script} nor ${devboot_script2} found"
+		echo "Create one of them and set as needed:"
+		echo "  devboot_image, devboot_initrd, devboot_oftree"
+		cd "${saved_pwd}"
+		exit 1
+	fi
+	devboot_script="${devboot_script2}"
+fi
+
+echo Sourcing "${devboot_path}/${devboot_script}"
+source "${devboot_path}/${devboot_script}"
+if [ $? -ne 0 ]; then
+	cd "${saved_pwd}"
+	exit 1
+fi
+
+override_args=""
+
+if [[ -v devboot_image ]]; then
+	override_args="${override_args} -o bootm.image=\"${devboot_image}\""
+fi
+
+if [[ -v devboot_oftree ]]; then
+	override_args="${override_args} -o bootm.oftree=\"${devboot_oftree}\""
+fi
+
+if [[ -v devboot_initrd ]]; then
+	override_args="${override_args} -o bootm.initrd=\"${devboot_initrd}\""
+fi
+
+echo boot ${override_args} $*
+boot ${override_args} $*
+
+cd "${saved_pwd}"
diff --git a/defaultenv/defaultenv-2-base/boot/devel b/defaultenv/defaultenv-2-base/boot/devel
new file mode 100755
index 000000000000..9bcaaffb5870
--- /dev/null
+++ b/defaultenv/defaultenv-2-base/boot/devel
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+devboot
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 15/16] Documentation: user: devboot: add section on forwarding build dirs
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (12 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 14/16] defaultenv: base: add new devboot script Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-12 14:44 ` [PATCH 16/16] libfile: remove file_to_sdram Ahmad Fatoum
  2026-03-18 10:06 ` [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Sascha Hauer
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

In case the kernel is built on a remote build server, it can be useful
for devboot to fetch directly from there with the TFTP server running in
the local LAN. Add some documentation about that.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 Documentation/user/devboot.rst | 53 ++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)

diff --git a/Documentation/user/devboot.rst b/Documentation/user/devboot.rst
index ad1b4c909ab0..4425f0ebe91a 100644
--- a/Documentation/user/devboot.rst
+++ b/Documentation/user/devboot.rst
@@ -202,3 +202,56 @@ script is loaded. Make them persistent with:
 
 ``global.hostname`` is typically derived from the device tree, but can be
 overridden.
+
+Forwarding a remote build directory over the internet
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If the build server is not reachable from the Device-Under-Test, one
+possibility is to forward the build artifacts to a local TFTP server
+via SSHFS::
+
+  sshfs -o follow_symlinks,allow_other buildserver:/tftpboot /tftpboot
+
+The local development host can then export /tftpboot via TFTP.
+
+If the TFTP server is not running on the local development host, the
+``dpipe`` utility can be used to connect a local SFTP server process to
+an SSH session that runs ``sshfs`` in slave mode on the remote machine:
+
+.. code-block:: sh
+
+  dpipe /usr/lib/openssh/sftp-server = ssh a3f@tftpserver.in-my.lan sshfs -o slave,follow_symlinks,allow_other -o idmap=user -o gid=1001 :/tftpboot /tftpboot
+
+This works as follows:
+
+- ``dpipe`` connects the standard input/output of both sides of ``=``.
+- The left side runs the local SFTP server, giving the remote end access
+  to the local filesystem.
+- The right side runs ``sshfs`` on the TFTP server host in ``slave``
+  mode, reading SFTP protocol directly from stdin instead of spawning its
+  own SSH connection.
+- ``follow_symlinks`` resolves symlinks on the build host, so the TFTP
+  server sees regular files even if the build tree uses symlinks.
+- ``allow_other`` permits the TFTP daemon (which typically runs as a
+  different user) to read the mounted files.
+- ``idmap=user`` maps the remote user's UID to the local user, avoiding
+  permission issues.
+- ``gid=1001`` sets the group ID for all files (adjust to match the
+  group that the TFTP daemon runs as on the server, e.g. ``tftp`` or
+  ``nogroup``).
+- The colon prefix in ``:/tftpboot`` refers to the root of the *local*
+  (build host) filesystem as exported by the SFTP server.
+
+The result is that ``/tftpboot`` on the remote TFTP server mirrors the
+``/tftpboot`` directory on the build host. Build output can be
+symlinked there and the board will fetch it directly over TFTP.
+
+To undo the mount, terminate the ``dpipe`` process and run
+``fusermount -u /tftpboot`` on the TFTP server.
+
+.. note::
+
+   On Debian/Ubuntu, ``dpipe`` can be installed via ``apt install vde2``
+   ``sshfs`` and ``fuse`` must be installed on the TFTP server.
+   ``/etc/fuse.conf`` on the server must have ``user_allow_other``
+   enabled for ``allow_other`` to work.
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 16/16] libfile: remove file_to_sdram
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (13 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 15/16] Documentation: user: devboot: add section on forwarding build dirs Ahmad Fatoum
@ 2026-03-12 14:44 ` Ahmad Fatoum
  2026-03-18 10:06 ` [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Sascha Hauer
  15 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-12 14:44 UTC (permalink / raw)
  To: barebox; +Cc: Ahmad Fatoum

All users of file_to_sdram have now been switched to use
loadable_extract_into_sdram_all() or other loadable API, so it's now ok
to remove this function.

Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
---
 include/libfile.h |  3 ---
 lib/libfile.c     | 66 -----------------------------------------------
 2 files changed, 69 deletions(-)

diff --git a/include/libfile.h b/include/libfile.h
index c1e51a77b169..3f5ea5255af7 100644
--- a/include/libfile.h
+++ b/include/libfile.h
@@ -55,9 +55,6 @@ char *make_temp(const char *template);
 
 int cache_file(const char *path, char **newpath);
 
-struct resource *file_to_sdram(const char *filename, unsigned long adr,
-			       enum resource_memtype memtype);
-
 int fixup_path_case(int dirfd, const char **path);
 
 int open_fdt(const char *filename, size_t *size);
diff --git a/lib/libfile.c b/lib/libfile.c
index 0670ef0584cb..cf78c70354d9 100644
--- a/lib/libfile.c
+++ b/lib/libfile.c
@@ -830,72 +830,6 @@ int __read_full_anywhere(int fd, void *buf, size_t size)
 	return now < 0 ? now : now + nbytes;
 }
 
-#define BUFSIZ	(PAGE_SIZE * 32)
-
-struct resource *file_to_sdram(const char *filename, unsigned long adr,
-			       enum resource_memtype memtype)
-{
-	struct resource *res;
-	unsigned memattrs;
-	size_t size = BUFSIZ;
-	size_t ofs = 0;
-	ssize_t now;
-	int fd;
-
-	fd = open(filename, O_RDONLY);
-	if (fd < 0)
-		return NULL;
-
-	/* FIXME: EFI payloads are started with MMU enabled, so for now
-	 * we keep attributes as RWX instead of remapping later on
-	 */
-	memattrs = IS_ENABLED(CONFIG_EFI_LOADER) ? MEMATTRS_RWX : MEMATTRS_RW;
-
-	while (1) {
-
-		res = request_sdram_region("image", adr, size,
-					   memtype, memattrs);
-		if (!res)
-			goto out;
-
-		if (zero_page_contains(res->start + ofs)) {
-			void *tmp = malloc(BUFSIZ);
-			if (!tmp)
-				now = -ENOMEM;
-			else
-				now = read_full(fd, tmp, BUFSIZ);
-
-			if (now > 0)
-				zero_page_memcpy((void *)(res->start + ofs), tmp, now);
-			free(tmp);
-		} else {
-			now = read_full(fd, (void *)(res->start + ofs), BUFSIZ);
-		}
-
-		if (now < 0) {
-			release_sdram_region(res);
-			res = NULL;
-			goto out;
-		}
-
-		if (now < BUFSIZ) {
-			release_sdram_region(res);
-			res = request_sdram_region("image", adr, ofs + now,
-						   memtype, memattrs);
-			goto out;
-		}
-
-		release_sdram_region(res);
-
-		ofs += BUFSIZ;
-		size += BUFSIZ;
-	}
-out:
-	close(fd);
-
-	return res;
-}
-
 int fixup_path_case(int fd, const char **path)
 {
 	DIR *dir;
-- 
2.47.3




^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 11/16] bootm: implement plain and FIT bootm.image override
  2026-03-12 14:44 ` [PATCH 11/16] bootm: implement plain and FIT bootm.image override Ahmad Fatoum
@ 2026-03-18  9:01   ` Sascha Hauer
  2026-03-18  9:17     ` Ahmad Fatoum
  0 siblings, 1 reply; 22+ messages in thread
From: Sascha Hauer @ 2026-03-18  9:01 UTC (permalink / raw)
  To: Ahmad Fatoum; +Cc: barebox, Claude Opus 4.5

Hi Ahmad,

On Thu, Mar 12, 2026 at 03:44:54PM +0100, Ahmad Fatoum wrote:
> Add support for overriding the boot image via bootm.image when using
> either plain files or FIT images. The override behavior depends on the
> file types involved:
> 
> - If the override is not a FIT image, it replaces only the OS loadable,
>   regardless of whether the original was a FIT image or not.
> 
> - If the override is a FIT image, it replaces the current OS, whether
>   it's a FIT or not.

What's the "current OS" here? Is it just the kernel or dtb/initrd as
well?

Sascha

> 
> This allows developers to use devboot scripts to redirect FIT image
> loading to alternate locations (e.g., network) while preserving the
> configuration selection from the original boot entry.
> 
> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
> ---
>  commands/boot.c           |   2 +-
>  common/bootm-fit.c        |  11 +++-
>  common/bootm-overrides.c  | 119 +++++++++++++++++++++++++++++++++++++-
>  common/bootm.c            |   4 +-
>  include/bootm-fit.h       |   4 +-
>  include/bootm-overrides.h |   3 +
>  include/bootm.h           |   2 +
>  7 files changed, 134 insertions(+), 11 deletions(-)
> 
> diff --git a/commands/boot.c b/commands/boot.c
> index 555667ee098c..04bb9eb9186f 100644
> --- a/commands/boot.c
> +++ b/commands/boot.c
> @@ -44,7 +44,7 @@ static int boot_add_override(struct bootm_overrides *overrides, char *var)
>  	if (!strcmp(var, "bootm.image")) {
>  		if (isempty(val))
>  			return -EINVAL;
> -		return -ENOSYS;
> +		overrides->os_file = val;
>  	} else if (!strcmp(var, "bootm.oftree")) {
>  		overrides->oftree_file = val;
>  	} else if (!strcmp(var, "bootm.initrd")) {
> diff --git a/common/bootm-fit.c b/common/bootm-fit.c
> index 5b110ad2f19e..995bd7181223 100644
> --- a/common/bootm-fit.c
> +++ b/common/bootm-fit.c
> @@ -4,6 +4,7 @@
>  #include <image-fit.h>
>  #include <bootm-fit.h>
>  #include <memory.h>
> +#include <string.h>
>  #include <zero_page.h>
>  #include <filetype.h>
>  #include <fs.h>
> @@ -143,7 +144,7 @@ static enum filetype bootm_fit_update_os_header(struct image_data *data)
>  	return os_type;
>  }
>  
> -int bootm_open_fit(struct image_data *data)
> +int bootm_open_fit(struct image_data *data, bool override)
>  {
>  	struct fit_handle *fit;
>  	void *fit_config;
> @@ -164,8 +165,12 @@ int bootm_open_fit(struct image_data *data)
>  	}
>  
>  	loadable_from_fit_os(data, fit, fit_config);
> -	loadable_from_fit_initrd(data, fit, fit_config);
> -	loadable_from_fit_oftree(data, fit, fit_config);
> +	if (override)
> +		data->is_override.os = true;
> +	if (loadable_from_fit_initrd(data, fit, fit_config) && override)
> +		data->is_override.initrd = true;
> +	if (loadable_from_fit_oftree(data, fit, fit_config) && override)
> +		data->is_override.oftree = true;
>  	loadable_from_fit_tee(data, fit, fit_config);
>  
>  	data->kernel_type = bootm_fit_update_os_header(data);
> diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
> index 59ca90a58684..8c71ceb5f80d 100644
> --- a/common/bootm-overrides.c
> +++ b/common/bootm-overrides.c
> @@ -2,14 +2,118 @@
>  
>  #include <bootm.h>
>  #include <bootm-overrides.h>
> +#include <bootm-fit.h>
> +#include <libfile.h>
> +#include <linux/pagemap.h>
> +
> +/**
> + * bootm_apply_image_override - apply bootm.image override
> + * @data: image data context
> + * @override: override path (may include @config suffix for FIT)
> + *
> + * Applies a bootm.image override according to these rules:
> + * - If the override is not a FIT image, it replaces only data->os.
> + * - If the override is a FIT image, it is re-opened via bootm_open_fit()
> + *   using the default configuration or the @config suffix if given.
> + *
> + * Return: 0 on success, negative errno on failure
> + */
> +static int bootm_apply_image_override(struct image_data *data,
> +				      const char *override)
> +{
> +	char *override_file, *override_part;
> +	void *override_header = NULL;
> +	int ret;
> +
> +	ret = bootm_image_name_and_part(override, &override_file, &override_part);
> +	if (ret)
> +		return ret;
> +
> +	/* Read header to detect file type */
> +	ret = file_read_and_detect_boot_image_type(override_file, &override_header);
> +	if (ret < 0) {
> +		free(override_file);
> +		return ret;
> +	}
> +
> +	data->image_type = ret;
> +
> +	/*
> +	 * bootm_image_name_and_part() returns os_part pointing inside the
> +	 * os_file allocation, so we must NULLify os_part before freeing
> +	 * os_file to avoid a use-after-free.
> +	 */
> +	data->os_part = NULL;
> +	free(data->os_file);
> +	data->os_file = override_file;
> +	if (override_part)
> +		data->os_part = override_part;
> +
> +	/* Update the detected type for the handler */
> +	free(data->os_header);
> +	data->os_header = override_header;
> +
> +	switch (data->image_type) {
> +	case filetype_fit:
> +		/* Restore, so bootm_open_fit looks for them in the new FIT */
> +		data->os_address = data->os_address_hint;
> +		data->os_entry = data->os_entry_hint;
> +
> +		/* Re-open configuration and collect loadables */
> +		return bootm_open_fit(data, true);
> +	default:
> +		loadable_release(&data->os);
> +		data->os = loadable_from_file(override_file, LOADABLE_KERNEL);
> +		if (IS_ERR(data->os))
> +			return PTR_ERR(data->os);
> +
> +		data->kernel_type = data->image_type;
> +		data->is_override.os = true;
> +
> +		if (file_is_compressed_file(data->kernel_type))
> +			return bootm_open_os_compressed(data);
> +
> +		return 0;
> +	}
> +}
> +
> +static void report_active_override(const char *type_str, struct loadable *l)
> +{
> +	struct loadable *lc;
> +	char *buf = NULL;
> +
> +	if (!l) {
> +		pr_notice("[bootm override active] removed %s\n", type_str);
> +		return;
> +	}
> +
> +	buf = xrasprintf(buf, "[bootm override active] replacement %s: %s",
> +		      type_str, l->name);
> +
> +	list_for_each_entry(lc, &l->chained_loadables, list)
> +		buf = xrasprintf(buf, ", %s", lc->name);
> +
> +	pr_notice("%s\n", buf);
> +	free(buf);
> +}
> +
> +static void report_active_overrides(const struct image_data *data)
> +{
> +	if (data->is_override.os)
> +		report_active_override("OS", data->os);
> +	if (data->is_override.initrd)
> +		report_active_override("initrd", data->initrd);
> +	if (data->is_override.oftree)
> +		report_active_override("FDT", data->oftree);
> +}
>  
>  /**
>   * bootm_apply_overrides - apply overrides
>   * @data: image data context
>   * @overrides: overrides to apply
>   *
> - * Applies bootm.initrd and bootm.oftree overrides by translating
> - * them into file-based loadables.
> + * Applies bootm.image, bootm.initrd, and bootm.oftree overrides by translating
> + * them into file-based loadables or FIT image replacements.
>   *
>   * Context: Called during boot preparation
>   * Return: 0 on success, negative error code otherwise
> @@ -17,9 +121,17 @@
>  int bootm_apply_overrides(struct image_data *data,
>  			  const struct bootm_overrides *overrides)
>  {
> +	int ret;
> +
>  	if (bootm_signed_images_are_forced())
>  		return 0;
>  
> +	if (nonempty(overrides->os_file)) {
> +		ret = bootm_apply_image_override(data, overrides->os_file);
> +		if (ret)
> +			return ret;
> +	}
> +
>  	if (IS_ENABLED(CONFIG_BOOTM_INITRD) && overrides->initrd_file) {
>  		/* loadables_from_files() will set data->initrd on empty initrd_file */
>  		int ret = loadables_from_files(&data->initrd, overrides->initrd_file, ":",
> @@ -42,6 +154,7 @@ int bootm_apply_overrides(struct image_data *data,
>  		data->is_override.oftree = true;
>  	}
>  
> +	report_active_overrides(data);
> +
>  	return 0;
>  }
> -
> diff --git a/common/bootm.c b/common/bootm.c
> index ddc656fb34c8..ed79c20c0972 100644
> --- a/common/bootm.c
> +++ b/common/bootm.c
> @@ -400,7 +400,7 @@ static void bootm_print_info(struct image_data *data)
>  		printf("OS image not yet relocated\n");
>  }
>  
> -static int bootm_image_name_and_part(const char *name, char **filename, char **part)
> +int bootm_image_name_and_part(const char *name, char **filename, char **part)
>  {
>  	char *at, *ret;
>  
> @@ -570,7 +570,7 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
>  
>  	switch (data->image_type) {
>  	case filetype_fit:
> -		ret = bootm_open_fit(data);
> +		ret = bootm_open_fit(data, false);
>  		break;
>  	case filetype_uimage:
>  		ret = bootm_open_uimage(data);
> diff --git a/include/bootm-fit.h b/include/bootm-fit.h
> index 05f4f5acfe9a..972d9e4fac8e 100644
> --- a/include/bootm-fit.h
> +++ b/include/bootm-fit.h
> @@ -10,11 +10,11 @@ struct image_data;
>  
>  #ifdef CONFIG_BOOTM_FITIMAGE
>  
> -int bootm_open_fit(struct image_data *data);
> +int bootm_open_fit(struct image_data *data, bool override);
>  
>  #else
>  
> -static inline int bootm_open_fit(struct image_data *data)
> +static inline int bootm_open_fit(struct image_data *data, bool override)
>  {
>  	return -ENOSYS;
>  }
> diff --git a/include/bootm-overrides.h b/include/bootm-overrides.h
> index a52a498b97a2..9b133928a923 100644
> --- a/include/bootm-overrides.h
> +++ b/include/bootm-overrides.h
> @@ -3,6 +3,7 @@
>  #define __BOOTM_OVERRIDES_H
>  
>  struct bootm_overrides {
> +	const char *os_file;
>  	const char *oftree_file;
>  	const char *initrd_file;
>  };
> @@ -13,6 +14,8 @@ struct image_data;
>  static inline void bootm_merge_overrides(struct bootm_overrides *dst,
>  					 const struct bootm_overrides *src)
>  {
> +	if (src->os_file)
> +		dst->os_file = src->os_file;
>  	if (src->oftree_file)
>  		dst->oftree_file = src->oftree_file;
>  	if (src->initrd_file)
> diff --git a/include/bootm.h b/include/bootm.h
> index 570ac4d289f2..85199fc702b4 100644
> --- a/include/bootm.h
> +++ b/include/bootm.h
> @@ -53,6 +53,8 @@ struct bootm_data {
>  	unsigned long os_entry;
>  };
>  
> +int bootm_image_name_and_part(const char *name, char **filename, char **part);
> +
>  int bootm_boot(const struct bootm_data *data);
>  
>  struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data);
> -- 
> 2.47.3
> 
> 
> 

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 11/16] bootm: implement plain and FIT bootm.image override
  2026-03-18  9:01   ` Sascha Hauer
@ 2026-03-18  9:17     ` Ahmad Fatoum
  0 siblings, 0 replies; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-18  9:17 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: barebox, Claude Opus 4.5

Hi,

On 3/18/26 10:01, Sascha Hauer wrote:
> Hi Ahmad,
> 
> On Thu, Mar 12, 2026 at 03:44:54PM +0100, Ahmad Fatoum wrote:
>> Add support for overriding the boot image via bootm.image when using
>> either plain files or FIT images. The override behavior depends on the
>> file types involved:
>>
>> - If the override is not a FIT image, it replaces only the OS loadable,
>>   regardless of whether the original was a FIT image or not.
>>
>> - If the override is a FIT image, it replaces the current OS, whether
>>   it's a FIT or not.
> 
> What's the "current OS" here? Is it just the kernel or dtb/initrd as
> well?

Sorry, that's confusingly written, A FIT replaces everything in the
selected configuration. So if it has kernel + DT, it overrides those and
leaves only the original initramfs.

Cheers,
Ahmad

> 
> Sascha
> 
>>
>> This allows developers to use devboot scripts to redirect FIT image
>> loading to alternate locations (e.g., network) while preserving the
>> configuration selection from the original boot entry.
>>
>> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
>> Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
>> ---
>>  commands/boot.c           |   2 +-
>>  common/bootm-fit.c        |  11 +++-
>>  common/bootm-overrides.c  | 119 +++++++++++++++++++++++++++++++++++++-
>>  common/bootm.c            |   4 +-
>>  include/bootm-fit.h       |   4 +-
>>  include/bootm-overrides.h |   3 +
>>  include/bootm.h           |   2 +
>>  7 files changed, 134 insertions(+), 11 deletions(-)
>>
>> diff --git a/commands/boot.c b/commands/boot.c
>> index 555667ee098c..04bb9eb9186f 100644
>> --- a/commands/boot.c
>> +++ b/commands/boot.c
>> @@ -44,7 +44,7 @@ static int boot_add_override(struct bootm_overrides *overrides, char *var)
>>  	if (!strcmp(var, "bootm.image")) {
>>  		if (isempty(val))
>>  			return -EINVAL;
>> -		return -ENOSYS;
>> +		overrides->os_file = val;
>>  	} else if (!strcmp(var, "bootm.oftree")) {
>>  		overrides->oftree_file = val;
>>  	} else if (!strcmp(var, "bootm.initrd")) {
>> diff --git a/common/bootm-fit.c b/common/bootm-fit.c
>> index 5b110ad2f19e..995bd7181223 100644
>> --- a/common/bootm-fit.c
>> +++ b/common/bootm-fit.c
>> @@ -4,6 +4,7 @@
>>  #include <image-fit.h>
>>  #include <bootm-fit.h>
>>  #include <memory.h>
>> +#include <string.h>
>>  #include <zero_page.h>
>>  #include <filetype.h>
>>  #include <fs.h>
>> @@ -143,7 +144,7 @@ static enum filetype bootm_fit_update_os_header(struct image_data *data)
>>  	return os_type;
>>  }
>>  
>> -int bootm_open_fit(struct image_data *data)
>> +int bootm_open_fit(struct image_data *data, bool override)
>>  {
>>  	struct fit_handle *fit;
>>  	void *fit_config;
>> @@ -164,8 +165,12 @@ int bootm_open_fit(struct image_data *data)
>>  	}
>>  
>>  	loadable_from_fit_os(data, fit, fit_config);
>> -	loadable_from_fit_initrd(data, fit, fit_config);
>> -	loadable_from_fit_oftree(data, fit, fit_config);
>> +	if (override)
>> +		data->is_override.os = true;
>> +	if (loadable_from_fit_initrd(data, fit, fit_config) && override)
>> +		data->is_override.initrd = true;
>> +	if (loadable_from_fit_oftree(data, fit, fit_config) && override)
>> +		data->is_override.oftree = true;
>>  	loadable_from_fit_tee(data, fit, fit_config);
>>  
>>  	data->kernel_type = bootm_fit_update_os_header(data);
>> diff --git a/common/bootm-overrides.c b/common/bootm-overrides.c
>> index 59ca90a58684..8c71ceb5f80d 100644
>> --- a/common/bootm-overrides.c
>> +++ b/common/bootm-overrides.c
>> @@ -2,14 +2,118 @@
>>  
>>  #include <bootm.h>
>>  #include <bootm-overrides.h>
>> +#include <bootm-fit.h>
>> +#include <libfile.h>
>> +#include <linux/pagemap.h>
>> +
>> +/**
>> + * bootm_apply_image_override - apply bootm.image override
>> + * @data: image data context
>> + * @override: override path (may include @config suffix for FIT)
>> + *
>> + * Applies a bootm.image override according to these rules:
>> + * - If the override is not a FIT image, it replaces only data->os.
>> + * - If the override is a FIT image, it is re-opened via bootm_open_fit()
>> + *   using the default configuration or the @config suffix if given.
>> + *
>> + * Return: 0 on success, negative errno on failure
>> + */
>> +static int bootm_apply_image_override(struct image_data *data,
>> +				      const char *override)
>> +{
>> +	char *override_file, *override_part;
>> +	void *override_header = NULL;
>> +	int ret;
>> +
>> +	ret = bootm_image_name_and_part(override, &override_file, &override_part);
>> +	if (ret)
>> +		return ret;
>> +
>> +	/* Read header to detect file type */
>> +	ret = file_read_and_detect_boot_image_type(override_file, &override_header);
>> +	if (ret < 0) {
>> +		free(override_file);
>> +		return ret;
>> +	}
>> +
>> +	data->image_type = ret;
>> +
>> +	/*
>> +	 * bootm_image_name_and_part() returns os_part pointing inside the
>> +	 * os_file allocation, so we must NULLify os_part before freeing
>> +	 * os_file to avoid a use-after-free.
>> +	 */
>> +	data->os_part = NULL;
>> +	free(data->os_file);
>> +	data->os_file = override_file;
>> +	if (override_part)
>> +		data->os_part = override_part;
>> +
>> +	/* Update the detected type for the handler */
>> +	free(data->os_header);
>> +	data->os_header = override_header;
>> +
>> +	switch (data->image_type) {
>> +	case filetype_fit:
>> +		/* Restore, so bootm_open_fit looks for them in the new FIT */
>> +		data->os_address = data->os_address_hint;
>> +		data->os_entry = data->os_entry_hint;
>> +
>> +		/* Re-open configuration and collect loadables */
>> +		return bootm_open_fit(data, true);
>> +	default:
>> +		loadable_release(&data->os);
>> +		data->os = loadable_from_file(override_file, LOADABLE_KERNEL);
>> +		if (IS_ERR(data->os))
>> +			return PTR_ERR(data->os);
>> +
>> +		data->kernel_type = data->image_type;
>> +		data->is_override.os = true;
>> +
>> +		if (file_is_compressed_file(data->kernel_type))
>> +			return bootm_open_os_compressed(data);
>> +
>> +		return 0;
>> +	}
>> +}
>> +
>> +static void report_active_override(const char *type_str, struct loadable *l)
>> +{
>> +	struct loadable *lc;
>> +	char *buf = NULL;
>> +
>> +	if (!l) {
>> +		pr_notice("[bootm override active] removed %s\n", type_str);
>> +		return;
>> +	}
>> +
>> +	buf = xrasprintf(buf, "[bootm override active] replacement %s: %s",
>> +		      type_str, l->name);
>> +
>> +	list_for_each_entry(lc, &l->chained_loadables, list)
>> +		buf = xrasprintf(buf, ", %s", lc->name);
>> +
>> +	pr_notice("%s\n", buf);
>> +	free(buf);
>> +}
>> +
>> +static void report_active_overrides(const struct image_data *data)
>> +{
>> +	if (data->is_override.os)
>> +		report_active_override("OS", data->os);
>> +	if (data->is_override.initrd)
>> +		report_active_override("initrd", data->initrd);
>> +	if (data->is_override.oftree)
>> +		report_active_override("FDT", data->oftree);
>> +}
>>  
>>  /**
>>   * bootm_apply_overrides - apply overrides
>>   * @data: image data context
>>   * @overrides: overrides to apply
>>   *
>> - * Applies bootm.initrd and bootm.oftree overrides by translating
>> - * them into file-based loadables.
>> + * Applies bootm.image, bootm.initrd, and bootm.oftree overrides by translating
>> + * them into file-based loadables or FIT image replacements.
>>   *
>>   * Context: Called during boot preparation
>>   * Return: 0 on success, negative error code otherwise
>> @@ -17,9 +121,17 @@
>>  int bootm_apply_overrides(struct image_data *data,
>>  			  const struct bootm_overrides *overrides)
>>  {
>> +	int ret;
>> +
>>  	if (bootm_signed_images_are_forced())
>>  		return 0;
>>  
>> +	if (nonempty(overrides->os_file)) {
>> +		ret = bootm_apply_image_override(data, overrides->os_file);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>>  	if (IS_ENABLED(CONFIG_BOOTM_INITRD) && overrides->initrd_file) {
>>  		/* loadables_from_files() will set data->initrd on empty initrd_file */
>>  		int ret = loadables_from_files(&data->initrd, overrides->initrd_file, ":",
>> @@ -42,6 +154,7 @@ int bootm_apply_overrides(struct image_data *data,
>>  		data->is_override.oftree = true;
>>  	}
>>  
>> +	report_active_overrides(data);
>> +
>>  	return 0;
>>  }
>> -
>> diff --git a/common/bootm.c b/common/bootm.c
>> index ddc656fb34c8..ed79c20c0972 100644
>> --- a/common/bootm.c
>> +++ b/common/bootm.c
>> @@ -400,7 +400,7 @@ static void bootm_print_info(struct image_data *data)
>>  		printf("OS image not yet relocated\n");
>>  }
>>  
>> -static int bootm_image_name_and_part(const char *name, char **filename, char **part)
>> +int bootm_image_name_and_part(const char *name, char **filename, char **part)
>>  {
>>  	char *at, *ret;
>>  
>> @@ -570,7 +570,7 @@ struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data)
>>  
>>  	switch (data->image_type) {
>>  	case filetype_fit:
>> -		ret = bootm_open_fit(data);
>> +		ret = bootm_open_fit(data, false);
>>  		break;
>>  	case filetype_uimage:
>>  		ret = bootm_open_uimage(data);
>> diff --git a/include/bootm-fit.h b/include/bootm-fit.h
>> index 05f4f5acfe9a..972d9e4fac8e 100644
>> --- a/include/bootm-fit.h
>> +++ b/include/bootm-fit.h
>> @@ -10,11 +10,11 @@ struct image_data;
>>  
>>  #ifdef CONFIG_BOOTM_FITIMAGE
>>  
>> -int bootm_open_fit(struct image_data *data);
>> +int bootm_open_fit(struct image_data *data, bool override);
>>  
>>  #else
>>  
>> -static inline int bootm_open_fit(struct image_data *data)
>> +static inline int bootm_open_fit(struct image_data *data, bool override)
>>  {
>>  	return -ENOSYS;
>>  }
>> diff --git a/include/bootm-overrides.h b/include/bootm-overrides.h
>> index a52a498b97a2..9b133928a923 100644
>> --- a/include/bootm-overrides.h
>> +++ b/include/bootm-overrides.h
>> @@ -3,6 +3,7 @@
>>  #define __BOOTM_OVERRIDES_H
>>  
>>  struct bootm_overrides {
>> +	const char *os_file;
>>  	const char *oftree_file;
>>  	const char *initrd_file;
>>  };
>> @@ -13,6 +14,8 @@ struct image_data;
>>  static inline void bootm_merge_overrides(struct bootm_overrides *dst,
>>  					 const struct bootm_overrides *src)
>>  {
>> +	if (src->os_file)
>> +		dst->os_file = src->os_file;
>>  	if (src->oftree_file)
>>  		dst->oftree_file = src->oftree_file;
>>  	if (src->initrd_file)
>> diff --git a/include/bootm.h b/include/bootm.h
>> index 570ac4d289f2..85199fc702b4 100644
>> --- a/include/bootm.h
>> +++ b/include/bootm.h
>> @@ -53,6 +53,8 @@ struct bootm_data {
>>  	unsigned long os_entry;
>>  };
>>  
>> +int bootm_image_name_and_part(const char *name, char **filename, char **part);
>> +
>>  int bootm_boot(const struct bootm_data *data);
>>  
>>  struct image_data *bootm_boot_prep(const struct bootm_data *bootm_data);
>> -- 
>> 2.47.3
>>
>>
>>
> 


-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 14/16] defaultenv: base: add new devboot script
  2026-03-12 14:44 ` [PATCH 14/16] defaultenv: base: add new devboot script Ahmad Fatoum
@ 2026-03-18  9:50   ` Sascha Hauer
  2026-03-18 10:50     ` Ahmad Fatoum
  0 siblings, 1 reply; 22+ messages in thread
From: Sascha Hauer @ 2026-03-18  9:50 UTC (permalink / raw)
  To: Ahmad Fatoum; +Cc: barebox

On Thu, Mar 12, 2026 at 03:44:57PM +0100, Ahmad Fatoum wrote:
> +The devboot script
> +------------------
> +
> +The ``devboot`` command (``/env/bin/devboot``) automates the override
> +workflow for network-based development:
> +
> +1. Brings up the network (``ifup -a1``).
> +2. Changes into the fetch directory
> +   (:ref:`global.net.fetchdir <magicvar_global_net_fetchdir>`, default
> +   ``/mnt/tftp``).

Is this default actually set somewhere? I can't find it.

> +3. Sources a configuration script named
> +   ``${global.user}-devboot-${global.hostname}``. If that file does not
> +   exist, it falls back to ``${global.user}-devboot-${global.arch}``.
> +4. Translates the variables set by the script into ``boot -o`` arguments.
> +5. Passes any extra arguments through to the ``boot`` command.
> +
> +.. note::
> +
> +   TFTP is not a real file system, e.g., listing of directories is not
> +   possible. Keep that in mind if the devboot script does more than
> +   just fetch files by name from TFTP.
> +
> +Usage
> +^^^^^
> +
> +.. code-block:: sh
> +
> +  # Boot from MMC with devboot overrides
> +  devboot mmc
> +
> +  # Boot from the default boot entry
> +  devboot
> +
> +  # Make devboot the default for subsequent boots
> +  nv.boot.default=devel

So that would be equivalent to a permanent "devboot". Is there also a
way to specifiy permanent "devboot mmc"?

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading
  2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
                   ` (14 preceding siblings ...)
  2026-03-12 14:44 ` [PATCH 16/16] libfile: remove file_to_sdram Ahmad Fatoum
@ 2026-03-18 10:06 ` Sascha Hauer
  15 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-03-18 10:06 UTC (permalink / raw)
  To: barebox, Ahmad Fatoum


On Thu, 12 Mar 2026 15:44:44 +0100, Ahmad Fatoum wrote:
> This adds a new "loadable" abstraction that allows boot components (kernel,
> initrd, FDT) to be represented without immediately loading their data.
> Metadata can be queried via get_info() and actual loading happens on
> demand via extract(), extract_into_buf() or mmap().
> 
> 

Applied, thanks!

[01/16] lib: add lazy loadable infrastructure for deferred boot component loading
        https://git.pengutronix.de/cgit/barebox/commit/?id=b4a3f1da19d8 (link may not be stable)
[02/16] bootm: split preparatory step from handler invocation
        https://git.pengutronix.de/cgit/barebox/commit/?id=7c5de879f9d0 (link may not be stable)
[03/16] boot: add bootm_boot wrapper that takes struct bootentry
        https://git.pengutronix.de/cgit/barebox/commit/?id=797d4cf2115c (link may not be stable)
[04/16] bootchooser: pass along struct bootentry
        https://git.pengutronix.de/cgit/barebox/commit/?id=d768a46f9960 (link may not be stable)
[05/16] bootm: switch plain file names case to loadable API
        https://git.pengutronix.de/cgit/barebox/commit/?id=6c65dec5f7cc (link may not be stable)
[06/16] uimage: add offset parameter to uimage_load
        https://git.pengutronix.de/cgit/barebox/commit/?id=8355a1a55aa3 (link may not be stable)
[07/16] bootm: uimage: switch to loadable API
        https://git.pengutronix.de/cgit/barebox/commit/?id=3e6541814a3d (link may not be stable)
[08/16] bootm: fit: switch to new loadable API
        https://git.pengutronix.de/cgit/barebox/commit/?id=cd86928a7b4a (link may not be stable)
[09/16] bootm: stash initial OS address/entry in image_data
        https://git.pengutronix.de/cgit/barebox/commit/?id=55acd3ee34ec (link may not be stable)
[10/16] bootm: support multiple entries for bootm.initrd
        https://git.pengutronix.de/cgit/barebox/commit/?id=d907c0987359 (link may not be stable)
[11/16] bootm: implement plain and FIT bootm.image override
        https://git.pengutronix.de/cgit/barebox/commit/?id=10130dd5ff19 (link may not be stable)
[12/16] bootm: overrides: add support for overlays
        https://git.pengutronix.de/cgit/barebox/commit/?id=7f66aaf6e6e4 (link may not be stable)
[13/16] test: py: add test for initrd concatenation
        https://git.pengutronix.de/cgit/barebox/commit/?id=90a736b917f0 (link may not be stable)
[14/16] defaultenv: base: add new devboot script
        https://git.pengutronix.de/cgit/barebox/commit/?id=7901159d712b (link may not be stable)
[15/16] Documentation: user: devboot: add section on forwarding build dirs
        https://git.pengutronix.de/cgit/barebox/commit/?id=886de762c869 (link may not be stable)
[16/16] libfile: remove file_to_sdram
        https://git.pengutronix.de/cgit/barebox/commit/?id=03d683fe8999 (link may not be stable)

Best regards,
-- 
Sascha Hauer <s.hauer@pengutronix.de>




^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 14/16] defaultenv: base: add new devboot script
  2026-03-18  9:50   ` Sascha Hauer
@ 2026-03-18 10:50     ` Ahmad Fatoum
  2026-03-18 14:49       ` Sascha Hauer
  0 siblings, 1 reply; 22+ messages in thread
From: Ahmad Fatoum @ 2026-03-18 10:50 UTC (permalink / raw)
  To: Sascha Hauer; +Cc: barebox

On 3/18/26 10:50, Sascha Hauer wrote:
> On Thu, Mar 12, 2026 at 03:44:57PM +0100, Ahmad Fatoum wrote:
>> +The devboot script
>> +------------------
>> +
>> +The ``devboot`` command (``/env/bin/devboot``) automates the override
>> +workflow for network-based development:
>> +
>> +1. Brings up the network (``ifup -a1``).
>> +2. Changes into the fetch directory
>> +   (:ref:`global.net.fetchdir <magicvar_global_net_fetchdir>`, default
>> +   ``/mnt/tftp``).
> 
> Is this default actually set somewhere? I can't find it.

global.net.fetchdir is defined in net/net.c since commit
4f328e47e5c6 ("net: make default netboot artifact directory configurable")

> 
>> +3. Sources a configuration script named
>> +   ``${global.user}-devboot-${global.hostname}``. If that file does not
>> +   exist, it falls back to ``${global.user}-devboot-${global.arch}``.
>> +4. Translates the variables set by the script into ``boot -o`` arguments.
>> +5. Passes any extra arguments through to the ``boot`` command.
>> +
>> +.. note::
>> +
>> +   TFTP is not a real file system, e.g., listing of directories is not
>> +   possible. Keep that in mind if the devboot script does more than
>> +   just fetch files by name from TFTP.
>> +
>> +Usage
>> +^^^^^
>> +
>> +.. code-block:: sh
>> +
>> +  # Boot from MMC with devboot overrides
>> +  devboot mmc
>> +
>> +  # Boot from the default boot entry
>> +  devboot
>> +
>> +  # Make devboot the default for subsequent boots
>> +  nv.boot.default=devel
> 
> So that would be equivalent to a permanent "devboot". Is there also a
> way to specifiy permanent "devboot mmc"?

No, but it's clear to me this is a non-sense boot target as boot would
refer to boot.default... I'd just drop it from v2.

Please let me know when you are done with the review and I can resend.

Cheers,
Ahmad

> 
> Sascha
> 


-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 14/16] defaultenv: base: add new devboot script
  2026-03-18 10:50     ` Ahmad Fatoum
@ 2026-03-18 14:49       ` Sascha Hauer
  0 siblings, 0 replies; 22+ messages in thread
From: Sascha Hauer @ 2026-03-18 14:49 UTC (permalink / raw)
  To: Ahmad Fatoum; +Cc: barebox

On Wed, Mar 18, 2026 at 11:50:42AM +0100, Ahmad Fatoum wrote:
> On 3/18/26 10:50, Sascha Hauer wrote:
> > On Thu, Mar 12, 2026 at 03:44:57PM +0100, Ahmad Fatoum wrote:
> >> +The devboot script
> >> +------------------
> >> +
> >> +The ``devboot`` command (``/env/bin/devboot``) automates the override
> >> +workflow for network-based development:
> >> +
> >> +1. Brings up the network (``ifup -a1``).
> >> +2. Changes into the fetch directory
> >> +   (:ref:`global.net.fetchdir <magicvar_global_net_fetchdir>`, default
> >> +   ``/mnt/tftp``).
> > 
> > Is this default actually set somewhere? I can't find it.
> 
> global.net.fetchdir is defined in net/net.c since commit
> 4f328e47e5c6 ("net: make default netboot artifact directory configurable")

Ah, I thought this is new and searched for it in this series.

> > 
> > So that would be equivalent to a permanent "devboot". Is there also a
> > way to specifiy permanent "devboot mmc"?
> 
> No, but it's clear to me this is a non-sense boot target as boot would
> refer to boot.default... I'd just drop it from v2.
> 
> Please let me know when you are done with the review and I can resend.

I am done.

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-03-18 14:49 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-12 14:44 [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 02/16] bootm: split preparatory step from handler invocation Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 03/16] boot: add bootm_boot wrapper that takes struct bootentry Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 04/16] bootchooser: pass along " Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 05/16] bootm: switch plain file names case to loadable API Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 06/16] uimage: add offset parameter to uimage_load Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 07/16] bootm: uimage: switch to loadable API Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 08/16] bootm: fit: switch to new " Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 09/16] bootm: stash initial OS address/entry in image_data Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 10/16] bootm: support multiple entries for bootm.initrd Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 11/16] bootm: implement plain and FIT bootm.image override Ahmad Fatoum
2026-03-18  9:01   ` Sascha Hauer
2026-03-18  9:17     ` Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 12/16] bootm: overrides: add support for overlays Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 13/16] test: py: add test for initrd concatenation Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 14/16] defaultenv: base: add new devboot script Ahmad Fatoum
2026-03-18  9:50   ` Sascha Hauer
2026-03-18 10:50     ` Ahmad Fatoum
2026-03-18 14:49       ` Sascha Hauer
2026-03-12 14:44 ` [PATCH 15/16] Documentation: user: devboot: add section on forwarding build dirs Ahmad Fatoum
2026-03-12 14:44 ` [PATCH 16/16] libfile: remove file_to_sdram Ahmad Fatoum
2026-03-18 10:06 ` [PATCH 01/16] lib: add lazy loadable infrastructure for deferred boot component loading Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox