Files
u-boot/boot/vbe_abrec_os.c
Simon Glass 68c0c6f27e 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>
2025-08-04 14:40:00 -06:00

294 lines
6.5 KiB
C

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