From a1c456d199160dae463152858e5c1d642bcc811c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:02 -0700 Subject: [PATCH 01/22] vbe: Use blk_read() to read blocks We should not be using the old blk_d...() interface, is only there to aid migration to driver model. Move to blk_read() instead. Changes in v2: - Split patch into several pieces Signed-off-by: Simon Glass --- boot/vbe_simple.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index 189e86d2a22..dc4e98d13c5 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -44,7 +44,7 @@ static int simple_read_version(struct udevice *dev, struct blk_desc *desc, return log_msg_ret("get", -EBADF); start /= MMC_MAX_BLOCK_LEN; - if (blk_dread(desc, start, 1, buf) != 1) + if (blk_read(desc->bdev, start, 1, buf) != 1) return log_msg_ret("read", -EIO); strlcpy(state->fw_version, buf, MAX_VERSION_LEN); log_debug("version=%s\n", state->fw_version); @@ -68,7 +68,7 @@ static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, return log_msg_ret("get", -EBADF); start /= MMC_MAX_BLOCK_LEN; - if (blk_dread(desc, start, 1, buf) != 1) + if (blk_read(desc->bdev, start, 1, buf) != 1) return log_msg_ret("read", -EIO); nvd = (struct simple_nvdata *)buf; hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; From 20a1e837325a30828aa55ac15ec149ee337922f2 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:03 -0700 Subject: [PATCH 02/22] vbe: Start a common header file Move a few things into a new, common header file so that vbe-simple can share code with the upcoming abrec. Put struct simple_nvdata in it and rename it. Signed-off-by: Simon Glass --- boot/vbe_common.h | 51 +++++++++++++++++++++++++++++++++++++++++++++++ boot/vbe_simple.c | 13 ++---------- boot/vbe_simple.h | 16 ++------------- 3 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 boot/vbe_common.h diff --git a/boot/vbe_common.h b/boot/vbe_common.h new file mode 100644 index 00000000000..1fc70ee74c8 --- /dev/null +++ b/boot/vbe_common.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Verified Boot for Embedded (VBE) common functions + * + * Copyright 2024 Google LLC + * Written by Simon Glass + */ + +#ifndef __VBE_COMMON_H +#define __VBE_COMMON_H + +#include + +struct udevice; + +enum { + MAX_VERSION_LEN = 256, + + NVD_HDR_VER_SHIFT = 0, + NVD_HDR_VER_MASK = 0xf, + NVD_HDR_SIZE_SHIFT = 4, + NVD_HDR_SIZE_MASK = 0xf << NVD_HDR_SIZE_SHIFT, + + /* Firmware key-version is in the top 16 bits of fw_ver */ + FWVER_KEY_SHIFT = 16, + FWVER_FW_MASK = 0xffff, + + NVD_HDR_VER_CUR = 1, /* current version */ +}; + +/** + * struct vbe_nvdata - basic storage format for non-volatile data + * + * This is used for all VBE methods + * + * @crc8: crc8 for the entire record except @crc8 field itself + * @hdr: header size and version (NVD_HDR_...) + * @spare1: unused, must be 0 + * @fw_vernum: version and key version (FWVER_...) + * @flags: Flags controlling operation (enum vbe_flags) + */ +struct vbe_nvdata { + u8 crc8; + u8 hdr; + u16 spare1; + u32 fw_vernum; + u32 flags; + u8 spare2[0x34]; +}; + +#endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index dc4e98d13c5..aeb33f5cbbc 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -21,15 +21,6 @@ #include #include "vbe_simple.h" -/** struct simple_nvdata - storage format for non-volatile data */ -struct simple_nvdata { - u8 crc8; - u8 hdr; - u16 spare1; - u32 fw_vernum; - u8 spare2[0x38]; -}; - static int simple_read_version(struct udevice *dev, struct blk_desc *desc, u8 *buf, struct simple_state *state) { @@ -57,7 +48,7 @@ static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, { struct simple_priv *priv = dev_get_priv(dev); uint hdr_ver, hdr_size, size, crc; - const struct simple_nvdata *nvd; + const struct vbe_nvdata *nvd; int start; if (priv->state_size > MMC_MAX_BLOCK_LEN) @@ -70,7 +61,7 @@ static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, if (blk_read(desc->bdev, start, 1, buf) != 1) return log_msg_ret("read", -EIO); - nvd = (struct simple_nvdata *)buf; + nvd = (struct vbe_nvdata *)buf; hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT; if (hdr_ver != NVD_HDR_VER_CUR) diff --git a/boot/vbe_simple.h b/boot/vbe_simple.h index 56d319206f2..dc3f70052b0 100644 --- a/boot/vbe_simple.h +++ b/boot/vbe_simple.h @@ -9,20 +9,8 @@ #ifndef __VBE_SIMPLE_H #define __VBE_SIMPLE_H -enum { - MAX_VERSION_LEN = 256, - - NVD_HDR_VER_SHIFT = 0, - NVD_HDR_VER_MASK = 0xf, - NVD_HDR_SIZE_SHIFT = 4, - NVD_HDR_SIZE_MASK = 0xf << NVD_HDR_SIZE_SHIFT, - - /* Firmware key-version is in the top 16 bits of fw_ver */ - FWVER_KEY_SHIFT = 16, - FWVER_FW_MASK = 0xffff, - - NVD_HDR_VER_CUR = 1, /* current version */ -}; +#include +#include "vbe_common.h" /** struct simple_priv - information read from the device tree */ struct simple_priv { From b407b3e7d4a2bdd983c1836b2c6e3295a8fb7eca Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:04 -0700 Subject: [PATCH 03/22] vbe: Use a block device instead of descriptor Pass a struct udevice instead of the descriptor structure, since this is the native argument for blk_read() Signed-off-by: Simon Glass --- boot/vbe_simple.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index aeb33f5cbbc..937d5392bc8 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -21,7 +21,7 @@ #include #include "vbe_simple.h" -static int simple_read_version(struct udevice *dev, struct blk_desc *desc, +static int simple_read_version(struct udevice *dev, struct udevice *blk, u8 *buf, struct simple_state *state) { struct simple_priv *priv = dev_get_priv(dev); @@ -35,7 +35,7 @@ static int simple_read_version(struct udevice *dev, struct blk_desc *desc, return log_msg_ret("get", -EBADF); start /= MMC_MAX_BLOCK_LEN; - if (blk_read(desc->bdev, start, 1, buf) != 1) + if (blk_read(blk, start, 1, buf) != 1) return log_msg_ret("read", -EIO); strlcpy(state->fw_version, buf, MAX_VERSION_LEN); log_debug("version=%s\n", state->fw_version); @@ -43,7 +43,7 @@ static int simple_read_version(struct udevice *dev, struct blk_desc *desc, return 0; } -static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, +static int simple_read_nvdata(struct udevice *dev, struct udevice *blk, u8 *buf, struct simple_state *state) { struct simple_priv *priv = dev_get_priv(dev); @@ -59,7 +59,7 @@ static int simple_read_nvdata(struct udevice *dev, struct blk_desc *desc, return log_msg_ret("get", -EBADF); start /= MMC_MAX_BLOCK_LEN; - if (blk_read(desc->bdev, start, 1, buf) != 1) + if (blk_read(blk, start, 1, buf) != 1) return log_msg_ret("read", -EIO); nvd = (struct vbe_nvdata *)buf; hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; @@ -85,6 +85,7 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); struct simple_priv *priv = dev_get_priv(dev); struct blk_desc *desc; + struct udevice *blk; char devname[16]; const char *end; int devnum; @@ -104,11 +105,12 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) if (!desc) return log_msg_ret("get", -ENXIO); - ret = simple_read_version(dev, desc, buf, state); + blk = desc->bdev; + ret = simple_read_version(dev, blk, buf, state); if (ret) return log_msg_ret("ver", ret); - ret = simple_read_nvdata(dev, desc, buf, state); + ret = simple_read_nvdata(dev, blk, buf, state); if (ret) return log_msg_ret("nvd", ret); From 00f521903ee3768ef2327dbcfdac9029f243f3cd Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:05 -0700 Subject: [PATCH 04/22] vbe: Pass simple_priv to internal functions Pass the private data instead of the device, to help the compiler optimise better. This saves 16 bytes of code on pinecube (rk3288) Signed-off-by: Simon Glass --- boot/vbe_simple.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index 937d5392bc8..1b9cf276921 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -21,10 +21,10 @@ #include #include "vbe_simple.h" -static int simple_read_version(struct udevice *dev, struct udevice *blk, - u8 *buf, struct simple_state *state) +static int simple_read_version(const struct simple_priv *priv, + struct udevice *blk, u8 *buf, + struct simple_state *state) { - struct simple_priv *priv = dev_get_priv(dev); int start; if (priv->version_size > MMC_MAX_BLOCK_LEN) @@ -43,10 +43,10 @@ static int simple_read_version(struct udevice *dev, struct udevice *blk, return 0; } -static int simple_read_nvdata(struct udevice *dev, struct udevice *blk, - u8 *buf, struct simple_state *state) +static int simple_read_nvdata(const struct simple_priv *priv, + struct udevice *blk, u8 *buf, + struct simple_state *state) { - struct simple_priv *priv = dev_get_priv(dev); uint hdr_ver, hdr_size, size, crc; const struct vbe_nvdata *nvd; int start; @@ -106,11 +106,11 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) return log_msg_ret("get", -ENXIO); blk = desc->bdev; - ret = simple_read_version(dev, blk, buf, state); + ret = simple_read_version(priv, blk, buf, state); if (ret) return log_msg_ret("ver", ret); - ret = simple_read_nvdata(dev, blk, buf, state); + ret = simple_read_nvdata(priv, blk, buf, state); if (ret) return log_msg_ret("nvd", ret); From 27008ce51388faa448bf7b20b7e8104b8bc972a6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:06 -0700 Subject: [PATCH 05/22] vbe: Convert some checks to assertions VBE is currently quite careful with function arguments because it is used in VPL which cannot be updated after manufacture. Bugs can cause security holes. Unfortunately this adds to code size. In several cases we are reading values from a devicetree which is part of U-Boot (or at least VPL) and so known to be good. Also, in several places, getting bad values does not matter. So change a few checks to assert() to reduce code size. Signed-off-by: Simon Glass --- boot/vbe_simple.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index 1b9cf276921..313f063fa18 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -27,12 +27,17 @@ static int simple_read_version(const struct simple_priv *priv, { int start; - if (priv->version_size > MMC_MAX_BLOCK_LEN) - return log_msg_ret("ver", -E2BIG); + /* we can use an assert() here since we already read only one block */ + assert(priv->version_size <= MMC_MAX_BLOCK_LEN); start = priv->area_start + priv->version_offset; - if (start & (MMC_MAX_BLOCK_LEN - 1)) - return log_msg_ret("get", -EBADF); + + /* + * we can use an assert() here since reading the wrong block will just + * cause an invalid version-string to be (safely) read + */ + assert(!(start & (MMC_MAX_BLOCK_LEN - 1))); + start /= MMC_MAX_BLOCK_LEN; if (blk_read(blk, start, 1, buf) != 1) @@ -51,12 +56,21 @@ static int simple_read_nvdata(const struct simple_priv *priv, const struct vbe_nvdata *nvd; int start; - if (priv->state_size > MMC_MAX_BLOCK_LEN) - return log_msg_ret("state", -E2BIG); + /* we can use an assert() here since we already read only one block */ + assert(priv->state_size <= MMC_MAX_BLOCK_LEN); start = priv->area_start + priv->state_offset; - if (start & (MMC_MAX_BLOCK_LEN - 1)) - return log_msg_ret("get", -EBADF); + + /* + * We can use an assert() here since reading the wrong block will just + * cause invalid state to be (safely) read. If the crc passes, then we + * obtain invalid state and it will likely cause booting to fail. + * + * VBE relies on valid values being in U-Boot's devicetree, so this + * should not every be wrong on a production device. + */ + assert(!(start & (MMC_MAX_BLOCK_LEN - 1))); + start /= MMC_MAX_BLOCK_LEN; if (blk_read(blk, start, 1, buf) != 1) @@ -67,7 +81,7 @@ static int simple_read_nvdata(const struct simple_priv *priv, if (hdr_ver != NVD_HDR_VER_CUR) return log_msg_ret("hdr", -EPERM); size = 1 << hdr_size; - if (size > sizeof(*nvd)) + if (!size || size > sizeof(*nvd)) return log_msg_ret("sz", -ENOEXEC); crc = crc8(0, buf + 1, size - 1); From 190b128252b3861865d63c73ac9f13731aeb950f Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:07 -0700 Subject: [PATCH 06/22] vbe: Create a common function to get the block device Add a vbe_get_blk() function and use it to obtain the block device used by VBE. Signed-off-by: Simon Glass --- boot/Makefile | 2 +- boot/vbe_common.c | 36 ++++++++++++++++++++++++++++++++++++ boot/vbe_common.h | 13 +++++++++++++ boot/vbe_simple.c | 21 +++------------------ 4 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 boot/vbe_common.c diff --git a/boot/Makefile b/boot/Makefile index a24fd90c510..c2753de8163 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -65,7 +65,7 @@ endif obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE) += vbe.o obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_REQUEST) += vbe_request.o -obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o +obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o vbe_common.o obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o obj-$(CONFIG_$(PHASE_)BOOTMETH_VBE_SIMPLE_OS) += vbe_simple_os.o diff --git a/boot/vbe_common.c b/boot/vbe_common.c new file mode 100644 index 00000000000..ede452ba306 --- /dev/null +++ b/boot/vbe_common.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Verified Boot for Embedded (VBE) common functions + * + * Copyright 2024 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include "vbe_common.h" + +int vbe_get_blk(const char *storage, struct udevice **blkp) +{ + struct blk_desc *desc; + char devname[16]; + const char *end; + int devnum; + + /* First figure out the block device */ + log_debug("storage=%s\n", storage); + devnum = trailing_strtoln_end(storage, NULL, &end); + if (devnum == -1) + return log_msg_ret("num", -ENODEV); + if (end - storage >= sizeof(devname)) + return log_msg_ret("end", -E2BIG); + strlcpy(devname, storage, end - storage + 1); + log_debug("dev=%s, %x\n", devname, devnum); + + desc = blk_get_dev(devname, devnum); + if (!desc) + return log_msg_ret("get", -ENXIO); + *blkp = desc->bdev; + + return 0; +} diff --git a/boot/vbe_common.h b/boot/vbe_common.h index 1fc70ee74c8..0cd9617b5b1 100644 --- a/boot/vbe_common.h +++ b/boot/vbe_common.h @@ -48,4 +48,17 @@ struct vbe_nvdata { u8 spare2[0x34]; }; +/** + * vbe_get_blk() - Obtain the block device to use for VBE + * + * Decodes the string to produce a block device + * + * @storage: String indicating the device to use, e.g. "mmc1" + * @blkp: Returns associated block device, on success + * Return 0 if OK, -ENODEV if @storage does not end with a number, -E2BIG if + * the device name is more than 15 characters, -ENXIO if the block device could + * not be found + */ +int vbe_get_blk(const char *storage, struct udevice **blkp); + #endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index 313f063fa18..8a370cf02ef 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -98,28 +98,13 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) { ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); struct simple_priv *priv = dev_get_priv(dev); - struct blk_desc *desc; struct udevice *blk; - char devname[16]; - const char *end; - int devnum; int ret; - /* First figure out the block device */ - log_debug("storage=%s\n", priv->storage); - devnum = trailing_strtoln_end(priv->storage, NULL, &end); - if (devnum == -1) - return log_msg_ret("num", -ENODEV); - if (end - priv->storage >= sizeof(devname)) - return log_msg_ret("end", -E2BIG); - strlcpy(devname, priv->storage, end - priv->storage + 1); - log_debug("dev=%s, %x\n", devname, devnum); + ret = vbe_get_blk(priv->storage, &blk); + if (ret) + return log_msg_ret("blk", ret); - desc = blk_get_dev(devname, devnum); - if (!desc) - return log_msg_ret("get", -ENXIO); - - blk = desc->bdev; ret = simple_read_version(priv, blk, buf, state); if (ret) return log_msg_ret("ver", ret); From 47e56185084941f3bccc8b8352260ba2ee5e5e56 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:08 -0700 Subject: [PATCH 07/22] vbe: Move reading the version into the common file All VBE methods read a version string, so move this function into a common file. Signed-off-by: Simon Glass --- boot/vbe_common.c | 29 +++++++++++++++++++++++++++-- boot/vbe_common.h | 17 +++++++++++++++++ boot/vbe_simple.c | 30 ++---------------------------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index ede452ba306..8bbcc37e67e 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -6,8 +6,9 @@ * Written by Simon Glass */ -#include -#include +#include +#include +#include #include "vbe_common.h" int vbe_get_blk(const char *storage, struct udevice **blkp) @@ -34,3 +35,27 @@ int vbe_get_blk(const char *storage, struct udevice **blkp) return 0; } + +int vbe_read_version(struct udevice *blk, ulong offset, char *version, + int max_size) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); + + /* we can use an assert() here since we already read only one block */ + assert(max_size <= MMC_MAX_BLOCK_LEN); + + /* + * we can use an assert() here since reading the wrong block will just + * cause an invalid version-string to be (safely) read + */ + assert(!(offset & (MMC_MAX_BLOCK_LEN - 1))); + + offset /= MMC_MAX_BLOCK_LEN; + + if (blk_read(blk, offset, 1, buf) != 1) + return log_msg_ret("read", -EIO); + strlcpy(version, buf, max_size); + log_debug("version=%s\n", version); + + return 0; +} diff --git a/boot/vbe_common.h b/boot/vbe_common.h index 0cd9617b5b1..a122bead93e 100644 --- a/boot/vbe_common.h +++ b/boot/vbe_common.h @@ -61,4 +61,21 @@ struct vbe_nvdata { */ int vbe_get_blk(const char *storage, struct udevice **blkp); +/** + * vbe_read_version() - Read version-string from a block device + * + * Reads the VBE version-string from a device. This function reads a single + * block from the device, so the string cannot be larger than that. It uses a + * temporary buffer for the read, then copies in up to @size bytes + * + * @blk: Device to read from + * @offset: Offset to read, in bytes + * @version: Place to put the string + * @max_size: Maximum size of @version + * Return: 0 if OK, -E2BIG if @max_size > block size, -EBADF if the offset is + * not block-aligned, -EIO if an I/O error occurred + */ +int vbe_read_version(struct udevice *blk, ulong offset, char *version, + int max_size); + #endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index 8a370cf02ef..ad98ac37197 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -21,33 +21,6 @@ #include #include "vbe_simple.h" -static int simple_read_version(const struct simple_priv *priv, - struct udevice *blk, u8 *buf, - struct simple_state *state) -{ - int start; - - /* we can use an assert() here since we already read only one block */ - assert(priv->version_size <= MMC_MAX_BLOCK_LEN); - - start = priv->area_start + priv->version_offset; - - /* - * we can use an assert() here since reading the wrong block will just - * cause an invalid version-string to be (safely) read - */ - assert(!(start & (MMC_MAX_BLOCK_LEN - 1))); - - start /= MMC_MAX_BLOCK_LEN; - - if (blk_read(blk, start, 1, buf) != 1) - return log_msg_ret("read", -EIO); - strlcpy(state->fw_version, buf, MAX_VERSION_LEN); - log_debug("version=%s\n", state->fw_version); - - return 0; -} - static int simple_read_nvdata(const struct simple_priv *priv, struct udevice *blk, u8 *buf, struct simple_state *state) @@ -105,7 +78,8 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) if (ret) return log_msg_ret("blk", ret); - ret = simple_read_version(priv, blk, buf, state); + ret = vbe_read_version(blk, priv->area_start + priv->version_offset, + state->fw_version, MAX_VERSION_LEN); if (ret) return log_msg_ret("ver", ret); From 0a59dc41999c673017798aeb152c4e49e87aa864 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:09 -0700 Subject: [PATCH 08/22] vbe: Move reading the nvdata into the common file All VBE methods read non-volatile data, so move this function into a common file. Signed-off-by: Simon Glass --- boot/vbe_common.c | 41 +++++++++++++++++++++++++++++++++++++++++ boot/vbe_common.h | 16 ++++++++++++++++ boot/vbe_simple.c | 44 ++++++++------------------------------------ 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 8bbcc37e67e..672878fe6fe 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "vbe_common.h" int vbe_get_blk(const char *storage, struct udevice **blkp) @@ -59,3 +60,43 @@ int vbe_read_version(struct udevice *blk, ulong offset, char *version, return 0; } + +int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf) +{ + uint hdr_ver, hdr_size, data_size, crc; + const struct vbe_nvdata *nvd; + + /* we can use an assert() here since we already read only one block */ + assert(size <= MMC_MAX_BLOCK_LEN); + + /* + * We can use an assert() here since reading the wrong block will just + * cause invalid state to be (safely) read. If the crc passes, then we + * obtain invalid state and it will likely cause booting to fail. + * + * VBE relies on valid values being in U-Boot's devicetree, so this + * should not every be wrong on a production device. + */ + assert(!(offset & (MMC_MAX_BLOCK_LEN - 1))); + + if (offset & (MMC_MAX_BLOCK_LEN - 1)) + return log_msg_ret("get", -EBADF); + offset /= MMC_MAX_BLOCK_LEN; + + if (blk_read(blk, offset, 1, buf) != 1) + return log_msg_ret("read", -EIO); + nvd = (struct vbe_nvdata *)buf; + hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; + hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT; + if (hdr_ver != NVD_HDR_VER_CUR) + return log_msg_ret("hdr", -EPERM); + data_size = 1 << hdr_size; + if (!data_size || data_size > sizeof(*nvd)) + return log_msg_ret("sz", -EPERM); + + crc = crc8(0, buf + 1, data_size - 1); + if (crc != nvd->crc8) + return log_msg_ret("crc", -EPERM); + + return 0; +} diff --git a/boot/vbe_common.h b/boot/vbe_common.h index a122bead93e..37a81330325 100644 --- a/boot/vbe_common.h +++ b/boot/vbe_common.h @@ -78,4 +78,20 @@ int vbe_get_blk(const char *storage, struct udevice **blkp); int vbe_read_version(struct udevice *blk, ulong offset, char *version, int max_size); +/** + * vbe_read_nvdata() - Read non-volatile data from a block device + * + * Reads the VBE nvdata from a device. This function reads a single block from + * the device, so the nvdata cannot be larger than that. + * + * @blk: Device to read from + * @offset: Offset to read, in bytes + * @size: Number of bytes to read + * @buf: Buffer to hold the data + * Return: 0 if OK, -E2BIG if @size > block size, -EBADF if the offset is not + * block-aligned, -EIO if an I/O error occurred, -EPERM if the header version is + * incorrect, the header size is invalid or the data fails its CRC check + */ +int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf); + #endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple.c b/boot/vbe_simple.c index ad98ac37197..832588fc04e 100644 --- a/boot/vbe_simple.c +++ b/boot/vbe_simple.c @@ -18,48 +18,21 @@ #include #include #include -#include #include "vbe_simple.h" static int simple_read_nvdata(const struct simple_priv *priv, - struct udevice *blk, u8 *buf, - struct simple_state *state) + struct udevice *blk, struct simple_state *state) { - uint hdr_ver, hdr_size, size, crc; + ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); const struct vbe_nvdata *nvd; - int start; + int ret; - /* we can use an assert() here since we already read only one block */ - assert(priv->state_size <= MMC_MAX_BLOCK_LEN); + ret = vbe_read_nvdata(blk, priv->area_start + priv->state_offset, + priv->state_size, buf); + if (ret) + return log_msg_ret("nv", ret); - start = priv->area_start + priv->state_offset; - - /* - * We can use an assert() here since reading the wrong block will just - * cause invalid state to be (safely) read. If the crc passes, then we - * obtain invalid state and it will likely cause booting to fail. - * - * VBE relies on valid values being in U-Boot's devicetree, so this - * should not every be wrong on a production device. - */ - assert(!(start & (MMC_MAX_BLOCK_LEN - 1))); - - start /= MMC_MAX_BLOCK_LEN; - - if (blk_read(blk, start, 1, buf) != 1) - return log_msg_ret("read", -EIO); nvd = (struct vbe_nvdata *)buf; - hdr_ver = (nvd->hdr & NVD_HDR_VER_MASK) >> NVD_HDR_VER_SHIFT; - hdr_size = (nvd->hdr & NVD_HDR_SIZE_MASK) >> NVD_HDR_SIZE_SHIFT; - if (hdr_ver != NVD_HDR_VER_CUR) - return log_msg_ret("hdr", -EPERM); - size = 1 << hdr_size; - if (!size || size > sizeof(*nvd)) - return log_msg_ret("sz", -ENOEXEC); - - crc = crc8(0, buf + 1, size - 1); - if (crc != nvd->crc8) - return log_msg_ret("crc", -EPERM); state->fw_vernum = nvd->fw_vernum; log_debug("version=%s\n", state->fw_version); @@ -69,7 +42,6 @@ static int simple_read_nvdata(const struct simple_priv *priv, int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) { - ALLOC_CACHE_ALIGN_BUFFER(u8, buf, MMC_MAX_BLOCK_LEN); struct simple_priv *priv = dev_get_priv(dev); struct udevice *blk; int ret; @@ -83,7 +55,7 @@ int vbe_simple_read_state(struct udevice *dev, struct simple_state *state) if (ret) return log_msg_ret("ver", ret); - ret = simple_read_nvdata(priv, blk, buf, state); + ret = simple_read_nvdata(priv, blk, state); if (ret) return log_msg_ret("nvd", ret); From ea6cfc55e0f244dbdcb8af8eb0fff5db2f77ce6d Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:10 -0700 Subject: [PATCH 09/22] vbe: Split out reading a FIT into the common file Loading a FIT is useful for other VBE methods, such as ABrec. Create a new function to handling reading it. Signed-off-by: Simon Glass --- boot/vbe_common.c | 107 +++++++++++++++++++++++++++++++++++++++++++ boot/vbe_common.h | 28 +++++++++++ boot/vbe_simple_fw.c | 94 ++----------------------------------- 3 files changed, 139 insertions(+), 90 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 672878fe6fe..0105d550bdf 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -6,7 +6,11 @@ * Written by Simon Glass */ +#include +#include #include +#include +#include #include #include #include @@ -100,3 +104,106 @@ int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf) return 0; } + +int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, + ulong *load_addrp, ulong *lenp, char **namep) +{ + ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN); + ulong size, blknum, addr, len, load_addr, num_blks; + const char *fit_uname, *fit_uname_config; + struct bootm_headers images = {}; + enum image_phase_t phase; + struct blk_desc *desc; + int node, ret; + void *buf; + + desc = dev_get_uclass_plat(blk); + + /* read in one block to find the FIT size */ + blknum = area_offset / desc->blksz; + log_debug("read at %lx, blknum %lx\n", area_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 > 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 = (area_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); + } + if (load_addrp) + *load_addrp = load_addr; + if (lenp) + *lenp = len; + if (namep) { + *namep = strdup(fdt_get_name(buf, node, NULL)); + if (!namep) + return log_msg_ret("nam", -ENOMEM); + } + + return 0; +} diff --git a/boot/vbe_common.h b/boot/vbe_common.h index 37a81330325..bfa339008e1 100644 --- a/boot/vbe_common.h +++ b/boot/vbe_common.h @@ -94,4 +94,32 @@ int vbe_read_version(struct udevice *blk, ulong offset, char *version, */ int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf); +/** + * vbe_read_fit() - Read an image from a FIT + * + * This handles most of the VBE logic for reading from a FIT. It reads the FIT + * metadata, decides which image to load and loads it to a suitable address, + * ready for jumping to the next phase of VBE. + * + * This supports transition from VPL to SPL as well as SPL to U-Boot proper. For + * now, TPL->VPL is not supported. + * + * Both embedded and external data are supported for the FIT + * + * @blk: Block device containing FIT + * @area_offset: Byte offset of the VBE area in @blk containing the FIT + * @area_size: Size of the VBE area + * @load_addrp: If non-null, returns the address where the image was loaded + * @lenp: If non-null, returns the size of the image loaded, in bytes + * @namep: If non-null, returns the name of the FIT-image node that was loaded + * (allocated by this function) + * Return: 0 if OK, -EINVAL if the area does not contain an FDT (the underlying + * format for FIT), -E2BIG if the FIT extends past @area_size, -ENOMEM if there + * was not space to allocate the image-node name, other error if a read error + * occurred (see blk_read()), or something went wrong with the actually + * FIT-parsing (see fit_image_load()). + */ +int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, + ulong *load_addrp, ulong *lenp, char **namep); + #endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c index da9701f9eb9..0bf25ccad23 100644 --- a/boot/vbe_simple_fw.c +++ b/boot/vbe_simple_fw.c @@ -38,109 +38,23 @@ */ 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; + ulong len, load_addr; struct udevice *blk; - int node, ret; - void *buf; + int ret; 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); - } + ret = vbe_read_fit(blk, priv->area_start + priv->skip_offset, + priv->area_size, &load_addr, &len, &bflow->name); /* 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; From 0148c14e04e8ac8855a1dfc7aeeef325ca2d9146 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:11 -0700 Subject: [PATCH 10/22] vbe: Allocate space for the FIT header It is convenient to use TEXT_BASE as a place to hold the FIT header, but this does not work in VPL, since SDRAM is not inited yet. Allocate the memory instead. Ensure the size is aligned to the media block-size so that it can be read in directly. Improve the error-checking for blk_read() and add some more debugging. Keep the existing TEXT_BASE mechanism in sandbox to avoid an 'Exec format error' when trying to run the image. Signed-off-by: Simon Glass --- boot/vbe_common.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 0105d550bdf..5f45724f74b 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -114,6 +114,7 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, struct bootm_headers images = {}; enum image_phase_t phase; struct blk_desc *desc; + ulong aligned_size; int node, ret; void *buf; @@ -133,20 +134,37 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, if (size > area_size) return log_msg_ret("fdt", -E2BIG); log_debug("FIT size %lx\n", size); + aligned_size = ALIGN(size, desc->blksz); /* * 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); + if (IS_ENABLED(CONFIG_SANDBOX)) { + addr = CONFIG_VAL(TEXT_BASE); + buf = map_sysmem(addr, size); + } else { + buf = malloc(aligned_size); + if (!buf) + return log_msg_ret("fit", -ENOMEM); + addr = map_to_sysmem(buf); + } + num_blks = aligned_size / desc->blksz; + log_debug("read %lx, %lx blocks to %lx / %p\n", aligned_size, num_blks, + addr, buf); ret = blk_read(blk, blknum, num_blks, buf); if (ret < 0) - return log_msg_ret("rd", ret); + return log_msg_ret("rd3", ret); + else if (ret != num_blks) + return log_msg_ret("rd4", -EIO); + log_debug("check total size %x off_dt_strings %x\n", fdt_totalsize(buf), + fdt_off_dt_strings(buf)); +#if CONFIG_IS_ENABLED(SYS_MALLOC_F) + log_debug("malloc base %lx ptr %x limit %x top %lx\n", + gd->malloc_base, gd->malloc_ptr, gd->malloc_limit, + gd->malloc_base + gd->malloc_limit); +#endif /* figure out the phase to load */ phase = IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT; @@ -169,7 +187,9 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, log_debug("loaded to %lx\n", load_addr); /* For FIT external data, read in the external data */ - if (load_addr + len > addr + size) { + log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n", + load_addr, len, addr, aligned_size); + if (load_addr + len > addr + aligned_size) { ulong base, full_size; void *base_buf; From 91f27b5b0735e6ef18902af12f74d8f357e6dc42 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:12 -0700 Subject: [PATCH 11/22] vbe: Allow VBE to load FITs on any architecture At present the VBE implementation is limited to sandbox only. Adjust the call to fit_image_load() to remove this limitation. Signed-off-by: Simon Glass --- boot/vbe_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 5f45724f74b..ecf4ad916e0 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -178,7 +178,7 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, 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), + IH_ARCH_DEFAULT, image_ph(phase, IH_TYPE_FIRMWARE), BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED, &load_addr, &len); if (ret < 0) From d337037e1a7b74c05dcaff62739df45a2933078e Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:13 -0700 Subject: [PATCH 12/22] vbe: Tidy up error checking with blk_read() This function can read fewer blocks than requested, so update the checks to handle this. Signed-off-by: Simon Glass --- boot/vbe_common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index ecf4ad916e0..0f5e0e4ca98 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -126,6 +126,8 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, ret = blk_read(blk, blknum, 1, sbuf); if (ret < 0) return log_msg_ret("rd", ret); + else if (ret != 1) + return log_msg_ret("rd2", -EIO); ret = fdt_check_header(sbuf); if (ret < 0) @@ -214,6 +216,8 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, blknum, full_size, num_blks, base, base_buf, ret); if (ret < 0) return log_msg_ret("rd", ret); + if (ret != num_blks) + return log_msg_ret("rd", -EIO); } if (load_addrp) *load_addrp = load_addr; From 36d6c89950e560094f8b21d773be44409077ea59 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:14 -0700 Subject: [PATCH 13/22] vbe: Handle loading from an unaligned offset There is no guarantee that an FIT image starts on a block boundary. When it doesn't, the image starts part-way through the first block. Add logic to detect this and copy the image down into place. Signed-off-by: Simon Glass --- boot/vbe_common.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 0f5e0e4ca98..8fe278ba1a9 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -192,32 +192,41 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n", load_addr, len, addr, aligned_size); if (load_addr + len > addr + aligned_size) { - ulong base, full_size; + ulong base, full_size, offset, extra; void *base_buf; /* Find the start address to load from */ base = ALIGN_DOWN(load_addr, desc->blksz); + offset = area_offset + load_addr - addr; + blknum = offset / desc->blksz; + extra = offset % desc->blksz; + /* * Get the total number of bytes to load, taking care of * block alignment */ - full_size = load_addr + len - base; + full_size = len + extra; /* * Get the start block number, number of blocks and the address * to load to, then load the blocks */ - blknum = (area_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); + log_debug("read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n", + offset - 0x8000, blknum, full_size, num_blks, base, base_buf, + ret); if (ret < 0) return log_msg_ret("rd", ret); if (ret != num_blks) return log_msg_ret("rd", -EIO); + if (extra && !IS_ENABLED(CONFIG_SANDBOX)) { + log_debug("move %p %p %lx\n", base_buf, + base_buf + extra, len); + memmove(base_buf, base_buf + extra, len); + } } if (load_addrp) *load_addrp = load_addr; From 42fb767da4439e52282fb3f84046b25131573f82 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:15 -0700 Subject: [PATCH 14/22] vbe: Allow loading loadables if there is no firmware In some cases only the 'loadable' property is present in the FIT. Handle this by loading the first such image. Signed-off-by: Simon Glass --- boot/vbe_common.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 8fe278ba1a9..b935513d44c 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -183,6 +183,13 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, IH_ARCH_DEFAULT, image_ph(phase, IH_TYPE_FIRMWARE), BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED, &load_addr, &len); + if (ret == -ENOENT) { + ret = fit_image_load(&images, addr, &fit_uname, + &fit_uname_config, IH_ARCH_DEFAULT, + image_ph(phase, IH_TYPE_LOADABLE), + BOOTSTAGE_ID_FIT_SPL_START, + FIT_LOAD_IGNORED, &load_addr, &len); + } if (ret < 0) return log_msg_ret("ld", ret); node = ret; From df42d54b965746d177f78e14f1090b8efa916306 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:16 -0700 Subject: [PATCH 15/22] vbe: Support loading an FDT from the FIT In many cases the FIT includes a devicetree. Add support for loading this into a suitable place in memory. Signed-off-by: Simon Glass --- boot/vbe_common.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index b935513d44c..30dd18d3c38 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -110,11 +110,11 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, { ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN); ulong size, blknum, addr, len, load_addr, num_blks; + ulong aligned_size, fdt_load_addr, fdt_size; const char *fit_uname, *fit_uname_config; struct bootm_headers images = {}; enum image_phase_t phase; struct blk_desc *desc; - ulong aligned_size; int node, ret; void *buf; @@ -195,12 +195,16 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, node = ret; log_debug("loaded to %lx\n", load_addr); + fdt_load_addr = 0; + fdt_size = 0; + /* For FIT external data, read in the external data */ log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n", load_addr, len, addr, aligned_size); if (load_addr + len > addr + aligned_size) { - ulong base, full_size, offset, extra; - void *base_buf; + ulong base, full_size, offset, extra, fdt_base, fdt_full_size; + ulong fdt_offset; + void *base_buf, *fdt_base_buf; /* Find the start address to load from */ base = ALIGN_DOWN(load_addr, desc->blksz); @@ -234,6 +238,29 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, base_buf + extra, len); memmove(base_buf, base_buf + extra, len); } + + /* now the FDT */ + if (fdt_size) { + fdt_offset = area_offset + fdt_load_addr - addr; + blknum = fdt_offset / desc->blksz; + extra = fdt_offset % desc->blksz; + fdt_full_size = fdt_size + extra; + num_blks = DIV_ROUND_UP(fdt_full_size, desc->blksz); + fdt_base = ALIGN(base + len, 4); + fdt_base_buf = map_sysmem(fdt_base, fdt_size); + ret = blk_read(blk, blknum, num_blks, fdt_base_buf); + log_debug("fdt read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n", + fdt_offset - 0x8000, blknum, fdt_full_size, num_blks, + fdt_base, fdt_base_buf, ret); + if (ret != num_blks) + return log_msg_ret("rdf", -EIO); + if (extra) { + log_debug("move %p %p %lx\n", fdt_base_buf, + fdt_base_buf + extra, fdt_size); + memmove(fdt_base_buf, fdt_base_buf + extra, + fdt_size); + } + } } if (load_addrp) *load_addrp = load_addr; From d86bdb60b51f864d188edc5d27847db5d2dfe75d Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:17 -0700 Subject: [PATCH 16/22] spl: Add fields for VBE Add some fields to track the VBE state in SPL. Signed-off-by: Simon Glass --- include/spl.h | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/include/spl.h b/include/spl.h index 43b344dbc55..781e5a2d638 100644 --- a/include/spl.h +++ b/include/spl.h @@ -14,6 +14,7 @@ #include #include #include +#include #include struct blk_desc; @@ -265,6 +266,11 @@ enum spl_sandbox_flags { SPL_SANDBOXF_ARG_IS_BUF, }; +/** + * struct spl_image_info - Information about the SPL image being loaded + * + * @fdt_size: Size of the FDT for the image (0 if none) + */ struct spl_image_info { const char *name; u8 os; @@ -276,6 +282,7 @@ struct spl_image_info { u32 boot_device; u32 offset; u32 size; + ulong fdt_size; u32 flags; void *arg; #ifdef CONFIG_SPL_LEGACY_IMAGE_CRC_CHECK @@ -316,12 +323,18 @@ typedef ulong (*spl_load_reader)(struct spl_load_info *load, ulong sector, * @read: Function to call to read from the device * @priv: Private data for the device * @bl_len: Block length for reading in bytes + * @phase: Image phase to load + * @fit_loaded: true if the FIT has been loaded, except for external data */ struct spl_load_info { spl_load_reader read; void *priv; #if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) - int bl_len; + u16 bl_len; +#endif +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + u8 phase; + u8 fit_loaded; #endif }; @@ -344,6 +357,32 @@ static inline void spl_set_bl_len(struct spl_load_info *info, int bl_len) #endif } +static inline void xpl_set_phase(struct spl_load_info *info, + enum image_phase_t phase) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + info->phase = phase; +#endif +} + +static inline enum image_phase_t xpl_get_phase(struct spl_load_info *info) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + return info->phase; +#else + return IH_PHASE_NONE; +#endif +} + +static inline bool xpl_get_fit_loaded(struct spl_load_info *info) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + return info->fit_loaded; +#else + return false; +#endif +} + /** * spl_load_init() - Set up a new spl_load_info structure */ From bed7c4599d14c251021b82186ecf558689873486 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:18 -0700 Subject: [PATCH 17/22] spl: Add a type for the jumper function This function will be used by the relocating jumper too, so add a typedef to the header file to avoid mismatches. Signed-off-by: Simon Glass --- common/spl/spl.c | 3 +-- include/spl.h | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/spl/spl.c b/common/spl/spl.c index ad31a2f8b6c..09e6dc26f5e 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -671,8 +671,7 @@ void board_init_r(gd_t *dummy1, ulong dummy2) BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, }; - typedef void __noreturn (*jump_to_image_t)(struct spl_image_info *); - jump_to_image_t jump_to_image = &jump_to_image_no_args; + spl_jump_to_image_t jump_to_image = &jump_to_image_no_args; struct spl_image_info spl_image; int ret, os; diff --git a/include/spl.h b/include/spl.h index 781e5a2d638..488adbeb1bc 100644 --- a/include/spl.h +++ b/include/spl.h @@ -292,6 +292,9 @@ struct spl_image_info { #endif }; +/* function to jump to an image from SPL */ +typedef void __noreturn (*spl_jump_to_image_t)(struct spl_image_info *); + static inline void *spl_image_fdt_addr(struct spl_image_info *info) { #if CONFIG_IS_ENABLED(LOAD_FIT) || CONFIG_IS_ENABLED(LOAD_FIT_FULL) From 20ad3fa0e1af3982ddfe824aae1b8221c1f1fa0c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:19 -0700 Subject: [PATCH 18/22] spl: Add support for a relocating jump to the next phase When one xPL phase wants to jump to the next, the next phase must be loaded into its required address. This means that the TEXT_BASE for the two phases must be different and there cannot be any memory overlap between the code used by the two phases. It also can mean that phases need to be moved around to accommodate any size growth. Having two xPL phases in SRAM at the same time can be tricky if SRAM is limited, which it often is. It would be better if the second phase could be loaded somewhere else, then decompressed into place over the top of the first phase. Introduce a relocating jump for xPL to support this. This selects a suitable place to load the (typically compressed) next phase, copies some decompression code out of the first phase, then jumps to this code to decompress and start the next phase. This feature makes it much easier to support Verified Boot for Embedded (VBE) on RK3399 boards, which have 192KB of SRAM. Signed-off-by: Simon Glass --- common/spl/Kconfig | 8 ++ common/spl/Kconfig.tpl | 8 ++ common/spl/Kconfig.vpl | 8 ++ common/spl/Makefile | 1 + common/spl/spl_reloc.c | 183 +++++++++++++++++++++++++++++++++++++++++ include/spl.h | 45 ++++++++++ 6 files changed, 253 insertions(+) create mode 100644 common/spl/spl_reloc.c diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 4e56d9909c8..94e118f8465 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -983,6 +983,14 @@ config SPL_NAND_IDENT help SPL uses the chip ID list to identify the NAND flash. +config SPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config SPL_UBI bool "Support UBI" help diff --git a/common/spl/Kconfig.tpl b/common/spl/Kconfig.tpl index 92d4d43ec87..22ca7016453 100644 --- a/common/spl/Kconfig.tpl +++ b/common/spl/Kconfig.tpl @@ -268,6 +268,14 @@ config TPL_RAM_DEVICE be already in memory when TPL takes over, e.g. loaded by the boot ROM. +config TPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config TPL_RTC bool "Support RTC drivers" help diff --git a/common/spl/Kconfig.vpl b/common/spl/Kconfig.vpl index eb57dfabea5..97dfc630152 100644 --- a/common/spl/Kconfig.vpl +++ b/common/spl/Kconfig.vpl @@ -181,6 +181,14 @@ config VPL_PCI necessary driver support. This enables the drivers in drivers/pci as part of a VPL build. +config VPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config VPL_RTC bool "Support RTC drivers" help diff --git a/common/spl/Makefile b/common/spl/Makefile index 75123eb666b..4c9482bd309 100644 --- a/common/spl/Makefile +++ b/common/spl/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_$(PHASE_)BOOTROM_SUPPORT) += spl_bootrom.o obj-$(CONFIG_$(PHASE_)LOAD_FIT) += spl_fit.o obj-$(CONFIG_$(PHASE_)BLK_FS) += spl_blk_fs.o obj-$(CONFIG_$(PHASE_)LEGACY_IMAGE_FORMAT) += spl_legacy.o +obj-$(CONFIG_$(PHASE_)RELOC_LOADER) += spl_reloc.o obj-$(CONFIG_$(PHASE_)NOR_SUPPORT) += spl_nor.o obj-$(CONFIG_$(PHASE_)XIP_SUPPORT) += spl_xip.o obj-$(CONFIG_$(PHASE_)YMODEM_SUPPORT) += spl_ymodem.o diff --git a/common/spl/spl_reloc.c b/common/spl/spl_reloc.c new file mode 100644 index 00000000000..be8349b535b --- /dev/null +++ b/common/spl/spl_reloc.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* provide a way to jump straight into the relocation code, for debugging */ +#define DEBUG_JUMP 0 + +enum { + /* margin to allow for stack growth */ + RELOC_STACK_MARGIN = 0x800, + + /* align base address for DMA controllers which require it */ + BASE_ALIGN = 0x200, + + STACK_PROT_VALUE = 0x51ce4697, +}; + +typedef int (*rcode_func)(struct spl_image_info *image); + +static int setup_layout(struct spl_image_info *image, ulong *addrp) +{ + ulong base, fdt_size; + ulong limit, rcode_base; + uint rcode_size; + int buf_size, margin; + char *rcode_buf; + + limit = ALIGN(map_to_sysmem(&limit) - RELOC_STACK_MARGIN, 8); + image->stack_prot = map_sysmem(limit, sizeof(uint)); + *image->stack_prot = STACK_PROT_VALUE; + + fdt_size = fdt_totalsize(gd->fdt_blob); + base = ALIGN(map_to_sysmem(gd->fdt_blob) + fdt_size + BASE_ALIGN - 1, + BASE_ALIGN); + + rcode_size = _rcode_end - _rcode_start; + rcode_base = limit - rcode_size; + buf_size = rcode_base - base; + uint need_size = image->size + image->fdt_size; + margin = buf_size - need_size; + log_debug("spl_reloc %s->%s: margin%s%lx limit %lx fdt_size %lx base %lx avail %x image %x fdt %lx need %x\n", + spl_phase_name(spl_phase()), spl_phase_name(spl_phase() + 1), + margin >= 0 ? " " : " -", abs(margin), limit, fdt_size, base, + buf_size, image->size, image->fdt_size, need_size); + if (margin < 0) { + log_err("Image size %x but buffer is only %x\n", need_size, + buf_size); + return -ENOSPC; + } + + rcode_buf = map_sysmem(rcode_base, rcode_size); + log_debug("_rcode_start %p: %x -- func %p %x\n", _rcode_start, + *(uint *)_rcode_start, setup_layout, *(uint *)setup_layout); + + image->reloc_offset = rcode_buf - _rcode_start; + log_debug("_rcode start %lx base %lx size %x offset %lx\n", + (ulong)map_to_sysmem(_rcode_start), rcode_base, rcode_size, + image->reloc_offset); + + memcpy(rcode_buf, _rcode_start, rcode_size); + + image->buf = map_sysmem(base, need_size); + image->fdt_buf = image->buf + image->size; + image->rcode_buf = rcode_buf; + *addrp = base; + + return 0; +} + +int spl_reloc_prepare(struct spl_image_info *image, ulong *addrp) +{ + int ret; + + ret = setup_layout(image, addrp); + if (ret) + return ret; + + return 0; +} + +typedef void __noreturn (*image_entry_noargs_t)(uint crc, uint unc_len); + +/* this is the relocation + jump code that is copied to the top of memory */ +__rcode int rcode_reloc_and_jump(struct spl_image_info *image) +{ + image_entry_noargs_t entry = (image_entry_noargs_t)image->entry_point; + u32 *dst; + ulong image_len; + size_t unc_len; + int ret, crc; + uint magic; + + dst = map_sysmem(image->load_addr, image->size); + unc_len = (void *)image->rcode_buf - (void *)dst; + image_len = image->size; + if (*image->stack_prot != STACK_PROT_VALUE) + return -EFAULT; + magic = get_unaligned_le32(image->buf); + if (CONFIG_IS_ENABLED(LZMA)) { + SizeT lzma_len = unc_len; + + ret = lzmaBuffToBuffDecompress((u8 *)dst, &lzma_len, + image->buf, image_len); + unc_len = lzma_len; + } else if (CONFIG_IS_ENABLED(GZIP)) { + ret = gunzip(dst, unc_len, image->buf, &image_len); + } else if (CONFIG_IS_ENABLED(LZ4) && magic == LZ4F_MAGIC) { + ret = ulz4fn(image->buf, image_len, dst, &unc_len); + if (ret) + return ret; + } else { + u32 *src, *end, *ptr; + + unc_len = image->size; + for (src = image->buf, end = (void *)src + image->size, + ptr = dst; src < end;) + *ptr++ = *src++; + } + if (*image->stack_prot != STACK_PROT_VALUE) + return -EFAULT; + + /* copy in the FDT if needed */ + if (image->fdt_size) + memcpy(image->fdt_start, image->fdt_buf, image->fdt_size); + + crc = crc8(0, (u8 *)dst, unc_len); + + /* jump to the entry point */ + entry(crc, unc_len); +} + +int spl_reloc_jump(struct spl_image_info *image, spl_jump_to_image_t jump) +{ + rcode_func loader; + int ret; + + log_debug("malloc usage %lx bytes (%ld KB of %d KB)\n", gd->malloc_ptr, + gd->malloc_ptr / 1024, CONFIG_VAL(SYS_MALLOC_F_LEN) / 1024); + + if (*image->stack_prot != STACK_PROT_VALUE) { + log_err("stack busted, cannot continue\n"); + return -EFAULT; + } + loader = (rcode_func)(void *)rcode_reloc_and_jump + image->reloc_offset; + log_debug("Jumping via %p to %lx - image %p size %x load %lx\n", loader, + image->entry_point, image, image->size, image->load_addr); + + log_debug("unc_len %lx\n", + image->rcode_buf - map_sysmem(image->load_addr, image->size)); + if (DEBUG_JUMP) { + rcode_reloc_and_jump(image); + } else { + /* + * Must disable LOG_DEBUG since the decompressor cannot call + * log functions, printf(), etc. + */ + _Static_assert(DEBUG_JUMP || !_DEBUG, + "Cannot have debug output from decompressor"); + ret = loader(image); + } + + return -EFAULT; +} diff --git a/include/spl.h b/include/spl.h index 488adbeb1bc..9cfba98db55 100644 --- a/include/spl.h +++ b/include/spl.h @@ -270,6 +270,16 @@ enum spl_sandbox_flags { * struct spl_image_info - Information about the SPL image being loaded * * @fdt_size: Size of the FDT for the image (0 if none) + * @buf: Buffer where the image should be loaded + * @fdt_buf: Buffer where the FDT will be copied by spl_reloc_jump(), only used + * if @fdt_size is non-zero + * @fdt_start: Pointer to the FDT to be copied (must be set up before calling + * spl_reloc_jump() + * @rcode_buf: Buffer to hold the relocating-jump code + * @stack_prot: Pointer to the stack-protection value, used to ensure the stack + * does not overflow + * @reloc_offset: offset between the relocating-jump code and its place in the + * currently running image */ struct spl_image_info { const char *name; @@ -290,6 +300,14 @@ struct spl_image_info { ulong dcrc_length; ulong dcrc; #endif +#if CONFIG_IS_ENABLED(RELOC_LOADER) + void *buf; + void *fdt_buf; + void *fdt_start; + void *rcode_buf; + uint *stack_prot; + ulong reloc_offset; +#endif }; /* function to jump to an image from SPL */ @@ -1155,4 +1173,31 @@ int spl_write_upl_handoff(struct spl_image_info *spl_image); */ void spl_upl_init(void); +/** + * spl_reloc_prepare() - Prepare the relocating loader ready for use + * + * Sets up the relocating loader ready for use. This must be called before + * spl_reloc_jump() can be used. + * + * The memory layout is figured out, making use of the space between the top of + * the current image and the top of memory. + * + * Once this is done, the relocating-jump code is copied into place at + * image->rcode_buf + * + * @image: SPL image containing information. This is updated with various + * necessary values. On entry, the size and fdt_size fields must be valid + * @addrp: Returns the address to which the image should be loaded into memory + * Return 0 if OK, -ENOSPC if there is not enough memory available + */ +int spl_reloc_prepare(struct spl_image_info *image, ulong *addrp); + +/** + * spl_reloc_jump() - Jump to an image, via a 'relocating-jump' region + * + * @image: SPL image to jump to + * @func: Function to call in the final image + */ +int spl_reloc_jump(struct spl_image_info *image, spl_jump_to_image_t func); + #endif From ca055155f4179e92caae47ff21ee858884b47b21 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:20 -0700 Subject: [PATCH 19/22] spl: Plumb in the relocating loader This is fairly easy to use. The SPL loader sets up some fields in the spl_image_info struct and calls spl_reloc_prepare(). When SPL is ready to do the jump it must call spl_reloc_jump() instead of jump_to_image(). Add this logic. Signed-off-by: Simon Glass --- common/spl/spl.c | 12 ++++++++++++ include/spl.h | 1 + 2 files changed, 13 insertions(+) diff --git a/common/spl/spl.c b/common/spl/spl.c index 09e6dc26f5e..7cfbab06419 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -826,6 +826,18 @@ void board_init_r(gd_t *dummy1, ulong dummy2) } spl_board_prepare_for_boot(); + + if (CONFIG_IS_ENABLED(RELOC_LOADER)) { + int ret; + + ret = spl_reloc_jump(&spl_image, jump_to_image); + if (ret) { + if (xpl_phase() == PHASE_VPL) + printf("jump failed %d\n", ret); + hang(); + } + } + jump_to_image(&spl_image); } diff --git a/include/spl.h b/include/spl.h index 9cfba98db55..7155e9c67aa 100644 --- a/include/spl.h +++ b/include/spl.h @@ -414,6 +414,7 @@ static inline void spl_load_init(struct spl_load_info *load, load->read = h_read; load->priv = priv; spl_set_bl_len(load, bl_len); + xpl_set_phase(load, IH_PHASE_NONE); } /* From 65250625c518c56b931116f23d62e0cc52f7879b Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:21 -0700 Subject: [PATCH 20/22] vbe: Support loading an FDT with the relocating loader Add FDT support so that this can be copied down in memory after loading and made available to the new image. Signed-off-by: Simon Glass --- boot/vbe_common.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 30dd18d3c38..1926e851398 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -260,6 +260,26 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, memmove(fdt_base_buf, fdt_base_buf + extra, fdt_size); } +#if CONFIG_IS_ENABLED(RELOC_LOADER) + image->fdt_buf = fdt_base_buf; + + ulong xpl_size; + ulong xpl_pad; + ulong fdt_start; + + if (xpl_phase() == PHASE_TPL) { + xpl_size = binman_sym(ulong, u_boot_vpl_nodtb, size); + xpl_pad = binman_sym(ulong, u_boot_vpl_bss_pad, size); + } else { + xpl_size = binman_sym(ulong, u_boot_spl_nodtb, size); + xpl_pad = binman_sym(ulong, u_boot_spl_bss_pad, size); + } + fdt_start = image->load_addr + xpl_size + xpl_pad; + log_debug("load_addr %lx xpl_size %lx copy-to %lx\n", + image->load_addr, xpl_size + xpl_pad, + fdt_start); + image->fdt_start = map_sysmem(fdt_start, fdt_size); +#endif } } if (load_addrp) From 9ecc1cabe1dd9c013ace4f9d5144bfd69fa2eedf Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:22 -0700 Subject: [PATCH 21/22] vbe: Support loading SPL images VBE needs to load different images from a FIT depending on the xPL phase in use. The IH_PHASE value is used to select the image to load. Add the required logic to handle this. For compatibility with the SPL-loader driver, fill out a struct spl_image_info with the details needed to boot the next phase. This is good enough for VBE-simple but ABrec will need the full set of bootstd features. So add a USE_BOOTMETH define to control this. Signed-off-by: Simon Glass --- boot/vbe_common.c | 85 ++++++++++++++++++++++++++++++++++++++++++-- boot/vbe_common.h | 14 +++++++- boot/vbe_simple_fw.c | 3 +- 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 1926e851398..2bc6c4d73ab 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -16,6 +16,11 @@ #include #include "vbe_common.h" +binman_sym_declare(ulong, u_boot_vpl_nodtb, size); +binman_sym_declare(ulong, u_boot_vpl_bss_pad, size); +binman_sym_declare(ulong, u_boot_spl_nodtb, size); +binman_sym_declare(ulong, u_boot_spl_bss_pad, size); + int vbe_get_blk(const char *storage, struct udevice **blkp) { struct blk_desc *desc; @@ -105,17 +110,43 @@ int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf) return 0; } +/** + * h_vbe_load_read() - Handler for reading an SPL image from a FIT + * + * See spl_load_reader for the definition + */ +ulong h_vbe_load_read(struct spl_load_info *load, ulong off, ulong size, + void *buf) +{ + struct blk_desc *desc = load->priv; + lbaint_t sector = off >> desc->log2blksz; + lbaint_t count = size >> desc->log2blksz; + int ret; + + log_debug("vbe read log2blksz %x offset %lx sector %lx count %lx\n", + desc->log2blksz, (ulong)off, (long)sector, (ulong)count); + + ret = blk_dread(desc, sector, count, buf); + log_debug("ret=%x\n", ret); + if (ret < 0) + return ret; + + return ret << desc->log2blksz; +} + int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, - ulong *load_addrp, ulong *lenp, char **namep) + struct spl_image_info *image, ulong *load_addrp, ulong *lenp, + char **namep) { ALLOC_CACHE_ALIGN_BUFFER(u8, sbuf, MMC_MAX_BLOCK_LEN); - ulong size, blknum, addr, len, load_addr, num_blks; + ulong size, blknum, addr, len, load_addr, num_blks, spl_load_addr; ulong aligned_size, fdt_load_addr, fdt_size; const char *fit_uname, *fit_uname_config; struct bootm_headers images = {}; enum image_phase_t phase; struct blk_desc *desc; int node, ret; + bool for_xpl; void *buf; desc = dev_get_uclass_plat(blk); @@ -168,7 +199,8 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, gd->malloc_base + gd->malloc_limit); #endif /* figure out the phase to load */ - phase = IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT; + phase = IS_ENABLED(CONFIG_TPL_BUILD) ? IH_PHASE_NONE : + IS_ENABLED(CONFIG_VPL_BUILD) ? IH_PHASE_SPL : IH_PHASE_U_BOOT; /* * Load the image from the FIT. We ignore any load-address information @@ -179,6 +211,20 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, fit_uname = NULL; fit_uname_config = NULL; log_debug("loading FIT\n"); + + if (xpl_phase() == PHASE_SPL && !IS_ENABLED(CONFIG_SANDBOX)) { + struct spl_load_info info; + + spl_load_init(&info, h_vbe_load_read, desc, desc->blksz); + xpl_set_phase(&info, IH_PHASE_U_BOOT); + log_debug("doing SPL from %s blksz %lx log2blksz %x area_offset %lx + fdt_size %lx\n", + blk->name, desc->blksz, desc->log2blksz, area_offset, ALIGN(size, 4)); + ret = spl_load_simple_fit(image, &info, area_offset, buf); + log_debug("spl_load_abrec_fit() ret=%d\n", ret); + + return ret; + } + ret = fit_image_load(&images, addr, &fit_uname, &fit_uname_config, IH_ARCH_DEFAULT, image_ph(phase, IH_TYPE_FIRMWARE), BOOTSTAGE_ID_FIT_SPL_START, FIT_LOAD_IGNORED, @@ -197,6 +243,31 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, fdt_load_addr = 0; fdt_size = 0; + if ((xpl_phase() == PHASE_TPL || xpl_phase() == PHASE_VPL) && + !IS_ENABLED(CONFIG_SANDBOX)) { + /* allow use of a different image from the configuration node */ + fit_uname = NULL; + ret = fit_image_load(&images, addr, &fit_uname, + &fit_uname_config, IH_ARCH_DEFAULT, + image_ph(phase, IH_TYPE_FLATDT), + BOOTSTAGE_ID_FIT_SPL_START, + FIT_LOAD_IGNORED, &fdt_load_addr, + &fdt_size); + fdt_size = ALIGN(fdt_size, desc->blksz); + log_debug("FDT noload to %lx size %lx\n", fdt_load_addr, + fdt_size); + } + + for_xpl = !USE_BOOTMETH && CONFIG_IS_ENABLED(RELOC_LOADER); + if (for_xpl) { + image->size = len; + image->fdt_size = fdt_size; + ret = spl_reloc_prepare(image, &spl_load_addr); + if (ret) + return log_msg_ret("spl", ret); + } + if (!IS_ENABLED(CONFIG_SANDBOX)) + image->os = IH_OS_U_BOOT; /* For FIT external data, read in the external data */ log_debug("load_addr %lx len %lx addr %lx aligned_size %lx\n", @@ -224,6 +295,8 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, * to load to, then load the blocks */ num_blks = DIV_ROUND_UP(full_size, desc->blksz); + if (for_xpl) + base = spl_load_addr; base_buf = map_sysmem(base, full_size); ret = blk_read(blk, blknum, num_blks, base_buf); log_debug("read foffset %lx blknum %lx full_size %lx num_blks %lx to %lx / %p: ret=%d\n", @@ -239,6 +312,12 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, memmove(base_buf, base_buf + extra, len); } + if ((xpl_phase() == PHASE_VPL || xpl_phase() == PHASE_TPL) && + !IS_ENABLED(CONFIG_SANDBOX)) { + image->load_addr = spl_get_image_text_base(); + image->entry_point = image->load_addr; + } + /* now the FDT */ if (fdt_size) { fdt_offset = area_offset + fdt_load_addr - addr; diff --git a/boot/vbe_common.h b/boot/vbe_common.h index bfa339008e1..84117815a19 100644 --- a/boot/vbe_common.h +++ b/boot/vbe_common.h @@ -11,8 +11,18 @@ #include +struct spl_image_info; struct udevice; +/* + * Controls whether we use a full bootmeth driver with VBE in this phase, or + * just access the information directly. + * + * For now VBE-simple uses the full bootmeth, but VBE-abrec does not, to reduce + * code size + */ +#define USE_BOOTMETH CONFIG_IS_ENABLED(BOOTMETH_VBE_SIMPLE) + enum { MAX_VERSION_LEN = 256, @@ -109,6 +119,7 @@ int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf); * @blk: Block device containing FIT * @area_offset: Byte offset of the VBE area in @blk containing the FIT * @area_size: Size of the VBE area + * @image: SPL image to fill in with details of the loaded image, or NULL * @load_addrp: If non-null, returns the address where the image was loaded * @lenp: If non-null, returns the size of the image loaded, in bytes * @namep: If non-null, returns the name of the FIT-image node that was loaded @@ -120,6 +131,7 @@ int vbe_read_nvdata(struct udevice *blk, ulong offset, ulong size, u8 *buf); * FIT-parsing (see fit_image_load()). */ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, - ulong *load_addrp, ulong *lenp, char **namep); + struct spl_image_info *image, ulong *load_addrp, ulong *lenp, + char **namep); #endif /* __VBE_ABREC_H */ diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c index 0bf25ccad23..9da3e49a66e 100644 --- a/boot/vbe_simple_fw.c +++ b/boot/vbe_simple_fw.c @@ -52,7 +52,8 @@ int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) log_debug("blk=%s\n", blk->name); ret = vbe_read_fit(blk, priv->area_start + priv->skip_offset, - priv->area_size, &load_addr, &len, &bflow->name); + priv->area_size, NULL, &load_addr, &len, + &bflow->name); /* set up the bootflow with the info we obtained */ bflow->blk = blk; From 68727fac696537b368324fbff61094f8b0d82048 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:23 -0700 Subject: [PATCH 22/22] vbe: Update simple-fw to support using the SPL loader For a sandbox implementation, where code size is no object, it makes sense to use the full bootstd drivers to load images. For real boards, running from SRAM, this adds quite a bit of overhead. Add a way to load the next phase using just the underlying storage driver, to reduce code size. For now, only MMC is supported. Change the log_debug() to show the load address and size in a more neutral way, rather than suggesting that the load has already happened. Signed-off-by: Simon Glass --- boot/vbe_common.c | 2 +- boot/vbe_simple_fw.c | 104 ++++++++++++++++++++++++++++++------------- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/boot/vbe_common.c b/boot/vbe_common.c index 2bc6c4d73ab..0d51fe762c3 100644 --- a/boot/vbe_common.c +++ b/boot/vbe_common.c @@ -239,7 +239,7 @@ int vbe_read_fit(struct udevice *blk, ulong area_offset, ulong area_size, if (ret < 0) return log_msg_ret("ld", ret); node = ret; - log_debug("loaded to %lx\n", load_addr); + log_debug("load %lx size %lx\n", load_addr, len); fdt_load_addr = 0; fdt_size = 0; diff --git a/boot/vbe_simple_fw.c b/boot/vbe_simple_fw.c index 9da3e49a66e..cb5534fc731 100644 --- a/boot/vbe_simple_fw.c +++ b/boot/vbe_simple_fw.c @@ -8,6 +8,7 @@ #define LOG_CATEGORY LOGC_BOOT +#include #include #include #include @@ -17,13 +18,24 @@ #include #include #include -#include #include #include #include #include +#include "vbe_common.h" #include "vbe_simple.h" +#ifdef CONFIG_BOOTMETH_VBE_SIMPLE +binman_sym_extern(ulong, vbe_a, image_pos); +binman_sym_extern(ulong, vbe_a, size); +#else +binman_sym_declare(ulong, vbe_a, image_pos); +binman_sym_declare(ulong, vbe_a, size); +#endif + +binman_sym_declare(ulong, vpl, image_pos); +binman_sym_declare(ulong, vpl, size); + /** * vbe_simple_read_bootflow_fw() - Create a bootflow for firmware * @@ -54,6 +66,8 @@ int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) ret = vbe_read_fit(blk, priv->area_start + priv->skip_offset, priv->area_size, NULL, &load_addr, &len, &bflow->name); + if (ret) + return log_msg_ret("vbe", ret); /* set up the bootflow with the info we obtained */ bflow->blk = blk; @@ -63,16 +77,14 @@ int vbe_simple_read_bootflow_fw(struct udevice *dev, struct bootflow *bflow) return 0; } -static int simple_load_from_image(struct spl_image_info *spl_image, +static int simple_load_from_image(struct spl_image_info *image, struct spl_boot_device *bootdev) { - struct udevice *meth, *bdev; - struct simple_priv *priv; - struct bootflow bflow; struct vbe_handoff *handoff; int ret; - if (xpl_phase() != PHASE_VPL && xpl_phase() != PHASE_SPL) + if (xpl_phase() != PHASE_VPL && xpl_phase() != PHASE_SPL && + xpl_phase() != PHASE_TPL) return -ENOENT; ret = bloblist_ensure_size(BLOBLISTT_VBE, sizeof(struct vbe_handoff), @@ -80,36 +92,64 @@ static int simple_load_from_image(struct spl_image_info *spl_image, 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); + if (USE_BOOTMETH) { + struct udevice *meth, *bdev; + struct simple_priv *priv; + struct bootflow bflow; - 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); + 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); - 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); + 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); - /* 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); + 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); - /* this is not used from now on, so free it */ - bootflow_free(&bflow); + /* jump to the image */ + image->flags = SPL_SANDBOXF_ARG_IS_BUF; + image->arg = bflow.buf; + 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); + } else { + struct udevice *media, *blk; + ulong offset, size; + + ret = uclass_get_device_by_seq(UCLASS_MMC, 1, &media); + if (ret) + return log_msg_ret("vdv", ret); + ret = blk_get_from_parent(media, &blk); + if (ret) + return log_msg_ret("med", ret); + if (xpl_phase() == PHASE_TPL) { + offset = binman_sym(ulong, vpl, image_pos); + size = binman_sym(ulong, vpl, size); + } else { + offset = binman_sym(ulong, vbe_a, image_pos); + size = binman_sym(ulong, vbe_a, size); + printf("offset=%lx\n", offset); + } + + ret = vbe_read_fit(blk, offset, size, image, NULL, NULL, NULL); + if (ret) + return log_msg_ret("vbe", ret); + } /* Record that VBE was used in this phase */ handoff->phases |= 1 << xpl_phase();