diff options
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); |