Hi Daniel,
On Mon, 16 Feb 2026 at 14:24, Daniel Golle <[email protected]> wrote:
>
> Add configurable multi-slot boot with slot names and boot order
> defined via environment variables. This enables production/recovery
> dual-boot (or any number of named slots).
>
> New environment variables:
>
> - openwrt_slot_<name>=<location> -- defines a named slot. <name> is
> an arbitrary label (e.g. "production", "recovery"). <location> is
> a GPT partition label, MTD partition name, or UBI volume name.
>
> - openwrt_boot_order=<name1> <name2> ... -- space-separated list of
> slot names that are eligible for booting. Only partitions/volumes
> whose name matches a configured slot's location are probed.
>
> Without openwrt_boot_order, all partitions/volumes are probed
> (backward-compatible with the Milestone 1/2 behavior). When set,
> only partitions matching a defined slot pass the filter.
>
> The matched slot name is stored in bflow->os_name for later use by
> boot-loop avoidance (Milestone 4) and boot menu display.
>
> The MTD and UBI bootdevs now delegate to bootmeth_read_bootflow()
> instead of setting BOOTFLOWST_READY directly, so that slot filtering
> is applied uniformly across all storage backends.
>
> Example configuration:
>
> openwrt_slot_production=firmware
> openwrt_slot_recovery=recovery
> openwrt_boot_order=production recovery
>
> Signed-off-by: Daniel Golle <[email protected]>
> ---
> boot/bootmeth_openwrt.c | 130 ++++++++++++++++++++++++++++++++--------
> 1 file changed, 104 insertions(+), 26 deletions(-)
>
> diff --git a/boot/bootmeth_openwrt.c b/boot/bootmeth_openwrt.c
> index d448697fe08..6e90a203ed6 100644
> --- a/boot/bootmeth_openwrt.c
> +++ b/boot/bootmeth_openwrt.c
> @@ -24,6 +24,63 @@
> #include <linux/libfdt.h>
> #include <linux/sizes.h>
>
> +/**
> + * openwrt_match_slot() - check if a partition name matches a configured slot
> + * @part_name: GPT label, MTD partition name, or UBI volume name
> + * @slot_namep: set to strdup'd slot name on match (caller must free)
> + *
> + * When ``openwrt_boot_order`` is set in the environment, only partitions
> + * whose name matches one of the ``openwrt_slot_<name>`` locations are
> + * accepted. Without ``openwrt_boot_order``, all partitions pass.
> + *
> + * Return: 0 if accepted, -ENOENT if filtered out
> + */
> +static int openwrt_match_slot(const char *part_name, char **slot_namep)
> +{
> + const char *order, *p;
> +
> + *slot_namep = NULL;
> +
> + order = env_get("openwrt_boot_order");
> + if (!order)
> + return 0;
> +
> + if (!part_name)
> + return -ENOENT;
> +
> + p = order;
> + while (*p) {
> + char name[64], var[80];
> + const char *location, *end;
> + int len;
> +
> + while (*p == ' ')
> + p++;
> + if (!*p)
> + break;
> +
> + end = p;
> + while (*end && *end != ' ')
> + end++;
> +
> + len = end - p;
> + if (len >= sizeof(name))
> + len = sizeof(name) - 1;
> + memcpy(name, p, len);
> + name[len] = '\0';
> + p = end;
> +
> + snprintf(var, sizeof(var), "openwrt_slot_%s", name);
> + location = env_get(var);
> + if (location && !strcmp(part_name, location)) {
> + *slot_namep = strdup(name);
> + return 0;
> + }
> + }
> +
> + return -ENOENT;
> +}
> +
> static int openwrt_check(struct udevice *dev, struct bootflow_iter *iter)
> {
> if (bootflow_iter_check_blk(iter))
> @@ -34,40 +91,61 @@ static int openwrt_check(struct udevice *dev, struct
> bootflow_iter *iter)
>
> static int openwrt_read_bootflow(struct udevice *dev, struct bootflow *bflow)
> {
> - struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> const char *part_name = NULL;
> - struct disk_partition info;
> - void *buf;
> + char *slot_name = NULL;
> int ret;
>
> - /* Get partition geometry */
> - ret = part_get_info(desc, bflow->part, &info);
> - if (ret)
> - return log_msg_ret("part", ret);
> -
> - part_name = (const char *)info.name;
> -
> - /* Read first block to probe for an FDT/FIT header */
> - buf = memalign(SZ_1K, desc->blksz);
> - if (!buf)
> - return log_msg_ret("mem", -ENOMEM);
> + if (bflow->blk) {
> + struct blk_desc *desc = dev_get_uclass_plat(bflow->blk);
> + struct disk_partition info;
> + void *buf;
> +
> + ret = part_get_info(desc, bflow->part, &info);
> + if (ret)
> + return log_msg_ret("part", ret);
> +
> + part_name = (const char *)info.name;
> +
> + /* Check slot filter before expensive I/O */
> + ret = openwrt_match_slot(part_name, &slot_name);
> + if (ret)
> + return -ENOENT;
> +
> + /* Read first block to probe for an FDT/FIT header */
> + buf = memalign(SZ_1K, desc->blksz);
> + if (!buf) {
> + free(slot_name);
> + return log_msg_ret("mem", -ENOMEM);
> + }
> +
> + ret = blk_read(bflow->blk, info.start, 1, buf);
> + if (ret != 1) {
> + free(buf);
> + free(slot_name);
> + return log_msg_ret("rd", -EIO);
> + }
> +
> + if (fdt_check_header(buf)) {
> + free(buf);
> + free(slot_name);
> + return -ENOENT;
> + }
The code above would benefit from being in its own function.
>
> - ret = blk_read(bflow->blk, info.start, 1, buf);
> - if (ret != 1) {
> free(buf);
> - return log_msg_ret("rd", -EIO);
> - }
>
> - /* Must start with a valid FDT header */
> - if (fdt_check_header(buf)) {
> - free(buf);
> - return -ENOENT;
> - }
> + /* Show the GPT partition label as Filename */
> + bflow->fname = strdup(part_name);
> + } else {
> + /* MTD or UBI — partition/volume name in bootmeth_priv */
> + part_name = bflow->bootmeth_priv;
>
> - free(buf);
> + ret = openwrt_match_slot(part_name, &slot_name);
> + if (ret)
> + return -ENOENT;
> + }
>
> - /* Show the GPT partition label as Filename */
> - bflow->fname = strdup(part_name);
> + if (slot_name)
> + bflow->os_name = slot_name;
>
> bflow->state = BOOTFLOWST_READY;
>
> --
> 2.53.0
Regards,
SIMon