Hi Daniel,

On 2026-08-23T19:12:43, Daniel Golle <[email protected]> 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

Reply via email to