* [PATCH 01/11] test: cmd_ut: keep the suite list alphabetical
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
@ 2026-10-14 8:38 ` Daniel Golle
2026-10-14 8:38 ` [PATCH 02/11] boot: bootm: derive the header probe size Daniel Golle
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:38 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
The imagemap suite was appended after setexpr in both the declaration
and the registration list, which are otherwise alphabetical.
Move it next to image_fdt.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
test/cmd_ut.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/cmd_ut.c b/test/cmd_ut.c
index fab55d5be8c..966cf44c511 100644
--- a/test/cmd_ut.c
+++ b/test/cmd_ut.c
@@ -64,6 +64,7 @@ SUITE_DECL(fit_verity);
SUITE_DECL(font);
SUITE_DECL(hush);
SUITE_DECL(image_fdt);
+SUITE_DECL(imagemap);
SUITE_DECL(lib);
SUITE_DECL(loadm);
SUITE_DECL(log);
@@ -74,7 +75,6 @@ SUITE_DECL(optee);
SUITE_DECL(pci_mps);
SUITE_DECL(seama);
SUITE_DECL(setexpr);
-SUITE_DECL(imagemap);
SUITE_DECL(upl);
static struct suite suites[] = {
@@ -95,6 +95,7 @@ static struct suite suites[] = {
SUITE(font, "font command"),
SUITE(hush, "hush behaviour"),
SUITE(image_fdt, "image fdt parsing"),
+ SUITE(imagemap, "imagemap on-demand storage loading"),
SUITE(lib, "library functions"),
SUITE(loadm, "loadm command parameters and loading memory blob"),
SUITE(log, "logging functions"),
@@ -105,7 +106,6 @@ 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.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 02/11] boot: bootm: derive the header probe size
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
2026-10-14 8:38 ` [PATCH 01/11] test: cmd_ut: keep the suite list alphabetical Daniel Golle
@ 2026-10-14 8:38 ` Daniel Golle
2026-10-14 8:39 ` [PATCH 03/11] boot: imagemap: correct the lookup guard rationale Daniel Golle
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:38 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
boot_get_kernel() maps a bare 64 bytes to identify the image format.
genimg_get_format() reads at most a 40-byte FDT header or an 8-byte
Android magic, but the legacy branch that follows verifies the whole
64-byte legacy header, so that is the size the mapping has to cover.
Say so with sizeof(struct legacy_img_hdr).
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/bootm.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/boot/bootm.c b/boot/bootm.c
index 92c8452a5d3..b9aa83c3328 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -158,7 +158,8 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
* parsed any #config / :subimage suffix so the FIT
* selection variables are populated.
*/
- buf = imagemap_map(images->imagemap, 0, 64);
+ buf = imagemap_map(images->imagemap, 0,
+ sizeof(struct legacy_img_hdr));
if (IS_ERR(buf)) {
puts("Cannot read image header from storage\n");
return PTR_ERR(buf);
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 03/11] boot: imagemap: correct the lookup guard rationale
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
2026-10-14 8:38 ` [PATCH 01/11] test: cmd_ut: keep the suite list alphabetical Daniel Golle
2026-10-14 8:38 ` [PATCH 02/11] boot: bootm: derive the header probe size Daniel Golle
@ 2026-10-14 8:39 ` Daniel Golle
2026-10-14 8:39 ` [PATCH 04/11] test: boot: imagemap: declare locals at the top Daniel Golle
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:39 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
The comment on imagemap_lookup() attributes the need for its second
guard to ulong and loff_t sharing a rank on LP64. loff_t is a signed
long long on every target U-Boot builds for, so it never shares a rank
with ulong on ILP32, and the guard is needed regardless: the comparison
is against an unsigned @size, so an underflowing subtraction makes any
range look like it fits.
State the actual reason.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/imagemap.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/boot/imagemap.c b/boot/imagemap.c
index b004aa1644b..e7222d58c93 100644
--- a/boot/imagemap.c
+++ b/boot/imagemap.c
@@ -72,10 +72,10 @@ void *imagemap_lookup(struct udevice *dev, loff_t img_offset, ulong size)
*
* 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.
+ * region size. This guard is essential: @size is
+ * unsigned, so without it the subtraction in (3)
+ * wraps to a huge value and every range appears to
+ * fit.
*
* 3) size <= r->size - (img_offset - r->img_offset)
* The requested range fits in the remaining space.
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 04/11] test: boot: imagemap: declare locals at the top
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (2 preceding siblings ...)
2026-10-14 8:39 ` [PATCH 03/11] boot: imagemap: correct the lookup guard rationale Daniel Golle
@ 2026-10-14 8:39 ` Daniel Golle
2026-10-14 8:39 ` [PATCH 05/11] doc: imagemap: describe the API and phase gating Daniel Golle
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:39 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
Two tests declare variables away from the top of the function body: the
extend test wraps its last assertions in an anonymous block to scope p3
and p2_end, and the out-of-bounds test declares dst after a statement.
Neither shape is used elsewhere in U-Boot.
Hoist all four to the declaration block.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
test/boot/imagemap.c | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
index de00a174470..c7289db3995 100644
--- a/test/boot/imagemap.c
+++ b/test/boot/imagemap.c
@@ -231,7 +231,8 @@ static int imagemap_test_map_extend(struct unit_test_state *uts)
struct imagemap_priv *priv;
struct mock_priv *mock;
u8 image[IMAGE_SIZE];
- void *p1, *p2;
+ void *p1, *p2, *p3;
+ ulong p2_end;
for (int i = 0; i < IMAGE_SIZE; i++)
image[i] = (u8)(i & 0xff);
@@ -264,17 +265,15 @@ static int imagemap_test_map_extend(struct unit_test_state *uts)
* 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);
- }
+ p3 = imagemap_map(dev, 256, 128);
+ 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);
@@ -455,6 +454,7 @@ static int imagemap_test_read_oob(struct unit_test_state *uts)
{
struct udevice *dev;
u8 image[IMAGE_SIZE];
+ u8 dst[64];
void *p;
ut_assertok(create_mock_loader(image, IMAGE_SIZE, &dev));
@@ -464,8 +464,6 @@ static int imagemap_test_read_oob(struct unit_test_state *uts)
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));
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 05/11] doc: imagemap: describe the API and phase gating
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (3 preceding siblings ...)
2026-10-14 8:39 ` [PATCH 04/11] test: boot: imagemap: declare locals at the top Daniel Golle
@ 2026-10-14 8:39 ` Daniel Golle
2026-10-14 8:39 ` [PATCH 06/11] doc: develop: sort the imagemap index entry Daniel Golle
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:39 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
The imagemap_create() entry omits the devp out parameter the function
actually takes, the boot-integration list names boot_get_kernel() and
imagemap_map_to() without saying which caller a reader can reach, and
bootmeth_openwrt is described as the primary consumer although it is not
in tree.
Correct the signature, name the callers, and add the section on phase
gating that the commit adding this file already claimed was here.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
doc/develop/imagemap.rst | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/doc/develop/imagemap.rst b/doc/develop/imagemap.rst
index f1fea1bba78..66c0db9e8a8 100644
--- a/doc/develop/imagemap.rst
+++ b/doc/develop/imagemap.rst
@@ -13,9 +13,9 @@ Architecture Overview
The framework is built around the ``UCLASS_IMAGEMAP`` device class
(``include/imagemap.h``), which provides:
-``imagemap_create(dev, name, part)``
+``imagemap_create(dev, name, part, devp)``
Create an imagemap device over a partition of block device *dev*,
- selected by *name* or by index *part*.
+ selected by *name* or by index *part*, and return it through *devp*.
``imagemap_map(dev, offset, size)``
Return a pointer to a buffer holding the requested region.
@@ -76,6 +76,16 @@ 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.
+Build Phases
+------------
+
+``CONFIG_IMAGEMAP`` builds only in full U-Boot. Nothing in the xpl
+phases sets ``images->imagemap``, so SPL and TPL would carry the code
+without ever reaching it, and their FIT loading is unaffected. The
+``struct spl_load_info`` reader and ``spl_load_region()`` that imagemap
+builds on are shared with SPL rather than duplicated, which is why they
+live in ``include/spl.h``.
+
Boot Integration
----------------
@@ -85,16 +95,20 @@ The ``imagemap`` framework integrates with the boot pipeline through
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.
+2. While finding the OS, ``imagemap_map()`` reads the image header and
+ FIT metadata without loading the full image into RAM. This happens
+ in ``boot_get_kernel()``, an internal helper of ``bootm_find_os()``.
3. ``fit_image_load()`` uses the on-demand storage path via
``images->imagemap`` to stream individual sub-images directly to
- their final load addresses.
+ their final load addresses. Its internal
+ ``fit_image_load_storage()`` calls ``imagemap_map_to()`` for an
+ uncompressed sub-image that has a load address, and
+ ``imagemap_map()`` otherwise.
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.
+The ``bootmeth_openwrt`` boot method will be the first in-tree consumer
+of this framework, using it to boot OpenWrt-style FIT firmware images
+stored directly on raw storage.
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 06/11] doc: develop: sort the imagemap index entry
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (4 preceding siblings ...)
2026-10-14 8:39 ` [PATCH 05/11] doc: imagemap: describe the API and phase gating Daniel Golle
@ 2026-10-14 8:39 ` Daniel Golle
2026-10-14 8:39 ` [PATCH 07/11] boot: imagemap: name the device after its partition Daniel Golle
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:39 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
The imagemap entry sits between bloblist and bootstd/index, where the
surrounding list is alphabetical.
Move it after global_data.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
doc/develop/index.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index 3b4cc2e28a7..cb172b66b12 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -31,7 +31,6 @@ Implementation
directories
bloblist
- imagemap
bootstd/index
ci_testing
commands
@@ -46,6 +45,7 @@ Implementation
cedit
event
global_data
+ imagemap
lmb
logging
makefiles
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 07/11] boot: imagemap: name the device after its partition
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (5 preceding siblings ...)
2026-10-14 8:39 ` [PATCH 06/11] doc: develop: sort the imagemap index entry Daniel Golle
@ 2026-10-14 8:39 ` Daniel Golle
2026-10-14 8:40 ` [PATCH 08/11] boot: bootm: set the imagemap from bootm_start() Daniel Golle
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:39 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
imagemap_create() names every device it binds by index "imagemap", so
two mappings taken on one block device appear as two identically named
children and the device tree does not say which partition each covers.
The name also comes straight from the caller, whose string has to outlive
the device for the driver-model pointer to stay valid.
Derive the name from the partition, by name where one was given and by
index otherwise, and hand driver model an allocated copy it releases on
unbind.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/imagemap.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/boot/imagemap.c b/boot/imagemap.c
index e7222d58c93..57a078fbc55 100644
--- a/boot/imagemap.c
+++ b/boot/imagemap.c
@@ -11,6 +11,7 @@
#include <dm.h>
#include <imagemap.h>
#include <lmb.h>
+#include <malloc.h>
#include <mapmem.h>
#include <memalign.h>
#include <part.h>
@@ -441,6 +442,8 @@ int imagemap_create(struct udevice *dev, const char *name,
struct imagemap_plat *plat;
struct disk_partition info;
struct udevice *imdev;
+ char buf[32];
+ char *devname;
int ret;
if (name && *name)
@@ -450,10 +453,26 @@ int imagemap_create(struct udevice *dev, const char *name,
if (ret < 0)
return ret;
- ret = device_bind_driver(desc->bdev, "imagemap",
- name && *name ? name : "imagemap", &imdev);
- if (ret)
+ /*
+ * Name the device after the partition it covers, so that two mappings
+ * on one block device stay distinct in the device tree.
+ */
+ if (name && *name)
+ snprintf(buf, sizeof(buf), "%s", name);
+ else
+ snprintf(buf, sizeof(buf), "imagemap%d", part);
+
+ /* device_bind_driver() keeps the pointer, so hand it one DM can own */
+ devname = strdup(buf);
+ if (!devname)
+ return -ENOMEM;
+
+ ret = device_bind_driver(desc->bdev, "imagemap", devname, &imdev);
+ if (ret) {
+ free(devname);
return ret;
+ }
+ device_set_name_alloced(imdev);
plat = dev_get_plat(imdev);
plat->part_start = info.start;
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 08/11] boot: bootm: set the imagemap from bootm_start()
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (6 preceding siblings ...)
2026-10-14 8:39 ` [PATCH 07/11] boot: imagemap: name the device after its partition Daniel Golle
@ 2026-10-14 8:40 ` Daniel Golle
2026-10-14 8:40 ` [PATCH 09/11] boot: fit: share the external-data offset calculation Daniel Golle
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:40 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
bootm_run_states() puts the imagemap back into the global images struct
after bootm_start() has zeroed it, which leaves the invariant split
across two functions: any later state that calls bootm_start() again
drops the pointer, and BOOTM_STATE_RESTART is already close to doing so.
Pass the imagemap to bootm_start() and let it set the field while it
initialises the rest of the struct.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/bootm.c | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/boot/bootm.c b/boot/bootm.c
index b9aa83c3328..67df647d176 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -275,7 +275,7 @@ static int boot_get_kernel(const char *addr_fit, struct bootm_headers *images,
return 0;
}
-static int bootm_start(void)
+static int bootm_start(struct udevice *imagemap)
{
/*
* Free dm-verity allocations from a prior boot attempt before
@@ -286,6 +286,7 @@ static int bootm_start(void)
fit_verity_free(&images);
memset((void *)&images, 0, sizeof(images));
images.verify = env_get_yesno("verify");
+ images.imagemap = imagemap;
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
images.state = BOOTM_STATE_START;
@@ -1198,15 +1199,8 @@ 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) {
- 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 (states & BOOTM_STATE_START)
+ ret = bootm_start(bmi->imagemap);
if (!ret && (states & BOOTM_STATE_PRE_LOAD)) {
/* Pre-load verification is not applicable to storage boot */
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 09/11] boot: fit: share the external-data offset calculation
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (7 preceding siblings ...)
2026-10-14 8:40 ` [PATCH 08/11] boot: bootm: set the imagemap from bootm_start() Daniel Golle
@ 2026-10-14 8:40 ` Daniel Golle
2026-10-14 8:40 ` [PATCH 10/11] boot: build the imagemap pointers only when enabled Daniel Golle
2026-10-14 8:40 ` [PATCH 11/11] test: boot: imagemap: caller-owned region not resized Daniel Golle
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:40 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
fit_image_load_storage() repeats the data-position / data-offset /
totalsize-alignment sequence that fit_image_get_data() performs, and
writes the result through an int pointer where the getters expect a u32.
The copy also omits the range check on the sum, so an offset that the
embedded path rejects is accepted here.
Extract fit_image_ext_data_offset() and call it from both. The storage
path gains the range check and both agree on one expression for the
alignment.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/image-fit.c | 82 ++++++++++++++++++++++++++++++------------------
1 file changed, 51 insertions(+), 31 deletions(-)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 6ab6c6c7da9..f02ac58d733 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -1056,6 +1056,47 @@ int fit_image_get_data_size_unciphered(const void *fit, int noffset,
return 0;
}
+/**
+ * fit_image_ext_data_offset - locate a sub-image's external data
+ * @fit: pointer to the FIT format image header
+ * @noffset: component image node offset
+ * @offsetp: holds the data offset from the start of the FIT, on success
+ *
+ * returns:
+ * 0, if the sub-image has external data
+ * -ENOENT, if its data is embedded in the FIT
+ * -EINVAL, if the recorded offset is out of range
+ */
+static int fit_image_ext_data_offset(const void *fit, int noffset, u32 *offsetp)
+{
+ size_t fdt_total_size_aligned;
+ u32 offset;
+
+ if (!fit_image_get_data_position(fit, noffset, &offset)) {
+ *offsetp = offset;
+
+ return 0;
+ }
+
+ if (fit_image_get_data_offset(fit, noffset, &offset))
+ return -ENOENT;
+
+ /*
+ * data-offset counts from the end of the FDT, so that is the base for
+ * every image; the sum has to stay within a u32.
+ */
+ fdt_total_size_aligned = ALIGN(fdt_totalsize(fit), 4);
+ if (fdt_total_size_aligned > UINT32_MAX - offset) {
+ printf("Invalid external data offset: %u\n", offset);
+
+ return -EINVAL;
+ }
+
+ *offsetp = offset + fdt_total_size_aligned;
+
+ return 0;
+}
+
/**
* fit_image_get_data - get data and its size including
* both embedded and external data
@@ -1077,29 +1118,14 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
{
bool external_data = false;
int ret;
- size_t fdt_total_size_aligned;
u32 offset;
u32 len;
uintptr_t max_offset;
- if (!fit_image_get_data_position(fit, noffset, &offset)) {
- external_data = true;
- } else if (!fit_image_get_data_offset(fit, noffset, &offset)) {
- /*
- * For FIT with external data, figure out where
- * the external images start. This is the base
- * for the data-offset properties in each image.
- */
- fdt_total_size_aligned = ((fdt_totalsize(fit) + 3) & ~3);
- /* The resulting offset cannot exceed UINT32_MAX */
- if (fdt_total_size_aligned > UINT32_MAX - offset) {
- printf("Invalid external data offset: %u\n", offset);
- return -EINVAL;
- }
- offset += fdt_total_size_aligned;
-
- external_data = true;
- }
+ ret = fit_image_ext_data_offset(fit, noffset, &offset);
+ if (ret && ret != -ENOENT)
+ return ret;
+ external_data = !ret;
if (external_data) {
debug("External Data\n");
@@ -2185,12 +2211,12 @@ 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;
+ u32 data_off = 0, data_sz = 0;
ulong img_load;
u8 img_comp = IH_COMP_NONE;
void *mapped;
void *dst;
+ int ret;
if (CONFIG_IS_ENABLED(FIT_VERITY)) {
u8 img_type;
@@ -2207,19 +2233,13 @@ static int fit_image_load_storage(struct bootm_headers *images, const void *fit,
}
}
- 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);
- }
+ ret = fit_image_ext_data_offset(fit, noffset, &data_off);
+ if (ret)
+ return ret == -ENOENT ? 0 : ret;
- if (!external || fit_image_get_data_size(fit, noffset, &data_sz))
+ if (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 &&
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 10/11] boot: build the imagemap pointers only when enabled
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (8 preceding siblings ...)
2026-10-14 8:40 ` [PATCH 09/11] boot: fit: share the external-data offset calculation Daniel Golle
@ 2026-10-14 8:40 ` Daniel Golle
2026-10-14 8:40 ` [PATCH 11/11] test: boot: imagemap: caller-owned region not resized Daniel Golle
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:40 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
struct bootm_headers and struct bootm_info carry an imagemap pointer
whatever the configuration, so every board pays for it with the feature
off, and each use site has to spell out an IS_ENABLED() test beside the
NULL check.
Compile the two fields under CONFIG_IMAGEMAP and reach them through
bootm_imagemap(), bootm_set_imagemap() and bootm_info_imagemap(), which
fold to NULL and to nothing when it is off.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
boot/bootm.c | 28 ++++++++++++++--------------
boot/image-fit.c | 12 ++++++------
include/bootm.h | 17 +++++++++++++++++
include/image.h | 31 +++++++++++++++++++++++++++++++
4 files changed, 68 insertions(+), 20 deletions(-)
diff --git a/boot/bootm.c b/boot/bootm.c
index 67df647d176..8d3fabab39b 100644
--- a/boot/bootm.c
+++ b/boot/bootm.c
@@ -151,14 +151,14 @@ 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;
- if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
+ if (bootm_imagemap(images)) {
/*
* 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,
+ buf = imagemap_map(bootm_imagemap(images), 0,
sizeof(struct legacy_img_hdr));
if (IS_ERR(buf)) {
puts("Cannot read image header from storage\n");
@@ -213,14 +213,14 @@ 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) {
+ if (bootm_imagemap(images)) {
/*
* 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);
+ buf = imagemap_map(bootm_imagemap(images), 0, fdt_sz);
if (IS_ERR(buf)) {
puts("Cannot read FIT header from storage\n");
return PTR_ERR(buf);
@@ -286,7 +286,7 @@ static int bootm_start(struct udevice *imagemap)
fit_verity_free(&images);
memset((void *)&images, 0, sizeof(images));
images.verify = env_get_yesno("verify");
- images.imagemap = imagemap;
+ bootm_set_imagemap(&images, imagemap);
bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
images.state = BOOTM_STATE_START;
@@ -1200,11 +1200,11 @@ int bootm_run_states(struct bootm_info *bmi, int states)
* any error.
*/
if (states & BOOTM_STATE_START)
- ret = bootm_start(bmi->imagemap);
+ ret = bootm_start(bootm_info_imagemap(bmi));
if (!ret && (states & BOOTM_STATE_PRE_LOAD)) {
/* Pre-load verification is not applicable to storage boot */
- if (!IS_ENABLED(CONFIG_IMAGEMAP) || !images->imagemap)
+ if (!bootm_imagemap(images))
ret = bootm_pre_load(bmi->addr_img);
}
@@ -1214,7 +1214,7 @@ int bootm_run_states(struct bootm_info *bmi, int states)
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ulong img_addr;
- if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap)
+ if (bootm_imagemap(images))
img_addr = images->os.start;
else
img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
@@ -1318,9 +1318,9 @@ 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)) {
/* Release storage backend before jumping - no return expected */
- if (IS_ENABLED(CONFIG_IMAGEMAP) && images->imagemap) {
- imagemap_cleanup(images->imagemap);
- images->imagemap = NULL;
+ if (bootm_imagemap(images)) {
+ imagemap_cleanup(bootm_imagemap(images));
+ bootm_set_imagemap(images, NULL);
}
ret = boot_selected_os(BOOTM_STATE_OS_GO, bmi, boot_fn);
@@ -1329,9 +1329,9 @@ int bootm_run_states(struct bootm_info *bmi, int states)
/* 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 (bootm_imagemap(images)) {
+ imagemap_cleanup(bootm_imagemap(images));
+ bootm_set_imagemap(images, NULL);
}
if (iflag)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index f02ac58d733..a461dc8f215 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -1164,10 +1164,10 @@ int fit_image_get_data(const void *fit, int noffset, const void **data,
}
*data = fit + offset;
#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
- if (images.imagemap) {
+ if (bootm_imagemap(&images)) {
void *mapped;
- mapped = imagemap_lookup(images.imagemap,
+ mapped = imagemap_lookup(bootm_imagemap(&images),
offset, len);
if (mapped)
*data = mapped;
@@ -2246,11 +2246,11 @@ static int fit_image_load_storage(struct bootm_headers *images, const void *fit,
!fit_image_get_load(fit, noffset, &img_load)) {
dst = map_sysmem(img_load, data_sz);
- mapped = imagemap_map_to(images->imagemap, data_off, data_sz,
+ mapped = imagemap_map_to(bootm_imagemap(images), data_off, data_sz,
dst);
unmap_sysmem(dst);
} else {
- mapped = imagemap_map(images->imagemap, data_off, data_sz);
+ mapped = imagemap_map(bootm_imagemap(images), data_off, data_sz);
}
return IS_ERR(mapped) ? PTR_ERR(mapped) : 0;
@@ -2365,7 +2365,7 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
* 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) {
+ if (!tools_build() && bootm_imagemap(images)) {
bool early = false;
ret = fit_image_load_storage(images, fit, noffset, load_op,
@@ -2490,7 +2490,7 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
}
/* A storage-backed image was read straight to its load address */
- if (!images->imagemap || data != load)
+ if (!bootm_imagemap(images) || data != load)
printf(" Loading %s from 0x%08lx to 0x%08lx\n",
prop_name, data, load);
} else {
diff --git a/include/bootm.h b/include/bootm.h
index d1aac3d44df..ce13ce3dc61 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -52,12 +52,29 @@ struct bootm_info {
const char *conf_fdt;
bool boot_progress;
struct bootm_headers *images;
+#if CONFIG_IS_ENABLED(IMAGEMAP)
struct udevice *imagemap;
+#endif
const char *cmd_name;
int argc;
char *const *argv;
};
+/**
+ * bootm_info_imagemap() - Get the storage loader a boot was asked to use
+ *
+ * @bmi: Boot info
+ * Return: the imagemap device, or NULL to boot an image already in RAM
+ */
+static inline struct udevice *bootm_info_imagemap(struct bootm_info *bmi)
+{
+#if CONFIG_IS_ENABLED(IMAGEMAP)
+ return bmi->imagemap;
+#else
+ return NULL;
+#endif
+}
+
/**
* bootm_init() - Set up a bootm_info struct with useful defaults
*
diff --git a/include/image.h b/include/image.h
index 5248ca98d41..14189581646 100644
--- a/include/image.h
+++ b/include/image.h
@@ -412,7 +412,9 @@ struct bootm_headers {
int verify; /* env_get("verify")[0] != 'n' */
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
struct udevice *imagemap; /* on-demand storage loader, or NULL */
+#endif
#define BOOTM_STATE_START 0x00000001
#define BOOTM_STATE_FINDOS 0x00000002
@@ -432,6 +434,35 @@ struct bootm_headers {
extern struct bootm_headers images;
+/**
+ * bootm_imagemap() - Get the on-demand storage loader in use, if any
+ *
+ * @images: Images being booted
+ * Return: the imagemap device, or NULL when the image is already in RAM
+ */
+static inline struct udevice *bootm_imagemap(struct bootm_headers *images)
+{
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
+ return images->imagemap;
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * bootm_set_imagemap() - Record the on-demand storage loader to read through
+ *
+ * @images: Images being booted
+ * @imagemap: The imagemap device, or NULL for an image already in RAM
+ */
+static inline void bootm_set_imagemap(struct bootm_headers *images,
+ struct udevice *imagemap)
+{
+#if !defined(USE_HOSTCC) && CONFIG_IS_ENABLED(IMAGEMAP)
+ images->imagemap = imagemap;
+#endif
+}
+
/*
* Some systems (for example LWMON) have very short watchdog periods;
* we must make sure to split long operations like memmove() or
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH 11/11] test: boot: imagemap: caller-owned region not resized
2026-10-14 8:38 [PATCH 00/11] imagemap: structural and cosmetic review follow-ups Daniel Golle
` (9 preceding siblings ...)
2026-10-14 8:40 ` [PATCH 10/11] boot: build the imagemap pointers only when enabled Daniel Golle
@ 2026-10-14 8:40 ` Daniel Golle
10 siblings, 0 replies; 12+ messages in thread
From: Daniel Golle @ 2026-10-14 8:40 UTC (permalink / raw)
To: Tom Rini, Daniel Golle, Simon Glass, Aristo Chen, Vincent Jardin,
Anton Ivanov, Randolph Sapp, Anders Roxell, u-boot, openwrt-devel
Add a unit test that locks down the fix in imagemap_map(): a larger
request against a region recorded by imagemap_map_to() (which the
caller owns and is not LMB-reserved) falls through to a fresh LMB
allocation instead of resizing the caller's memory.
The test reserves a caller-owned buffer inside the LMB-managed RAM,
records a 64-byte region there via imagemap_map_to() with a guard
pattern beyond the payload, then issues a 256-byte imagemap_map() at the
same base. It verifies the returned pointer differs from the caller's
buffer, the guard tail is unchanged, and the new region is LMB-reserved.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
test/boot/imagemap.c | 97 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/test/boot/imagemap.c b/test/boot/imagemap.c
index c7289db3995..e038f5377cd 100644
--- a/test/boot/imagemap.c
+++ b/test/boot/imagemap.c
@@ -316,6 +316,103 @@ static int imagemap_test_map_to(struct unit_test_state *uts)
IMAGEMAP_TEST(imagemap_test_map_to, UTF_DM);
+/*
+ * Test: map() must NOT resize a region the caller recorded via map_to().
+ *
+ * The caller reserves (owns) its own load address through the LMB, then
+ * asks imagemap_map_to() to load 64 bytes there and records it as a
+ * caller-owned region (lmb_reserved = false). A later, larger request at
+ * the same base must fall through to a fresh LMB allocation, leaving the
+ * caller's memory untouched.
+ *
+ * Without the guard in imagemap_map() this path would lmb_free() the
+ * caller's reservation, re-reserve the same address and re-read in place,
+ * clobbering whatever the caller kept beyond the 64 requested bytes.
+ *
+ * dst must live inside the LMB-managed RAM (here: a caller-reserved
+ * buffer) so the buggy in-place re-read actually happens; a plain stack
+ * buffer sits outside that region and would mask the bug.
+ *
+ * Mirrors the scenario Simon Glass asked to be locked down: a guard
+ * pattern in dst beyond the requested 64 bytes must survive, the returned
+ * pointer must differ from dst, and the new region must be LMB-reserved.
+ */
+static int imagemap_test_map_to_not_resized(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ struct imagemap_priv *priv;
+ struct mock_priv *mock;
+ u8 image[IMAGE_SIZE];
+ phys_addr_t dst_phys;
+ u8 *dst;
+ const ulong dst_size = 512;
+ u8 guard[512 - 64];
+ struct imagemap_region *r;
+ 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);
+
+ /* Caller reserves its own load address and owns the memory */
+ ut_assertok(lmb_alloc_mem(LMB_MEM_ALLOC_ANY, ARCH_DMA_MINALIGN,
+ &dst_phys, dst_size, LMB_NONE));
+ dst = map_sysmem(dst_phys, dst_size);
+
+ /* Distinct guard pattern beyond the 64 requested payload bytes */
+ memset(guard, 0xa5, sizeof(guard));
+ memcpy(dst + 64, guard, sizeof(guard));
+
+ /*
+ * Caller records a 64-byte region at its chosen address; imagemap
+ * must NOT claim an LMB reservation for it.
+ */
+ p = imagemap_map_to(dev, 0, 64, dst);
+ ut_assert(!IS_ERR(p));
+ ut_asserteq_ptr(dst, p);
+ ut_asserteq_mem(image, dst, 64);
+ r = alist_get(&priv->regions, 0, struct imagemap_region);
+ ut_assert(r && !r->lmb_reserved);
+ ut_asserteq(1, priv->regions.count);
+
+ /*
+ * Larger request at the same base: must fall through to a fresh
+ * LMB allocation rather than resizing the caller's region.
+ */
+ p = imagemap_map(dev, 0, 256);
+ ut_assert(!IS_ERR(p));
+ /* Returned pointer must be a different buffer */
+ ut_assert((char *)p != (char *)dst);
+ /* Caller's guard tail must remain intact */
+ ut_asserteq_mem(guard, dst + 64, sizeof(guard));
+ /* The new region is LMB-reserved and holds the full 256 bytes */
+ ut_assert(lmb_is_reserved_flags(map_to_sysmem(p), LMB_NONE));
+ ut_asserteq_mem(image, p, 256);
+
+ /*
+ * The fall-through replaced the caller's record via
+ * imagemap_record() (same img_offset); the table keeps one entry,
+ * now LMB-reserved (the fresh allocation).
+ */
+ ut_asserteq(1, priv->regions.count);
+ ut_assert(alist_get(&priv->regions, 0, struct imagemap_region)->lmb_reserved);
+
+ imagemap_cleanup(dev);
+
+ /*
+ * The caller's own reservation was never taken over by imagemap and
+ * is still ours to release.
+ */
+ lmb_free(dst_phys, dst_size, LMB_NONE);
+
+ return 0;
+}
+
+IMAGEMAP_TEST(imagemap_test_map_to_not_resized, UTF_DM);
+
/* Test: lookup() returns NULL for unmapped ranges */
static int imagemap_test_lookup_miss(struct unit_test_state *uts)
{
--
2.56.0
^ permalink raw reply related [flat|nested] 12+ messages in thread