
From: Simon Glass <sjg@chromium.org> When booting an OS it is useful to be able to read devicetrees provided by the OEM, from a separate FIT to the OS. Add a new method which supports this, along with the usual A/B/recovery flows, using a state file on the boot device. Signed-off-by: Simon Glass <sjg@chromium.org> --- boot/Kconfig | 12 ++ boot/bootflow.c | 2 + boot/vbe_abrec.c | 2 +- boot/vbe_abrec.h | 5 + boot/vbe_abrec_fw.c | 1 + boot/vbe_abrec_os.c | 293 ++++++++++++++++++++++++++++++++++++++++++++ include/bootflow.h | 8 +- include/vbe.h | 13 ++ 8 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 boot/vbe_abrec_os.c diff --git a/boot/Kconfig b/boot/Kconfig index 45fe5a07696..6f42cd7ec23 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -818,6 +818,18 @@ config BOOTMETH_VBE_ABREC verification step, a recovery image is booted. This method will eventually provide rollback protection as well. +config BOOTMETH_VBE_ABREC_OS + bool "Bootdev support for VBE 'a/b/recovery' method for the OS" + imply SPL_CRC8 + imply VPL_CRC8 + help + Enables support for VBE 'abrec' boot. This allows updating one of an + A or B Operating System in boot media such as MMC. The new OS is + tried and if it boots, it is copied to the other image, so that both + A and B have the same version. If neither firmware image passes the + verification step, a recovery image is booted. This method will + eventually provide rollback protection as well. + if BOOTMETH_VBE_SIMPLE config BOOTMETH_VBE_SIMPLE_OS diff --git a/boot/bootflow.c b/boot/bootflow.c index 2e77875c52f..c088300ea96 100644 --- a/boot/bootflow.c +++ b/boot/bootflow.c @@ -28,6 +28,8 @@ static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = { "logo", "efi", "cmdline", + "vbe-state", + "vbe-oem-fit", }; /** diff --git a/boot/vbe_abrec.c b/boot/vbe_abrec.c index 3b5f75421ff..4736e6147d2 100644 --- a/boot/vbe_abrec.c +++ b/boot/vbe_abrec.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Verified Boot for Embedded (VBE) 'abrec' method + * Verified Boot for Embedded (VBE) 'abrec' method (for firmware) * * Copyright 2024 Google LLC * Written by Simon Glass <sjg@chromium.org> diff --git a/boot/vbe_abrec.h b/boot/vbe_abrec.h index 63c73297351..590ad3cfacb 100644 --- a/boot/vbe_abrec.h +++ b/boot/vbe_abrec.h @@ -17,6 +17,9 @@ struct bootflow; struct udevice; +#define VBE_STATE_FNAME "vbe-state" +#define VBE_OEM_FIT_FNAME "oem.fit" + /** * struct abrec_priv - information read from the device tree * @@ -30,6 +33,7 @@ struct udevice; * @version_offset: Offset from from area_start of the VBE version info * @version_size: Size of the version info * @storage: Storage device to use, in the form <uclass><devnum>, e.g. "mmc1" + * @oem_devicetree: true if we should read an OEM devicetree */ struct abrec_priv { u32 area_start; @@ -40,6 +44,7 @@ struct abrec_priv { u32 version_offset; u32 version_size; const char *storage; + bool oem_devicetree; }; /** struct abrec_state - state information read from media diff --git a/boot/vbe_abrec_fw.c b/boot/vbe_abrec_fw.c index d52bd9ddff0..2a11abc967a 100644 --- a/boot/vbe_abrec_fw.c +++ b/boot/vbe_abrec_fw.c @@ -150,6 +150,7 @@ static int abrec_run_vpl(struct udevice *blk, struct spl_image_info *image, ub_size = binman_sym(ulong, u_boot_b, size); break; case VBEP_RECOVERY: + case VBEP_COUNT: offset = binman_sym(ulong, spl_recovery, image_pos); size = binman_sym(ulong, spl_recovery, size); ub_offset = binman_sym(ulong, u_boot_recovery, image_pos); diff --git a/boot/vbe_abrec_os.c b/boot/vbe_abrec_os.c new file mode 100644 index 00000000000..53eb3d79aca --- /dev/null +++ b/boot/vbe_abrec_os.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Verified Boot for Embedded (VBE) 'abrec' method (for OS) + * + * Copyright 2025 Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include <bootm.h> +#include <bootmeth.h> +#include <dm.h> +#include <extlinux.h> +#include <fs_legacy.h> +#include <memalign.h> +#include <mmc.h> +#include <dm/ofnode.h> +#include "vbe_abrec.h" + +static const char *const pick_names[VBEP_COUNT] = {"a", "b", "recovery"}; + +static int vbe_abrec_read_check(struct udevice *dev, struct bootflow_iter *iter) +{ + int ret; + + /* This only works on block devices */ + ret = bootflow_iter_check_blk(iter); + if (ret) + return log_msg_ret("blk", ret); + + return 0; +} + +static enum vbe_pick_t find_pick(const char *name) +{ + int i; + + for (i = 0; i < VBEP_COUNT; i++) + if (!strcmp(pick_names[i], name)) + return i; + + return -1; +} + +static int vbe_abrec_getfile(struct pxe_context *ctx, const char *file_path, + char *file_addr, enum bootflow_img_t type, + ulong *sizep) +{ + struct extlinux_info *info = ctx->userdata; + ulong addr; + int ret; + + addr = simple_strtoul(file_addr, NULL, 16); + + /* Allow up to 1GB */ + *sizep = 1 << 30; + ret = bootmeth_read_file(info->dev, info->bflow, file_path, addr, + type, sizep); + if (ret) + return log_msg_ret("read", ret); + + return 0; +} + +static int decode_state(struct vbe_bflow_priv *priv, oftree tree) +{ + ofnode root, next, osn; + const char *slot; + int ret; + + if (!oftree_valid(tree)) + return log_msg_ret("vtr", -ENOENT); + + root = oftree_root(tree); + if (strcmp("vbe,abrec-state", ofnode_read_string(root, "compatible"))) + return log_msg_ret("vco", -ENOENT); + + osn = ofnode_find_subnode(root, "os"); + if (!oftree_valid(tree)) + return log_msg_ret("vos", -ENOENT); + + next = ofnode_find_subnode(osn, "next-boot"); + if (!oftree_valid(tree)) + return log_msg_ret("vnn", -ENOENT); + + slot = ofnode_read_string(next, "slot"); + if (!slot) + return log_msg_ret("vnn", -ENOENT); + + ret = find_pick(slot); + if (ret == -1) + return log_msg_ret("vsl", -EINVAL); + priv->pick_slot = ret; + + return 0; +} + +static int vbe_abrec_read_bootflow(struct udevice *dev, struct bootflow *bflow) +{ + struct abrec_priv *priv = dev_get_priv(dev); + struct vbe_bflow_priv *bfpriv; + struct blk_desc *desc; + struct abuf buf, oem; + char subdir[20]; + oftree tree; + int ret; + + // log_debug("part %d\n", bflow->part); + /* we expect a boot partition; for now we assume it is partition 2 */ + if (bflow->part != 2) { + // log_debug("wrong partition\n"); + return -ENOENT; + } + bflow->state = BOOTFLOWST_FS; + + desc = dev_get_uclass_plat(bflow->blk); + + bflow->subdir = strdup(""); + if (!bflow->subdir) + return log_msg_ret("bss", -ENOMEM); + + ret = bootmeth_alloc_other(bflow, VBE_STATE_FNAME, BFI_VBE_STATE, &buf); + if (ret) + return log_msg_ret("bst", ret); + if (!buf.size) { + ret = log_msg_ret("bst", -ENOENT); + goto err_buf; + } + + bflow->state = BOOTFLOWST_FILE; + + bfpriv = calloc(1, sizeof(struct vbe_bflow_priv)); + if (!bfpriv) { + ret = log_msg_ret("val", -ENOMEM); + goto err_priv; + } + bflow->bootmeth_priv = bfpriv; + + tree = oftree_from_fdt(buf.data); + if (!oftree_valid(tree)) { + ret = log_msg_ret("vtr", -ENOENT); + goto err_tree; + } + + ret = decode_state(bfpriv, tree); + oftree_dispose(tree); + if (ret) { + ret = log_msg_ret("vds", ret); + goto err_decode; + } + + printf("VBE: Picked slot %s\n", pick_names[bfpriv->pick_slot]); + ret = bootmeth_setup_fs(bflow, desc); + if (ret) { + ret = log_msg_ret("vsf", ret); + goto err_fs; + } + + snprintf(subdir, sizeof(subdir), "%s/", pick_names[bfpriv->pick_slot]); + free(bflow->subdir); + bflow->subdir = strdup(subdir); + if (!bflow->subdir) { + ret = log_msg_ret("vsd", -ENOMEM); + if (ret) + goto err_fs; + } + + ret = bootmeth_try_file(bflow, desc, subdir, EXTLINUX_FNAME); + if (ret) { + log_debug("part %d: ret %d\n", bflow->part, ret); + ret = log_msg_ret("vtr", ret); + goto err_file; + } + + ret = bootmeth_alloc_file(bflow, 0x10000, ARCH_DMA_MINALIGN, + BFI_EXTLINUX_CFG); + if (ret) { + ret = log_msg_ret("vaf", ret); + goto err_file; + } + + if (priv->oem_devicetree) { + /* Locate the OEM FIT in the same slot, if it exists */ + ret = bootmeth_alloc_other(bflow, VBE_OEM_FIT_FNAME, BFI_VBE_OEM_FIT, + &oem); + if (ret == -ENOMEM) { + ret = log_msg_ret("bst", ret); + goto err_file; + } + } + + bflow->state = BOOTFLOWST_READY; + + return 0; + +err_file: +err_fs: +err_decode: +err_tree: + free(priv); +err_priv: +err_buf: + abuf_uninit(&buf); + + return ret; +} + +static int vbe_abrec_boot(struct udevice *dev, struct bootflow *bflow) +{ + const struct bootflow_img *img; + int ret; + + /* load the devicetree first */ + img = bootflow_img_find(bflow, BFI_VBE_OEM_FIT); + if (img) { + struct bootm_info bmi; + char addr_str[30]; + int states; + + printf("Loading OEM devicetree from FIT\n"); + bootm_init(&bmi); + snprintf(addr_str, sizeof(addr_str), "%lx", img->addr); + bmi.addr_img = addr_str; + bmi.cmd_name = "vbe_os"; + states = BOOTM_STATE_START | BOOTM_STATE_FINDOS | + BOOTM_STATE_PRE_LOAD | BOOTM_STATE_FINDOTHER | + BOOTM_STATE_LOADOS; + ret = bootm_run_states(&bmi, states); + + /* we should get -ENOPKG, indicating that there was no OS */ + if (ret != -ENOPKG) + return log_msg_ret("vab", ret ? ret : -EFAULT); + } + + printf("Loading OS FIT%s\n", img ? " keeping existing FDT" : ""); + + return extlinux_boot(dev, bflow, vbe_abrec_getfile, true, bflow->fname, + img); +} + +#if CONFIG_IS_ENABLED(BOOTSTD_FULL) +static int vbe_abrec_read_all(struct udevice *dev, struct bootflow *bflow) +{ + return extlinux_read_all(dev, bflow, vbe_abrec_getfile, true, + bflow->fname); +} +#endif + +static struct bootmeth_ops bootmeth_vbe_abrec_os_ops = { + .check = vbe_abrec_read_check, + .read_file = bootmeth_common_read_file, + .read_bootflow = vbe_abrec_read_bootflow, + .boot = vbe_abrec_boot, +#if CONFIG_IS_ENABLED(BOOTSTD_FULL) + .read_all = vbe_abrec_read_all, +#endif +}; + +static int bootmeth_vbe_abrec_os_probe(struct udevice *dev) +{ + struct abrec_priv *priv = dev_get_priv(dev); + + priv->oem_devicetree = true; + + return 0; +} + +static int bootmeth_vbe_abrec_os_bind(struct udevice *dev) +{ + struct bootmeth_uc_plat *plat = dev_get_uclass_plat(dev); + + plat->desc = "VBE A/B/recovery for OS"; + + return 0; +} + +#if CONFIG_IS_ENABLED(OF_REAL) +static const struct udevice_id vbe_abrec_os_ids[] = { + { .compatible = "vbe,abrec-os" }, + { } +}; +#endif + +U_BOOT_DRIVER(vbe_abrec_os) = { + .name = "vbe_abrec_os", + .id = UCLASS_BOOTMETH, + .of_match = of_match_ptr(vbe_abrec_os_ids), + .ops = &bootmeth_vbe_abrec_os_ops, + .bind = bootmeth_vbe_abrec_os_bind, + .probe = bootmeth_vbe_abrec_os_probe, + .priv_auto = sizeof(struct abrec_priv), + .plat_auto = sizeof(struct extlinux_plat) +}; diff --git a/include/bootflow.h b/include/bootflow.h index d2ffa3c5aad..ec5baff7e01 100644 --- a/include/bootflow.h +++ b/include/bootflow.h @@ -120,12 +120,16 @@ struct bootflow { /** * bootflow_img_t: Supported image types * - * This uses image_type_t for most types, but extends it + * This uses image_type_t for most types, but extends it. See the names in + * bootflow_img[] * * @BFI_EXTLINUX_CFG: extlinux configuration-file * @BFI_LOGO: logo image * @BFI_EFI: EFI PE image * @BFI_CMDLINE: OS command-line string + * @BFI_VBE_STATE: Verified Boot for Embedded (VBE) state + * @BFI_VBE_OEM_FIT: Verified Boot for Embedded (VBE) OEM FIT containing + * devicetrees */ enum bootflow_img_t { BFI_FIRST = IH_TYPE_COUNT, @@ -133,6 +137,8 @@ enum bootflow_img_t { BFI_LOGO, BFI_EFI, BFI_CMDLINE, + BFI_VBE_STATE, + BFI_VBE_OEM_FIT, BFI_COUNT, }; diff --git a/include/vbe.h b/include/vbe.h index 84a996cdf7f..2f5e8b78471 100644 --- a/include/vbe.h +++ b/include/vbe.h @@ -31,6 +31,8 @@ enum vbe_phase_t { /** * enum vbe_pick_t - indicates which firmware is picked * + * The names are in pick_names[] + * * @VBEFT_A: Firmware A * @VBEFT_B: Firmware B * @VBEFT_RECOVERY: Recovery firmware @@ -39,6 +41,8 @@ enum vbe_pick_t { VBEP_A, VBEP_B, VBEP_RECOVERY, + + VBEP_COUNT, }; /** @@ -71,6 +75,15 @@ static inline enum vbe_phase_t vbe_phase(void) return VBE_PHASE_OS; } +/** + * struct vbe_bflow_priv - Information attached to the VBE bootflow + * + * @pick: Boot path to pick for the current boot + */ +struct vbe_bflow_priv { + enum vbe_pick_t pick_slot; +}; + /** * vbe_list() - List the VBE bootmeths * -- 2.43.0