Add a bootmeth driver which supports booting A/B system with RAUC as
their update client.

Signed-off-by: Martin Schwan <m.sch...@phytec.de>
---
 boot/Kconfig         |  51 ++++++
 boot/Makefile        |   1 +
 boot/bootmeth_rauc.c | 432 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 484 insertions(+)

diff --git a/boot/Kconfig b/boot/Kconfig
index 
30eb5b328d7ad8a73a2e84812dd7853b66039164..a610cf9faf99cd5480f94dcac89c3401cca394ae
 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -855,6 +855,57 @@ config EXPO
          The expo can be presented in graphics form using a vidconsole, or in
          text form on a serial console.
 
+config BOOTMETH_RAUC
+       bool "Bootdev support for RAUC A/B systems"
+       default y if BOOTSTD_FULL
+       depends on CMDLINE
+       select BOOTMETH_GLOBAL
+       select HUSH_PARSER
+       help
+         Enables support for booting RAUC A/B systems from MMC devices. This
+         makes the bootdevs look for a 'boot.scr.uimg' or 'boot.scr' in the
+         respective boot partitions, describing how to boot the distro.
+
+if BOOTMETH_RAUC
+
+config BOOTMETH_RAUC_BOOT_ORDER
+       string "RAUC boot order"
+       default "A B"
+       help
+         Sets the default boot order. This must be list of space-separated
+         strings naming the individual boot slots. Each entry in this string
+         should correspond to an existing slot on the target's flash device.
+
+config BOOTMETH_RAUC_PARTITIONS
+       string "RAUC boot and root partitions indexes"
+       default "1,2 3,4"
+       help
+         Sets the partition indexes of boot and root slots. This must be a list
+         of comma-separated pair values, which in turn are separated by spaces.
+         The first value in pair is for the boot partition and second for the
+         root partition.
+
+config BOOTMETH_RAUC_DEFAULT_TRIES
+       int "RAUC slot default tries"
+       default 3
+       help
+         Sets how many times a slot should be tried booting, before considering
+         it to be bad.
+
+config BOOTMETH_RAUC_RESET_ALL_ZERO_TRIES
+       bool "Reset slot tries when all RAUC slots have zero tries left"
+       default y
+       help
+         When all slots have zero tries left or no valid slot was found, reset
+         to the default boot order set by BOOTMETH_RAUC_BOOT_ORDER and set the
+         slot tries to their default value specified by
+         BOOTMETH_RAUC_DEFAULT_TRIES.
+
+         This prevents a system from remaining in an unbootable state, after
+         all slot tries were decremented to zero.
+
+endif # BOOTMETH_RAUC
+
 config BOOTMETH_SANDBOX
        def_bool y
        depends on SANDBOX
diff --git a/boot/Makefile b/boot/Makefile
index 
71dafaefa76bca7896741dd896829575bd9e8f10..46f27e71856c598a866424cb93ee01444ea6a19a
 100644
--- a/boot/Makefile
+++ b/boot/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_$(PHASE_)BOOTMETH_EXTLINUX_PXE) += bootmeth_pxe.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_EFILOADER) += bootmeth_efi.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_CROS) += bootm.o bootm_os.o bootmeth_cros.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_QFW) += bootmeth_qfw.o
+obj-$(CONFIG_$(PHASE_)BOOTMETH_RAUC) += bootmeth_rauc.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_SANDBOX) += bootmeth_sandbox.o
 obj-$(CONFIG_$(PHASE_)BOOTMETH_SCRIPT) += bootmeth_script.o
 obj-$(CONFIG_$(PHASE_)CEDIT) += cedit.o
diff --git a/boot/bootmeth_rauc.c b/boot/bootmeth_rauc.c
new file mode 100644
index 
0000000000000000000000000000000000000000..4d48d643c0a727d5ebd5f3514c173f0dd824b7cd
--- /dev/null
+++ b/boot/bootmeth_rauc.c
@@ -0,0 +1,432 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Bootmethod for distro boot with RAUC
+ *
+ * Copyright 2025 PHYTEC Messtechnik GmbH
+ * Written by Martin Schwan <m.sch...@phytec.de>
+ */
+
+#define LOG_CATEGORY UCLASS_BOOTSTD
+
+#include <blk.h>
+#include <bootflow.h>
+#include <bootmeth.h>
+#include <bootstd.h>
+#include <dm.h>
+#include <env.h>
+#include <fs.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <string.h>
+#include <asm/cache.h>
+
+/* Length of env var "BOOT_*_LEFT" */
+#define BOOT_LEFT_LEN  (5 + 32 + 5)
+
+static const char * const script_names[] = { "boot.scr", "boot.scr.uimg", NULL 
};
+
+/**
+ * struct distro_rauc_slot - Slot information
+ *
+ * A slot describes the unit of a bootable system consisting of one or multiple
+ * partitions. This usually includes a root filesystem, kernel and potentially 
other
+ * files, like device trees and boot scripts for that particular distribution.
+ *
+ * @name       The slot name
+ * @boot_part  The boot partition number on disk
+ * @root_part  The root partition number on disk
+ */
+struct distro_rauc_slot {
+       char *name;
+       int boot_part;
+       int root_part;
+};
+
+/**
+ * struct distro_rauc_priv - Private data
+ *
+ * @slots      All slots of the device in default order
+ * @boot_order String of the current boot order containing the active slot 
names
+ */
+struct distro_rauc_priv {
+       struct distro_rauc_slot **slots;
+};
+
+static struct distro_rauc_slot *get_slot(struct distro_rauc_priv *priv,
+                                        const char *slot_name)
+{
+       int i;
+
+       for (i = 0; priv->slots[i]->name; i++) {
+               if (!strcmp(priv->slots[i]->name, slot_name))
+                       return priv->slots[i];
+       }
+
+       return NULL;
+}
+
+static int distro_rauc_check(struct udevice *dev, struct bootflow_iter *iter)
+{
+       /*
+        * This distro only works on whole MMC devices, as multiple partitions
+        * are needed for an A/B system.
+        */
+       if (bootflow_iter_check_mmc(iter))
+               return log_msg_ret("mmc", -EOPNOTSUPP);
+       if (iter->part)
+               return log_msg_ret("part", -EOPNOTSUPP);
+
+       return 0;
+}
+
+static int distro_rauc_scan_boot_part(struct bootflow *bflow)
+{
+       struct blk_desc *desc;
+       struct distro_rauc_priv *priv;
+       char *boot_order;
+       const char **boot_order_list;
+       bool exists;
+       int ret;
+       int i;
+       int j;
+
+       desc = dev_get_uclass_plat(bflow->blk);
+
+       priv = bflow->bootmeth_priv;
+       if (!priv || !priv->slots)
+               return log_msg_ret("priv", -EINVAL);
+
+       boot_order = env_get("BOOT_ORDER");
+       boot_order_list = str_to_list(boot_order);
+       for (i = 0; boot_order_list[i]; i++) {
+               exists = false;
+               for (j = 0; script_names[j]; j++) {
+                       const struct distro_rauc_slot *slot;
+
+                       slot = get_slot(priv, boot_order_list[i]);
+                       if (!slot)
+                               return log_msg_ret("env", -ENOENT);
+                       ret = fs_set_blk_dev_with_part(desc, slot->boot_part);
+                       if (ret)
+                               return log_msg_ret("blk", ret);
+                       exists |= fs_exists(script_names[j]);
+               }
+               if (!exists)
+                       return log_msg_ret("fs", -ENOENT);
+       }
+       str_free_list(boot_order_list);
+
+       return 0;
+}
+
+static int distro_rauc_read_bootflow(struct udevice *dev, struct bootflow 
*bflow)
+{
+       struct distro_rauc_priv *priv;
+       int ret;
+       char *slot;
+       int i;
+       char *partitions;
+       char *boot_order;
+       const char *default_boot_order;
+       const char **default_boot_order_list;
+       char *boot_order_copy;
+       char boot_left[BOOT_LEFT_LEN];
+       char *parts;
+
+       /* Get RAUC variables or set their default values */
+       boot_order = env_get("BOOT_ORDER");
+       if (!boot_order) {
+               log_debug("BOOT_ORDER did not exist yet, setting default 
value\n");
+               if (env_set("BOOT_ORDER", CONFIG_BOOTMETH_RAUC_BOOT_ORDER))
+                       return log_msg_ret("env", -EPERM);
+               boot_order = CONFIG_BOOTMETH_RAUC_BOOT_ORDER;
+       }
+       default_boot_order = CONFIG_BOOTMETH_RAUC_BOOT_ORDER;
+       default_boot_order_list = str_to_list(default_boot_order);
+       for (i = 0; default_boot_order_list[i]; i++) {
+               sprintf(boot_left, "BOOT_%s_LEFT", default_boot_order_list[i]);
+               if (!env_get(boot_left)) {
+                       log_debug("%s did not exist yet, setting default 
value\n",
+                                 boot_left);
+                       if (!env_set_ulong(boot_left, 
CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES))
+                               return log_msg_ret("env", -EPERM);
+               }
+       }
+       str_free_list(default_boot_order_list);
+
+       priv = calloc(1, sizeof(struct distro_rauc_priv));
+       if (!priv)
+               return log_msg_ret("buf", -ENOMEM);
+       priv->slots = calloc(1, sizeof(struct distro_rauc_slot));
+
+       /* Copy default boot_order, so we can leave the original unmodified */
+       boot_order_copy = strdup(default_boot_order);
+       partitions = strdup(CONFIG_BOOTMETH_RAUC_PARTITIONS);
+
+       for (i = 1;
+            (parts = strsep(&partitions, " ")) &&
+            (slot = strsep(&boot_order_copy, " "));
+            i++) {
+               struct distro_rauc_slot *s;
+
+               s = calloc(1, sizeof(struct distro_rauc_slot));
+               s->name = strdup(slot);
+               s->boot_part = simple_strtoul(strsep(&parts, ","), NULL, 10);
+               s->root_part = simple_strtoul(strsep(&parts, ","), NULL, 10);
+               priv->slots = realloc(priv->slots, (i + 1) *
+                                     sizeof(struct distro_rauc_slot));
+               priv->slots[i - 1] = s;
+               priv->slots[i]->name = NULL;
+       }
+
+       bflow->bootmeth_priv = priv;
+
+       ret = distro_rauc_scan_boot_part(bflow);
+       if (ret < 0) {
+               for (i = 0; priv->slots[i]->name; i++) {
+                       free(priv->slots[i]->name);
+                       free(priv->slots[i]);
+               }
+               free(priv);
+               free(boot_order_copy);
+               bflow->bootmeth_priv = NULL;
+               return ret;
+       }
+
+       bflow->state = BOOTFLOWST_READY;
+
+       return 0;
+}
+
+static int distro_rauc_read_file(struct udevice *dev, struct bootflow *bflow,
+                                const char *file_path, ulong addr,
+                                enum bootflow_img_t type, ulong *sizep)
+{
+       /*
+        * Reading individual files is not supported since we only operate on
+        * whole MMC devices (because we require multiple partitions).
+        */
+       return log_msg_ret("Unsupported", -ENOSYS);
+}
+
+static int distro_rauc_load_boot_script(struct bootflow *bflow,
+                                       const struct distro_rauc_slot *slot)
+{
+       struct blk_desc *desc;
+       struct distro_rauc_priv *priv;
+       struct udevice *bootstd;
+       const char *const *prefixes;
+       int ret;
+       int i;
+       int j;
+
+       ret = uclass_first_device_err(UCLASS_BOOTSTD, &bootstd);
+       if (ret)
+               return log_msg_ret("std", ret);
+       prefixes = bootstd_get_prefixes(bootstd);
+
+       desc = dev_get_uclass_plat(bflow->blk);
+       priv = bflow->bootmeth_priv;
+       if (!priv || !priv->slots)
+               return log_msg_ret("priv", -EINVAL);
+
+       bflow->part = slot->boot_part;
+       if (!bflow->part)
+               return log_msg_ret("part", -ENOENT);
+
+       ret = bootmeth_setup_fs(bflow, desc);
+       if (ret)
+               return log_msg_ret("set", ret);
+
+       for (i = 0; prefixes[i] && bflow->state != BOOTFLOWST_FILE; i++) {
+               for (j = 0; script_names[j] && bflow->state != BOOTFLOWST_FILE; 
j++) {
+                       if (!bootmeth_try_file(bflow, desc, prefixes[i], 
script_names[j])) {
+                               log_debug("Found file '%s%s' in %s.part_%x\n",
+                                         prefixes[i], script_names[j],
+                                         bflow->dev->name, bflow->part);
+                               bflow->subdir = strdup(prefixes[i]);
+                       }
+               }
+       }
+       if (bflow->state != BOOTFLOWST_FILE)
+               return log_msg_ret("file", -ENOENT);
+
+       ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN,
+                                 (enum bootflow_img_t)IH_TYPE_SCRIPT);
+       if (ret)
+               return log_msg_ret("read", ret);
+
+       return 0;
+}
+
+static int find_active_slot(char **slot_name, ulong *slot_tries)
+{
+       ulong tries;
+       char boot_left[BOOT_LEFT_LEN];
+       char *boot_order;
+       const char **boot_order_list;
+       bool slot_found = false;
+       int ret;
+       int i;
+
+       boot_order = env_get("BOOT_ORDER");
+       if (!boot_order)
+               return log_msg_ret("env", -ENOENT);
+       boot_order_list = str_to_list(boot_order);
+       for (i = 0; boot_order_list[i] && !slot_found; i++) {
+               sprintf(boot_left, "BOOT_%s_LEFT", boot_order_list[i]);
+               tries = env_get_ulong(boot_left, 10, ULONG_MAX);
+               if (tries == ULONG_MAX)
+                       return log_msg_ret("env", -ENOENT);
+
+               if (tries) {
+                       ret = env_set_ulong(boot_left, tries - 1);
+                       if (ret)
+                               return log_msg_ret("env", ret);
+                       *slot_name = strdup(boot_order_list[i]);
+                       *slot_tries = tries;
+                       slot_found = true;
+               }
+       }
+       str_free_list(boot_order_list);
+
+       if (!slot_found) {
+               if (IS_ENABLED(CONFIG_BOOTMETH_RAUC_RESET_ALL_ZERO_TRIES)) {
+                       log_warning("WARNING: No valid slot found\n");
+                       log_info("INFO: Resetting boot order and all slot 
tries\n");
+                       boot_order_list = 
str_to_list(CONFIG_BOOTMETH_RAUC_BOOT_ORDER);
+                       for (i = 0; boot_order_list[i]; i++) {
+                               sprintf(boot_left, "BOOT_%s_LEFT", 
boot_order_list[i]);
+                               ret = env_set_ulong(boot_left, 
CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES);
+                               if (ret)
+                                       return log_msg_ret("env", ret);
+                       }
+                       str_free_list(boot_order_list);
+                       ret = env_save();
+                       if (ret)
+                               return log_msg_ret("env", ret);
+                       do_reset(NULL, 0, 0, NULL);
+               }
+               log_err("ERROR: No valid slot found\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int distro_rauc_boot(struct udevice *dev, struct bootflow *bflow)
+{
+       struct blk_desc *desc;
+       struct distro_rauc_priv *priv;
+       const struct distro_rauc_slot *slot;
+       char *boot_order;
+       const char **boot_order_list;
+       char *active_slot;
+       ulong active_slot_tries;
+       char raucargs[64];
+       char boot_left[BOOT_LEFT_LEN];
+       ulong addr;
+       int ret = 0;
+       int i;
+
+       desc = dev_get_uclass_plat(bflow->blk);
+       if (desc->uclass_id != UCLASS_MMC)
+               return log_msg_ret("blk", -EINVAL);
+       priv = bflow->bootmeth_priv;
+
+       /* Device info variables */
+       ret = env_set("devtype", blk_get_devtype(bflow->blk));
+       if (ret)
+               return log_msg_ret("env", ret);
+
+       ret = env_set_hex("devnum", desc->devnum);
+       if (ret)
+               return log_msg_ret("env", ret);
+
+       /* Find active, valid slot */
+       ret = find_active_slot(&active_slot, &active_slot_tries);
+       if (ret)
+               return log_msg_ret("env", ret);
+
+       /* Kernel command line arguments */
+       sprintf(raucargs, "rauc.slot=%s", active_slot);
+       ret = env_set("raucargs", raucargs);
+       if (ret)
+               return log_msg_ret("env", ret);
+
+       /* Active slot info */
+       slot = get_slot(priv, active_slot);
+       if (!slot)
+               return log_msg_ret("env", -ENOENT);
+       ret = env_set_hex("distro_bootpart", slot->boot_part);
+       if (ret)
+               return log_msg_ret("env", ret);
+       ret = env_set_hex("distro_rootpart", slot->root_part);
+       if (ret)
+               return log_msg_ret("env", ret);
+       ret = env_save();
+       if (ret)
+               return log_msg_ret("env", ret);
+
+       /* Load distro boot script */
+       ret = distro_rauc_load_boot_script(bflow, slot);
+       if (ret)
+               return log_msg_ret("load", ret);
+
+       log_info("INFO: Booting slot %s, %lu of %d tries left\n",
+                active_slot, active_slot_tries, 
CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES);
+
+       log_debug("devtype: %s\n", env_get("devtype"));
+       log_debug("devnum: %s\n", env_get("devnum"));
+       log_debug("distro_bootpart: %s\n", env_get("distro_bootpart"));
+       log_debug("distro_rootpart: %s\n", env_get("distro_rootpart"));
+       log_debug("raucargs: %s\n", env_get("raucargs"));
+       boot_order = env_get("BOOT_ORDER");
+       if (!boot_order)
+               return log_msg_ret("env", -EPERM);
+       log_debug("BOOT_ORDER: %s\n", boot_order);
+       boot_order_list = str_to_list(boot_order);
+       for (i = 0; boot_order_list[i]; i++) {
+               sprintf(boot_left, "BOOT_%s_LEFT", boot_order_list[i]);
+               log_debug("%s: %s\n", boot_left, env_get(boot_left));
+       }
+       str_free_list(boot_order_list);
+
+       /* Run distro boot script */
+       addr = map_to_sysmem(bflow->buf);
+       ret = cmd_source_script(addr, NULL, NULL);
+       if (ret)
+               return log_msg_ret("boot", ret);
+
+       return 0;
+}
+
+static int distro_rauc_bootmeth_bind(struct udevice *dev)
+{
+       struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev);
+
+       plat->desc = "RAUC distro boot from MMC";
+       plat->flags = BOOTMETHF_GLOBAL;
+
+       return 0;
+}
+
+static struct bootmeth_ops distro_rauc_bootmeth_ops = {
+       .check          = distro_rauc_check,
+       .read_bootflow  = distro_rauc_read_bootflow,
+       .read_file      = distro_rauc_read_file,
+       .boot           = distro_rauc_boot,
+};
+
+static const struct udevice_id distro_rauc_bootmeth_ids[] = {
+       { .compatible = "u-boot,distro-rauc" },
+       { }
+};
+
+U_BOOT_DRIVER(bootmeth_rauc) = {
+       .name           = "bootmeth_rauc",
+       .id             = UCLASS_BOOTMETH,
+       .of_match       = distro_rauc_bootmeth_ids,
+       .ops            = &distro_rauc_bootmeth_ops,
+       .bind           = distro_rauc_bootmeth_bind,
+};

-- 
2.49.0

Reply via email to