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 <[email protected]>
---
 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 <[email protected]>
+L:     [email protected]
+S:     Maintained
+F:     boot/imagemap.c
+F:     include/imagemap.h
+
 INTERCONNECT
 M:     Neil Armstrong <[email protected]>
 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 <[email protected]>
+ */
+
+#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 <[email protected]>
+ */
+
+#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

Reply via email to