* [PATCH 0/9] boot: add imagemap on-demand image loading from storage
@ 2026-08-23 19:12 Daniel Golle
2026-08-23 19:13 ` [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters Daniel Golle
` (10 more replies)
0 siblings, 11 replies; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:12 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
This series adds "imagemap", a small layer for reading FIT images
directly from storage on demand instead of copying the whole image
into RAM first. It keeps a translation table of the byte ranges
already loaded, so a header probe, signature verification and the
final load can reuse a single read, and it hooks transparently into
fit_image_load() so all existing verification and decompression paths
run unchanged for both RAM- and storage-backed images.
An imagemap device is created over a partition of a block device. That
covers more than raw disks: an MTD partition is reached through
mtdblock and a UBI volume through ubiblock, each exposed as a named
block-device partition, so imagemap carries no storage-specific code
of its own. NAND bad-block handling and wear-levelling stay with UBI,
as they do elsewhere in U-Boot; on NOR a partition is read linearly
with end-to-end integrity guaranteed by the FIT hash or signature.
To make those two paths reachable and testable, the first patches fill
in the missing block devices: a non-NAND MTD master now gets an
mtd_blk device automatically (patch 1), and attaching UBI now binds a
ubi_blk device on any flash (patch 3, which makes the SPI-NAND-only
eager binding redundant, dropped in patch 4).
The read path is built on the SPL struct spl_load_info abstraction and
a new spl_load_region() helper. imagemap is full-U-Boot only; the SPL
FIT loader is unaffected.
Testing (sandbox): "ut imagemap" (13 tests) covers the
translation-table core plus real end-to-end reads of a NOR partition
through mtd_blk and a UBI volume through ubiblock; a CONFIG_IMAGEMAP=n
build is unaffected.
On hardware it has been tested end-to-end on a BananaPi R3 (mt7986),
booting a dm-verity-protected rootfs from every storage path the board
exposes: eMMC and SD (block partition), SPI-NAND (UBI volume via
ubiblock) and SPI-NOR (raw MTD partition via mtdblock). The first
in-tree consumer, a bootmeth for OpenWrt-style FIT firmware, will
follow as a separate series.
This series began as part of a combined RFC that also carried the
OpenWrt boot method and its bootdevs [1]. Following the review there
it has been reworked substantially:
- Build on U-Boot's existing SPL external-data loading path rather
than a new abstraction: imagemap now uses struct spl_load_info and
a new spl_load_region() helper (suggested by Marek Vasut).
- Do not extend the bootm command with a storage source; on-demand
loading hooks into fit_image_load() transparently, and the in-tree
consumer is a bootstd boot method reached through
bootflow/bootdev/bootmeth rather than new bootm syntax (per Tom
Rini).
- Move the storage-backed path out of the long fit_image_load() into
its own helper, gated with if()/tools_build() instead of an inline
#if (per Simon Glass).
- Collapse the three storage-specific backends (block, MTD and UBI)
into a single block backend: NOR is read through mtd_blk and
SPI-NAND through UBI/ubiblock, so imagemap holds no
storage-specific code.
- Send the on-demand loading core on its own; the OpenWrt boot
method, the MTD/UBI bootdevs and board enablement follow as
separate series once this lands.
In order to test imagemap end-to-end with the intended bootmeth user
I'm still relying on the downstream install-uuid mechanism of [2],
which [3] could supersede. Both have stalled right now.
[1] https://lore.kernel.org/u-boot/cover.1771275704.git.daniel@makrotopia.org/
[2] https://github.com/open-source-firmware/flat-image-tree/pull/40
[3] https://github.com/open-source-firmware/flat-image-tree/pull/44
Based on u-boot/next.
Daniel Golle (9):
mtd: bind an mtd_blk device for non-NAND MTD masters
mtd: nand: sandbox: forbid sub-page writes
cmd: ubi: create a ubi_blk device when attaching UBI
mtd: nand: spi: drop the eager ubi_blk binding
boot: add imagemap on-demand loading from storage
boot: fit: support on-demand loading in fit_image_load()
configs: sandbox: enable imagemap and its storage backends
test: boot: add imagemap unit tests
doc: imagemap: document on-demand loading framework
MAINTAINERS | 9 +
arch/sandbox/dts/test.dts | 29 ++
boot/Kconfig | 16 +
boot/Makefile | 2 +
boot/bootm.c | 70 +++-
boot/image-fit.c | 109 +++++-
boot/imagemap.c | 451 +++++++++++++++++++++
cmd/ubi.c | 27 ++
configs/sandbox_defconfig | 6 +
doc/develop/imagemap.rst | 100 +++++
doc/develop/index.rst | 1 +
drivers/mtd/mtdcore.c | 24 ++
drivers/mtd/nand/raw/sand_nand.c | 2 +
drivers/mtd/nand/spi/core.c | 3 -
include/bootm.h | 2 +
include/dm/uclass-id.h | 1 +
include/image.h | 2 +
include/imagemap.h | 136 +++++++
include/spl.h | 47 ++-
test/boot/Makefile | 2 +
test/boot/imagemap.c | 645 +++++++++++++++++++++++++++++++
test/cmd_ut.c | 2 +
22 files changed, 1671 insertions(+), 15 deletions(-)
create mode 100644 boot/imagemap.c
create mode 100644 doc/develop/imagemap.rst
create mode 100644 include/imagemap.h
create mode 100644 test/boot/imagemap.c
base-commit: 8841b7b292c74864acf4ed0bd8ce05839252c1f2
--
2.55.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
@ 2026-08-23 19:13 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes Daniel Golle
` (9 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:13 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Only the SPI-NAND bind path currently creates an mtd_blk block device,
so a bare NOR (parallel NOR, SPI-NOR, ...) has no block-device access
at all. Bind an mtd_blk device for every non-NAND MTD master when
CONFIG_MTD_BLOCK is enabled, so its partitions become reachable
through the block layer (and thus by bootstd, imagemap, ...).
NAND is excluded on purpose: it needs UBI, which brings its own block
device. Partitions inherit the master's block device via the "mtd"
partition type, so only masters are bound.
mtd_bind() keeps a pointer to the passed mtd_info pointer (SPI-NAND
fills it after bind), so the generic caller provides a small heap slot
that lives as long as the block device; an MTD master is never removed
in practice.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/mtd/mtdcore.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 3bfa5aebbc6..e0d0b5ae2e9 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -494,6 +494,30 @@ int add_mtd_device(struct mtd_info *mtd)
#endif
mutex_unlock(&mtd_table_mutex);
+
+ if (IS_ENABLED(CONFIG_MTD_BLOCK) && mtd->dev &&
+ !mtd_is_partition(mtd) && !mtd_type_is_nand(mtd)) {
+ /*
+ * Expose a non-NAND MTD master (parallel NOR, SPI-NOR, ...) as
+ * a mtd_blk block device, so its partitions are reachable
+ * through the block layer (and thus by imagemap, bootstd, ...).
+ * NAND is excluded: it needs UBI, which brings its own block
+ * device. Partitions inherit the master's block device via the
+ * "mtd" partition type, so only masters are bound.
+ *
+ * mtd_bind() keeps a pointer to the passed mtd_info pointer, so
+ * it needs storage that lives as long as the block device; the
+ * MTD master is never removed in practice, so a small heap slot
+ * is fine.
+ */
+ struct mtd_info **mtdp = kmalloc(sizeof(*mtdp), GFP_KERNEL);
+
+ if (mtdp) {
+ *mtdp = mtd;
+ mtd_bind(mtd->dev, mtdp);
+ }
+ }
+
/* We _know_ we aren't being removed, because
our caller is still holding us here. So none
of this try_ nonsense, and no bitching about it
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
2026-08-23 19:13 ` [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters Daniel Golle
@ 2026-08-23 19:13 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI Daniel Golle
` (8 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:13 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
The sandbox NAND model programs each page in a single operation and
rejects a second write to an already-programmed page. UBI writes the
EC and VID headers into the first page in two separate sub-page
operations, which the model fails with -EIO, so UBI cannot attach to a
sandbox NAND.
Advertise NAND_NO_SUBPAGE_WRITE so callers place each header in its
own page, matching what the model can actually do. This lets the
imagemap UBI-volume unit test attach UBI on the sandbox NAND.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/mtd/nand/raw/sand_nand.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/mtd/nand/raw/sand_nand.c b/drivers/mtd/nand/raw/sand_nand.c
index 3678bb8a41b..5c48b43a882 100644
--- a/drivers/mtd/nand/raw/sand_nand.c
+++ b/drivers/mtd/nand/raw/sand_nand.c
@@ -602,6 +602,8 @@ static int sand_nand_probe(struct udevice *dev)
nand = &chip->nand;
nand->options = not_xpl() ? 0 : NAND_SKIP_BBTSCAN;
+ /* Each page is programmed in a single operation only */
+ nand->options |= NAND_NO_SUBPAGE_WRITE;
nand->flash_node = np;
nand->dev_ready = sand_nand_dev_ready;
nand->cmdfunc = sand_nand_command;
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
2026-08-23 19:13 ` [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters Daniel Golle
2026-08-23 19:13 ` [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes Daniel Golle
@ 2026-08-23 19:13 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 4/9] mtd: nand: spi: drop the eager ubi_blk binding Daniel Golle
` (7 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:13 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
A UBI volume can be reached as a partition on the ubi_blk block
device, but that device is only bound from spinand_bind(), so it is
missing on every other flash type (raw NAND, SPI-NOR, ...).
Bind a ubi_blk device when a UBI partition is attached, unless one has
already been created, parented to the MTD device UBI sits on. UBI
volumes then become reachable as block-device partitions regardless of
the underlying flash. The imagemap block path relies on this to read a
UBI volume.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
cmd/ubi.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/cmd/ubi.c b/cmd/ubi.c
index 2b206141a21..91ba21504f4 100644
--- a/cmd/ubi.c
+++ b/cmd/ubi.c
@@ -20,7 +20,10 @@
#include <mtd.h>
#include <nand.h>
#include <onenand_uboot.h>
+#include <dm/device.h>
#include <dm/devres.h>
+#include <dm/root.h>
+#include <dm/uclass-internal.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/err.h>
@@ -670,6 +673,29 @@ int ubi_detach(void)
return 0;
}
+#if CONFIG_IS_ENABLED(UBI_BLOCK)
+static void ubi_blk_bind_once(void)
+{
+ struct udevice *dev;
+
+ /*
+ * A single ubi_blk device serves all volumes of the attached UBI
+ * device, the volume being selected through the block descriptor's
+ * hwpart. Bind one when UBI is attached -- unless a previous attach
+ * already did -- parented to the MTD device UBI sits on.
+ */
+ for (uclass_find_first_device(UCLASS_BLK, &dev); dev;
+ uclass_find_next_device(&dev)) {
+ if (dev->driver == DM_DRIVER_GET(ubi_blk))
+ return;
+ }
+
+ ubi_bind(ubi && ubi->mtd && ubi->mtd->dev ? ubi->mtd->dev : dm_root());
+}
+#else
+static inline void ubi_blk_bind_once(void) { }
+#endif
+
int ubi_part(const char *part_name, const char *vid_header_offset)
{
struct mtd_info *mtd;
@@ -698,6 +724,7 @@ int ubi_part(const char *part_name, const char *vid_header_offset)
}
ubi = ubi_devices[0];
+ ubi_blk_bind_once();
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 4/9] mtd: nand: spi: drop the eager ubi_blk binding
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (2 preceding siblings ...)
2026-08-23 19:13 ` [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI Daniel Golle
@ 2026-08-23 19:13 ` Daniel Golle
2026-08-23 19:13 ` [PATCH 5/9] boot: add imagemap on-demand loading from storage Daniel Golle
` (6 subsequent siblings)
10 siblings, 0 replies; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:13 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
spinand_bind() binds a ubi_blk device at bind time. Now that "cmd:
ubi: create a ubi_blk device when attaching UBI" binds one generically
when UBI is attached -- with an identical result, only lazily and for
any flash type -- the SPI-NAND-specific eager call is redundant.
Drop it to avoid the duplicate code path (a ubi_blk is unusable before
UBI is attached anyway). The mtd_blk binding is kept: bare mtd_blk
access to SPI-NAND remains available for users who want it.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/mtd/nand/spi/core.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 14af4264612..fb8ca0a787d 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1654,9 +1654,6 @@ static int spinand_bind(struct udevice *dev)
if (ret)
return ret;
}
-
- if (CONFIG_IS_ENABLED(UBI_BLOCK))
- return ubi_bind(dev);
}
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 5/9] boot: add imagemap on-demand loading from storage
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (3 preceding siblings ...)
2026-08-23 19:13 ` [PATCH 4/9] mtd: nand: spi: drop the eager ubi_blk binding Daniel Golle
@ 2026-08-23 19:13 ` Daniel Golle
2026-08-24 22:55 ` Tom Rini
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:14 ` [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load() Daniel Golle
` (5 subsequent siblings)
10 siblings, 2 replies; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:13 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Introduce imagemap, a small layer that reads image data from a storage
device on demand and keeps a translation table of the byte ranges
already loaded into RAM, instead of copying the whole image up front.
An imagemap device (UCLASS_IMAGEMAP) is created over a partition of a
block device with imagemap_create(); imagemap_map() then returns a RAM
pointer for any byte range, reading it in on first access and reusing
it on later accesses -- so a header probe, verification and the final
load of the same range share a single read. Block devices cover more
than raw disks: an MTD partition is reached through mtdblock and a UBI
volume through ubiblock, each exposed as a named block-device
partition, so imagemap needs no storage-specific code of its own.
The read path is built on the SPL struct spl_load_info abstraction and
a new spl_load_region() helper (added to spl.h): the block reader
reports the device block length and spl_load_region() performs the
native sector alignment. imagemap_map_to() keeps a payload byte-exact
at a caller's load address, reading the aligned middle straight to the
destination and bouncing only the partial head/tail block.
Regions that are not placed at a caller-supplied address are allocated
through the LMB allocator and released on cleanup; the translation
table doubles as the registry of those allocations. imagemap is gated
to the full-U-Boot phase, so SPL and TPL carry no new cost.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 7 +
boot/Kconfig | 16 ++
boot/Makefile | 2 +
boot/imagemap.c | 451 +++++++++++++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 1 +
include/imagemap.h | 136 +++++++++++++
include/spl.h | 47 ++++-
7 files changed, 657 insertions(+), 3 deletions(-)
create mode 100644 boot/imagemap.c
create mode 100644 include/imagemap.h
diff --git a/MAINTAINERS b/MAINTAINERS
index acaba95ed03..48ee01e033d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1186,6 +1186,13 @@ S: Maintained
F: drivers/timer/goldfish_timer.c
F: include/goldfish_timer.h
+IMAGEMAP
+M: Daniel Golle <daniel@makrotopia.org>
+L: openwrt-devel@lists.openwrt.org
+S: Maintained
+F: boot/imagemap.c
+F: include/imagemap.h
+
INTERCONNECT
M: Neil Armstrong <neil.armstrong@linaro.org>
S: Maintained
diff --git a/boot/Kconfig b/boot/Kconfig
index c67dc0ba493..0d060aa25e4 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -1205,6 +1205,22 @@ config SYS_BOOT_RAMDISK_HIGH
endmenu # Boot images
+config IMAGEMAP
+ bool "On-demand image loading from storage"
+ depends on DM && LMB && BLK && PARTITIONS
+ help
+ Read image data from a storage device on demand instead of
+ copying the whole image into RAM first. A translation table maps
+ already-loaded byte ranges to their RAM addresses to avoid
+ redundant reads, and scratch allocations are managed through the
+ LMB allocator.
+
+ Images are read from a partition on a block device, identified by
+ number or name. MTD partitions (via mtdblock) and UBI volumes
+ (via ubiblock) are reached as block-device partitions too. Used
+ by bootm when a storage device is specified instead of a RAM
+ address.
+
config DISTRO_DEFAULTS
bool "(deprecated) Script-based booting of Linux distributions"
select CMDLINE
diff --git a/boot/Makefile b/boot/Makefile
index 7fb56e7ef37..b4200ebab3d 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -73,6 +73,8 @@ obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o
obj-$(CONFIG_$(PHASE_)BOOTMETH_ANDROID) += bootmeth_android.o
+obj-$(CONFIG_$(PHASE_)IMAGEMAP) += imagemap.o
+
obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC) += vbe_abrec.o vbe_common.o
obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC_FW) += vbe_abrec_fw.o
obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC_OS) += vbe_abrec_os.o
diff --git a/boot/imagemap.c b/boot/imagemap.c
new file mode 100644
index 00000000000..4bdfa6109f8
--- /dev/null
+++ b/boot/imagemap.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * On-demand image loading from storage (UCLASS_IMAGEMAP)
+ *
+ * Copyright (C) 2026 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#define LOG_CATEGORY UCLASS_IMAGEMAP
+
+#include <blk.h>
+#include <dm.h>
+#include <imagemap.h>
+#include <lmb.h>
+#include <mapmem.h>
+#include <memalign.h>
+#include <part.h>
+#include <spl.h>
+#include <asm/cache.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <log.h>
+
+/**
+ * struct imagemap_plat - Platform data set before probe
+ *
+ * @part_start: Partition start LBA
+ * @part_size: Partition size in blocks
+ * @hwpart: Hardware partition to select before each read
+ */
+struct imagemap_plat {
+ lbaint_t part_start;
+ lbaint_t part_size;
+ int hwpart;
+};
+
+/**
+ * struct imagemap_blk - Runtime block-read state (the spl_load_info priv)
+ *
+ * @desc: Block device descriptor
+ * @part_start: Partition start LBA
+ * @part_size: Partition size in blocks
+ * @hwpart: Hardware partition to (re-)select before each read. For a UBI
+ * block device this selects the UBI volume (encoded in
+ * blk_desc->hwpart by the "ubi" partition driver); for normal
+ * media it is the hardware partition (0 for the user area).
+ */
+struct imagemap_blk {
+ struct blk_desc *desc;
+ lbaint_t part_start;
+ lbaint_t part_size;
+ int hwpart;
+};
+
+void *imagemap_lookup(struct udevice *dev, loff_t img_offset, ulong size)
+{
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+ const struct imagemap_region *r;
+
+ alist_for_each(r, &priv->regions) {
+ /*
+ * Check whether [img_offset, img_offset + size) is fully
+ * contained within [r->img_offset, r->img_offset + r->size).
+ *
+ * The three conditions are ordered to avoid unsigned
+ * underflow in the subtraction on the third line:
+ *
+ * 1) img_offset >= r->img_offset
+ * The requested start is at or past the region start.
+ *
+ * 2) img_offset - r->img_offset <= r->size
+ * The offset into the region does not exceed the
+ * region size. This guard is essential: without it,
+ * the subtraction in (3) wraps to a huge value on
+ * LP64 where ulong and loff_t have the same rank
+ * and the arithmetic is performed as unsigned.
+ *
+ * 3) size <= r->size - (img_offset - r->img_offset)
+ * The requested range fits in the remaining space.
+ * Safe because (2) guarantees the subtraction does
+ * not underflow.
+ */
+ if (img_offset >= r->img_offset &&
+ img_offset - r->img_offset <= r->size &&
+ size <= r->size - (img_offset - r->img_offset))
+ return (char *)r->ram + (img_offset - r->img_offset);
+ }
+
+ return NULL;
+}
+
+/**
+ * imagemap_record() - Record a region in the translation table
+ *
+ * If an entry with the same img_offset already exists and the new size
+ * is larger, update the existing entry. Otherwise add a new entry.
+ *
+ * @dev: The imagemap device
+ * @img_offset: Byte offset within the source image
+ * @size: Region size
+ * @ram: RAM pointer where the region was loaded
+ * @lmb_reserved: true if this region was allocated via LMB
+ * Return: pointer to the region entry, or NULL if the table is full
+ */
+static struct imagemap_region *
+imagemap_record(struct udevice *dev, loff_t img_offset, ulong size,
+ void *ram, bool lmb_reserved)
+{
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+ struct imagemap_region *r;
+ struct imagemap_region entry;
+
+ /* Check for an existing entry at the same base that we can extend */
+ alist_for_each(r, &priv->regions) {
+ if (r->img_offset == img_offset) {
+ r->size = size;
+ r->ram = ram;
+ r->lmb_reserved = lmb_reserved;
+ return r;
+ }
+ }
+
+ /* Append new region */
+ entry.img_offset = img_offset;
+ entry.size = size;
+ entry.ram = ram;
+ entry.lmb_reserved = lmb_reserved;
+
+ r = alist_add(&priv->regions, entry);
+ if (!r) {
+ log_err("imagemap: cannot add region (out of memory)\n");
+ return NULL;
+ }
+
+ return r;
+}
+
+/**
+ * imagemap_read_exact() - Read [off, off+size) byte-exact into @dst
+ *
+ * For byte-addressed sources (bl_len == 1) this is a straight
+ * spl_load_region(). For block sources it reads the fully-contained
+ * middle blocks straight into @dst (zero-copy) and bounces only the
+ * partial head/tail block, so the wanted bytes land exactly at @dst
+ * without over-reading the destination buffer.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int imagemap_read_exact(struct spl_load_info *info, loff_t off,
+ ulong size, void *dst)
+{
+ ulong bl_len = spl_get_bl_len(info);
+ ulong overhead = off & (bl_len - 1);
+ u8 *out = dst;
+ loff_t cur = off;
+ ulong left = size;
+
+ if (bl_len == 1)
+ return spl_load_region(info, off, size, dst) < 0 ? -EIO : 0;
+
+ /* Partial head block: bounce and copy the wanted tail of the block */
+ if (overhead) {
+ ALLOC_CACHE_ALIGN_BUFFER(u8, blk, bl_len);
+ ulong chunk = min(left, bl_len - overhead);
+
+ if (info->read(info, cur - overhead, bl_len, blk) < bl_len)
+ return -EIO;
+ memcpy(out, blk + overhead, chunk);
+ out += chunk;
+ cur += chunk;
+ left -= chunk;
+ }
+
+ /* Aligned middle: whole blocks straight into @dst (zero-copy) */
+ if (left >= bl_len) {
+ ulong nbytes = left & ~(bl_len - 1);
+
+ if (info->read(info, cur, nbytes, out) < nbytes)
+ return -EIO;
+ out += nbytes;
+ cur += nbytes;
+ left -= nbytes;
+ }
+
+ /* Partial tail block: bounce and copy the wanted head of the block */
+ if (left) {
+ ALLOC_CACHE_ALIGN_BUFFER(u8, blk, bl_len);
+
+ if (info->read(info, cur, bl_len, blk) < bl_len)
+ return -EIO;
+ memcpy(out, blk, left);
+ }
+
+ return 0;
+}
+
+void *imagemap_map(struct udevice *dev, loff_t img_offset, ulong size)
+{
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+ ulong bl_len = spl_get_bl_len(&priv->info);
+ ulong overhead = img_offset & (bl_len - 1);
+ loff_t base_off = img_offset - overhead;
+ ulong read_size = ALIGN(size + overhead, bl_len);
+ phys_addr_t addr;
+ phys_size_t alloc_size;
+ void *base;
+ int ret;
+ struct imagemap_region *r;
+ void *p;
+
+ /* Return existing mapping if the range is already covered */
+ p = imagemap_lookup(dev, img_offset, size);
+ if (p)
+ return p;
+
+ alloc_size = ALIGN(read_size, ARCH_DMA_MINALIGN);
+
+ /*
+ * Extend a block-aligned region at the same base offset to the
+ * larger size, re-reading the full range from storage.
+ */
+ alist_for_each(r, &priv->regions) {
+ if (r->img_offset == base_off && r->size < read_size) {
+ addr = map_to_sysmem(r->ram);
+
+ /* Free old LMB reservation if we own it */
+ if (r->lmb_reserved)
+ lmb_free(addr,
+ ALIGN(r->size, ARCH_DMA_MINALIGN),
+ LMB_NONE);
+
+ /* Try to re-reserve at the same address with new size */
+ if (lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &addr,
+ alloc_size, LMB_NONE)) {
+ /* In-place extend failed, allocate elsewhere */
+ if (lmb_alloc_mem(LMB_MEM_ALLOC_ANY,
+ ARCH_DMA_MINALIGN,
+ &addr, alloc_size,
+ LMB_NONE)) {
+ log_err("imagemap: LMB alloc failed (0x%lx bytes)\n",
+ (ulong)alloc_size);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+ base = map_sysmem(addr, alloc_size);
+
+ ret = spl_load_region(&priv->info, img_offset, size,
+ base);
+ if (ret < 0) {
+ log_err("imagemap: read failed at offset 0x%llx (size 0x%lx): %d\n",
+ (unsigned long long)img_offset, size, ret);
+ lmb_free(addr, alloc_size, LMB_NONE);
+ return ERR_PTR(ret);
+ }
+ r->size = read_size;
+ r->ram = base;
+ r->lmb_reserved = true;
+
+ return base + overhead;
+ }
+ }
+
+ /* New region — allocate from LMB */
+ if (lmb_alloc_mem(LMB_MEM_ALLOC_ANY, ARCH_DMA_MINALIGN,
+ &addr, alloc_size, LMB_NONE)) {
+ log_err("imagemap: LMB alloc failed (0x%lx bytes)\n",
+ (ulong)alloc_size);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ base = map_sysmem(addr, alloc_size);
+
+ ret = spl_load_region(&priv->info, img_offset, size, base);
+ if (ret < 0) {
+ log_err("imagemap: read failed at offset 0x%llx (size 0x%lx): %d\n",
+ (unsigned long long)img_offset, size, ret);
+ lmb_free(addr, alloc_size, LMB_NONE);
+ return ERR_PTR(ret);
+ }
+
+ if (!imagemap_record(dev, base_off, read_size, base, true)) {
+ lmb_free(addr, alloc_size, LMB_NONE);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return base + overhead;
+}
+
+void *imagemap_map_to(struct udevice *dev, loff_t img_offset,
+ ulong size, void *dst)
+{
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+ void *p;
+ int ret;
+
+ /* If already mapped to this exact destination, return it */
+ p = imagemap_lookup(dev, img_offset, size);
+ if (p && p == dst)
+ return p;
+
+ ret = imagemap_read_exact(&priv->info, img_offset, size, dst);
+ if (ret) {
+ log_err("imagemap: read failed at offset 0x%llx (size 0x%lx): %d\n",
+ (unsigned long long)img_offset, size, ret);
+ return ERR_PTR(ret);
+ }
+
+ if (!imagemap_record(dev, img_offset, size, dst, false))
+ return ERR_PTR(-ENOMEM);
+
+ return dst;
+}
+
+void imagemap_cleanup(struct udevice *dev)
+{
+ struct imagemap_priv *priv;
+ struct imagemap_region *r;
+
+ if (!dev)
+ return;
+
+ priv = dev_get_uclass_priv(dev);
+
+ alist_for_each(r, &priv->regions) {
+ if (r->lmb_reserved)
+ lmb_free(map_to_sysmem(r->ram),
+ ALIGN(r->size, ARCH_DMA_MINALIGN),
+ LMB_NONE);
+ }
+
+ alist_uninit(&priv->regions);
+
+ device_remove(dev, DM_REMOVE_NORMAL);
+ device_unbind(dev);
+}
+
+static int imagemap_post_probe(struct udevice *dev)
+{
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+
+ alist_init_struct(&priv->regions, struct imagemap_region);
+
+ return 0;
+}
+
+UCLASS_DRIVER(imagemap) = {
+ .id = UCLASS_IMAGEMAP,
+ .name = "imagemap",
+ .post_probe = imagemap_post_probe,
+ .per_device_auto = sizeof(struct imagemap_priv),
+};
+
+/*
+ * Block-addressed reader (bl_len == blksz): @off/@size are byte quantities
+ * pre-aligned to the block length by spl_load_region()/imagemap, so this is
+ * a plain whole-sector transfer. Returns bytes read, 0 on failure.
+ */
+static ulong imagemap_blk_reader(struct spl_load_info *info, ulong off,
+ ulong size, void *buf)
+{
+ struct imagemap_blk *bp = info->priv;
+ struct blk_desc *desc = bp->desc;
+ lbaint_t blk_off = off >> desc->log2blksz;
+ lbaint_t count = size >> desc->log2blksz;
+
+ if (blk_off + count > bp->part_size) {
+ log_err("imagemap: read at 0x%lx+0x%lx exceeds partition size\n",
+ off, size);
+ return 0;
+ }
+
+ /*
+ * Re-select our hardware partition / UBI volume in case another
+ * blk access changed it since we were created. ubi_blk provides no
+ * select_hwpart op, so drive blk_desc->hwpart directly as well.
+ */
+ if (desc->hwpart != bp->hwpart) {
+ blk_dselect_hwpart(desc, bp->hwpart);
+ desc->hwpart = bp->hwpart;
+ }
+
+ return blk_dread(desc, bp->part_start + blk_off, count, buf)
+ << desc->log2blksz;
+}
+
+static int imagemap_probe(struct udevice *dev)
+{
+ struct imagemap_plat *plat = dev_get_plat(dev);
+ struct imagemap_blk *bp = dev_get_priv(dev);
+ struct imagemap_priv *priv = dev_get_uclass_priv(dev);
+
+ bp->desc = dev_get_uclass_plat(dev_get_parent(dev));
+ bp->part_start = plat->part_start;
+ bp->part_size = plat->part_size;
+ bp->hwpart = plat->hwpart;
+
+ spl_load_init(&priv->info, imagemap_blk_reader, bp, bp->desc->blksz);
+
+ return 0;
+}
+
+U_BOOT_DRIVER(imagemap) = {
+ .name = "imagemap",
+ .id = UCLASS_IMAGEMAP,
+ .probe = imagemap_probe,
+ .plat_auto = sizeof(struct imagemap_plat),
+ .priv_auto = sizeof(struct imagemap_blk),
+};
+
+int imagemap_create(struct udevice *dev, const char *name,
+ int part, struct udevice **devp)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(dev);
+ struct imagemap_plat *plat;
+ struct disk_partition info;
+ struct udevice *imdev;
+ int ret;
+
+ if (name && *name)
+ ret = part_get_info_by_name(desc, name, &info);
+ else
+ ret = part_get_info(desc, part, &info);
+ if (ret < 0)
+ return ret;
+
+ ret = device_bind_driver(desc->bdev, "imagemap",
+ name && *name ? name : "imagemap", &imdev);
+ if (ret)
+ return ret;
+
+ plat = dev_get_plat(imdev);
+ plat->part_start = info.start;
+ plat->part_size = info.size;
+ /*
+ * part_get_info_by_name() records the target in blk_desc->hwpart for
+ * ubi_blk (the UBI volume id); capture it so reads re-select it.
+ */
+ plat->hwpart = desc->hwpart;
+
+ ret = device_probe(imdev);
+ if (ret) {
+ device_unbind(imdev);
+ return ret;
+ }
+
+ *devp = imdev;
+
+ return 0;
+}
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index fe0aae2720c..7de9312cde7 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -75,6 +75,7 @@ enum uclass_id {
UCLASS_HASH, /* Hash device */
UCLASS_HWSPINLOCK, /* Hardware semaphores */
UCLASS_HOST, /* Sandbox host device */
+ UCLASS_IMAGEMAP, /* On-demand image loading from storage */
UCLASS_I2C, /* I2C bus */
UCLASS_I2C_EEPROM, /* I2C EEPROM device */
UCLASS_I2C_GENERIC, /* Generic I2C device */
diff --git a/include/imagemap.h b/include/imagemap.h
new file mode 100644
index 00000000000..984a2fa8934
--- /dev/null
+++ b/include/imagemap.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * On-demand image loading from storage (UCLASS_IMAGEMAP)
+ *
+ * Copyright (C) 2026 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#ifndef __IMAGEMAP_H
+#define __IMAGEMAP_H
+
+#include <alist.h>
+#include <spl.h>
+#include <linux/err.h>
+#include <linux/types.h>
+
+struct udevice;
+
+/**
+ * struct imagemap_region - One mapped region of the image
+ *
+ * Records the fact that image bytes [img_offset, img_offset + size)
+ * have been loaded into RAM at address @ram.
+ *
+ * @img_offset: Start offset within the source image (bytes)
+ * @size: Region size (bytes)
+ * @ram: RAM pointer where this region was loaded
+ * @lmb_reserved: true if this region was allocated via LMB and should
+ * be freed on cleanup
+ */
+struct imagemap_region {
+ loff_t img_offset;
+ ulong size;
+ void *ram;
+ bool lmb_reserved;
+};
+
+/**
+ * struct imagemap_priv - Per-device uclass data for imagemap
+ *
+ * Managed by the UCLASS_IMAGEMAP uclass via per_device_auto.
+ *
+ * @info: Shared load-to-mem abstraction (the same one SPL uses); the
+ * device's probe points its spl_load_reader at the storage,
+ * and all reads go through spl_load_region().
+ * @regions: Translation table of already-loaded regions, used both as
+ * the reuse cache and as the LMB allocation registry (freed on
+ * cleanup).
+ */
+struct imagemap_priv {
+ struct spl_load_info info;
+ struct alist regions;
+};
+
+/**
+ * imagemap_lookup() - Look up an already-mapped region
+ *
+ * Checks the translation table to see if the requested range
+ * [img_offset, img_offset + size) is fully contained within a
+ * previously loaded region.
+ *
+ * @dev: The imagemap device
+ * @img_offset: Byte offset within the source image
+ * @size: Number of bytes needed
+ * Return: RAM pointer on hit, NULL on miss (does not trigger a read)
+ */
+void *imagemap_lookup(struct udevice *dev, loff_t img_offset, ulong size);
+
+/**
+ * imagemap_map() - Ensure an image region is accessible in RAM
+ *
+ * If the region is already in the translation table, returns the
+ * existing RAM pointer. Otherwise allocates RAM via the LMB allocator,
+ * reads the data from storage, records the mapping, and returns the
+ * new pointer.
+ *
+ * If the requested range starts at the same offset as an existing
+ * region but is larger, the existing region is extended (LMB
+ * reservation adjusted, data re-read).
+ *
+ * @dev: The imagemap device
+ * @img_offset: Byte offset within the source image
+ * @size: Number of bytes needed
+ * Return: RAM pointer on success, ERR_PTR on failure
+ */
+void *imagemap_map(struct udevice *dev, loff_t img_offset, ulong size);
+
+/**
+ * imagemap_map_to() - Load an image region to a specific RAM address
+ *
+ * Like imagemap_map() but reads into a caller-specified address
+ * instead of allocating from the scratch area. Used when the sub-image
+ * has a known load address for a zero-copy path.
+ *
+ * @dev: The imagemap device
+ * @img_offset: Byte offset within the source image
+ * @size: Number of bytes to load
+ * @dst: Destination address in RAM
+ * Return: @dst on success, ERR_PTR on failure
+ */
+void *imagemap_map_to(struct udevice *dev, loff_t img_offset,
+ ulong size, void *dst);
+
+/**
+ * imagemap_cleanup() - Release all resources and unbind the device
+ *
+ * Frees all LMB reservations from the translation table, removes the
+ * driver, and unbinds the device. The device pointer is invalid after
+ * this call.
+ *
+ * Safe to call with a NULL @dev pointer.
+ *
+ * @dev: The imagemap device, or NULL
+ */
+void imagemap_cleanup(struct udevice *dev);
+
+/**
+ * imagemap_create() - Create an imagemap device over a block device
+ *
+ * Resolves a partition on @dev (by name if @name is given, otherwise by
+ * index @part), binds an imagemap device as a child of the block device
+ * and probes it, pointing its reader at that partition.
+ *
+ * MTD partitions and UBI volumes are reached the same way: with
+ * CONFIG_MTD_BLOCK / CONFIG_UBI_BLOCK they are exposed as named
+ * partitions on the mtd_blk / ubi_blk block devices.
+ *
+ * @dev: Block device (UCLASS_BLK)
+ * @name: Partition/volume name, or NULL to select by index
+ * @part: Partition index (used when @name is NULL)
+ * @devp: On success, the new imagemap device
+ * Return: 0 on success, negative errno on failure
+ */
+int imagemap_create(struct udevice *dev, const char *name,
+ int part, struct udevice **devp);
+
+#endif /* __IMAGEMAP_H */
diff --git a/include/spl.h b/include/spl.h
index 5078d7525ab..24df44d23e1 100644
--- a/include/spl.h
+++ b/include/spl.h
@@ -350,10 +350,21 @@ typedef ulong (*spl_load_reader)(struct spl_load_info *load, ulong sector,
* @phase: Image phase to load
* @no_fdt_update: true to update the FDT with any loadables that are loaded
*/
+/*
+ * struct spl_load_info carries a device block length for the SPL block
+ * loaders (CONFIG_SPL_LOAD_BLOCK) and, in full U-Boot, for the imagemap
+ * on-demand loader (CONFIG_IMAGEMAP); imagemap is gated out of the xpl
+ * phases so SPL and TPL carry no new cost.
+ */
+#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || \
+ (IS_ENABLED(CONFIG_IMAGEMAP) && !defined(CONFIG_XPL_BUILD))
+#define SPL_LOAD_INFO_HAS_BL_LEN
+#endif
+
struct spl_load_info {
spl_load_reader read;
void *priv;
-#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK)
+#ifdef SPL_LOAD_INFO_HAS_BL_LEN
u16 bl_len;
#endif
#if CONFIG_IS_ENABLED(BOOTMETH_VBE)
@@ -364,7 +375,7 @@ struct spl_load_info {
static inline int spl_get_bl_len(struct spl_load_info *info)
{
-#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK)
+#ifdef SPL_LOAD_INFO_HAS_BL_LEN
return info->bl_len;
#else
return 1;
@@ -373,7 +384,7 @@ static inline int spl_get_bl_len(struct spl_load_info *info)
static inline void spl_set_bl_len(struct spl_load_info *info, int bl_len)
{
-#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK)
+#ifdef SPL_LOAD_INFO_HAS_BL_LEN
info->bl_len = bl_len;
#else
if (bl_len != 1)
@@ -429,6 +440,36 @@ static inline void spl_load_init(struct spl_load_info *load,
xpl_set_fdt_update(load, true);
}
+/**
+ * spl_load_region() - Read a block-aligned region into a buffer
+ *
+ * Reads @size bytes starting at @offset from the device described by @info
+ * into @dst, rounding the read down/up to the device block length so that
+ * block media are read on their native boundaries. On byte-addressed media
+ * (bl_len == 1) every alignment folds to identity.
+ *
+ * @info: information about the device to read from
+ * @offset: byte offset on the device of the first wanted byte
+ * @size: number of wanted bytes
+ * @dst: buffer to read the block-aligned region into
+ * Return: on success, the number of leading padding bytes in @dst that
+ * precede the wanted data (0 on byte media); a negative error
+ * number on failure.
+ */
+static inline long spl_load_region(struct spl_load_info *info, loff_t offset,
+ ulong size, void *dst)
+{
+ ulong bl_len = spl_get_bl_len(info);
+ ulong overhead = offset & (bl_len - 1);
+ loff_t roff = ALIGN_DOWN(offset, bl_len);
+ ulong rsize = ALIGN(size + overhead, bl_len);
+
+ if (info->read(info, roff, rsize, dst) < rsize)
+ return -EIO;
+
+ return overhead;
+}
+
/*
* We need to know the position of U-Boot in memory so we can jump to it. We
* allow any U-Boot binary to be used (u-boot.bin, u-boot-nodtb.bin,
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load()
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (4 preceding siblings ...)
2026-08-23 19:13 ` [PATCH 5/9] boot: add imagemap on-demand loading from storage Daniel Golle
@ 2026-08-23 19:14 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:14 ` [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends Daniel Golle
` (4 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:14 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Integrate storage-backed image loading transparently into the existing
FIT flow so that all verification, decompression, and copy-to-load-
address code runs unchanged for both RAM-backed and storage-backed
images.
Hook imagemap into fit_image_get_data(): when gd->imagemap is set and
the sub-image uses external data, call imagemap_map() to bring the
payload into RAM instead of returning the unmapped fit + offset
pointer. Every existing caller, including fit_image_verify() called
from fit_image_select(), transparently gets valid data without any
changes to the authentication path.
For uncompressed sub-images with a known load address, pre-populate
the translation table via imagemap_map_to() before verification runs.
This loads data directly to the final RAM destination; when
fit_image_get_data() is later called, the translation table hit
returns the same pointer, achieving zero-copy. Compressed images or
those without a load address fall through to the lazy imagemap_map()
path.
Filesystem sub-images protected by dm-verity are returned early and
stay on storage, as the kernel verifies them at block level.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/bootm.c | 70 +++++++++++++++++++++++++++---
boot/image-fit.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++-
include/bootm.h | 2 +
include/image.h | 2 +
4 files changed, 174 insertions(+), 9 deletions(-)
diff --git a/boot/bootm.c b/boot/bootm.c
index 3bce8586834..6422bdc16f6 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -14,6 +14,7 @@
#include <env.h>
#include <errno.h>
#include <fdt_support.h>
+#include <imagemap.h>
#include <irq_func.h>
#include <lmb.h>
#include <log.h>
@@ -147,7 +148,22 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
/* check image type, for FIT images get FIT kernel node */
*os_data = *os_len = 0;
- buf = map_sysmem(img_addr, 0);
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
+ /*
+ * Storage path: read enough bytes to detect the image
+ * format. genimg_get_kernel_addr_fit() above still
+ * parsed any #config / :subimage suffix so the FIT
+ * selection variables are populated.
+ */
+ buf = imagemap_map(images->imagemap, 0, 64);
+ if (IS_ERR(buf)) {
+ puts("Cannot read image header from storage\n");
+ return PTR_ERR(buf);
+ }
+ img_addr = map_to_sysmem(buf);
+ } else {
+ buf = map_sysmem(img_addr, 0);
+ }
switch (genimg_get_format(buf)) {
#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
case IMAGE_FORMAT_LEGACY:
@@ -193,6 +209,20 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
#endif
#if CONFIG_IS_ENABLED(FIT)
case IMAGE_FORMAT_FIT:
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
+ /*
+ * Extend the mapping to cover the full FIT
+ * FDT structure so all metadata is accessible.
+ */
+ size_t fdt_sz = fdt_totalsize(buf);
+
+ buf = imagemap_map(images->imagemap, 0, fdt_sz);
+ if (IS_ERR(buf)) {
+ puts("Cannot read FIT header from storage\n");
+ return PTR_ERR(buf);
+ }
+ img_addr = map_to_sysmem(buf);
+ }
os_noffset = fit_image_load(images, img_addr,
&fit_uname_kernel, &fit_uname_config,
IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
@@ -1033,11 +1063,21 @@ int bootm_run_states(struct bootm_info *bmi, int states)
* Work through the states and see how far we get. We stop on
* any error.
*/
- if (states & BOOTM_STATE_START)
+ if (states & BOOTM_STATE_START) {
ret = bootm_start();
+ /*
+ * bootm_start() zeroes the global images struct. Restore
+ * the loader pointer so the storage-backed path works.
+ */
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && bmi->imagemap)
+ images->imagemap = bmi->imagemap;
+ }
- if (!ret && (states & BOOTM_STATE_PRE_LOAD))
- ret = bootm_pre_load(bmi->addr_img);
+ if (!ret && (states & BOOTM_STATE_PRE_LOAD)) {
+ /* Pre-load verification is not applicable to storage boot */
+ if (!IS_ENABLED(CONFIG_IMAGEMAP) || !images->imagemap)
+ ret = bootm_pre_load(bmi->addr_img);
+ }
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(bmi->cmd_name, bmi->addr_img);
@@ -1045,8 +1085,11 @@ int bootm_run_states(struct bootm_info *bmi, int states)
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ulong img_addr;
- img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
- : image_load_addr;
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap)
+ img_addr = images->os.start;
+ else
+ img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
+ : image_load_addr;
ret = bootm_find_other(img_addr, bmi->conf_ramdisk,
bmi->conf_fdt);
}
@@ -1145,11 +1188,24 @@ int bootm_run_states(struct bootm_info *bmi, int states)
}
/* Now run the OS! We hope this doesn't return */
- if (!ret && (states & BOOTM_STATE_OS_GO))
+ if (!ret && (states & BOOTM_STATE_OS_GO)) {
+ /* Release storage backend before jumping — no return expected */
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
+ imagemap_cleanup(images->imagemap);
+ images->imagemap = NULL;
+ }
+
ret = boot_selected_os(BOOTM_STATE_OS_GO, bmi, boot_fn);
+ }
/* Deal with any fallout */
err:
+ /* Clean up imagemap on error (not reached on successful boot) */
+ if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
+ imagemap_cleanup(images->imagemap);
+ images->imagemap = NULL;
+ }
+
if (iflag)
enable_interrupts();
diff --git a/boot/image-fit.c b/boot/image-fit.c
index ef90c5abd18..9b05273d14a 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -26,6 +26,7 @@ extern void *aligned_alloc(size_t alignment, size_t size);
#include <env.h>
#include <errno.h>
#include <hexdump.h>
+#include <imagemap.h>
#include <log.h>
#include <mapmem.h>
#include <asm/io.h>
@@ -1140,6 +1141,16 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
return -EINVAL;
}
*data = fit + offset;
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
+ if (images.imagemap) {
+ void *mapped;
+
+ mapped = imagemap_lookup(images.imagemap,
+ offset, len);
+ if (mapped)
+ *data = mapped;
+ }
+#endif
*size = len;
}
} else {
@@ -2140,6 +2151,84 @@ static const char *fit_get_image_type_property(int ph_type)
return "unknown";
}
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
+/**
+ * fit_image_load_storage() - Pre-load a sub-image from on-demand storage
+ *
+ * Brings an external-data sub-image into RAM through the imagemap loader before
+ * fit_image_select() runs verification, so the existing verify/copy path sees a
+ * valid RAM pointer. Uncompressed sub-images with a load address are read
+ * straight to their destination (zero-copy); everything else goes to scratch
+ * RAM. Filesystem sub-images protected by dm-verity are left on storage and
+ * verified by the kernel at block level; @early is set for those so the caller
+ * returns the node without loading the payload.
+ *
+ * @early is set to true when the caller should stop and return @noffset with an
+ * empty payload.
+ *
+ * Return: 0 to continue, negative errno on failure
+ */
+static int fit_image_load_storage(struct bootm_headers *images, const void *fit,
+ int noffset, enum fit_load_op load_op,
+ ulong *datap, ulong *lenp, bool *early)
+{
+ int data_off = 0, data_sz = 0;
+ bool external = false;
+ ulong img_load;
+ u8 img_comp = IH_COMP_NONE;
+ void *mapped;
+
+ if (CONFIG_IS_ENABLED(FIT_VERITY)) {
+ u8 img_type;
+
+ if (!fit_image_get_type(fit, noffset, &img_type) &&
+ img_type == IH_TYPE_FILESYSTEM &&
+ fdt_subnode_offset(fit, noffset, "dm-verity") >= 0) {
+ fit_image_print(fit, noffset, " ");
+ *datap = 0;
+ *lenp = 0;
+ *early = true;
+ return 0;
+ }
+ }
+
+ if (!fit_image_get_data_position(fit, noffset, &data_off)) {
+ external = true;
+ } else if (!fit_image_get_data_offset(fit, noffset, &data_off)) {
+ external = true;
+ data_off += ALIGN(fdt_totalsize(fit), 4);
+ }
+
+ if (!external || fit_image_get_data_size(fit, noffset, &data_sz))
+ return 0;
+
+ if (data_off < 0 || data_sz < 0)
+ return -EINVAL;
+
+ fit_image_get_comp(fit, noffset, &img_comp);
+
+ if (img_comp == IH_COMP_NONE && load_op != FIT_LOAD_IGNORED &&
+ !fit_image_get_load(fit, noffset, &img_load)) {
+ void *dst = map_sysmem(img_load, data_sz);
+
+ mapped = imagemap_map_to(images->imagemap, data_off, data_sz,
+ dst);
+ } else {
+ mapped = imagemap_map(images->imagemap, data_off, data_sz);
+ }
+
+ return IS_ERR(mapped) ? PTR_ERR(mapped) : 0;
+}
+#else
+static inline int fit_image_load_storage(struct bootm_headers *images,
+ const void *fit, int noffset,
+ enum fit_load_op load_op, ulong *datap,
+ ulong *lenp, bool *early)
+{
+ return 0;
+}
+#endif
+
int fit_image_load(struct bootm_headers *images, ulong addr,
const char **fit_unamep, const char **fit_uname_configp,
int arch, int ph_type, int bootstage_id,
@@ -2236,6 +2325,21 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
printf(" Trying '%s' %s subimage\n", fit_uname, prop_name);
+ /*
+ * On-demand storage: pre-load external-data payloads into RAM (or leave
+ * dm-verity filesystems on storage) before fit_image_select() verifies.
+ */
+ if (CONFIG_IS_ENABLED(IMAGEMAP) && !tools_build() && images->imagemap) {
+ bool early = false;
+
+ ret = fit_image_load_storage(images, fit, noffset, load_op,
+ datap, lenp, &early);
+ if (ret)
+ return ret;
+ if (early)
+ return noffset;
+ }
+
ret = fit_image_select(fit, noffset, images->verify);
if (ret) {
bootstage_error(bootstage_id + BOOTSTAGE_SUB_HASH);
@@ -2349,8 +2453,9 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
return -EXDEV;
}
- printf(" Loading %s from 0x%08lx to 0x%08lx\n",
- prop_name, data, load);
+ if (!CONFIG_IS_ENABLED(IMAGEMAP) || data != load)
+ printf(" Loading %s from 0x%08lx to 0x%08lx\n",
+ prop_name, data, load);
} else {
load = data; /* load address specified but set to 0 */
}
diff --git a/include/bootm.h b/include/bootm.h
index f6958be751a..d1aac3d44df 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -40,6 +40,7 @@ struct cmd_tbl;
* boot_get_fdt() for processing, or NULL for none
* @boot_progress: true to show boot progress
* @images: images information
+ * @imagemap: imagemap device for storage-backed boot (NULL for in-memory)
* @cmd_name: command which invoked this operation, e.g. "bootm"
* @argc: Number of arguments to the command (excluding the actual command).
* This is 0 if there are no arguments
@@ -51,6 +52,7 @@ struct bootm_info {
const char *conf_fdt;
bool boot_progress;
struct bootm_headers *images;
+ struct udevice *imagemap;
const char *cmd_name;
int argc;
char *const *argv;
diff --git a/include/image.h b/include/image.h
index 6edcb1995bf..f5baec9a516 100644
--- a/include/image.h
+++ b/include/image.h
@@ -412,6 +412,8 @@ struct bootm_headers {
int verify; /* env_get("verify")[0] != 'n' */
+ struct udevice *imagemap; /* on-demand storage loader, or NULL */
+
#define BOOTM_STATE_START 0x00000001
#define BOOTM_STATE_FINDOS 0x00000002
#define BOOTM_STATE_FINDOTHER 0x00000004
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (5 preceding siblings ...)
2026-08-23 19:14 ` [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load() Daniel Golle
@ 2026-08-23 19:14 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:16 ` [PATCH 8/9] test: boot: add imagemap unit tests Daniel Golle
` (3 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:14 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Enable CONFIG_IMAGEMAP and, so the end-to-end unit tests can exercise
the real block paths, the storage that feeds them: CONFIG_MTD_BLOCK
and CONFIG_SPI_FLASH_MTD (a bare non-NAND MTD reached via mtd_blk) and
CONFIG_CMD_UBI / CONFIG_UBI_BLOCK (a UBI volume reached via ubi_blk).
Give the sandbox SPI-NOR two device-tree partitions for the mtd_blk
test and add a clean sandbox NAND chip (nand@2, no injected errors) so
UBI can format on it for the ubiblock test.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
arch/sandbox/dts/test.dts | 29 +++++++++++++++++++++++++++++
configs/sandbox_defconfig | 6 ++++++
2 files changed, 35 insertions(+)
diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
index bed2a4b65ab..b794a44a4e1 100644
--- a/arch/sandbox/dts/test.dts
+++ b/arch/sandbox/dts/test.dts
@@ -1623,6 +1623,22 @@
compatible = "spansion,m25p16", "jedec,spi-nor";
spi-max-frequency = <40000000>;
sandbox,filename = "spi.bin";
+
+ partitions {
+ compatible = "fixed-partitions";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ partition@0 {
+ label = "nor-fit";
+ reg = <0x0 0x100000>;
+ };
+
+ partition@100000 {
+ label = "nor-spare";
+ reg = <0x100000 0x100000>;
+ };
+ };
};
spi.bin@1 {
reg = <1>;
@@ -2232,6 +2248,19 @@
sandbox,err-count = <3>;
sandbox,err-step-size = <512>;
};
+
+ /* Clean chip (no injected errors) for UBI/ubiblock tests */
+ nand@2 {
+ reg = <2>;
+ nand-ecc-mode = "soft";
+ sandbox,id = [00 e3];
+ sandbox,erasesize = <(8 * 1024)>;
+ sandbox,oobsize = <16>;
+ sandbox,pagesize = <512>;
+ sandbox,pages = <0x2000>;
+ sandbox,err-count = <0>;
+ sandbox,err-step-size = <512>;
+ };
};
graph1 {
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index f65e50590a3..05876cb36ba 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -30,6 +30,7 @@ CONFIG_BOOTMETH_RAUC=y
CONFIG_UPL=y
CONFIG_LEGACY_IMAGE_FORMAT=y
CONFIG_MEASURED_BOOT=y
+CONFIG_IMAGEMAP=y
CONFIG_BOOTSTAGE=y
CONFIG_BOOTSTAGE_REPORT=y
CONFIG_BOOTSTAGE_FDT=y
@@ -399,3 +400,8 @@ CONFIG_UTHREAD=y
CONFIG_UNIT_TEST=y
CONFIG_UT_TIME=y
CONFIG_UT_DM=y
+CONFIG_MTD_BLOCK=y
+CONFIG_SPI_FLASH_MTD=y
+CONFIG_CMD_UBI=y
+CONFIG_MTD_UBI=y
+CONFIG_UBI_BLOCK=y
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 8/9] test: boot: add imagemap unit tests
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (6 preceding siblings ...)
2026-08-23 19:14 ` [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends Daniel Golle
@ 2026-08-23 19:16 ` Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:16 ` [PATCH 9/9] doc: imagemap: document on-demand loading framework Daniel Golle
` (2 subsequent siblings)
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:16 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Add a test suite exercising imagemap:
- the translation-table core (map, lookup, map_to, cleanup, region
growth, the block-aligned read path, and edge cases) using a mock
RAM-backed reader, so most tests need no real storage;
- a NOR read through mtd_blk: write a known pattern into a
device-tree partition of the sandbox SPI-NOR and read it back
through imagemap, exercising imagemap -> spl_load_region ->
blk_dread -> mtd_blk_read -> mtd_read for real;
- a UBI-volume read through ubiblock: attach UBI on the sandbox NAND,
create a volume, write a pattern and read it back through imagemap
on the ubi_blk device.
Registered under ut_boot_imagemap in cmd_ut.c.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 1 +
test/boot/Makefile | 2 +
test/boot/imagemap.c | 645 +++++++++++++++++++++++++++++++++++++++++++
test/cmd_ut.c | 2 +
4 files changed, 650 insertions(+)
create mode 100644 test/boot/imagemap.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 48ee01e033d..e8f0e94597f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1192,6 +1192,7 @@ L: openwrt-devel@lists.openwrt.org
S: Maintained
F: boot/imagemap.c
F: include/imagemap.h
+F: test/boot/imagemap.c
INTERCONNECT
M: Neil Armstrong <neil.armstrong@linaro.org>
diff --git a/test/boot/Makefile b/test/boot/Makefile
index 59a87028704..8dbecabde79 100644
--- a/test/boot/Makefile
+++ b/test/boot/Makefile
@@ -27,3 +27,5 @@ endif
obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o
obj-$(CONFIG_UPL) += upl.o
+
+obj-$(CONFIG_IMAGEMAP) += imagemap.o
diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
new file mode 100644
index 00000000000..540a55676a8
--- /dev/null
+++ b/test/boot/imagemap.c
@@ -0,0 +1,645 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for imagemap framework (UCLASS_IMAGEMAP)
+ *
+ * Copyright (C) 2026 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#include <dm.h>
+#include <blk.h>
+#include <imagemap.h>
+#include <mtd.h>
+#include <part.h>
+#include <ubi_uboot.h>
+#include <linux/err.h>
+#include <linux/mtd/mtd.h>
+#include <lmb.h>
+#include <mapmem.h>
+#include <malloc.h>
+#include <asm/cache.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/root.h>
+#include <dm/uclass-internal.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+#define IMAGEMAP_TEST(_name, _flags) \
+ UNIT_TEST(_name, _flags, imagemap)
+
+/* Synthetic image size used throughout the tests */
+#define IMAGE_SIZE 4096
+
+/**
+ * struct mock_priv - private data for the mock storage backend
+ *
+ * @image: pointer to synthetic image data in RAM
+ * @image_size: size of the synthetic image
+ * @read_count: number of times .read() was called
+ * @last_off: offset from the most recent .read() call
+ * @last_size: size from the most recent .read() call
+ */
+struct mock_priv {
+ const void *image;
+ size_t image_size;
+ int read_count;
+ loff_t last_off;
+ ulong last_size;
+};
+
+static ulong mock_reader(struct spl_load_info *info, ulong sector,
+ ulong count, void *buf)
+{
+ struct mock_priv *p = info->priv;
+
+ if (sector + count > p->image_size)
+ return 0;
+
+ memcpy(buf, (const char *)p->image + sector, count);
+ p->read_count++;
+ p->last_off = sector;
+ p->last_size = count;
+
+ return count;
+}
+
+U_BOOT_DRIVER(imagemap_mock) = {
+ .name = "imagemap_mock",
+ .id = UCLASS_IMAGEMAP,
+ .priv_auto = sizeof(struct mock_priv),
+};
+
+/**
+ * create_mock_loader() - create a mock imagemap device for testing
+ *
+ * @image: synthetic image buffer
+ * @image_size: size of @image
+ * @devp: on success, the new imagemap device
+ * Return: 0 on success, negative errno on failure
+ */
+static int create_mock_loader_bl(const void *image, size_t image_size,
+ uint bl_len, struct udevice **devp)
+{
+ struct udevice *dev;
+ struct imagemap_priv *im;
+ struct mock_priv *priv;
+ int ret;
+
+ ret = device_bind_driver(dm_root(), "imagemap_mock",
+ "imagemap-test", &dev);
+ if (ret)
+ return ret;
+
+ ret = device_probe(dev);
+ if (ret) {
+ device_unbind(dev);
+ return ret;
+ }
+
+ priv = dev_get_priv(dev);
+ priv->image = image;
+ priv->image_size = image_size;
+ priv->read_count = 0;
+
+ /* Point the imagemap's shared load-to-mem reader at the mock image */
+ im = dev_get_uclass_priv(dev);
+ spl_load_init(&im->info, mock_reader, priv, bl_len);
+
+ *devp = dev;
+
+ return 0;
+}
+
+static int create_mock_loader(const void *image, size_t image_size,
+ struct udevice **devp)
+{
+ return create_mock_loader_bl(image, image_size, 1, devp);
+}
+
+/**
+ * get_mock_priv() - get mock private data from an imagemap device
+ */
+static struct mock_priv *get_mock_priv(struct udevice *dev)
+{
+ return dev_get_priv(dev);
+}
+
+/* Test: map() allocates, reads and records a region */
+static int imagemap_test_map_basic(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct imagemap_priv *priv;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ void *p;
+
+ /* Fill image with a recognisable pattern */
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+ priv = dev_get_uclass_priv(dev);
+
+ /* Map a 64-byte region at offset 0 */
+ p = imagemap_map(dev, 0, 64);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_mem(image, p, 64);
+ ut_asserteq(1, mock->read_count);
+ ut_asserteq(1, priv->regions.count);
+ ut_asserteq(0, (int)alist_get(&priv->regions, 0, struct imagemap_region)->img_offset);
+ ut_asserteq(64, (int)alist_get(&priv->regions, 0, struct imagemap_region)->size);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_basic, 0);
+
+/* Test: map() returns cached pointer for already-mapped range */
+static int imagemap_test_map_cached(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ void *p1, *p2;
+
+ memset(image, 0xaa, IMAGE_SIZE);
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+
+ p1 = imagemap_map(dev, 0, 128);
+ ut_assert(!IS_ERR(p1));
+ ut_asserteq(1, mock->read_count);
+
+ /* Same range — should return same pointer, no new read */
+ p2 = imagemap_map(dev, 0, 128);
+ ut_asserteq_ptr(p1, p2);
+ ut_asserteq(1, mock->read_count);
+
+ /* Subset of the already-mapped range — still cached */
+ p2 = imagemap_map(dev, 0, 64);
+ ut_asserteq_ptr(p1, p2);
+ ut_asserteq(1, mock->read_count);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_cached, 0);
+
+/* Test: map() returns correct offset within a larger region */
+static int imagemap_test_map_offset(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ void *p1, *p2;
+
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+
+ /* Map a 256-byte region starting at offset 0 */
+ p1 = imagemap_map(dev, 0, 256);
+ ut_assert(!IS_ERR(p1));
+
+ /* Request a sub-range within the previously mapped region */
+ p2 = imagemap_map(dev, 64, 64);
+ ut_assert(!IS_ERR(p2));
+ /* p2 should point 64 bytes into p1 */
+ ut_asserteq_ptr((char *)p1 + 64, p2);
+ ut_asserteq_mem(image + 64, p2, 64);
+ /* Only one read should have occurred */
+ ut_asserteq(1, mock->read_count);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_offset, 0);
+
+/* Test: map() re-reads when extending a region to a larger size */
+static int imagemap_test_map_extend(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct imagemap_priv *priv;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ void *p1, *p2;
+
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+ priv = dev_get_uclass_priv(dev);
+
+ /* Initial small mapping */
+ p1 = imagemap_map(dev, 0, 64);
+ ut_assert(!IS_ERR(p1));
+ ut_asserteq(1, mock->read_count);
+
+ /* Request larger range at same base — should re-read (extend) */
+ p2 = imagemap_map(dev, 0, 256);
+ ut_assert(!IS_ERR(p2));
+ ut_asserteq(2, mock->read_count);
+ ut_asserteq_mem(image, p2, 256);
+
+ /* Region count should still be 1 (updated, not added) */
+ ut_asserteq(1, priv->regions.count);
+ ut_asserteq(256, (int)alist_get(&priv->regions, 0, struct imagemap_region)->size);
+
+ /*
+ * Map a new region after the extend — it must not overlap the
+ * extended first region. This is the exact pattern that bit us
+ * on real hardware: FIT header at offset 0 extended from 64 to
+ * 4096, then kernel payload at offset 4096 was allocated at
+ * an address that overlapped the header, clobbering it.
+ * With LMB allocation this cannot happen because each allocation
+ * reserves distinct memory.
+ */
+ {
+ void *p3 = imagemap_map(dev, 256, 128);
+ ulong p2_end = map_to_sysmem(p2) + 256;
+
+ ut_assert(!IS_ERR(p3));
+ ut_asserteq(2, priv->regions.count);
+ /* New region must not overlap the extended first region */
+ ut_assert(map_to_sysmem(p3) >= p2_end ||
+ map_to_sysmem(p3) + 128 <= map_to_sysmem(p2));
+ ut_asserteq_mem(image + 256, p3, 128);
+ }
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_extend, 0);
+
+/* Test: map_to() reads to a specified address and records it */
+static int imagemap_test_map_to(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct imagemap_priv *priv;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ u8 dst[256];
+ void *p;
+
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+ priv = dev_get_uclass_priv(dev);
+
+ p = imagemap_map_to(dev, 128, 256, dst);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_ptr(dst, p);
+ ut_asserteq_mem(image + 128, dst, 256);
+ ut_asserteq(1, mock->read_count);
+ ut_asserteq(1, priv->regions.count);
+ ut_asserteq(128, (int)alist_get(&priv->regions, 0, struct imagemap_region)->img_offset);
+ ut_asserteq(256, (int)alist_get(&priv->regions, 0, struct imagemap_region)->size);
+ ut_asserteq_ptr(dst, alist_get(&priv->regions, 0, struct imagemap_region)->ram);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_to, 0);
+
+/* Test: lookup() returns NULL for unmapped ranges */
+static int imagemap_test_lookup_miss(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 image[IMAGE_SIZE];
+ void *p;
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+
+ /* Nothing mapped yet — should return NULL */
+ p = imagemap_lookup(dev, 0, 64);
+ ut_assertnull(p);
+
+ /* Map a region at offset 0 */
+ ut_assert(!IS_ERR(imagemap_map(dev, 0, 64)));
+
+ /* Lookup within the mapped region — should succeed */
+ p = imagemap_lookup(dev, 0, 32);
+ ut_assertnonnull(p);
+
+ /* Lookup at a different offset — should miss */
+ p = imagemap_lookup(dev, 128, 32);
+ ut_assertnull(p);
+
+ /* Lookup extending beyond the mapped region — should miss */
+ p = imagemap_lookup(dev, 0, 128);
+ ut_assertnull(p);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_lookup_miss, 0);
+
+/* Test: LMB reservations are made for map() allocations */
+static int imagemap_test_lmb_reserve(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 image[IMAGE_SIZE];
+ void *p1, *p2;
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+
+ /* Map 100 bytes — should be LMB reserved */
+ p1 = imagemap_map(dev, 0, 100);
+ ut_assert(!IS_ERR(p1));
+ ut_assert(lmb_is_reserved_flags(map_to_sysmem(p1), LMB_NONE));
+
+ /* Map another 200 bytes at a different offset */
+ p2 = imagemap_map(dev, 200, 200);
+ ut_assert(!IS_ERR(p2));
+ ut_assert(lmb_is_reserved_flags(map_to_sysmem(p2), LMB_NONE));
+
+ /* Regions must not overlap */
+ ut_assert(map_to_sysmem(p2) >= map_to_sysmem(p1) + 100 ||
+ map_to_sysmem(p1) >= map_to_sysmem(p2) + 200);
+
+ /* Cleanup frees the LMB reservations */
+ imagemap_cleanup(dev);
+ ut_assert(!lmb_is_reserved_flags(map_to_sysmem(p1), LMB_NONE));
+ ut_assert(!lmb_is_reserved_flags(map_to_sysmem(p2), LMB_NONE));
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_lmb_reserve, 0);
+
+/* Test: cleanup() removes the device */
+static int imagemap_test_cleanup(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 image[IMAGE_SIZE];
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+
+ /* Map something so regions are not empty */
+ ut_assert(!IS_ERR(imagemap_map(dev, 0, 64)));
+
+ /* Cleanup should remove and unbind the device */
+ imagemap_cleanup(dev);
+
+ /* NULL cleanup should be safe */
+ imagemap_cleanup(NULL);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_cleanup, 0);
+
+/* Test: map() with multiple disjoint regions */
+static int imagemap_test_multi_region(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct imagemap_priv *priv;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ void *p1, *p2, *p3;
+
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+ mock = get_mock_priv(dev);
+ priv = dev_get_uclass_priv(dev);
+
+ p1 = imagemap_map(dev, 0, 64);
+ ut_assert(!IS_ERR(p1));
+ p2 = imagemap_map(dev, 512, 128);
+ ut_assert(!IS_ERR(p2));
+ p3 = imagemap_map(dev, 1024, 256);
+ ut_assert(!IS_ERR(p3));
+
+ ut_asserteq(3, priv->regions.count);
+ ut_asserteq(3, mock->read_count);
+
+ /* Verify data in each region */
+ ut_asserteq_mem(image, p1, 64);
+ ut_asserteq_mem(image + 512, p2, 128);
+ ut_asserteq_mem(image + 1024, p3, 256);
+
+ /* Lookup each region */
+ ut_asserteq_ptr(p1, imagemap_lookup(dev, 0, 64));
+ ut_asserteq_ptr(p2, imagemap_lookup(dev, 512, 128));
+ ut_asserteq_ptr(p3, imagemap_lookup(dev, 1024, 256));
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_multi_region, 0);
+
+/* Test: read beyond image size returns error */
+static int imagemap_test_read_oob(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 image[IMAGE_SIZE];
+ void *p;
+
+ ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
+
+ /* Attempt to map beyond the end of the image */
+ p = imagemap_map(dev, IMAGE_SIZE - 32, 64);
+ ut_assert(IS_ERR(p));
+
+ /* map_to should also fail */
+ u8 dst[64];
+
+ p = imagemap_map_to(dev, IMAGE_SIZE - 32, 64, dst);
+ ut_assert(IS_ERR(p));
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_read_oob, 0);
+
+/*
+ * Test: block-addressed backend (bl_len > 1) still returns/places the
+ * wanted bytes byte-exact for unaligned offsets and multi-block ranges.
+ */
+static int imagemap_test_blocklen(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 image[IMAGE_SIZE];
+ u8 dst[1024];
+ void *p;
+
+ for (int i = 0; i < IMAGE_SIZE; i++)
+ image[i] = (u8)(i & 0xff);
+
+ ut_assertok(create_mock_loader_bl(image, IMAGE_SIZE, 512, &dev));
+
+ /* map() at an unaligned offset returns byte-exact data */
+ p = imagemap_map(dev, 100, 200);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_mem(image + 100, p, 200);
+
+ /* map_to() places the payload byte-exact at dst (partial head only) */
+ p = imagemap_map_to(dev, 100, 200, dst);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_ptr(dst, p);
+ ut_asserteq_mem(image + 100, dst, 200);
+
+ /* map_to() spanning partial head + aligned middle + partial tail */
+ p = imagemap_map_to(dev, 500, 600, dst);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_ptr(dst, p);
+ ut_asserteq_mem(image + 500, dst, 600);
+
+ imagemap_cleanup(dev);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_blocklen, 0);
+
+#if CONFIG_IS_ENABLED(MTD_BLOCK)
+/*
+ * End-to-end: read a known pattern back from a bare (non-NAND) MTD
+ * partition through imagemap on the mtd_blk block device. Exercises the
+ * real path imagemap -> spl_load_region -> blk_dread -> mtd_blk_read ->
+ * mtd_read on the sandbox SPI-NOR (no mock).
+ */
+static int imagemap_test_mtd_blk(struct unit_test_state *uts)
+{
+ const ulong blks = 4, len = blks * 512;
+ struct udevice *blk = NULL, *dev, *imdev;
+ struct blk_desc *desc = NULL;
+ struct disk_partition info;
+ void *mapped;
+ u8 *wbuf;
+ int i;
+
+ /*
+ * Probe the SPI-NOR (add_mtd_device() then binds its mtd_blk) and
+ * parse its device-tree partitions.
+ */
+ mtd_probe_devices();
+
+ /* Find the bound mtd_blk (a PART_TYPE_MTD block device) */
+ for (uclass_find_first_device(UCLASS_BLK, &dev); dev;
+ uclass_find_next_device(&dev)) {
+ desc = dev_get_uclass_plat(dev);
+ if (desc->part_type == PART_TYPE_MTD) {
+ blk = dev;
+ break;
+ }
+ }
+ ut_assertnonnull(blk);
+ ut_assertok(device_probe(blk));
+ desc = dev_get_uclass_plat(blk);
+
+ /* Write a known pattern into the "nor-fit" partition */
+ ut_assert(part_get_info_by_name(desc, "nor-fit", &info) >= 1);
+ wbuf = malloc(len);
+ ut_assertnonnull(wbuf);
+ for (i = 0; i < len; i++)
+ wbuf[i] = (u8)(i * 5 + 1);
+ ut_asserteq(blks, blk_dwrite(desc, info.start, blks, wbuf));
+
+ /* Read it back through imagemap on the mtd_blk device */
+ ut_assertok(imagemap_create(blk, "nor-fit", 0, &imdev));
+ mapped = imagemap_map(imdev, 0, len);
+ ut_assert(!IS_ERR(mapped));
+ ut_asserteq_mem(wbuf, mapped, len);
+
+ imagemap_cleanup(imdev);
+ free(wbuf);
+
+ return 0;
+}
+IMAGEMAP_TEST(imagemap_test_mtd_blk, 0);
+#endif /* MTD_BLOCK */
+
+#if CONFIG_IS_ENABLED(MTD_UBI) && CONFIG_IS_ENABLED(UBI_BLOCK)
+/*
+ * End-to-end: attach UBI on the sandbox NAND, create a volume, write a
+ * known pattern and read it back through imagemap on the ubi_blk block
+ * device. Exercises imagemap -> spl_load_region -> blk_dread ->
+ * ubi_bread -> ubi_volume_read for real (no mock).
+ */
+static int imagemap_test_ubiblock(struct unit_test_state *uts)
+{
+ const ulong len = 4096;
+ struct udevice *blk = NULL, *dev, *imdev;
+ struct erase_info ei;
+ struct mtd_info *mtd;
+ void *mapped;
+ u8 *wbuf;
+ u64 off;
+ int i;
+
+ mtd_probe_devices();
+ mtd = get_mtd_device_nm("nand2");
+ ut_assert(!IS_ERR_OR_NULL(mtd));
+
+ /* Erase so UBI can format the (empty) clean chip */
+ for (off = 0; off < mtd->size; off += mtd->erasesize) {
+ if (mtd_block_isbad(mtd, off))
+ continue;
+ memset(&ei, 0, sizeof(ei));
+ ei.mtd = mtd;
+ ei.addr = off;
+ ei.len = mtd->erasesize;
+ ut_assertok(mtd_erase(mtd, &ei));
+ }
+ put_mtd_device(mtd);
+
+ /* Attach UBI by name; this binds the ubi_blk block device */
+ ut_assertok(ubi_part("nand2", NULL));
+ ut_assertok(ubi_create_vol("vol0", 0x10000, true, UBI_VOL_NUM_AUTO,
+ false));
+
+ wbuf = malloc(len);
+ ut_assertnonnull(wbuf);
+ for (i = 0; i < len; i++)
+ wbuf[i] = (u8)(i * 7 + 3);
+ ut_assertok(ubi_volume_write("vol0", wbuf, 0, len));
+
+ /* Find the ubi_blk block device */
+ for (uclass_find_first_device(UCLASS_BLK, &dev); dev;
+ uclass_find_next_device(&dev)) {
+ if (dev->driver == DM_DRIVER_GET(ubi_blk)) {
+ blk = dev;
+ break;
+ }
+ }
+ ut_assertnonnull(blk);
+ ut_assertok(device_probe(blk));
+
+ /* Read the volume back through imagemap and compare */
+ ut_assertok(imagemap_create(blk, "vol0", 0, &imdev));
+ mapped = imagemap_map(imdev, 0, len);
+ ut_assert(!IS_ERR(mapped));
+ ut_asserteq_mem(wbuf, mapped, len);
+
+ imagemap_cleanup(imdev);
+ free(wbuf);
+
+ return 0;
+}
+IMAGEMAP_TEST(imagemap_test_ubiblock, 0);
+#endif /* MTD_UBI && UBI_BLOCK */
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index 4328670d0d6..17610cdc2af 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -73,6 +73,7 @@ SUITE_DECL(optee);
SUITE_DECL(pci_mps);
SUITE_DECL(seama);
SUITE_DECL(setexpr);
+SUITE_DECL(imagemap);
SUITE_DECL(upl);
static struct suite suites[] = {
@@ -102,6 +103,7 @@ static struct suite suites[] = {
SUITE(pci_mps, "PCI Express Maximum Payload Size"),
SUITE(seama, "seama command parameters loading and decoding"),
SUITE(setexpr, "setexpr command"),
+ SUITE(imagemap, "imagemap on-demand storage loading"),
SUITE(upl, "Universal payload support"),
};
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH 9/9] doc: imagemap: document on-demand loading framework
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (7 preceding siblings ...)
2026-08-23 19:16 ` [PATCH 8/9] test: boot: add imagemap unit tests Daniel Golle
@ 2026-08-23 19:16 ` Daniel Golle
2026-09-28 15:17 ` Simon Glass
2026-08-24 22:59 ` [PATCH 0/9] boot: add imagemap on-demand image loading from storage Tom Rini
2026-09-28 15:13 ` [0/9] " Simon Glass
10 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-08-23 19:16 UTC (permalink / raw)
To: Tom Rini, Simon Glass, Daniel Golle, Dario Binacchi,
Michael Trimarchi, Frieder Schrempf, Quentin Schulz, Marek Vasut,
Lucien.Jheng, Casey Connolly, Michal Simek, Johan Jonker,
Aristo Chen, Nora Schiffer, Rasmus Villemoes, Weijie Gao,
Peng Fan, Vincent Jardin, Neil Armstrong, Randolph Sapp,
Miquel Raynal, Richard Genoud, u-boot, openwrt-devel
Describe the imagemap framework architecture: the translation table
that tracks mapped regions and its phase-determined caching, reading
from a block-device partition (including MTD via mtdblock and UBI via
ubiblock), the integration with bootm for on-demand FIT sub-image
loading, and the SPL build.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 1 +
doc/develop/imagemap.rst | 100 +++++++++++++++++++++++++++++++++++++++
doc/develop/index.rst | 1 +
3 files changed, 102 insertions(+)
create mode 100644 doc/develop/imagemap.rst
diff --git a/MAINTAINERS b/MAINTAINERS
index e8f0e94597f..f874443d87c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1191,6 +1191,7 @@ M: Daniel Golle <daniel@makrotopia.org>
L: openwrt-devel@lists.openwrt.org
S: Maintained
F: boot/imagemap.c
+F: doc/develop/imagemap.rst
F: include/imagemap.h
F: test/boot/imagemap.c
diff --git a/doc/develop/imagemap.rst b/doc/develop/imagemap.rst
new file mode 100644
index 00000000000..7e461c6c955
--- /dev/null
+++ b/doc/develop/imagemap.rst
@@ -0,0 +1,100 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+On-Demand Image Loading from Storage (imagemap)
+================================================
+
+This document describes the ``imagemap`` framework that enables
+loading FIT images directly from storage devices without first
+copying the entire image into RAM.
+
+Architecture Overview
+---------------------
+
+The framework is built around the ``UCLASS_IMAGEMAP`` device class
+(``include/imagemap.h``), which provides:
+
+``imagemap_create(dev, name, part)``
+ Create an imagemap device over a partition of block device *dev*,
+ selected by *name* or by index *part*.
+
+``imagemap_map(dev, offset, size)``
+ Return a pointer to a buffer holding the requested region.
+ Consecutive calls that fall within the same region reuse the
+ existing mapping, so sub-image header look-ups are essentially
+ free.
+
+``imagemap_map_to(dev, offset, size, dst_addr)``
+ Like ``imagemap_map()`` but place the data at a specific physical
+ address (``dst_addr``). This is used by ``fit_image_load()`` to
+ stream sub-images directly into their final load addresses.
+
+Translation Table
+~~~~~~~~~~~~~~~~~
+
+Every ``imagemap`` device maintains an internal translation
+table that records which byte ranges of the storage image have been
+read into memory and where they reside. When ``imagemap_map()`` is
+called:
+
+1. The table is checked for an existing mapping that covers the
+ requested range.
+2. If found, the existing buffer pointer (adjusted for the offset
+ within the region) is returned immediately.
+3. If not found, a buffer is allocated via the LMB allocator, the
+ data is read from storage, and a new entry is added to the table.
+
+Reuse of already-loaded regions (steps 1 and 2) is what lets a header
+probe, signature verification and the final load of the same range share
+a single read; the FIT flow relies on it to hand already-read data back
+through ``fit_image_get_data()``. The table also serves as the registry
+of LMB allocations that ``imagemap_cleanup()`` frees.
+
+``imagemap_cleanup()`` frees all allocated buffers and resets
+the table.
+
+Storage Access
+--------------
+
+``imagemap_create()`` reads from a partition on a block device (MMC,
+SCSI, NVMe, …), selected by name or by index. Reads go through the
+same ``struct spl_load_info`` load-to-mem abstraction that SPL uses, so
+a byte range is fetched with native sector alignment via
+``spl_load_region()``.
+
+MTD partitions and UBI volumes are read the same way, by way of the
+existing block layers that expose them:
+
+- with ``CONFIG_MTD_BLOCK`` a non-NAND MTD (parallel NOR, SPI-NOR, ...)
+ is automatically given a ``mtd_blk`` block device, and each of its
+ partitions appears as a partition on it;
+- with ``CONFIG_UBI_BLOCK`` a UBI volume appears as a partition on the
+ ``ubi_blk`` block device (the block descriptor's ``hwpart`` selects
+ the volume).
+
+NAND bad-block handling and wear-levelling therefore stay with UBI, as
+they do for the rest of U-Boot; on NOR a partition is read linearly and
+end-to-end integrity is provided by the FIT hash or signature, so no
+MTD-specific read path is needed in imagemap.
+
+Boot Integration
+----------------
+
+The ``imagemap`` framework integrates with the boot pipeline through
+``bootm_run_states()`` in ``boot/bootm.c``:
+
+1. The imagemap device pointer is stored in ``struct bootm_info``
+ and propagated to the global ``images`` structure.
+
+2. ``boot_get_kernel()`` uses ``imagemap_map()`` to read the image
+ header and FIT metadata without loading the full image into RAM.
+
+3. ``fit_image_load()`` uses the on-demand storage path via
+ ``images->imagemap`` to stream individual sub-images directly to
+ their final load addresses.
+
+4. Before jumping to the OS, ``imagemap_cleanup()`` releases all
+ allocated resources.
+
+The ``bootmeth_openwrt`` boot method is the primary consumer of this
+framework, using it to boot OpenWrt-style FIT firmware images stored
+directly on raw storage.
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index 0b757e8601e..3b4cc2e28a7 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -31,6 +31,7 @@ Implementation
directories
bloblist
+ imagemap
bootstd/index
ci_testing
commands
--
2.55.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH 5/9] boot: add imagemap on-demand loading from storage
2026-08-23 19:13 ` [PATCH 5/9] boot: add imagemap on-demand loading from storage Daniel Golle
@ 2026-08-24 22:55 ` Tom Rini
2026-09-28 15:12 ` Simon Glass
1 sibling, 0 replies; 23+ messages in thread
From: Tom Rini @ 2026-08-24 22:55 UTC (permalink / raw)
To: Daniel Golle
Cc: Simon Glass, Dario Binacchi, Michael Trimarchi, Frieder Schrempf,
Quentin Schulz, Marek Vasut, Lucien.Jheng, Casey Connolly,
Michal Simek, Johan Jonker, Aristo Chen, Nora Schiffer,
Rasmus Villemoes, Weijie Gao, Peng Fan, Vincent Jardin,
Neil Armstrong, Randolph Sapp, Miquel Raynal, Richard Genoud,
u-boot, openwrt-devel
[-- Attachment #1: Type: text/plain, Size: 2935 bytes --]
On Sun, Aug 23, 2026 at 08:13:55PM +0100, Daniel Golle wrote:
> Introduce imagemap, a small layer that reads image data from a storage
> device on demand and keeps a translation table of the byte ranges
> already loaded into RAM, instead of copying the whole image up front.
>
> An imagemap device (UCLASS_IMAGEMAP) is created over a partition of a
> block device with imagemap_create(); imagemap_map() then returns a RAM
> pointer for any byte range, reading it in on first access and reusing
> it on later accesses -- so a header probe, verification and the final
> load of the same range share a single read. Block devices cover more
> than raw disks: an MTD partition is reached through mtdblock and a UBI
> volume through ubiblock, each exposed as a named block-device
> partition, so imagemap needs no storage-specific code of its own.
>
> The read path is built on the SPL struct spl_load_info abstraction and
> a new spl_load_region() helper (added to spl.h): the block reader
> reports the device block length and spl_load_region() performs the
> native sector alignment. imagemap_map_to() keeps a payload byte-exact
> at a caller's load address, reading the aligned middle straight to the
> destination and bouncing only the partial head/tail block.
>
> Regions that are not placed at a caller-supplied address are allocated
> through the LMB allocator and released on cleanup; the translation
> table doubles as the registry of those allocations. imagemap is gated
> to the full-U-Boot phase, so SPL and TPL carry no new cost.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
> ---
[snip]
> diff --git a/include/spl.h b/include/spl.h
> index 5078d7525ab..24df44d23e1 100644
> --- a/include/spl.h
> +++ b/include/spl.h
> @@ -350,10 +350,21 @@ typedef ulong (*spl_load_reader)(struct spl_load_info *load, ulong sector,
> * @phase: Image phase to load
> * @no_fdt_update: true to update the FDT with any loadables that are loaded
> */
> +/*
> + * struct spl_load_info carries a device block length for the SPL block
> + * loaders (CONFIG_SPL_LOAD_BLOCK) and, in full U-Boot, for the imagemap
> + * on-demand loader (CONFIG_IMAGEMAP);
This part should be made clear in updates to the existing comment block.
> imagemap is gated out of the xpl
> + * phases so SPL and TPL carry no new cost.
This doesn't need to be noted I think because..
> + */
> +#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || \
> + (IS_ENABLED(CONFIG_IMAGEMAP) && !defined(CONFIG_XPL_BUILD))
> +#define SPL_LOAD_INFO_HAS_BL_LEN
> +#endif
> +
> struct spl_load_info {
> spl_load_reader read;
> void *priv;
> -#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK)
> +#ifdef SPL_LOAD_INFO_HAS_BL_LEN
Why not just:
#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || CONFIG_IS_ENABLED(IMAGEMAP)
? Then for when someone says "Ah, I want to do all of this right in SPL"
there's less changes.
--
Tom
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 0/9] boot: add imagemap on-demand image loading from storage
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (8 preceding siblings ...)
2026-08-23 19:16 ` [PATCH 9/9] doc: imagemap: document on-demand loading framework Daniel Golle
@ 2026-08-24 22:59 ` Tom Rini
2026-09-28 15:13 ` [0/9] " Simon Glass
10 siblings, 0 replies; 23+ messages in thread
From: Tom Rini @ 2026-08-24 22:59 UTC (permalink / raw)
To: Daniel Golle
Cc: Simon Glass, Dario Binacchi, Michael Trimarchi, Frieder Schrempf,
Quentin Schulz, Marek Vasut, Lucien.Jheng, Casey Connolly,
Michal Simek, Johan Jonker, Aristo Chen, Nora Schiffer,
Rasmus Villemoes, Weijie Gao, Peng Fan, Vincent Jardin,
Neil Armstrong, Randolph Sapp, Miquel Raynal, Richard Genoud,
u-boot, openwrt-devel
[-- Attachment #1: Type: text/plain, Size: 1076 bytes --]
On Sun, Aug 23, 2026 at 08:12:43PM +0100, Daniel Golle wrote:
> This series adds "imagemap", a small layer for reading FIT images
> directly from storage on demand instead of copying the whole image
> into RAM first. It keeps a translation table of the byte ranges
> already loaded, so a header probe, signature verification and the
> final load can reuse a single read, and it hooks transparently into
> fit_image_load() so all existing verification and decompression paths
> run unchanged for both RAM- and storage-backed images.
As I was looking at the patches, I saw at least one checkpatch.pl
alignment place to fix, so please cover those in v2.
Conceptually, I'm OK with this. I need to throw the series at a world
build before/after and see what the fallout is on not-enabled platforms.
I'm assuming that once this gets where I can throw Coverity at it, it
will complain about a few things too.
Before posting v2, please throw this though CI too, I'm pretty sure I
saw a documentation style problem that would blow up there. Thanks!
--
Tom
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters
2026-08-23 19:13 ` [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> mtd: bind an mtd_blk device for non-NAND MTD masters
>
> Only the SPI-NAND bind path currently creates an mtd_blk block device,
> so a bare NOR (parallel NOR, SPI-NOR, ...) has no block-device access
> at all. Bind an mtd_blk device for every non-NAND MTD master when
> CONFIG_MTD_BLOCK is enabled, so its partitions become reachable
> through the block layer (and thus by bootstd, imagemap, ...).
>
> NAND is excluded on purpose: it needs UBI, which brings its own block
> device. Partitions inherit the master's block device via the "mtd"
> partition type, so only masters are bound.
>
> mtd_bind() keeps a pointer to the passed mtd_info pointer (SPI-NAND
> fills it after bind), so the generic caller provides a small heap slot
> that lives as long as the block device; an MTD master is never removed
> in practice.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> drivers/mtd/mtdcore.c | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
> diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
> @@ -494,6 +494,30 @@ int add_mtd_device(struct mtd_info *mtd)
> + struct mtd_info **mtdp = kmalloc(sizeof(*mtdp), GFP_KERNEL);
> +
> + if (mtdp) {
> + *mtdp = mtd;
> + mtd_bind(mtd->dev, mtdp);
> + }
Two issues. The return value from mtd_bind() is dropped, so on failure
mtdp is leaked (mtd_bind() only pr_err()s and returns; it doesn't free
what was passed in). And if kmalloc() returns NULL the block device is
silently not created, with no hint why partitions are unreachable.
Please log a warning in both paths and free mtdp on the mtd_bind()
failure path.
> diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
> @@ -494,6 +494,30 @@ int add_mtd_device(struct mtd_info *mtd)
> + * mtd_bind() keeps a pointer to the passed mtd_info pointer, so
> + * it needs storage that lives as long as the block device; the
> + * MTD master is never removed in practice, so a small heap slot
> + * is fine.
> + */
The pointer-to-pointer indirection only exists because SPI-NAND fills
its mtd in after bind. Every other caller (including this one) already
knows the mtd, so the heap slot is pure overhead and, as the comment
concedes, leaks if the master is ever removed. Since you're touching
this path anyway, how about a second helper (say
mtd_bind_master(struct udevice *, struct mtd_info *)) that stashes the
mtd pointer itself, and leave the double-indirection variant to the
one caller that needs it. What do you think?
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes
2026-08-23 19:13 ` [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> mtd: nand: sandbox: forbid sub-page writes
>
> The sandbox NAND model programs each page in a single operation and
> rejects a second write to an already-programmed page. UBI writes the
> EC and VID headers into the first page in two separate sub-page
> operations, which the model fails with -EIO, so UBI cannot attach to a
> sandbox NAND.
>
> Advertise NAND_NO_SUBPAGE_WRITE so callers place each header in its
> own page, matching what the model can actually do. This lets the
> imagemap UBI-volume unit test attach UBI on the sandbox NAND.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> drivers/mtd/nand/raw/sand_nand.c | 2 ++
> 1 file changed, 2 insertions(+)
Reviewed-by: Simon Glass <sjg@chromium.org>
BTW, longer term it would be nicer for the sandbox model to actually
support sub-page writes so we can exercise the code path UBI uses on
real hardware, but that's a separate improvement.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI
2026-08-23 19:13 ` [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> cmd: ubi: create a ubi_blk device when attaching UBI
>
> A UBI volume can be reached as a partition on the ubi_blk block
> device, but that device is only bound from spinand_bind(), so it is
> missing on every other flash type (raw NAND, SPI-NOR, ...).
>
> Bind a ubi_blk device when a UBI partition is attached, unless one has
> already been created, parented to the MTD device UBI sits on. UBI
> volumes then become reachable as block-device partitions regardless of
> the underlying flash. The imagemap block path relies on this to read a
> UBI volume.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> cmd/ubi.c | 27 +++++++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
> diff --git a/cmd/ubi.c b/cmd/ubi.c
> @@ -670,6 +673,29 @@ int ubi_detach(void)
> + ubi_bind(ubi && ubi->mtd && ubi->mtd->dev ? ubi->mtd->dev : dm_root());
> +}
The return value from ubi_bind() is silently dropped, so a failed bind
(e.g. -ENOMEM) leaves ubi_part() reporting success while there is no
ubi_blk at all. Please propagate it, or at least log a warning.
> diff --git a/cmd/ubi.c b/cmd/ubi.c
> @@ -670,6 +673,29 @@ int ubi_detach(void)
> + /*
> + * A single ubi_blk device serves all volumes of the attached UBI
> + * device, the volume being selected through the block descriptor's
> + * hwpart. Bind one when UBI is attached -- unless a previous attach
> + * already did -- parented to the MTD device UBI sits on.
> + */
> + for (uclass_find_first_device(UCLASS_BLK, &dev); dev;
> + uclass_find_next_device(&dev)) {
> + if (dev->driver == DM_DRIVER_GET(ubi_blk))
> + return;
> + }
ubi_detach() doesn't unbind the ubi_blk, so after 'ubi part A; ubi
detach; ubi part B' the existing ubi_blk stays parented to A's MTD
device even though UBI now sits on B. Reads still work because
get_ubi_device() returns ubi_devices[0], but the DM tree is left with
a stale parent (and A's MTD isn't safe to remove). Either unbind in
ubi_detach(), or re-parent here when the target MTD has changed.
Also, walking every UCLASS_BLK to detect a single ubi_blk is awkward.
Since ubi_blk is always parented to an MTD device, iterating the
children of that parent e.g. with device_find_first_child_by_uclass()
(or tracking the bound udevice in a static pointer) would be more
direct and avoid poking dev->driver.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 5/9] boot: add imagemap on-demand loading from storage
2026-08-23 19:13 ` [PATCH 5/9] boot: add imagemap on-demand loading from storage Daniel Golle
2026-08-24 22:55 ` Tom Rini
@ 2026-09-28 15:12 ` Simon Glass
1 sibling, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> boot: add imagemap on-demand loading from storage
>
> Introduce imagemap, a small layer that reads image data from a storage
> device on demand and keeps a translation table of the byte ranges
> already loaded into RAM, instead of copying the whole image up front.
>
> An imagemap device (UCLASS_IMAGEMAP) is created over a partition of a
> block device with imagemap_create(); imagemap_map() then returns a RAM
> pointer for any byte range, reading it in on first access and reusing
> it on later accesses -- so a header probe, verification and the final
> load of the same range share a single read. Block devices cover more
> than raw disks: an MTD partition is reached through mtdblock and a UBI
> volume through ubiblock, each exposed as a named block-device
> partition, so imagemap needs no storage-specific code of its own.
>
> The read path is built on the SPL struct spl_load_info abstraction and
> a new spl_load_region() helper (added to spl.h): the block reader
> reports the device block length and spl_load_region() performs the
> native sector alignment. imagemap_map_to() keeps a payload byte-exact
> at a caller's load address, reading the aligned middle straight to the
> [...]
>
> MAINTAINERS | 7 +
> boot/Kconfig | 16 ++
> boot/Makefile | 2 +
> boot/imagemap.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++
> include/dm/uclass-id.h | 1 +
> include/imagemap.h | 136 +++++++++++++++
> include/spl.h | 47 +++++-
> 7 files changed, 657 insertions(+), 3 deletions(-)
> diff --git a/boot/imagemap.c b/boot/imagemap.c
> @@ -0,0 +1,451 @@
> +void *imagemap_map_to(struct udevice *dev, loff_t img_offset,
> + ulong size, void *dst)
> +{
> + struct imagemap_priv *priv = dev_get_uclass_priv(dev);
> + void *p;
> + int ret;
> +
> + /* If already mapped to this exact destination, return it */
> + p = imagemap_lookup(dev, img_offset, size);
> + if (p && p == dst)
> + return p;
> +
> + ret = imagemap_read_exact(&priv->info, img_offset, size, dst);
> + if (ret) {
> + log_err("imagemap: read failed at offset 0x%llx (size 0x%lx): %d\n",
> + (unsigned long long)img_offset, size, ret);
> + return ERR_PTR(ret);
> + }
> +
> + if (!imagemap_record(dev, img_offset, size, dst, false))
> + return ERR_PTR(-ENOMEM);
> +
> + return dst;
> +}
If an earlier imagemap_map() has already recorded a region at the same
img_offset with lmb_reserved=true, this calls imagemap_record() with
lmb_reserved=false, overwriting the entry in place - so it looks like
the LMB reservation is silently orphaned and never freed by
imagemap_cleanup(). Please free the old reservation before overwriting
or refuse the overwrite.
> diff --git a/boot/imagemap.c b/boot/imagemap.c
> @@ -0,0 +1,451 @@
> + if (bl_len == 1)
> + return spl_load_region(info, off, size, dst) < 0 ? -EIO : 0;
> +
> + /* Partial head block: bounce and copy the wanted tail of the block */
> + if (overhead) {
> + ALLOC_CACHE_ALIGN_BUFFER(u8, blk, bl_len);
> + ulong chunk = min(left, bl_len - overhead);
> +
> + if (info->read(info, cur - overhead, bl_len, blk) < bl_len)
> + return -EIO;
ALLOC_CACHE_ALIGN_BUFFER() with a runtime bl_len expands to a stack
VLA. For MMC that's 512 bytes, but a UBI/ubiblock leb_size or MTD
erase-block-sized reader can push this into the KB range. Probably
this is fine, but do you know what the upper limit will be?
> diff --git a/boot/imagemap.c b/boot/imagemap.c
> @@ -0,0 +1,451 @@
> + alist_for_each(r, &priv->regions) {
> + if (r->img_offset == base_off && r->size < read_size) {
> + addr = map_to_sysmem(r->ram);
> +
> + /* Free old LMB reservation if we own it */
> + if (r->lmb_reserved)
> + lmb_free(addr,
> + ALIGN(r->size, ARCH_DMA_MINALIGN),
> + LMB_NONE);
> +
> + /* Try to re-reserve at the same address with new size */
> + if (lmb_alloc_mem(LMB_MEM_ALLOC_ADDR, 0, &addr,
> + alloc_size, LMB_NONE)) {
Two concerns with this extend path:
1. If r->lmb_reserved is false (the region was recorded by
imagemap_map_to() at a caller-supplied dst), this happily
lmb_alloc_mem()s over that memory and then re-reads on top of it.
Whatever the caller placed there is silently clobbered. Please skip
the extend path in that case, or refuse to extend a non-LMB region.
2. Even in the LMB-owned case, when the in-place re-reservation
succeeds the whole range gets re-read although the first r->size bytes
are already valid - the whole point of the translation table is to
avoid redundant reads. Only the newly-covered tail needs reading.
> diff --git a/boot/imagemap.c b/boot/imagemap.c
> @@ -0,0 +1,451 @@
> + ret = device_bind_driver(desc->bdev, "imagemap",
> + name && *name ? name : "imagemap", &imdev);
Just to check - is the intent that only one imagemap device is ever
active per block device? Two calls with name == NULL (or two different
partitions selected by index) will bind two children both called
imagemap under the same parent, which works but makes the DM tree
confusing. How about deriving a unique name (e.g. including the
partition number) or documenting the single-instance assumption in
imagemap_create()'s kernel-doc ?
> diff --git a/boot/imagemap.c b/boot/imagemap.c
> @@ -0,0 +1,451 @@
> +void *imagemap_lookup(struct udevice *dev, loff_t img_offset, ulong size)
> +{
> + struct imagemap_priv *priv = dev_get_uclass_priv(dev);
> + const struct imagemap_region *r;
The long block comment is useful but says the second guard prevents
underflow on LP64 'where ulong and loff_t have the same rank'. loff_t
is signed long long, 64-bit everywhere U-Boot runs; on ILP32 it
doesn't share rank with ulong. Please tighten the wording - the guard
is needed unconditionally because size is unsigned. Also you could
wrap the comment to 80cols so more of it fits on the screen.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load()
2026-08-23 19:14 ` [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load() Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> boot: fit: support on-demand loading in fit_image_load()
>
> Integrate storage-backed image loading transparently into the existing
> FIT flow so that all verification, decompression, and copy-to-load-
> address code runs unchanged for both RAM-backed and storage-backed
> images.
>
> Hook imagemap into fit_image_get_data(): when gd->imagemap is set and
> the sub-image uses external data, call imagemap_map() to bring the
> payload into RAM instead of returning the unmapped fit + offset
> pointer. Every existing caller, including fit_image_verify() called
> from fit_image_select(), transparently gets valid data without any
> changes to the authentication path.
>
> For uncompressed sub-images with a known load address, pre-populate
> the translation table via imagemap_map_to() before verification runs.
> This loads data directly to the final RAM destination; when
> fit_image_get_data() is later called, the translation table hit
> returns the same pointer, achieving zero-copy. Compressed images or
> those without a load address fall through to the lazy imagemap_map()
> [...]
>
> boot/bootm.c | 70 +++++++++++++++++++++++++++++++----
> boot/image-fit.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> include/bootm.h | 2 +
> include/image.h | 2 +
> 4 files changed, 174 insertions(+), 9 deletions(-)
> diff --git a/boot/bootm.c b/boot/bootm.c
> @@ -147,7 +148,22 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
> + buf = imagemap_map(images->imagemap, 0, 64);
Where does 64 come from? A short comment or named constant would help
- genimg_get_format() has to distinguish a legacy header, a FIT FDT
magic and possibly an Android header, each with different minimum
sizes, so an unexplained 64 looks like a coincidence.
> diff --git a/boot/bootm.c b/boot/bootm.c
> @@ -1033,11 +1063,21 @@ int bootm_run_states(struct bootm_info *bmi, int states)
> + if (states & BOOTM_STATE_START) {
> ret = bootm_start();
> + /*
> + * bootm_start() zeroes the global images struct. Restore
> + * the loader pointer so the storage-backed path works.
> + */
> + if (IS_ENABLED(CONFIG_IMAGEMAP) && bmi->imagemap)
> + images->imagemap = bmi->imagemap;
> + }
Restoring a field immediately after bootm_start() zeroes it is fragile
- any future state that also calls bootm_start() (BOOTM_STATE_RESTART
already runs bootm_restart(), similar in spirit) will silently drop
the pointer again. Please move this into bootm_start() itself, or
teach bootm_start() to preserve imagemap across the reset, so the
invariant is expressed in one place.
> diff --git a/boot/bootm.c b/boot/bootm.c
> @@ -1145,11 +1188,24 @@ int bootm_run_states(struct bootm_info *bmi, int states)
> +err:
> + /* Clean up imagemap on error (not reached on successful boot) */
> + if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
> + imagemap_cleanup(images->imagemap);
> + images->imagemap = NULL;
> + }
Several paths in bootm_run_states() return directly rather than goto
err - 'if (images->no_os) return -ENOPKG;', 'if (ret) return ret;'
after the OS-func lookup, and the 'ERROR: booting os …' return 1 all
bypass this cleanup, so imagemap leaks on those exits. Please convert
those to goto err, or drive the cleanup from a single place that runs
on every exit.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -1140,6 +1141,16 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
> +#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
> + if (images.imagemap) {
> + void *mapped;
> +
> + mapped = imagemap_lookup(images.imagemap,
> + offset, len);
> + if (mapped)
> + *data = mapped;
> + }
> +#endif
Reaching into the global images from a low-level helper couples
fit_image_get_data() to bootm state - until now this function has been
usable purely from (fit, noffset). Please pass the imagemap in
explicitly (as a parameter or via a small wrapper), which also keeps
'ut fit' style tests free of hidden global dependencies.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2140,6 +2151,84 @@ static const char *fit_get_image_type_property(int ph_type)
> + if (CONFIG_IS_ENABLED(FIT_VERITY)) {
> + u8 img_type;
> +
> + if (!fit_image_get_type(fit, noffset, &img_type) &&
> + img_type == IH_TYPE_FILESYSTEM &&
> + fdt_subnode_offset(fit, noffset, "dm-verity") >= 0) {
Please use FIT_VERITY_NODENAME from image.h rather than a literal
dm-verity, so there is one place to change the name.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2140,6 +2151,84 @@ static const char *fit_get_image_type_property(int ph_type)
> + if (!fit_image_get_data_position(fit, noffset, &data_off)) {
> + external = true;
> + } else if (!fit_image_get_data_offset(fit, noffset, &data_off)) {
> + external = true;
> + data_off += ALIGN(fdt_totalsize(fit), 4);
> + }
> +
> + if (!external || fit_image_get_data_size(fit, noffset, &data_sz))
> + return 0;
This re-implements the data-position / data-offset / totalsize-align
dance that fit_image_get_data() already does a few hundred lines up,
and uses a slightly different expression (ALIGN(fdt_totalsize(fit), 4)
vs ((fdt_totalsize(fit) + 3) & ~3)). Please extract a shared helper -
otherwise a future fix in one path (e.g. the INT_MAX bounds check that
fit_image_get_data() has) will silently miss this one.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2140,6 +2151,84 @@ static const char *fit_get_image_type_property(int ph_type)
> + if (img_comp == IH_COMP_NONE && load_op != FIT_LOAD_IGNORED &&
> + !fit_image_get_load(fit, noffset, &img_load)) {
> + void *dst = map_sysmem(img_load, data_sz);
> +
> + mapped = imagemap_map_to(images->imagemap, data_off, data_sz,
> + dst);
map_sysmem() should really be paired with unmap_sysmem() - a no-op on
most arches but not on sandbox, which is where the tests run.
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> @@ -2349,8 +2453,9 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
> - printf(" Loading %s from 0x%08lx to 0x%08lx\n",
> - prop_name, data, load);
> + if (!CONFIG_IS_ENABLED(IMAGEMAP) || data != load)
> + printf(" Loading %s from 0x%08lx to 0x%08lx\n",
> + prop_name, data, load);
This is a build-time gate, not a runtime one - any board with
CONFIG_IMAGEMAP=y silently loses the 'Loading … from … to …' message
for RAM-backed FITs whenever data happens to equal load, a
user-visible regression for boards that don't use imagemap at boot.
Please key the suppression on 'images->imagemap && data == load' so
the message only disappears on the genuine zero-copy path.
> diff --git a/include/image.h b/include/image.h
> @@ -412,6 +412,8 @@ struct bootm_headers {
> + struct udevice *imagemap; /* on-demand storage loader, or NULL */
It might be worth making this (and the equivalent field in struct
bootm_info) conditional on CONFIG_IMAGEMAP, in line with the
surrounding fields - otherwise every board pays the pointer with the
feature off.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends
2026-08-23 19:14 ` [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> configs: sandbox: enable imagemap and its storage backends
>
> Enable CONFIG_IMAGEMAP and, so the end-to-end unit tests can exercise
> the real block paths, the storage that feeds them: CONFIG_MTD_BLOCK
> and CONFIG_SPI_FLASH_MTD (a bare non-NAND MTD reached via mtd_blk) and
> CONFIG_CMD_UBI / CONFIG_UBI_BLOCK (a UBI volume reached via ubi_blk).
>
> Give the sandbox SPI-NOR two device-tree partitions for the mtd_blk
> test and add a clean sandbox NAND chip (nand@2, no injected errors) so
> UBI can format on it for the ubiblock test.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> arch/sandbox/dts/test.dts | 29 +++++++++++++++++++++++++++++
> configs/sandbox_defconfig | 6 ++++++
> 2 files changed, 35 insertions(+)
> diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
> index 5c97418783f..9e7d734a0b8 100644
> --- a/configs/sandbox_defconfig
> +++ b/configs/sandbox_defconfig
> @@ -400,3 +401,8 @@ CONFIG_UTHREAD=y
> CONFIG_UNIT_TEST=y
> CONFIG_UT_TIME=y
> CONFIG_UT_DM=y
> +CONFIG_MTD_BLOCK=y
> +CONFIG_SPI_FLASH_MTD=y
> +CONFIG_CMD_UBI=y
> +CONFIG_MTD_UBI=y
> +CONFIG_UBI_BLOCK=y
Tom does this anyway from time to time, but you could run make
savedefconfig to get these in the right order - also CONFIG_MTD_UBI is
implied by CONFIG_UBI_BLOCK
> diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
> @@ -1633,6 +1633,22 @@
> compatible = "spansion,m25p16", "jedec,spi-nor";
> spi-max-frequency = <40000000>;
> sandbox,filename = "spi.bin";
> +
> + partitions {
> + compatible = "fixed-partitions";
> + #address-cells = <1>;
> + #size-cells = <1>;
> +
> + partition@0 {
> + label = "nor-fit";
> + reg = <0x0 0x100000>;
> + };
> +
> + partition@100000 {
> + label = "nor-spare";
> + reg = <0x100000 0x100000>;
> + };
> + };
Adding fixed-partitions to spi.bin@0 combined with patch 1 (auto-bind
mtd_blk on non-NAND masters) means every sandbox test now grows two
extra block-device partitions on this flash. Please check the SPI
bootdev tests (bootdev_test_next_prio in test/boot/bootdev.c expects
only spi.bin@0.bootdev / spi.bin@1.bootdev) and dm_test_spi_flash in
test/dm/sf.c still pass; if not, the partition table would be better
hung off spi.bin@1 (which no existing test walks), or off a dedicated
third flash chip.
> diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts
> @@ -2242,6 +2258,19 @@
> + /* Clean chip (no injected errors) for UBI/ubiblock tests */
> + nand@2 {
> + reg = <2>;
> + nand-ecc-mode = "soft";
> + sandbox,id = [00 e3];
> + sandbox,erasesize = <(8 * 1024)>;
> + sandbox,oobsize = <16>;
> + sandbox,pagesize = <512>;
> + sandbox,pages = <0x2000>;
> + sandbox,err-count = <0>;
> + sandbox,err-step-size = <512>;
> + };
Just to check - sandbox,err-count = <0> is the default, so this and
sandbox,err-step-size can be omitted. The comment can go too; the
property values already say it.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 8/9] test: boot: add imagemap unit tests
2026-08-23 19:16 ` [PATCH 8/9] test: boot: add imagemap unit tests Daniel Golle
@ 2026-09-28 15:12 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:12 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> test: boot: add imagemap unit tests
>
> Add a test suite exercising imagemap:
>
> - the translation-table core (map, lookup, map_to, cleanup, region
> growth, the block-aligned read path, and edge cases) using a mock
> RAM-backed reader, so most tests need no real storage;
>
> - a NOR read through mtd_blk: write a known pattern into a
> device-tree partition of the sandbox SPI-NOR and read it back
> through imagemap, exercising imagemap -> spl_load_region ->
> blk_dread -> mtd_blk_read -> mtd_read for real;
>
> - a UBI-volume read through ubiblock: attach UBI on the sandbox NAND,
> create a volume, write a pattern and read it back through imagemap
> on the ubi_blk device.
>
> Registered under ut_boot_imagemap in cmd_ut.c.
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> MAINTAINERS | 1 +
> test/boot/Makefile | 2 +
> test/boot/imagemap.c | 645 +++++++++++++++++++++++++++++++++++++++++++++++++++
> test/cmd_ut.c | 2 +
> 4 files changed, 650 insertions(+)
> diff --git a/test/cmd_ut.c b/test/cmd_ut.c
> @@ -73,6 +73,7 @@ SUITE_DECL(optee);
> SUITE_DECL(pci_mps);
> SUITE_DECL(seama);
> SUITE_DECL(setexpr);
> +SUITE_DECL(imagemap);
> SUITE_DECL(upl);
Please keep the list alphabetical - imagemap belongs between image_fdt
and lib, not between setexpr and upl. Same for the SUITE() entry
below.
> diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
> @@ -0,0 +1,645 @@
> +/* Test: map() re-reads when extending a region to a larger size */
> +static int imagemap_test_map_extend(struct unit_test_state *uts)
...
> + {
> + void *p3 = imagemap_map(dev, 256, 128);
> + ulong p2_end = map_to_sysmem(p2) + 256;
> +
> + ut_assert(!IS_ERR(p3));
> + ut_asserteq(2, priv->regions.count);
> + /* New region must not overlap the extended first region */
> + ut_assert(map_to_sysmem(p3) >= p2_end ||
> + map_to_sysmem(p3) + 128 <= map_to_sysmem(p2));
> + ut_asserteq_mem(image + 256, p3, 128);
> + }
Please drop the anonymous block - hoist p3/p2_end to the top of the
function, or split into its own test case. The nested scope is unusual
in U-Boot tests.
> diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
> @@ -0,0 +1,645 @@
> +/* Test: read beyond image size returns error */
> +static int imagemap_test_read_oob(struct unit_test_state *uts)
> +{
> + struct udevice *dev;
> + u8 image[IMAGE_SIZE];
> + void *p;
> +
> + ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
> +
> + /* Attempt to map beyond the end of the image */
> + p = imagemap_map(dev, IMAGE_SIZE - 32, 64);
> + ut_assert(IS_ERR(p));
> +
> + /* map_to should also fail */
> + u8 dst[64];
> +
> + p = imagemap_map_to(dev, IMAGE_SIZE - 32, 64, dst);
> + ut_assert(IS_ERR(p));
Please declare dst at the top of the function - mid-block declarations
are still avoided in U-Boot.
> diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
> @@ -0,0 +1,645 @@
> +IMAGEMAP_TEST(imagemap_test_map_basic, 0);
All of these bind and probe devices under dm_root(), and the
end-to-end MTD/UBI tests rely on real DM state. Please pass UTF_DM
(and UTF_SCAN_FDT for the MTD test that walks device-tree partitions)
so DM state is reset between tests - otherwise a test that aborts
before imagemap_cleanup() leaks a bound imagemap-test device into the
next test and the second run fails on the name clash in
device_bind_driver().
> diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
> @@ -0,0 +1,645 @@
> +static int imagemap_test_ubiblock(struct unit_test_state *uts)
...
> + /* Attach UBI by name; this binds the ubi_blk block device */
> + ut_assertok(ubi_part("nand2", NULL));
> + ut_assertok(ubi_create_vol("vol0", 0x10000, true, UBI_VOL_NUM_AUTO,
> + false));
There is no matching detach, so a second run of the suite (or another
UBI-touching test afterwards) sees UBI already attached to nand2 with
vol0 present, and ubi_part()/ubi_create_vol() will fail. Please detach
UBI at the end (and ideally guard against the pre-attached case at the
start) so the test is re-runnable.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [0/9] boot: add imagemap on-demand image loading from storage
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
` (9 preceding siblings ...)
2026-08-24 22:59 ` [PATCH 0/9] boot: add imagemap on-demand image loading from storage Tom Rini
@ 2026-09-28 15:13 ` Simon Glass
2026-09-29 0:32 ` Daniel Golle
10 siblings, 1 reply; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:13 UTC (permalink / raw)
To: daniel; +Cc: u-boot
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> The first in-tree consumer, a bootmeth for OpenWrt-style FIT firmware, will
> follow as a separate series.
When that comes, will you be able to add a test for the bootmeth?
> boot: fit: support on-demand loading in fit_image_load()
Related: patch 6 adds a struct udevice *imagemap field to both struct
bootm_info and struct bootm_headers, but nothing in this series sets
either outside the tests. The FIT hook is dead code in the built tree.
> On hardware it has been tested end-to-end on a BananaPi R3 (mt7986), booting
> a dm-verity-protected rootfs from every storage path the board exposes
Sounds good.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH 9/9] doc: imagemap: document on-demand loading framework
2026-08-23 19:16 ` [PATCH 9/9] doc: imagemap: document on-demand loading framework Daniel Golle
@ 2026-09-28 15:17 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-09-28 15:17 UTC (permalink / raw)
To: daniel
Cc: Tom Rini, Simon Glass, Dario Binacchi, Michael Trimarchi,
Frieder Schrempf, Quentin Schulz, Marek Vasut, Lucien.Jheng,
Casey Connolly, Michal Simek, Johan Jonker, Aristo Chen,
Nora Schiffer, Rasmus Villemoes, Weijie Gao, Peng Fan,
Vincent Jardin, Neil Armstrong, Randolph Sapp, Miquel Raynal,
Richard Genoud, u-boot, openwrt-devel
Hi Daniel,
On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> doc: imagemap: document on-demand loading framework
>
> Describe the imagemap framework architecture: the translation table
> that tracks mapped regions and its phase-determined caching, reading
> from a block-device partition (including MTD via mtdblock and UBI via
> ubiblock), the integration with bootm for on-demand FIT sub-image
> loading, and the SPL build.
The doc doesn't actually describe the SPL build - imagemap is
full-U-Boot only. Please either drop that clause from the commit
message, or add a short paragraph in imagemap.rst noting the SPL
gating (why xpl phases carry no cost).
>
> Signed-off-by: Daniel Golle <daniel@makrotopia.org>
>
> MAINTAINERS | 1 +
> doc/develop/imagemap.rst | 100 +++++++++++++++++++++++++++++++++++++++++++++++
> doc/develop/index.rst | 1 +
> 3 files changed, 102 insertions(+)
> +The ``bootmeth_openwrt`` boot method is the primary consumer of this
> +framework, using it to boot OpenWrt-style FIT firmware images stored
> +directly on raw storage.
bootmeth_openwrt isn't in-tree yet - so better as 'will be the first
in-tree consumer' so the doc doesn't point at a missing symbol.
> diff --git a/doc/develop/index.rst b/doc/develop/index.rst
> @@ -31,6 +31,7 @@ Implementation
>
> directories
> bloblist
> + imagemap
> bootstd/index
> ci_testing
> commands
The surrounding entries are alphabetical (bloblist, bootstd, chid,
ci_testing, commands, ...) so imagemap belongs later - around
global_data/init, not between bloblist and bootstd/index.
> diff --git a/doc/develop/imagemap.rst b/doc/develop/imagemap.rst
> @@ -0,0 +1,100 @@
> +``imagemap_create(dev, name, part)``
> + Create an imagemap device over a partition of block device *dev*,
> + selected by *name* or by index *part*.
The real signature also takes a struct udevice **devp out parameter
(see include/imagemap.h). Please add it here so the doc matches the
API.
> diff --git a/doc/develop/imagemap.rst b/doc/develop/imagemap.rst
> @@ -0,0 +1,100 @@
> +Boot Integration
> +----------------
> +
> +The ``imagemap`` framework integrates with the boot pipeline through
> +``bootm_run_states()`` in ``boot/bootm.c``:
> +
> +1. The imagemap device pointer is stored in ``struct bootm_info``
> + and propagated to the global ``images`` structure.
> +
> +2. ``boot_get_kernel()`` uses ``imagemap_map()`` to read the image
> + header and FIT metadata without loading the full image into RAM.
boot_get_kernel() is a static helper in boot/bootm.c - a doc reader
won't easily find it. Please either name the entry point a caller
actually reaches (bootm_run_states() / bootm_find_os()) or note that
it's internal. Same for the imagemap_map_to() reference under
fit_image_load(): it would help to say which caller in patch 6 uses
it.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [0/9] boot: add imagemap on-demand image loading from storage
2026-09-28 15:13 ` [0/9] " Simon Glass
@ 2026-09-29 0:32 ` Daniel Golle
2026-10-01 17:23 ` Simon Glass
0 siblings, 1 reply; 23+ messages in thread
From: Daniel Golle @ 2026-09-29 0:32 UTC (permalink / raw)
To: Simon Glass; +Cc: u-boot
Hi Simon,
thank you for the extensive review of the series. Meanwhile v2 had
already been sent and Tom has already picked it into 'next'.
Apart from one issue (see below) I've addressed all correctness issues
you have pointed out in a follow up series which I have sent now.
On Mon, Sep 28, 2026 at 09:13:53AM -0600, Simon Glass wrote:
> Hi Daniel,
>
> On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
>
> > The first in-tree consumer, a bootmeth for OpenWrt-style FIT firmware, will
> > follow as a separate series.
>
> When that comes, will you be able to add a test for the bootmeth?
Yes. I've got that planned but it's not done yet. Compared to imagemap
and the dm-verity integration (which are both covered by unit tests)
the bootmeth by itself also isn't that spectacular ;)
> > boot: fit: support on-demand loading in fit_image_load()
>
> Related: patch 6 adds a struct udevice *imagemap field to both struct
> bootm_info and struct bootm_headers, but nothing in this series sets
> either outside the tests. The FIT hook is dead code in the built tree.
You are right, that is the gap the bootmeth series is going to close,
and until it lands the integration is carried on the strength of
out-of-tree testing, which is not a good place for it to sit.
I would rather fix that by getting the consumer in than by adding a
synthetic caller.
Cheers
Daniel
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [0/9] boot: add imagemap on-demand image loading from storage
2026-09-29 0:32 ` Daniel Golle
@ 2026-10-01 17:23 ` Simon Glass
0 siblings, 0 replies; 23+ messages in thread
From: Simon Glass @ 2026-10-01 17:23 UTC (permalink / raw)
To: Daniel Golle; +Cc: u-boot
Hi Daniel,
On Tue, 29 Sept 2026 at 01:32, Daniel Golle <daniel@makrotopia.org> wrote:
>
> Hi Simon,
>
> thank you for the extensive review of the series. Meanwhile v2 had
> already been sent and Tom has already picked it into 'next'.
> Apart from one issue (see below) I've addressed all correctness issues
> you have pointed out in a follow up series which I have sent now.
Great!
>
> On Mon, Sep 28, 2026 at 09:13:53AM -0600, Simon Glass wrote:
> > Hi Daniel,
> >
> > On 2026-08-23T19:12:43, Daniel Golle <daniel@makrotopia.org> wrote:
> >
> > > The first in-tree consumer, a bootmeth for OpenWrt-style FIT firmware, will
> > > follow as a separate series.
> >
> > When that comes, will you be able to add a test for the bootmeth?
>
> Yes. I've got that planned but it's not done yet. Compared to imagemap
> and the dm-verity integration (which are both covered by unit tests)
> the bootmeth by itself also isn't that spectacular ;)
>
> > > boot: fit: support on-demand loading in fit_image_load()
> >
> > Related: patch 6 adds a struct udevice *imagemap field to both struct
> > bootm_info and struct bootm_headers, but nothing in this series sets
> > either outside the tests. The FIT hook is dead code in the built tree.
>
> You are right, that is the gap the bootmeth series is going to close,
> and until it lands the integration is carried on the strength of
> out-of-tree testing, which is not a good place for it to sit.
> I would rather fix that by getting the consumer in than by adding a
> synthetic caller.
Sounds good.
Regards,
Simon
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-10-01 17:23 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-23 19:12 [PATCH 0/9] boot: add imagemap on-demand image loading from storage Daniel Golle
2026-08-23 19:13 ` [PATCH 1/9] mtd: bind an mtd_blk device for non-NAND MTD masters Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 2/9] mtd: nand: sandbox: forbid sub-page writes Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 3/9] cmd: ubi: create a ubi_blk device when attaching UBI Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:13 ` [PATCH 4/9] mtd: nand: spi: drop the eager ubi_blk binding Daniel Golle
2026-08-23 19:13 ` [PATCH 5/9] boot: add imagemap on-demand loading from storage Daniel Golle
2026-08-24 22:55 ` Tom Rini
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:14 ` [PATCH 6/9] boot: fit: support on-demand loading in fit_image_load() Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:14 ` [PATCH 7/9] configs: sandbox: enable imagemap and its storage backends Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:16 ` [PATCH 8/9] test: boot: add imagemap unit tests Daniel Golle
2026-09-28 15:12 ` Simon Glass
2026-08-23 19:16 ` [PATCH 9/9] doc: imagemap: document on-demand loading framework Daniel Golle
2026-09-28 15:17 ` Simon Glass
2026-08-24 22:59 ` [PATCH 0/9] boot: add imagemap on-demand image loading from storage Tom Rini
2026-09-28 15:13 ` [0/9] " Simon Glass
2026-09-29 0:32 ` Daniel Golle
2026-10-01 17:23 ` Simon Glass
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.