Hi Daniel,
On Mon, 16 Feb 2026 at 14:22, Daniel Golle <[email protected]> wrote:
>
> Extend the bootm command to accept a storage device type keyword as
> the first argument:
>
> bootm mmc <dev>:<part> - boot from MMC/SD/eMMC partition
> bootm mtd <name> - boot from MTD partition
> bootm ubi <volume> - boot from UBI volume
>
> When a known device-type keyword is found, an image_loader is
> constructed using the corresponding backend and stored in
> images.loader. The rest of the bootm flow proceeds unchanged:
> boot_get_kernel() uses image_loader_map() for format detection and
> FDT structure loading, then fit_image_load() uses the on-demand
> storage path for sub-image data.
>
> If the first argument does not match a device-type keyword (i.e. it
> looks like a hex address, or is absent), images.loader remains NULL
> and the existing in-memory path is taken -- full backward compatibility.
>
> The #config suffix follows the same convention as existing bootm FIT
> config selection. Multiple # suffixes can be chained to select a
> base config and one or more device-tree overlays:
>
> bootm mmc 0:4#config-1#overlay-wifi#overlay-lcd
> bootm mtd firmware#config-1
>
> Gated by CONFIG_BOOTM_STORAGE.
>
> Signed-off-by: Daniel Golle <[email protected]>
> ---
> boot/Kconfig | 13 +++++
> boot/bootm.c | 62 +++++++++++++++++---
> cmd/bootm.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++--
> include/bootm.h | 2 +
> 4 files changed, 212 insertions(+), 13 deletions(-)
>
> diff --git a/boot/Kconfig b/boot/Kconfig
> index 89832014af6..1f870c7d251 100644
> --- a/boot/Kconfig
> +++ b/boot/Kconfig
> @@ -1211,6 +1211,19 @@ config IMAGE_LOADER_UBI
> framework. Auto-attaches the UBI device from the device tree
> if not already attached.
>
> +config BOOTM_STORAGE
> + bool "Allow bootm to load images directly from storage"
> + depends on CMDLINE && FIT && IMAGE_LOADER
> + help
> + Extends the bootm command to accept "mmc <dev>:<part>",
> + "mtd <name>", or "ubi <volume>" as the image source instead
> + of a RAM address. Sub-images (kernel, FDT, ramdisk, etc.)
> + are loaded from storage on demand; filesystem sub-images are
> + never read.
> +
> + Requires at least one IMAGE_LOADER backend to be enabled
> + (IMAGE_LOADER_BLK, IMAGE_LOADER_MTD, or IMAGE_LOADER_UBI).
> +
> config DISTRO_DEFAULTS
> bool "(deprecated) Script-based booting of Linux distributions"
> select CMDLINE
> diff --git a/boot/bootm.c b/boot/bootm.c
> index 4bdca22ea8c..67d161f6e3a 100644
> --- a/boot/bootm.c
> +++ b/boot/bootm.c
> @@ -13,6 +13,7 @@
> #include <env.h>
> #include <errno.h>
> #include <fdt_support.h>
> +#include <image-loader.h>
> #include <irq_func.h>
> #include <lmb.h>
> #include <log.h>
> @@ -146,7 +147,22 @@ 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;
> - buf = map_sysmem(img_addr, 0);
> + if (IS_ENABLED(CONFIG_BOOTM_STORAGE) && images->loader) {
> + /*
> + * 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 = image_loader_map(images->loader, 0, 64);
> + if (!buf) {
> + puts("Cannot read image header from storage\n");
> + return -EIO;
> + }
> + img_addr = map_to_sysmem(buf);
> + } else {
> + buf = map_sysmem(img_addr, 0);
> + }
> switch (genimg_get_format(buf)) {
> #if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
> case IMAGE_FORMAT_LEGACY:
> @@ -192,6 +208,20 @@ 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_BOOTM_STORAGE) && images->loader) {
> + /*
> + * Extend the mapping to cover the full FIT
> + * FDT structure so all metadata is accessible.
> + */
> + size_t fdt_sz = fdt_totalsize(buf);
> +
> + buf = image_loader_map(images->loader, 0, fdt_sz);
> + if (!buf) {
> + puts("Cannot read FIT header from storage\n");
> + return -ENOMEM;
> + }
> + img_addr = map_to_sysmem(buf);
> + }
> os_noffset = fit_image_load(images, img_addr,
> &fit_uname_kernel, &fit_uname_config,
> IH_ARCH_DEFAULT, IH_TYPE_KERNEL,
> @@ -991,11 +1021,21 @@ 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)
> + 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_BOOTM_STORAGE) && bmi->loader)
> + images->loader = bmi->loader;
> + }
>
> - if (!ret && (states & BOOTM_STATE_PRE_LOAD))
> - ret = bootm_pre_load(bmi->addr_img);
> + if (!ret && (states & BOOTM_STATE_PRE_LOAD)) {
> + /* Pre-load verification is not applicable to storage boot */
> + if (!IS_ENABLED(CONFIG_BOOTM_STORAGE) || !images->loader)
> + ret = bootm_pre_load(bmi->addr_img);
> + }
>
> if (!ret && (states & BOOTM_STATE_FINDOS))
> ret = bootm_find_os(bmi->cmd_name, bmi->addr_img);
> @@ -1003,8 +1043,11 @@ int bootm_run_states(struct bootm_info *bmi, int
> states)
> if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
> ulong img_addr;
>
> - img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
> - : image_load_addr;
> + if (IS_ENABLED(CONFIG_BOOTM_STORAGE) && images->loader)
> + img_addr = images->os.start;
> + else
> + img_addr = bmi->addr_img ? hextoul(bmi->addr_img,
> NULL)
> + : image_load_addr;
> ret = bootm_find_other(img_addr, bmi->conf_ramdisk,
> bmi->conf_fdt);
> }
> @@ -1097,8 +1140,13 @@ 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))
> + if (!ret && (states & BOOTM_STATE_OS_GO)) {
> + /* Release storage backend before jumping — no return
> expected */
> + if (IS_ENABLED(CONFIG_BOOTM_STORAGE) && images->loader)
> + image_loader_cleanup(images->loader);
> +
> ret = boot_selected_os(BOOTM_STATE_OS_GO, bmi, boot_fn);
> + }
>
> /* Deal with any fallout */
> err:
> diff --git a/cmd/bootm.c b/cmd/bootm.c
> index 2c5aea26d98..6c26dd8e36d 100644
> --- a/cmd/bootm.c
> +++ b/cmd/bootm.c
> @@ -12,6 +12,7 @@
> #include <env.h>
> #include <errno.h>
> #include <image.h>
> +#include <image-loader.h>
> #include <malloc.h>
> #include <nand.h>
> #include <asm/byteorder.h>
> @@ -132,14 +133,123 @@ static int do_bootm_subcommand(struct cmd_tbl *cmdtp,
> int flag, int argc,
> /* bootm - boot application image from image in memory */
> /*******************************************************************/
>
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
Avoid #if - use if() and the toolchain will handle eliminating the code.
> +/**
> + * bootm_init_loader() - Try to parse a storage device spec from argv
> + *
> + * If argv[0] is a known device-type keyword ("mmc", "mtd", "ubi"),
> + * initialise @ldr as the corresponding backend and return the number
> + * of argv entries consumed. The optional #config[#overlay...] suffix
> + * is stripped from the device spec and returned via @conf_name.
> + *
> + * @ldr: loader to initialise
> + * @argc: argument count (after removing the "bootm" verb)
> + * @argv: argument vector
> + * @conf_name: on output, points to the #config string (incl. '#') or NULL
> + * Return: number of argv entries consumed (>0), 0 if no storage keyword
> + * was found, or negative errno on init failure
> + */
> +static int bootm_init_loader(struct image_loader *ldr,
> + int argc, char *const argv[],
> + const char **conf_name)
> +{
> + const char *devtype;
> + char *devspec;
> + char *hash;
> + int ret;
> +
> + if (argc < 2)
> + return 0;
> +
> + devtype = argv[0];
> + devspec = (char *)argv[1];
> + *conf_name = NULL;
> +
> + /* Locate the first '#' — its meaning depends on the backend */
> + hash = strchr(devspec, '#');
> +
> + if (IS_ENABLED(CONFIG_IMAGE_LOADER_BLK) &&
> + !strcmp(devtype, "mmc")) {
> + /*
> + * Block device specs use '#' for partition names when no ':'
> + * precedes it (e.g. "0#kernel"). Only treat the first '#'
> + * as the FIT config separator when ':' appears before it
> + * (e.g. "0:4#config-1"), otherwise the first '#' is the
> + * partition name and a second '#' starts the config
> + * (e.g. "0#kernel#config-1").
> + */
> + char *colon = strchr(devspec, ':');
> +
> + if (!(colon && hash && colon < hash) && hash)
> + hash = strchr(hash + 1, '#');
> + if (hash) {
> + *conf_name = hash;
> + *hash = '\0';
> + }
> + ret = image_loader_init_blk(ldr, "mmc", devspec);
> + if (hash)
> + *hash = '#';
> + } else if (IS_ENABLED(CONFIG_IMAGE_LOADER_MTD) &&
> + !strcmp(devtype, "mtd")) {
> + if (hash) {
> + *conf_name = hash;
> + *hash = '\0';
> + }
> + ret = image_loader_init_mtd(ldr, devspec);
> + if (hash)
> + *hash = '#';
> + } else if (IS_ENABLED(CONFIG_IMAGE_LOADER_UBI) &&
> + !strcmp(devtype, "ubi")) {
> + if (hash) {
> + *conf_name = hash;
> + *hash = '\0';
> + }
> + ret = image_loader_init_ubi(ldr, devspec);
> + if (hash)
> + *hash = '#';
> + } else {
> + return 0; /* not a storage keyword */
> + }
> +
> + if (ret) {
> + printf("Failed to init %s loader: %d\n", devtype, ret);
> + return ret;
> + }
> +
> + return 2; /* consumed: keyword + device-spec */
> +}
> +#endif
> +
> int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
> {
> struct bootm_info bmi;
> int ret;
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + struct image_loader ldr = {};
> + const char *conf_name = NULL;
> + int consumed = 0;
> +#endif
>
> /* determine if we have a sub command */
> argc--; argv++;
> +
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + /* Try storage device spec before checking for hex/subcommand */
> + consumed = bootm_init_loader(&ldr, argc, argv, &conf_name);
> + if (consumed < 0)
> + return CMD_RET_FAILURE;
> + if (consumed > 0) {
> + ldr.alloc_ptr = image_load_addr;
> + argc -= consumed;
> + argv += consumed;
> + }
> +#endif
> +
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + if (argc > 0 && !consumed) {
> +#else
> if (argc > 0) {
> +#endif
> char *endp;
>
> hextoul(argv[0], &endp);
> @@ -156,12 +266,25 @@ int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc,
> char *const argv[])
> }
>
> bootm_init(&bmi);
> - if (argc)
> - bmi.addr_img = argv[0];
> - if (argc > 1)
> - bmi.conf_ramdisk = argv[1];
> - if (argc > 2)
> - bmi.conf_fdt = argv[2];
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + if (consumed > 0) {
> + bmi.addr_img = conf_name; /* "#config-1" or NULL */
> + bmi.loader = &ldr;
> + if (argc > 0)
> + bmi.conf_ramdisk = argv[0];
> + if (argc > 1)
> + bmi.conf_fdt = argv[1];
> + } else {
> +#else
> + {
> +#endif
> + if (argc)
> + bmi.addr_img = argv[0];
> + if (argc > 1)
> + bmi.conf_ramdisk = argv[1];
> + if (argc > 2)
> + bmi.conf_fdt = argv[2];
> + }
>
> /* set up argc and argv[] since some OSes use them */
> bmi.argc = argc;
> @@ -169,6 +292,11 @@ int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc,
> char *const argv[])
>
> ret = bootm_run(&bmi);
>
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + if (ldr.read)
> + image_loader_cleanup(&ldr);
> +#endif
> +
> return ret ? CMD_RET_FAILURE : 0;
> }
>
> @@ -204,6 +332,14 @@ U_BOOT_LONGHELP(bootm,
> "\taddr#<conf_uname> - configuration specification\n"
> "\tUse iminfo command to get the list of existing component\n"
> "\timages and configurations.\n"
> +#endif
> +#if IS_ENABLED(CONFIG_BOOTM_STORAGE)
> + "\nAlternatively, boot directly from a storage device:\n"
> + "\tbootm mmc <dev>:<part>[#conf] - boot from MMC/SD partition\n"
> + "\tbootm mtd <name>[#conf] - boot from MTD partition\n"
> + "\tbootm ubi <volume>[#conf] - boot from UBI volume\n"
> + "\tThe #conf suffix selects a FIT configuration; additional\n"
> + "\t#overlay suffixes select device-tree overlays.\n"
> #endif
> "\nSub-commands to do part of the bootm sequence. The sub-commands "
> "must be\n"
> diff --git a/include/bootm.h b/include/bootm.h
> index 4060cec7fc0..ebc014b3468 100644
> --- a/include/bootm.h
> +++ b/include/bootm.h
> @@ -40,6 +40,7 @@ struct cmd_tbl;
> * boot_get_fdt() for processing, or NULL for none
> * @boot_progress: true to show boot progress
> * @images: images information
> + * @loader: image loader for storage-backed boot (NULL for in-memory)
> * @cmd_name: command which invoked this operation, e.g. "bootm"
> * @argc: Number of arguments to the command (excluding the actual command).
> * This is 0 if there are no arguments
> @@ -51,6 +52,7 @@ struct bootm_info {
> const char *conf_fdt;
> bool boot_progress;
> struct bootm_headers *images;
> + struct image_loader *loader;
This will probably end up being a struct udevice *
> const char *cmd_name;
> int argc;
> char *const *argv;
> --
> 2.53.0
Regards,
Simon