[PATCH v2 07/11] boot: add imagemap on-demand loading from storage
Daniel Golle <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <9a26c59e8ea47e51c221536187da94d523510152.1787673209.git.daniel@makrotopia.org> |
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 | 38 +++- 7 files changed, 647 insertions(+), 4 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..9e77ed25d00 --- /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..8a9f799f192 100644 --- a/include/spl.h +++ b/include/spl.h @@ -346,14 +346,14 @@ typedef ulong (*spl_load_reader)(struct spl_load_info *load, ulong sector, * * @read: Function to call to read from the device * @priv: Private data for the device - * @bl_len: Block length for reading in bytes + * @bl_len: Block length in bytes (CONFIG_SPL_LOAD_BLOCK, CONFIG_IMAGEMAP) * @phase: Image phase to load * @no_fdt_update: true to update the FDT with any loadables that are loaded */ struct spl_load_info { spl_load_reader read; void *priv; -#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) +#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || CONFIG_IS_ENABLED(IMAGEMAP) u16 bl_len; #endif #if CONFIG_IS_ENABLED(BOOTMETH_VBE) @@ -364,7 +364,7 @@ struct spl_load_info { static inline int spl_get_bl_len(struct spl_load_info *info) { -#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) +#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || CONFIG_IS_ENABLED(IMAGEMAP) return info->bl_len; #else return 1; @@ -373,7 +373,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) +#if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) || CONFIG_IS_ENABLED(IMAGEMAP) info->bl_len = bl_len; #else if (bl_len != 1) @@ -429,6 +429,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