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:
12
boot/Kconfig
12
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
|
||||
|
||||
@@ -28,6 +28,8 @@ static const char *const bootflow_img[BFI_COUNT - BFI_FIRST] = {
|
||||
"logo",
|
||||
"efi",
|
||||
"cmdline",
|
||||
"vbe-state",
|
||||
"vbe-oem-fit",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
293
boot/vbe_abrec_os.c
Normal 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)
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user