diff options
-rw-r--r-- | boot/Kconfig | 50 | ||||
-rw-r--r-- | boot/Makefile | 1 | ||||
-rw-r--r-- | boot/bootmeth_rauc.c | 432 | ||||
-rw-r--r-- | doc/develop/bootstd/index.rst | 1 | ||||
-rw-r--r-- | doc/develop/bootstd/overview.rst | 1 | ||||
-rw-r--r-- | doc/develop/bootstd/rauc.rst | 56 |
6 files changed, 541 insertions, 0 deletions
diff --git a/boot/Kconfig b/boot/Kconfig index 30eb5b328d7..f5dfae28f08 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -855,6 +855,56 @@ 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" + 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 e0d1579827d..3da6f7a0914 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 00000000000..fc60e6e355d --- /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.schwan@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, +}; diff --git a/doc/develop/bootstd/index.rst b/doc/develop/bootstd/index.rst index 4c4e26ccdb7..ec74fc2fb9d 100644 --- a/doc/develop/bootstd/index.rst +++ b/doc/develop/bootstd/index.rst @@ -12,5 +12,6 @@ Standard Boot qfw android cros + rauc script sandbox diff --git a/doc/develop/bootstd/overview.rst b/doc/develop/bootstd/overview.rst index 9fe5630ab16..0a237359575 100644 --- a/doc/develop/bootstd/overview.rst +++ b/doc/develop/bootstd/overview.rst @@ -443,6 +443,7 @@ Bootmeth drivers are provided for booting from various media: - :doc:`extlinux / syslinux <extlinux>` boot from a storage device - :doc:`extlinux / syslinux <extlinux>` boot from a network (PXE) - :doc:`sandbox <sandbox>` used only for testing + - :doc:`RAUC distro <rauc>`: A/B system with RAUC from MMC - :doc:`U-Boot scripts <script>` from disk, network or SPI flash - :doc:`QFW <qfw>`: QEMU firmware interface - :doc:`VBE </develop/vbe>`: Verified Boot for Embedded diff --git a/doc/develop/bootstd/rauc.rst b/doc/develop/bootstd/rauc.rst new file mode 100644 index 00000000000..b2661d18da4 --- /dev/null +++ b/doc/develop/bootstd/rauc.rst @@ -0,0 +1,56 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +RAUC Bootmeth +============= + +This bootmeth provides a way to locate and run an A/B system with RAUC as its +update client. The booted distro must supply a script on an MMC device +containing the final boot instructions necessary. + +This bootmeth assumes a symmetric A/B partition layout, with a separate boot +partition containing the kernel image and another partition for the root +filesystem each. The partition numbers must be specified with +``CONFIG_BOOTMETH_RAUC_PARTITIONS``. The content must be a list of pairs, with +the following syntax: ``1,2 3,4``, where 1 and 3 are the slots' boot partition +and 2 and 4 the slots' root partition. + +Each pair of boot and rootfs partition form a "slot". The default order in which +available slots are tried is set through ``CONFIG_BOOTMETH_RAUC_BOOT_ORDER``, +with the left one tried first. + +The default number of boot tries of each slot is set by +``CONFIG_BOOTMETH_RAUC_DEFAULT_TRIES``. + +In case no valid slot can be found and/or all slots have zero tries left, the +boot order and slot tries are reset to their default values, if +``CONFIG_BOOTMETH_RAUC_RESET_ALL_ZERO_TRIES`` is enabled. This prevents a system +from locking up in the bootloader and tries booting again after a specified +number of tries. + +The boot script must be located in each boot partition. The bootmeth searches +for "boot.scr.uimg" first, then "boot.scr" if not found. + +When the bootflow is booted, the bootmeth sets these environment variables: + +devtype + device type (e.g. "mmc") + +devnum + device number, corresponding to the device 'sequence' number + ``dev_seq(dev)`` + +distro_bootpart + partition number of the boot partition on the device (numbered from 1) + +distro_rootpart + partition number of the rootfs partition on the device (numbered from 1) + +raucargs + kernel command line arguments needed for RAUC to detect the currently booted + slot + +The script file must be a FIT or a legacy uImage. It is loaded into memory and +executed. + +The compatible string "u-boot,distro-rauc" is used for the driver. It is present +if ``CONFIG_BOOTMETH_RAUC`` is enabled. |