diff options
author | Tom Rini <trini@konsulko.com> | 2022-10-31 14:43:04 -0400 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2022-10-31 14:43:04 -0400 |
commit | a90afc6730e6c67ad37f4c98a02891a93b4ff971 (patch) | |
tree | 724c085433631e142a56c052d667139cba29b4a6 /boot/vbe_simple_fw.c | |
parent | 6f38d91158e7e4199753b79e0a25c1a65175aba4 (diff) | |
parent | 77bec9e3d8bd2dc307447b92a3d5cefd693a62ad (diff) |
Merge branch '2022-10-31-vbe-implement-the-full-firmware-flow'
To quote Simon:
This series provides an implementation of VBE from TPL through to U-Boot
proper, using VBE to load the relevant firmware stages. It buils a single
image.bin file containing all the phases:
TPL - initial phase, loads VPL using binman symbols
VPL - main firmware phase, loads SPL using VBE parameters
SPL - loads U-Boot proper using VBE parameters
U-Boot - final firmware phase, where OS booting is processed
This series does not include the OS-booting phase. That will be the
subject of a future series.
The implementation is entirely handled by sandbox. It should be possible
to enable this on a real board without much effort, but that is also the
subject of a future series.
Diffstat (limited to 'boot/vbe_simple_fw.c')
-rw-r--r-- | boot/vbe_simple_fw.c | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c new file mode 100644 index 00000000000..0a49d286703 --- /dev/null +++ b/boot/vbe_simple_fw.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verified Boot for Embedded (VBE) loading firmware phases + * + * Copyright 2022 Google LLC + * Written by Simon Glass <sjg@chromium.org> + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include <common.h> +#include <bloblist.h> +#include <bootdev.h> +#include <bootflow.h> +#include <bootmeth.h> +#include <bootstage.h> +#include <dm.h> +#include <image.h> +#include <log.h> +#include <mapmem.h> +#include <memalign.h> +#include <mmc.h> +#include <spl.h> +#include <vbe.h> +#include <dm/device-internal.h> +#include "vbe_simple.h" + +/** + * vbe_simple_read_bootflow_fw() - Create a bootflow for firmware + * + * Locates and loads the firmware image (FIT) needed for the next phase. The FIT + * should ideally use external data, to reduce the amount of it that needs to be + * read. + * + * @bdev: bootdev device containing the firmwre + * @meth: VBE simple bootmeth + * @blow: Place to put the created bootflow, on success + * @return 0 if OK, -ve on error + */ +int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN); + struct udevice *media = dev_get_parent(bflow->dev); + struct udevice *meth = bflow->method; + struct simple_priv *priv = dev_get_priv(meth); + const char *fit_uname, *fit_uname_config; + struct bootm_headers images = {}; + ulong offset, size, blknum, addr, len, load_addr, num_blks; + enum image_phase_t phase; + struct blk_desc *desc; + struct udevice *blk; + int node, ret; + void *buf; + + log_debug("media=%s\n", media->name); + ret = blk_get_from_parent(media, &blk); + if (ret) + return log_msg_ret("med", ret); + log_debug("blk=%s\n", blk->name); + desc = dev_get_uclass_plat(blk); + + offset = priv->area_start + priv->skip_offset; + + /* read in one block to find the FIT size */ + blknum = offset / desc->blksz; + log_debug("read at %lx, blknum %lx\n", offset, blknum); + ret = blk_read(blk, blknum, 1, sbuf); + if (ret < 0) + return log_msg_ret("rd", ret); + + ret = fdt_check_header(sbuf); + if (ret < 0) + return log_msg_ret("fdt", -EINVAL); + size = fdt_totalsize(sbuf); + if (size > priv->area_size) + return log_msg_ret("fdt", -E2BIG); + log_debug("FIT size %lx\n", size); + + /* + * Load the FIT into the SPL memory. This is typically a FIT with + * external data, so this is quite small, perhaps a few KB. + */ + addr = CONFIG_VAL(TEXT_BASE); + buf = map_sysmem(addr, size); + num_blks = DIV_ROUND_UP(size, desc->blksz); + log_debug("read %lx, %lx blocks to %lx / %p\n", size, num_blks, addr, + buf); + ret = blk_read(blk, blknum, num_blks, buf); + if (ret < 0) + return log_msg_ret("rd", ret); + + /* figure out the phase to load */ + phase = IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT; + + /* + * Load the image from the FIT. We ignore any load-address information + * so in practice this simply locates the image in the external-data + * region and returns its address and size. Since we only loaded the FIT + * itself, only a part of the image will be present, at best. + */ + fit_uname = NULL; + fit_uname_config = NULL; + log_debug("loading FIT\n"); + ret = fit_image_load(&images, addr, &fit_uname, &fit_uname_config, + IH_ARCH_SANDBOX, image_ph(phase, IH_TYPE_FIRMWARE), + BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED, + &load_addr, &len); + if (ret < 0) + return log_msg_ret("ld", ret); + node = ret; + log_debug("loaded to %lx\n", load_addr); + + /* For FIT external data, read in the external data */ + if (load_addr + len > addr + size) { + ulong base, full_size; + void *base_buf; + + /* Find the start address to load from */ + base = ALIGN_DOWN(load_addr, desc->blksz); + + /* + * Get the total number of bytes to load, taking care of + * block alignment + */ + full_size = load_addr + len - base; + + /* + * Get the start block number, number of blocks and the address + * to load to, then load the blocks + */ + blknum = (offset + base - addr) / desc->blksz; + num_blks = DIV_ROUND_UP(full_size, desc->blksz); + base_buf = map_sysmem(base, full_size); + ret = blk_read(blk, blknum, num_blks, base_buf); + log_debug("read %lx %lx, %lx blocks to %lx / %p: ret=%d\n", + blknum, full_size, num_blks, base, base_buf, ret); + if (ret < 0) + return log_msg_ret("rd", ret); + } + + /* set up the bootflow with the info we obtained */ + bflow->name = strdup(fdt_get_name(buf, node, NULL)); + if (!bflow->name) + return log_msg_ret("name", -ENOMEM); + bflow->blk = blk; + bflow->buf = map_sysmem(load_addr, len); + bflow->size = len; + + return 0; +} + +static int simple_load_from_image(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev) +{ + struct udevice *meth, *bdev; + struct simple_priv *priv; + struct bootflow bflow; + struct vbe_handoff *handoff; + int ret; + + if (spl_phase() != PHASE_VPL && spl_phase() != PHASE_SPL) + return -ENOENT; + + ret = bloblist_ensure_size(BLOBLISTT_VBE, sizeof(struct vbe_handoff), + 0, (void **)&handoff); + if (ret) + return log_msg_ret("ro", ret); + + vbe_find_first_device(&meth); + if (!meth) + return log_msg_ret("vd", -ENODEV); + log_debug("vbe dev %s\n", meth->name); + ret = device_probe(meth); + if (ret) + return log_msg_ret("probe", ret); + + priv = dev_get_priv(meth); + log_debug("simple %s\n", priv->storage); + ret = bootdev_find_by_label(priv->storage, &bdev); + if (ret) + return log_msg_ret("bd", ret); + log_debug("bootdev %s\n", bdev->name); + + bootflow_init(&bflow, bdev, meth); + ret = bootmeth_read_bootflow(meth, &bflow); + log_debug("\nfw ret=%d\n", ret); + if (ret) + return log_msg_ret("rd", ret); + + /* jump to the image */ + spl_image->flags = SPL_SANDBOXF_ARG_IS_BUF; + spl_image->arg = bflow.buf; + spl_image->size = bflow.size; + log_debug("Image: %s at %p size %x\n", bflow.name, bflow.buf, + bflow.size); + + /* this is not used from now on, so free it */ + bootflow_free(&bflow); + + /* Record that VBE was used in this phase */ + handoff->phases |= 1 << spl_phase(); + + return 0; +} +SPL_LOAD_IMAGE_METHOD("vbe_simple", 5, BOOT_DEVICE_VBE, + simple_load_from_image); |