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>
294 lines
6.5 KiB
C
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)
|
|
};
|