boot: Add support for VBE for the OS

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>
This commit is contained in:
Simon Glass
2025-08-02 10:02:27 +12:00
parent 324a3ac00a
commit 68c0c6f27e
8 changed files with 334 additions and 2 deletions

View File

@@ -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

View File

@@ -28,6 +28,8 @@ static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = {
"logo",
"efi",
"cmdline",
"vbe-state",
"vbe-oem-fit",
};
/**

View File

@@ -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>

View File

@@ -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

View File

@@ -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);

293
boot/vbe_abrec_os.c Normal file
View File

@@ -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)
};

View File

@@ -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,
};

View File

@@ -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
*