// SPDX-License-Identifier: GPL-2.0 /* * Verified Boot for Embedded (VBE) loading firmware phases * * Copyright 2022 Google LLC * Written by Simon Glass */ #define LOG_CATEGORY LOGC_BOOT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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, NULL); 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);