Add a block device storage backend for the image_loader framework.

image_loader_init_blk() takes a device and partition specification
string, resolves the partition, and installs a .read() callback that
translates byte-offset reads into blk_dread() calls with proper
sector alignment.

Partitions can be identified by number or by name, following the
syntax of part_get_info_by_dev_and_name_or_num():
  "0:4"        partition 4 on device 0
  "0#kernel"   partition named "kernel" on device 0

This is important for systems where partition numbers are not stable
across firmware updates.

Sub-sector reads (offset or size not aligned to blk_desc->blksz) use
a single-sector bounce buffer to avoid overreading into adjacent RAM.

The .cleanup callback frees the allocated private context.

Gated by CONFIG_IMAGE_LOADER_BLK (depends on BLK && PARTITIONS &&
IMAGE_LOADER).

Signed-off-by: Daniel Golle <[email protected]>
---
 boot/Kconfig            |   8 +++
 boot/Makefile           |   1 +
 boot/image-loader-blk.c | 133 ++++++++++++++++++++++++++++++++++++++++
 include/image-loader.h  |  20 ++++++
 4 files changed, 162 insertions(+)
 create mode 100644 boot/image-loader-blk.c

diff --git a/boot/Kconfig b/boot/Kconfig
index f6908e04a51..e94b52288a3 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -1187,6 +1187,14 @@ config IMAGE_LOADER_MAX_REGIONS
          images (FDT structure + kernel + device tree + ramdisk +
          a few loadable sub-images).
 
+config IMAGE_LOADER_BLK
+       bool "Block device backend for image loader"
+       depends on IMAGE_LOADER && BLK && PARTITIONS
+       help
+         Allows loading images from block device partitions (MMC, SATA,
+         USB, etc.) using the image_loader framework. Partitions can
+         be identified by number or name.
+
 config DISTRO_DEFAULTS
        bool "(deprecated) Script-based booting of Linux distributions"
        select CMDLINE
diff --git a/boot/Makefile b/boot/Makefile
index 1dbc285dad8..ac006bbaa82 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_OS) += 
vbe_simple_os.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_ANDROID) += bootmeth_android.o
 
 obj-$(CONFIG_IMAGE_LOADER) += image-loader.o
+obj-$(CONFIG_IMAGE_LOADER_BLK) += image-loader-blk.o
 
 obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC) += vbe_abrec.o vbe_common.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_ABREC_FW) += vbe_abrec_fw.o
diff --git a/boot/image-loader-blk.c b/boot/image-loader-blk.c
new file mode 100644
index 00000000000..3f9a309a60c
--- /dev/null
+++ b/boot/image-loader-blk.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Block device backend for image_loader
+ *
+ * Copyright (C) 2026 Daniel Golle <[email protected]>
+ */
+
+#include <blk.h>
+#include <image-loader.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <part.h>
+#include <log.h>
+
+struct image_loader_blk_priv {
+       struct blk_desc *desc;
+       lbaint_t part_start;
+       lbaint_t part_size;
+};
+
+/**
+ * blk_read_partial() - Read a partial sector via bounce buffer
+ *
+ * Reads one full sector into a stack-allocated bounce buffer, then
+ * copies @len bytes starting at byte offset @skip within that sector
+ * into @dst.
+ *
+ * @desc:      Block device descriptor
+ * @lba:       Absolute LBA of the sector to read
+ * @skip:      Byte offset within the sector
+ * @len:       Number of bytes to copy
+ * @dst:       Destination buffer
+ * Return: 0 on success, -EIO on read failure
+ */
+static int blk_read_partial(struct blk_desc *desc, lbaint_t lba,
+                           ulong skip, ulong len, void *dst)
+{
+       ALLOC_CACHE_ALIGN_BUFFER(u8, sec, desc->blksz);
+
+       if (blk_dread(desc, lba, 1, sec) != 1)
+               return -EIO;
+
+       memcpy(dst, sec + skip, len);
+
+       return 0;
+}
+
+static int image_loader_blk_read(struct image_loader *ldr, ulong src,
+                                ulong size, void *dst)
+{
+       struct image_loader_blk_priv *priv = ldr->priv;
+       struct blk_desc *desc = priv->desc;
+       unsigned long blksz = desc->blksz;
+       lbaint_t lba = priv->part_start + src / blksz;
+       ulong head = src % blksz;
+       u8 *out = dst;
+       lbaint_t n;
+       int ret;
+
+       /* Bounds check */
+       if (src + size > (ulong)priv->part_size * blksz) {
+               log_err("image_loader_blk: read at 0x%lx+0x%lx exceeds 
partition size\n",
+                       src, size);
+               return -EINVAL;
+       }
+
+       /* Handle unaligned head */
+       if (head) {
+               ulong chunk = min(size, blksz - head);
+
+               ret = blk_read_partial(desc, lba, head, chunk, out);
+               if (ret)
+                       return ret;
+
+               out += chunk;
+               size -= chunk;
+               lba++;
+       }
+
+       /* Aligned middle — read whole sectors directly into dst */
+       if (size >= blksz) {
+               n = size / blksz;
+
+               if (blk_dread(desc, lba, n, out) != n)
+                       return -EIO;
+
+               out += n * blksz;
+               size -= n * blksz;
+               lba += n;
+       }
+
+       /* Handle unaligned tail */
+       if (size) {
+               ret = blk_read_partial(desc, lba, 0, size, out);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void image_loader_blk_cleanup(struct image_loader *ldr)
+{
+       free(ldr->priv);
+}
+
+int image_loader_init_blk(struct image_loader *ldr, const char *ifname,
+                         const char *dev_part_str)
+{
+       struct image_loader_blk_priv *priv;
+       struct blk_desc *desc;
+       struct disk_partition info;
+       int ret;
+
+       ret = part_get_info_by_dev_and_name_or_num(ifname, dev_part_str,
+                                                  &desc, &info, 0);
+       if (ret < 0)
+               return ret;
+
+       priv = malloc(sizeof(*priv));
+       if (!priv)
+               return -ENOMEM;
+
+       priv->desc = desc;
+       priv->part_start = info.start;
+       priv->part_size = info.size;
+
+       ldr->read = image_loader_blk_read;
+       ldr->cleanup = image_loader_blk_cleanup;
+       ldr->priv = priv;
+
+       return 0;
+}
diff --git a/include/image-loader.h b/include/image-loader.h
index e273b1ca50f..1a9048ba482 100644
--- a/include/image-loader.h
+++ b/include/image-loader.h
@@ -138,4 +138,24 @@ void *image_loader_map(struct image_loader *ldr, ulong 
img_offset,
 void *image_loader_map_to(struct image_loader *ldr, ulong img_offset,
                          ulong size, void *dst);
 
+/**
+ * image_loader_init_blk() - Initialise loader for a block device partition
+ *
+ * Resolves the partition using @ifname and @dev_part_str, then installs
+ * a .read() callback that translates byte-offset reads into blk_dread()
+ * calls. The dev_part_str accepts the same formats as
+ * part_get_info_by_dev_and_name_or_num():
+ *
+ *   "0:4"        partition 4 on device 0
+ *   "0#kernel"   partition named "kernel" on device 0
+ *   "0:1"        partition 1 on device 0
+ *
+ * @ldr:               The image loader to initialise
+ * @ifname:            Block interface name (e.g. "mmc", "scsi")
+ * @dev_part_str:      Device and partition specification
+ * Return: 0 on success, negative errno on failure
+ */
+int image_loader_init_blk(struct image_loader *ldr, const char *ifname,
+                         const char *dev_part_str);
+
 #endif /* __IMAGE_LOADER_H */
-- 
2.53.0

Reply via email to