[PATCH v3 00/21] Universal Payload initial series

Universal Payload (UPL) is an Industry Standard for firmware components[1]. UPL is designed to improve interoperability within the firmware industry, allowing mixing and matching of projects with less friction and fewer project-specific implementations. UPL is cross-platform, supporting ARM, x86 and RISC-V initially.
This series provides some initial support for this, targeting 0.9.1 and sandbox only.
Features still to come include: - Support for architectures - FIT validation - Handoff validation - Interoperability tests
This series is available at dm/uplb-working and requires the alist series at dm/alist-working[2]
[1] https://universalpayload.github.io/spec/ [2] https://patchwork.ozlabs.org/project/uboot/list/?series=414642
Changes in v3: - Use longer error messages - Use -errno instead of inventing a new error - Use sizeof(*header) instead of 512 - Use a block size of 1 if !CONFIG_SPL_LOAD_BLOCK - Fix 'handed' typo - Rebase on earlier change - Allow up.h to be built by the host build - Split out into its own commit
Changes in v2: - Put the upl tests under their own subcommand to avoid bootstd init - Add a function to init the UPL process in SPL - Hang when something goes wrong, to avoid a broken boot - Add a runtime flag to enable UPL - Put upl_image() calls in a common place - Add a link to the spec - Add a link to the command - Support CI testing - Rework to use alist instead of fixed-length arrays
Simon Glass (21): sandbox: Use const in os_jump_to_file() sandbox: Fix a comment in os_find_u_boot() test: Move some SPL-loading test-code into sandbox common fdt: Don't overwrite bloblist devicetree sandbox: fdt: Avoid overwriting an existing fdt sandbox: Return error code from read/write/seek sandbox: Add ELF file to VPL u-boot.img sandbox: Set up global_data earlier upl: Add support for reading a upl handoff upl: Add support for writing a upl handoff upl: Add basic tests upl: Add a command upl: Add support for Universal Payload in SPL spl: Set SPL_FIT_FOUND for full FIT also spl: Plumb in the Universal Payload handoff upl: Plumb in universal payload to the init process sandbox_vpl: Enable Universal Payload upl: Add initial documentation sandbox: Add a flag to enable UPL sandbox: Add an SPL loader for UPL upl: Add an end-to-end test
MAINTAINERS | 13 + Makefile | 4 +- arch/sandbox/cpu/cpu.c | 2 + arch/sandbox/cpu/os.c | 30 +- arch/sandbox/cpu/spl.c | 120 +++++- arch/sandbox/cpu/start.c | 18 +- arch/sandbox/include/asm/spl.h | 15 + arch/sandbox/include/asm/state.h | 1 + boot/Kconfig | 70 ++++ boot/Makefile | 4 + boot/image-fit.c | 4 + boot/upl_common.c | 60 +++ boot/upl_common.h | 24 ++ boot/upl_read.c | 588 ++++++++++++++++++++++++++++ boot/upl_write.c | 622 ++++++++++++++++++++++++++++++ cmd/Kconfig | 7 + cmd/Makefile | 1 + cmd/upl.c | 118 ++++++ common/board_f.c | 22 ++ common/board_r.c | 2 + common/spl/Makefile | 2 + common/spl/spl.c | 8 + common/spl/spl_fit.c | 10 + common/spl/spl_upl.c | 171 ++++++++ configs/sandbox_defconfig | 1 + configs/sandbox_vpl_defconfig | 4 + doc/usage/cmd/upl.rst | 186 +++++++++ doc/usage/index.rst | 2 + doc/usage/upl.rst | 46 +++ drivers/block/sandbox.c | 4 +- drivers/usb/emul/sandbox_flash.c | 2 +- fs/sandbox/sandboxfs.c | 6 +- include/asm-generic/global_data.h | 19 + include/os.h | 6 +- include/spl.h | 16 + include/test/suites.h | 1 + include/upl.h | 364 +++++++++++++++++ lib/fdtdec.c | 1 + test/boot/Makefile | 2 + test/boot/upl.c | 437 +++++++++++++++++++++ test/cmd_ut.c | 3 + test/image/spl_load_os.c | 53 +-- test/py/tests/test_upl.py | 38 ++ 43 files changed, 3035 insertions(+), 72 deletions(-) create mode 100644 boot/upl_common.c create mode 100644 boot/upl_common.h create mode 100644 boot/upl_read.c create mode 100644 boot/upl_write.c create mode 100644 cmd/upl.c create mode 100644 common/spl/spl_upl.c create mode 100644 doc/usage/cmd/upl.rst create mode 100644 doc/usage/upl.rst create mode 100644 include/upl.h create mode 100644 test/boot/upl.c create mode 100644 test/py/tests/test_upl.py

The argument array is not changed by the callee, so mark it const.
Signed-off-by: Simon Glass sjg@chromium.org Reviewed-by: Mattijs Korpershoek mkorpershoek@baylibre.com ---
(no changes since v1)
arch/sandbox/cpu/os.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index 46ff305b536..a04eb5aa18c 100644 --- a/arch/sandbox/cpu/os.c +++ b/arch/sandbox/cpu/os.c @@ -808,7 +808,7 @@ static int make_exec(char *fname, const void *data, int size) * @count: Number of arguments in @add_args * Return: 0 if OK, -ENOMEM if out of memory */ -static int add_args(char ***argvp, char *add_args[], int count) +static int add_args(char ***argvp, const char *add_args[], int count) { char **argv, **ap; int argc; @@ -859,7 +859,7 @@ static int os_jump_to_file(const char *fname, bool delete_it) struct sandbox_state *state = state_get_current(); char mem_fname[30]; int fd, err; - char *extra_args[5]; + const char *extra_args[5]; char **argv = state->argv; int argc; #ifdef DEBUG

Fix a missing dot in a comment, since '..' is confusing.
Signed-off-by: Simon Glass sjg@chromium.org Reviewed-by: Mattijs Korpershoek mkorpershoek@baylibre.com ---
(no changes since v1)
arch/sandbox/cpu/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index a04eb5aa18c..da96ebe43c9 100644 --- a/arch/sandbox/cpu/os.c +++ b/arch/sandbox/cpu/os.c @@ -964,7 +964,7 @@ int os_find_u_boot(char *fname, int maxlen, bool use_img, p = strstr(fname, subdir); if (p) { if (*next_prefix) - /* e.g. ".../tpl/u-boot-spl" to "../spl/u-boot-spl" */ + /* e.g. ".../tpl/u-boot-spl" to ".../spl/u-boot-spl" */ memcpy(p + 1, next_prefix, strlen(next_prefix)); else /* e.g. ".../spl/u-boot" to ".../u-boot" */

This code is useful for loading an image in sandbox_spl so move it into a place where it can be called as needed.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v3: - Use longer error messages - Use -errno instead of inventing a new error - Use sizeof(*header) instead of 512 - Use a block size of 1 if !CONFIG_SPL_LOAD_BLOCK
arch/sandbox/cpu/spl.c | 73 ++++++++++++++++++++++++++++++++++ arch/sandbox/include/asm/spl.h | 14 +++++++ test/image/spl_load_os.c | 53 +----------------------- 3 files changed, 88 insertions(+), 52 deletions(-)
diff --git a/arch/sandbox/cpu/spl.c b/arch/sandbox/cpu/spl.c index 9ad9da686c6..49abe02f322 100644 --- a/arch/sandbox/cpu/spl.c +++ b/arch/sandbox/cpu/spl.c @@ -6,6 +6,7 @@ #include <dm.h> #include <hang.h> #include <handoff.h> +#include <image.h> #include <init.h> #include <log.h> #include <os.h> @@ -179,3 +180,75 @@ int handoff_arch_save(struct spl_handoff *ho)
return 0; } + +/* Context used to hold file descriptor */ +struct load_ctx { + int fd; +}; + +static ulong read_fit_image(struct spl_load_info *load, ulong offset, + ulong size, void *buf) +{ + struct load_ctx *load_ctx = load->priv; + off_t ret; + ssize_t res; + + ret = os_lseek(load_ctx->fd, offset, OS_SEEK_SET); + if (ret < 0) { + printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset, + ret, errno); + return log_msg_ret("lse", -errno); + } + + res = os_read(load_ctx->fd, buf, size); + if (res < 0) { + printf("Failed to read %lx bytes, got %ld (errno=%d)\n", + size, res, errno); + return log_msg_ret("osr", -errno); + } + + return size; +} + +int sandbox_spl_load_fit(char *fname, int maxlen, struct spl_image_info *image) +{ + struct legacy_img_hdr *header; + struct load_ctx load_ctx; + struct spl_load_info load; + int ret; + int fd; + + memset(&load, '\0', sizeof(load)); + spl_set_bl_len(&load, IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) ? 512 : 1); + load.read = read_fit_image; + + ret = sandbox_find_next_phase(fname, maxlen, true); + if (ret) { + printf("%s not found, error %d\n", fname, ret); + return log_msg_ret("nph", ret); + } + + header = spl_get_load_buffer(-sizeof(*header), sizeof(*header)); + + log_debug("reading from %s\n", fname); + fd = os_open(fname, OS_O_RDONLY); + if (fd < 0) { + printf("Failed to open '%s'\n", fname); + return log_msg_ret("ope", -errno); + } + ret = os_read(fd, header, sizeof(*header)); + if (ret != sizeof(*header)) { + printf("Failed to read %lx bytes, got %ld (errno=%d)\n", + sizeof(*header), ret, -errno); + return log_msg_ret("rea", -errno); + } + load_ctx.fd = fd; + + load.priv = &load_ctx; + + ret = spl_load_simple_fit(image, &load, 0, header); + if (ret) + return log_msg_ret("slf", ret); + + return 0; +} diff --git a/arch/sandbox/include/asm/spl.h b/arch/sandbox/include/asm/spl.h index 4fab24cd156..d50d9ad6b48 100644 --- a/arch/sandbox/include/asm/spl.h +++ b/arch/sandbox/include/asm/spl.h @@ -6,6 +6,8 @@ #ifndef __asm_spl_h #define __asm_spl_h
+struct spl_image_info; + enum { BOOT_DEVICE_MMC1, BOOT_DEVICE_MMC2, @@ -31,4 +33,16 @@ enum { */ int sandbox_find_next_phase(char *fname, int maxlen, bool use_img);
+/** + * sandbox_spl_load_fit() - Load the next phase from a FIT + * + * Loads a FIT containing the next phase and sets it up for booting + * + * @fname: Returns filename loaded + * @maxlen: Maximum length for @fname including \0 + * @image: Place to put SPL-image information + * Return: 0 if OK, -ve on error + */ +int sandbox_spl_load_fit(char *fname, int maxlen, struct spl_image_info *image); + #endif diff --git a/test/image/spl_load_os.c b/test/image/spl_load_os.c index 7d5fb9b07e0..56105a59236 100644 --- a/test/image/spl_load_os.c +++ b/test/image/spl_load_os.c @@ -10,63 +10,12 @@ #include <test/spl.h> #include <test/ut.h>
-/* Context used for this test */ -struct text_ctx { - int fd; -}; - -static ulong read_fit_image(struct spl_load_info *load, ulong offset, - ulong size, void *buf) -{ - struct text_ctx *text_ctx = load->priv; - off_t ret; - ssize_t res; - - ret = os_lseek(text_ctx->fd, offset, OS_SEEK_SET); - if (ret != offset) { - printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset, - ret, errno); - return 0; - } - - res = os_read(text_ctx->fd, buf, size); - if (res == -1) { - printf("Failed to read %lx bytes, got %ld (errno=%d)\n", - size, res, errno); - return 0; - } - - return size; -} - static int spl_test_load(struct unit_test_state *uts) { struct spl_image_info image; - struct legacy_img_hdr *header; - struct text_ctx text_ctx; - struct spl_load_info load; char fname[256]; - int ret; - int fd; - - memset(&load, '\0', sizeof(load)); - spl_set_bl_len(&load, 512); - load.read = read_fit_image; - - ret = sandbox_find_next_phase(fname, sizeof(fname), true); - if (ret) - ut_assertf(0, "%s not found, error %d\n", fname, ret); - - header = spl_get_load_buffer(-sizeof(*header), sizeof(*header)); - - fd = os_open(fname, OS_O_RDONLY); - ut_assert(fd >= 0); - ut_asserteq(512, os_read(fd, header, 512)); - text_ctx.fd = fd; - - load.priv = &text_ctx;
- ut_assertok(spl_load_simple_fit(&image, &load, 0, header)); + ut_assertok(sandbox_spl_load_fit(fname, sizeof(fname), &image));
return 0; }

When the devicetree comes from a bloblist, it is currently overwritten by the appended one, if present. It should be preserved.
Adjust the logic to support this.
Fixes: 70fe2385943 ("fdt: Allow the devicetree to come from a bloblist")
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
lib/fdtdec.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 5b3b26df968..01048b19737 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -1677,6 +1677,7 @@ int fdtdec_setup(void) gd->fdt_src = FDTSRC_BLOBLIST; log_debug("Devicetree is in bloblist at %p\n", gd->fdt_blob); + ret = 0; } else { log_debug("No FDT found in bloblist\n"); ret = -ENOENT;

Since the removal of OF_HOSTFILE logic in board_fdt_blob_setup(), the logic for obtaining the DT is handled in the OF_BOARD option. If a devicetree comes from a bloblist it is immediately overwritten by this function.
Fix this by skipping the function if a devicetree is already present.
This is sort-of a fix for e7fb7896 ("sandbox: Remove OF_HOSTFILE") but it has only come to light since bloblist was added, so I have not added a Fixes tag.
Unfortunately it is not possible to report the correct FDT source with the current code. It might be best to use an error-return code for board_fdt_blob_setup() so that an error can be reported if the board does not provide the DT.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v3: - Fix 'handed' typo
arch/sandbox/cpu/cpu.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/arch/sandbox/cpu/cpu.c b/arch/sandbox/cpu/cpu.c index 0ed85b354cf..4f15a560902 100644 --- a/arch/sandbox/cpu/cpu.c +++ b/arch/sandbox/cpu/cpu.c @@ -340,6 +340,8 @@ void *board_fdt_blob_setup(int *ret) int err; int fd;
+ if (gd->fdt_blob) + return (void *)gd->fdt_blob; blob = map_sysmem(CONFIG_SYS_FDT_LOAD_ADDR, 0); *ret = 0; if (!state->fdt_fname) {

The existing API for these functions is different from the rest of U-Boot, in that any error code must be obtained from the errno variable on failure. This variable is part of the C library, so accessing it outside of the special 'sandbox' shim-functions is not ideal.
Adjust the API to return an error code, to avoid this. Update existing uses to check for any negative value, rather than just -1.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v3: - Rebase on earlier change
arch/sandbox/cpu/os.c | 24 +++++++++++++++++++++--- arch/sandbox/cpu/spl.c | 16 +++++++--------- drivers/block/sandbox.c | 4 ++-- drivers/usb/emul/sandbox_flash.c | 2 +- fs/sandbox/sandboxfs.c | 6 +++--- include/os.h | 6 +++--- 6 files changed, 37 insertions(+), 21 deletions(-)
diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index da96ebe43c9..f5c9a8aecf2 100644 --- a/arch/sandbox/cpu/os.c +++ b/arch/sandbox/cpu/os.c @@ -47,12 +47,24 @@ struct os_mem_hdr {
ssize_t os_read(int fd, void *buf, size_t count) { - return read(fd, buf, count); + ssize_t ret; + + ret = read(fd, buf, count); + if (ret == -1) + return -errno; + + return ret; }
ssize_t os_write(int fd, const void *buf, size_t count) { - return write(fd, buf, count); + ssize_t ret; + + ret = write(fd, buf, count); + if (ret == -1) + return -errno; + + return ret; }
int os_printf(const char *fmt, ...) @@ -69,6 +81,8 @@ int os_printf(const char *fmt, ...)
off_t os_lseek(int fd, off_t offset, int whence) { + off_t ret; + if (whence == OS_SEEK_SET) whence = SEEK_SET; else if (whence == OS_SEEK_CUR) @@ -77,7 +91,11 @@ off_t os_lseek(int fd, off_t offset, int whence) whence = SEEK_END; else os_exit(1); - return lseek(fd, offset, whence); + ret = lseek(fd, offset, whence); + if (ret == -1) + return -errno; + + return ret; }
int os_open(const char *pathname, int os_flags) diff --git a/arch/sandbox/cpu/spl.c b/arch/sandbox/cpu/spl.c index 49abe02f322..e86193de419 100644 --- a/arch/sandbox/cpu/spl.c +++ b/arch/sandbox/cpu/spl.c @@ -195,16 +195,14 @@ static ulong read_fit_image(struct spl_load_info *load, ulong offset,
ret = os_lseek(load_ctx->fd, offset, OS_SEEK_SET); if (ret < 0) { - printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset, - ret, errno); - return log_msg_ret("lse", -errno); + printf("Failed to seek to %zx, got %zx\n", offset, ret); + return log_msg_ret("lse", ret); }
res = os_read(load_ctx->fd, buf, size); if (res < 0) { - printf("Failed to read %lx bytes, got %ld (errno=%d)\n", - size, res, errno); - return log_msg_ret("osr", -errno); + printf("Failed to read %lx bytes, got %ld\n", size, res); + return log_msg_ret("osr", res); }
return size; @@ -238,9 +236,9 @@ int sandbox_spl_load_fit(char *fname, int maxlen, struct spl_image_info *image) } ret = os_read(fd, header, sizeof(*header)); if (ret != sizeof(*header)) { - printf("Failed to read %lx bytes, got %ld (errno=%d)\n", - sizeof(*header), ret, -errno); - return log_msg_ret("rea", -errno); + printf("Failed to read %lx bytes, got %d\n", sizeof(*header), + ret); + return log_msg_ret("rea", ret); } load_ctx.fd = fd;
diff --git a/drivers/block/sandbox.c b/drivers/block/sandbox.c index ec34f1ad8c2..6c74d66037e 100644 --- a/drivers/block/sandbox.c +++ b/drivers/block/sandbox.c @@ -25,7 +25,7 @@ static unsigned long host_block_read(struct udevice *dev, struct udevice *host_dev = dev_get_parent(dev); struct host_sb_plat *plat = dev_get_plat(host_dev);
- if (os_lseek(plat->fd, start * desc->blksz, OS_SEEK_SET) == -1) { + if (os_lseek(plat->fd, start * desc->blksz, OS_SEEK_SET) < 0) { printf("ERROR: Invalid block %lx\n", start); return -1; } @@ -44,7 +44,7 @@ static unsigned long host_block_write(struct udevice *dev, struct udevice *host_dev = dev_get_parent(dev); struct host_sb_plat *plat = dev_get_plat(host_dev);
- if (os_lseek(plat->fd, start * desc->blksz, OS_SEEK_SET) == -1) { + if (os_lseek(plat->fd, start * desc->blksz, OS_SEEK_SET) < 0) { printf("ERROR: Invalid block %lx\n", start); return -1; } diff --git a/drivers/usb/emul/sandbox_flash.c b/drivers/usb/emul/sandbox_flash.c index 24420e3d51e..b5176bb30ce 100644 --- a/drivers/usb/emul/sandbox_flash.c +++ b/drivers/usb/emul/sandbox_flash.c @@ -196,7 +196,7 @@ static int handle_ufi_command(struct sandbox_flash_priv *priv, const void *buff, priv->fd != -1) { offset = os_lseek(priv->fd, info->seek_block * info->block_size, OS_SEEK_SET); - if (offset == (off_t)-1) + if (offset < 0) setup_fail_response(priv); else setup_response(priv); diff --git a/fs/sandbox/sandboxfs.c b/fs/sandbox/sandboxfs.c index 773b583fa43..76f1a71f412 100644 --- a/fs/sandbox/sandboxfs.c +++ b/fs/sandbox/sandboxfs.c @@ -28,7 +28,7 @@ int sandbox_fs_read_at(const char *filename, loff_t pos, void *buffer, if (fd < 0) return fd; ret = os_lseek(fd, pos, OS_SEEK_SET); - if (ret == -1) { + if (ret < 0) { os_close(fd); return ret; } @@ -65,14 +65,14 @@ int sandbox_fs_write_at(const char *filename, loff_t pos, void *buffer, if (fd < 0) return fd; ret = os_lseek(fd, pos, OS_SEEK_SET); - if (ret == -1) { + if (ret < 0) { os_close(fd); return ret; } size = os_write(fd, buffer, towrite); os_close(fd);
- if (size == -1) { + if (size < 0) { ret = -1; } else { ret = 0; diff --git a/include/os.h b/include/os.h index 877404a6c13..4371270a1ee 100644 --- a/include/os.h +++ b/include/os.h @@ -29,7 +29,7 @@ int os_printf(const char *format, ...); * @fd: File descriptor as returned by os_open() * @buf: Buffer to place data * @count: Number of bytes to read - * Return: number of bytes read, or -1 on error + * Return: number of bytes read, or -errno on error */ ssize_t os_read(int fd, void *buf, size_t count);
@@ -39,7 +39,7 @@ ssize_t os_read(int fd, void *buf, size_t count); * @fd: File descriptor as returned by os_open() * @buf: Buffer containing data to write * @count: Number of bytes to write - * Return: number of bytes written, or -1 on error + * Return: number of bytes written, or -errno on error */ ssize_t os_write(int fd, const void *buf, size_t count);
@@ -49,7 +49,7 @@ ssize_t os_write(int fd, const void *buf, size_t count); * @fd: File descriptor as returned by os_open() * @offset: File offset (based on whence) * @whence: Position offset is relative to (see below) - * Return: new file offset + * Return: new file offset, or -errno on error */ off_t os_lseek(int fd, off_t offset, int whence);

At present sandbox builds package up u-boot.bin in the .img file. This cannot actually be executed, since it is not an ELF file.
For sandbox_vpl we want to be able to run the full boot flow.
Adjust the build rule for sandbox_vpl to package the ELF file and thereby allow full testing of the sandbox transition from SPL to U-Boot proper.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile index f5b2512f369..f364cd31ade 100644 --- a/Makefile +++ b/Makefile @@ -1470,8 +1470,10 @@ u-boot.bin.lzma: u-boot.bin FORCE u-boot-lzma.img: u-boot.bin.lzma FORCE $(call if_changed,mkimage)
+fit_image := $(if $(CONFIG_SANDBOX_VPL),u-boot,u-boot-nodtb.bin) + u-boot-dtb.img u-boot.img u-boot.kwb u-boot.pbl u-boot-ivt.img: \ - $(if $(CONFIG_SPL_LOAD_FIT),u-boot-nodtb.bin \ + $(if $(CONFIG_SPL_LOAD_FIT),$(fit_image) \ $(if $(CONFIG_OF_SEPARATE)$(CONFIG_OF_EMBED)$(CONFIG_SANDBOX),dts/dt.dtb) \ ,$(UBOOT_BIN)) FORCE $(call if_changed,mkimage)

It is possible for U-Boot functions such as printf() to be called within state_init(). This can end up checking gd->flags (e.g. in putc()) before global_data is set up.
Move the setup earlier to avoid this. This fixes the suppression of some debug output in memory allocation (when enabled).
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
arch/sandbox/cpu/start.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c index dce80416529..0920ed55965 100644 --- a/arch/sandbox/cpu/start.c +++ b/arch/sandbox/cpu/start.c @@ -483,6 +483,9 @@ int sandbox_main(int argc, char *argv[])
text_base = os_find_text_base();
+ memset(&data, '\0', sizeof(data)); + gd = &data; + /* * This must be the first invocation of os_malloc() to have * state->ram_buf in the low 4 GiB. @@ -501,8 +504,6 @@ int sandbox_main(int argc, char *argv[]) os_exit(1); memcpy(os_argv, argv, size);
- memset(&data, '\0', sizeof(data)); - gd = &data; gd->arch.text_base = text_base;
state = state_get_current();

Universal Payload provides a standard way of handing off control between two firmware phases. Add support for reading the handoff information into a structure.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v3: - Allow up.h to be built by the host build
MAINTAINERS | 7 + boot/Kconfig | 19 ++ boot/Makefile | 3 + boot/upl_common.c | 60 ++++ boot/upl_common.h | 24 ++ boot/upl_read.c | 588 ++++++++++++++++++++++++++++++++++++++ configs/sandbox_defconfig | 1 + include/upl.h | 355 +++++++++++++++++++++++ 8 files changed, 1057 insertions(+) create mode 100644 boot/upl_common.c create mode 100644 boot/upl_common.h create mode 100644 boot/upl_read.c create mode 100644 include/upl.h
diff --git a/MAINTAINERS b/MAINTAINERS index a6e47e8a217..070a766a76f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1701,6 +1701,13 @@ M: Neha Malcom Francis n-francis@ti.com S: Maintained F: drivers/ufs/
+UPL +M: Simon Glass sjg@chromium.org +S: Maintained +T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git +F: boot/upl* +F: include/upl.h + USB M: Marek Vasut marex@denx.de S: Maintained diff --git a/boot/Kconfig b/boot/Kconfig index def483a41f1..6e907b45095 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -736,6 +736,25 @@ config BOOTMETH_SCRIPT This provides a way to try out standard boot on an existing boot flow. It is not enabled by default to save space.
+config UPL + bool "upl - Universal Payload Specification" + imply UPL_READ + help + Provides support for UPL payloads and handoff information. U-Boot + supports generating and accepting handoff information. The mkimage + tool will eventually support creating payloads. + +if UPL + +config UPL_READ + bool "upl - Support reading a Universal Payload handoff" + help + Provides support for decoding a UPL-format payload into a C structure + which can be used elsewhere in U-Boot. This is just the reading + implementation, useful for trying it out. + +endif # UPL + endif # BOOTSTD
config LEGACY_IMAGE_FORMAT diff --git a/boot/Makefile b/boot/Makefile index 75d1cd46fab..97b52a3404c 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -43,6 +43,9 @@ endif obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += fdt_support.o obj-$(CONFIG_$(SPL_TPL_)FDT_SIMPLEFB) += fdt_simplefb.o
+obj-$(CONFIG_$(SPL_TPL_)UPL) += upl_common.o +obj-$(CONFIG_$(SPL_TPL_)UPL_READ) += upl_read.o + obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o diff --git a/boot/upl_common.c b/boot/upl_common.c new file mode 100644 index 00000000000..3924423abd5 --- /dev/null +++ b/boot/upl_common.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UPL handoff command functions + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <string.h> +#include <upl.h> + +/* Names of bootmodes */ +const char *const bootmode_names[UPLBM_COUNT] = { + [UPLBM_FULL] = "full", + [UPLBM_MINIMAL] = "minimal", + [UPLBM_FAST] = "fast", + [UPLBM_DIAG] = "diag", + [UPLBM_DEFAULT] = "default", + [UPLBM_S2] = "s2", + [UPLBM_S3] = "s3", + [UPLBM_S4] = "s4", + [UPLBM_S5] = "s5", + [UPLBM_FACTORY] = "factory", + [UPLBM_FLASH] = "flash", + [UPLBM_RECOVERY] = "recovery", +}; + +/* Names of memory usages */ +const char *const usage_names[UPLUS_COUNT] = { + [UPLUS_ACPI_RECLAIM] = "acpi-reclaim", + [UPLUS_ACPI_NVS] = "acpi-nvs", + [UPLUS_BOOT_CODE] = "boot-code", + [UPLUS_BOOT_DATA] = "boot-data", + [UPLUS_RUNTIME_CODE] = "runtime-code", + [UPLUS_RUNTIME_DATA] = "runtime-data", +}; + +/* Names of access types */ +const char *const access_types[UPLUS_COUNT] = { + [UPLAT_MMIO] = "mmio", + [UPLAT_IO] = "io", +}; + +/* Names of graphics formats */ +const char *const graphics_formats[UPLUS_COUNT] = { + [UPLGF_ARGB32] = "a8r8g8b8", + [UPLGF_ABGR32] = "a8b8g8r8", + [UPLGF_ABGR64] = "a16b16g16r16", +}; + +void upl_init(struct upl *upl) +{ + memset(upl, '\0', sizeof(struct upl)); + alist_init_struct(&upl->image, struct upl_image); + alist_init_struct(&upl->mem, struct upl_mem); + alist_init_struct(&upl->memmap, struct upl_memmap); + alist_init_struct(&upl->memres, struct upl_memres); +} diff --git a/boot/upl_common.h b/boot/upl_common.h new file mode 100644 index 00000000000..cc517dc1de9 --- /dev/null +++ b/boot/upl_common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * UPL handoff command functions + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#ifndef __UPL_COMMON_H +#define __UPL_COMMON_H + +/* Names of bootmodes */ +extern const char *const bootmode_names[UPLBM_COUNT]; + +/* Names of memory usages */ +extern const char *const usage_names[UPLUS_COUNT]; + +/* Names of access types */ +extern const char *const access_types[UPLUS_COUNT]; + +/* Names of graphics formats */ +extern const char *const graphics_formats[UPLUS_COUNT]; + +#endif /* __UPL_COMMON_H */ diff --git a/boot/upl_read.c b/boot/upl_read.c new file mode 100644 index 00000000000..2d8caa6ef11 --- /dev/null +++ b/boot/upl_read.c @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UPL handoff parsing + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <log.h> +#include <upl.h> +#include <dm/ofnode.h> +#include "upl_common.h" + +/** + * read_addr() - Read an address + * + * Reads an address in the correct format, either 32- or 64-bit + * + * @upl: UPL state + * @node: Node to read from + * @prop: Property name to read + * @addr: Place to put the address + * Return: 0 if OK, -ve on error + */ +static int read_addr(const struct upl *upl, ofnode node, const char *prop, + ulong *addrp) +{ + int ret; + + if (upl->addr_cells == 1) { + u32 val; + + ret = ofnode_read_u32(node, prop, &val); + if (!ret) + *addrp = val; + } else { + u64 val; + + ret = ofnode_read_u64(node, prop, &val); + if (!ret) + *addrp = val; + } + + return ret; +} + +/** + * read_size() - Read a size + * + * Reads a size in the correct format, either 32- or 64-bit + * + * @upl: UPL state + * @node: Node to read from + * @prop: Property name to read + * @addr: Place to put the size + * Return: 0 if OK, -ve on error + */ +static int read_size(const struct upl *upl, ofnode node, const char *prop, + ulong *sizep) +{ + int ret; + + if (upl->size_cells == 1) { + u32 val; + + ret = ofnode_read_u32(node, prop, &val); + if (!ret) + *sizep = val; + } else { + u64 val; + + ret = ofnode_read_u64(node, prop, &val); + if (!ret) + *sizep = val; + } + + return ret; +} + +/** + * ofnode_read_bitmask() - Read a bit mask from a string list + * + * @node: Node to read from + * @prop: Property name to read + * @names: Array of names for each bit + * @count: Number of array entries + * @value: Returns resulting bit-mask value on success + * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the + * string is too long for the (internal) buffer, -EINVAL if no such property + */ +static int ofnode_read_bitmask(ofnode node, const char *prop, + const char *const names[], uint count, + uint *valuep) +{ + const char **list; + const char **strp; + uint val; + uint bit; + int ret; + + ret = ofnode_read_string_list(node, prop, &list); + if (ret < 0) + return log_msg_ret("rea", ret); + + val = 0; + for (strp = list; *strp; strp++) { + const char *str = *strp; + bool found = false; + + for (bit = 0; bit < count; bit++) { + if (!strcmp(str, names[bit])) { + found = true; + break; + } + } + if (found) + val |= BIT(bit); + else + log_warning("%s/%s: Invalid value '%s'\n", + ofnode_get_name(node), prop, str); + } + *valuep = val; + + return 0; +} + +/** + * ofnode_read_value() - Read a string value as an int using a lookup + * + * @node: Node to read from + * @prop: Property name to read + * @names: Array of names for each int value + * @count: Number of array entries + * @valuep: Returns int value read + * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOENT if the + * property does not exist + */ +static int ofnode_read_value(ofnode node, const char *prop, + const char *const names[], uint count, + uint *valuep) +{ + const char *str; + int i; + + str = ofnode_read_string(node, prop); + if (!str) + return log_msg_ret("rd", -ENOENT); + + for (i = 0; i < count; i++) { + if (!strcmp(names[i], str)) { + *valuep = i; + return 0; + } + } + + log_debug("Unnamed value '%s'\n", str); + return log_msg_ret("val", -EINVAL); +} + +static int read_uint(ofnode node, const char *prop, uint *valp) +{ + u32 val; + int ret; + + ret = ofnode_read_u32(node, prop, &val); + if (ret) + return ret; + *valp = val; + + return 0; +} + +/** + * decode_root_props() - Decode root properties from the tree + * + * @upl: UPL state + * @node: Node to decode + * Return 0 if OK, -ve on error + */ +static int decode_root_props(struct upl *upl, ofnode node) +{ + int ret; + + ret = read_uint(node, UPLP_ADDRESS_CELLS, &upl->addr_cells); + if (!ret) + ret = read_uint(node, UPLP_SIZE_CELLS, &upl->size_cells); + if (ret) + return log_msg_ret("cel", ret); + + return 0; +} + +/** + * decode_root_props() - Decode UPL parameters from the tree + * + * @upl: UPL state + * @node: Node to decode + * Return 0 if OK, -ve on error + */ +static int decode_upl_params(struct upl *upl, ofnode options) +{ + ofnode node; + int ret; + + node = ofnode_find_subnode(options, UPLN_UPL_PARAMS); + if (!ofnode_valid(node)) + return log_msg_ret("par", -EINVAL); + log_debug("decoding '%s'\n", ofnode_get_name(node)); + + ret = read_addr(upl, node, UPLP_SMBIOS, &upl->smbios); + if (ret) + return log_msg_ret("smb", ret); + ret = read_addr(upl, node, UPLP_ACPI, &upl->acpi); + if (ret) + return log_msg_ret("acp", ret); + ret = ofnode_read_bitmask(node, UPLP_BOOTMODE, bootmode_names, + UPLBM_COUNT, &upl->bootmode); + if (ret) + return log_msg_ret("boo", ret); + ret = read_uint(node, UPLP_ADDR_WIDTH, &upl->addr_width); + if (ret) + return log_msg_ret("add", ret); + ret = read_uint(node, UPLP_ACPI_NVS_SIZE, &upl->acpi_nvs_size); + if (ret) + return log_msg_ret("nvs", ret); + + return 0; +} + +/** + * decode_upl_images() - Decode /options/upl-image nodes + * + * @node: /options node in which to look for the node + * Return 0 if OK, -ve on error + */ +static int decode_upl_images(struct upl *upl, ofnode options) +{ + ofnode node, images; + int ret; + + images = ofnode_find_subnode(options, UPLN_UPL_IMAGE); + if (!ofnode_valid(images)) + return log_msg_ret("img", -EINVAL); + log_debug("decoding '%s'\n", ofnode_get_name(images)); + + ret = read_addr(upl, images, UPLP_FIT, &upl->fit); + if (!ret) + ret = read_uint(images, UPLP_CONF_OFFSET, &upl->conf_offset); + if (ret) + return log_msg_ret("cnf", ret); + + ofnode_for_each_subnode(node, images) { + struct upl_image img; + + ret = read_addr(upl, node, UPLP_LOAD, &img.load); + if (!ret) + ret = read_size(upl, node, UPLP_SIZE, &img.size); + if (!ret) + ret = read_uint(node, UPLP_OFFSET, &img.offset); + img.description = ofnode_read_string(node, UPLP_DESCRIPTION); + if (!img.description) + return log_msg_ret("sim", ret); + if (!alist_add(&upl->image, img, struct upl_image)) + return log_msg_ret("img", -ENOMEM); + } + + return 0; +} + +/** + * decode_addr_size() - Decide a set of addr/size pairs + * + * Each base/size value from the devicetree is written to the region list + * + * @upl: UPL state + * @buf: Bytes to decode + * @size: Number of bytes to decode + * @regions: List of regions to process (struct memregion) + * Returns: number of regions found, if OK, else -ve on error + */ +static int decode_addr_size(const struct upl *upl, const char *buf, int size, + struct alist *regions) +{ + const char *ptr, *end = buf + size; + int i; + + alist_init_struct(regions, struct memregion); + ptr = buf; + for (i = 0; ptr < end; i++) { + struct memregion reg; + + if (upl->addr_cells == 1) + reg.base = fdt32_to_cpu(*(u32 *)ptr); + else + reg.base = fdt64_to_cpu(*(u64 *)ptr); + ptr += upl->addr_cells * sizeof(u32); + + if (upl->size_cells == 1) + reg.size = fdt32_to_cpu(*(u32 *)ptr); + else + reg.size = fdt64_to_cpu(*(u64 *)ptr); + ptr += upl->size_cells * sizeof(u32); + if (ptr > end) + return -ENOSPC; + + if (!alist_add(regions, reg, struct memregion)) + return log_msg_ret("reg", -ENOMEM); + } + + return i; +} + +/** + * node_matches_at() - Check if a node name matches "base@..." + * + * Return: true if the node name matches the base string followed by an @ sign; + * false otherwise + */ +static bool node_matches_at(ofnode node, const char *base) +{ + const char *name = ofnode_get_name(node); + int len = strlen(base); + + return !strncmp(base, name, len) && name[len] == '@'; +} + +/** + * decode_upl_memory_node() - Decode a /memory node from the tree + * + * @upl: UPL state + * @node: Node to decode + * Return 0 if OK, -ve on error + */ +static int decode_upl_memory_node(struct upl *upl, ofnode node) +{ + struct upl_mem mem; + const char *buf; + int size, len; + + buf = ofnode_read_prop(node, UPLP_REG, &size); + if (!buf) { + log_warning("Node '%s': Missing '%s' property\n", + ofnode_get_name(node), UPLP_REG); + return log_msg_ret("reg", -EINVAL); + } + len = decode_addr_size(upl, buf, size, &mem.region); + if (len < 0) + return log_msg_ret("buf", len); + mem.hotpluggable = ofnode_read_bool(node, UPLP_HOTPLUGGABLE); + if (!alist_add(&upl->mem, mem, struct upl_mem)) + return log_msg_ret("mem", -ENOMEM); + + return 0; +} + +/** + * decode_upl_memmap() - Decode memory-map nodes from the tree + * + * @upl: UPL state + * @root: Parent node containing the /memory-map nodes + * Return 0 if OK, -ve on error + */ +static int decode_upl_memmap(struct upl *upl, ofnode root) +{ + ofnode node; + + ofnode_for_each_subnode(node, root) { + struct upl_memmap memmap; + int size, len, ret; + const char *buf; + + memmap.name = ofnode_get_name(node); + memmap.usage = 0; + + buf = ofnode_read_prop(node, UPLP_REG, &size); + if (!buf) { + log_warning("Node '%s': Missing '%s' property\n", + ofnode_get_name(node), UPLP_REG); + continue; + } + + len = decode_addr_size(upl, buf, size, &memmap.region); + if (len < 0) + return log_msg_ret("buf", len); + ret = ofnode_read_bitmask(node, UPLP_USAGE, usage_names, + UPLUS_COUNT, &memmap.usage); + if (ret && ret != -EINVAL) /* optional property */ + return log_msg_ret("bit", ret); + + if (!alist_add(&upl->memmap, memmap, struct upl_memmap)) + return log_msg_ret("mmp", -ENOMEM); + } + + return 0; +} + +/** + * decode_upl_memres() - Decode reserved-memory nodes from the tree + * + * @upl: UPL state + * @root: Parent node containing the reserved-memory nodes + * Return 0 if OK, -ve on error + */ +static int decode_upl_memres(struct upl *upl, ofnode root) +{ + ofnode node; + + ofnode_for_each_subnode(node, root) { + struct upl_memres memres; + const char *buf; + int size, len; + + log_debug("decoding '%s'\n", ofnode_get_name(node)); + memres.name = ofnode_get_name(node); + + buf = ofnode_read_prop(node, UPLP_REG, &size); + if (!buf) { + log_warning("Node '%s': Missing 'reg' property\n", + ofnode_get_name(node)); + continue; + } + + len = decode_addr_size(upl, buf, size, &memres.region); + if (len < 0) + return log_msg_ret("buf", len); + memres.no_map = ofnode_read_bool(node, UPLP_NO_MAP); + + if (!alist_add(&upl->memres, memres, struct upl_memres)) + return log_msg_ret("mre", -ENOMEM); + } + + return 0; +} + +/** + * decode_upl_serial() - Decode the serial node + * + * @upl: UPL state + * @root: Parent node contain node + * Return 0 if OK, -ve on error + */ +static int decode_upl_serial(struct upl *upl, ofnode node) +{ + struct upl_serial *ser = &upl->serial; + const char *buf; + int len, size; + int ret; + + ser->compatible = ofnode_read_string(node, UPLP_COMPATIBLE); + if (!ser->compatible) { + log_warning("Node '%s': Missing compatible string\n", + ofnode_get_name(node)); + return log_msg_ret("com", -EINVAL); + } + ret = read_uint(node, UPLP_CLOCK_FREQUENCY, &ser->clock_frequency); + if (!ret) + ret = read_uint(node, UPLP_CURRENT_SPEED, &ser->current_speed); + if (ret) + return log_msg_ret("spe", ret); + + buf = ofnode_read_prop(node, UPLP_REG, &size); + if (!buf) { + log_warning("Node '%s': Missing 'reg' property\n", + ofnode_get_name(node)); + return log_msg_ret("reg", -EINVAL); + } + + len = decode_addr_size(upl, buf, sizeof(buf), &ser->reg); + if (len < 0) + return log_msg_ret("buf", len); + + /* set defaults */ + ser->reg_io_shift = UPLD_REG_IO_SHIFT; + ser->reg_offset = UPLD_REG_OFFSET; + ser->reg_io_width = UPLD_REG_IO_WIDTH; + read_uint(node, UPLP_REG_IO_SHIFT, &ser->reg_io_shift); + read_uint(node, UPLP_REG_OFFSET, &ser->reg_offset); + read_uint(node, UPLP_REG_IO_WIDTH, &ser->reg_io_width); + read_addr(upl, node, UPLP_VIRTUAL_REG, &ser->virtual_reg); + ret = ofnode_read_value(node, UPLP_ACCESS_TYPE, access_types, + ARRAY_SIZE(access_types), &ser->access_type); + if (ret && ret != -ENOENT) + return log_msg_ret("ser", ret); + + return 0; +} + +/** + * decode_upl_graphics() - Decode graphics node + * + * @upl: UPL state + * @root: Node to decode + * Return 0 if OK, -ve on error + */ +static int decode_upl_graphics(struct upl *upl, ofnode node) +{ + struct upl_graphics *gra = &upl->graphics; + const char *buf, *compat; + int len, size; + int ret; + + compat = ofnode_read_string(node, UPLP_COMPATIBLE); + if (!compat) { + log_warning("Node '%s': Missing compatible string\n", + ofnode_get_name(node)); + return log_msg_ret("com", -EINVAL); + } + if (strcmp(UPLC_GRAPHICS, compat)) { + log_warning("Node '%s': Ignoring compatible '%s'\n", + ofnode_get_name(node), compat); + return 0; + } + + buf = ofnode_read_prop(node, UPLP_REG, &size); + if (!buf) { + log_warning("Node '%s': Missing 'reg' property\n", + ofnode_get_name(node)); + return log_msg_ret("reg", -EINVAL); + } + + len = decode_addr_size(upl, buf, sizeof(buf), &gra->reg); + if (len < 0) + return log_msg_ret("buf", len); + + ret = read_uint(node, UPLP_WIDTH, &gra->width); + if (!ret) + ret = read_uint(node, UPLP_HEIGHT, &gra->height); + if (!ret) + ret = read_uint(node, UPLP_STRIDE, &gra->stride); + if (!ret) { + ret = ofnode_read_value(node, UPLP_GRAPHICS_FORMAT, + graphics_formats, + ARRAY_SIZE(graphics_formats), + &gra->format); + } + if (ret) + return log_msg_ret("pro", ret); + + return 0; +} + +int upl_read_handoff(struct upl *upl, oftree tree) +{ + ofnode root, node; + int ret; + + if (!oftree_valid(tree)) + return log_msg_ret("tre", -EINVAL); + + root = oftree_root(tree); + + upl_init(upl); + ret = decode_root_props(upl, root); + if (ret) + return log_msg_ret("roo", ret); + + ofnode_for_each_subnode(node, root) { + const char *name = ofnode_get_name(node); + + log_debug("decoding '%s'\n", name); + if (!strcmp(UPLN_OPTIONS, name)) { + ret = decode_upl_params(upl, node); + if (ret) + return log_msg_ret("opt", ret); + + ret = decode_upl_images(upl, node); + } else if (node_matches_at(node, UPLN_MEMORY)) { + ret = decode_upl_memory_node(upl, node); + } else if (!strcmp(UPLN_MEMORY_MAP, name)) { + ret = decode_upl_memmap(upl, node); + } else if (!strcmp(UPLN_MEMORY_RESERVED, name)) { + ret = decode_upl_memres(upl, node); + } else if (node_matches_at(node, UPLN_SERIAL)) { + ret = decode_upl_serial(upl, node); + } else if (node_matches_at(node, UPLN_GRAPHICS)) { + ret = decode_upl_graphics(upl, node); + } else { + log_debug("Unknown node '%s'\n", name); + ret = 0; + } + if (ret) + return log_msg_ret("err", ret); + } + + return 0; +} diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index dc5fcdbd1c9..484f9e1bf8d 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -16,6 +16,7 @@ CONFIG_FIT_RSASSA_PSS=y CONFIG_FIT_CIPHER=y CONFIG_FIT_VERBOSE=y CONFIG_BOOTMETH_ANDROID=y +CONFIG_UPL=y CONFIG_LEGACY_IMAGE_FORMAT=y CONFIG_MEASURED_BOOT=y CONFIG_BOOTSTAGE=y diff --git a/include/upl.h b/include/upl.h new file mode 100644 index 00000000000..fca7e5fd49f --- /dev/null +++ b/include/upl.h @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * UPL handoff generation + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#ifndef __UPL_WRITE_H +#define __UPL_WRITE_H + +#ifndef USE_HOSTCC + +#include <alist.h> +#include <dm/ofnode_decl.h> + +struct unit_test_state; + +#define UPLP_ADDRESS_CELLS "#address-cells" +#define UPLP_SIZE_CELLS "#size-cells" + +#define UPLN_OPTIONS "options" +#define UPLN_UPL_PARAMS "upl-params" +#define UPLP_SMBIOS "smbios" +#define UPLP_ACPI "acpi" +#define UPLP_BOOTMODE "bootmode" +#define UPLP_ADDR_WIDTH "addr-width" +#define UPLP_ACPI_NVS_SIZE "acpi-nvs-size" + +#define UPLPATH_UPL_IMAGE "/options/upl-image" +#define UPLN_UPL_IMAGE "upl-image" +#define UPLN_IMAGE "image" +#define UPLP_FIT "fit" +#define UPLP_CONF_OFFSET "conf-offset" +#define UPLP_LOAD "load" +#define UPLP_SIZE "size" +#define UPLP_OFFSET "offset" +#define UPLP_DESCRIPTION "description" + +#define UPLN_MEMORY "memory" +#define UPLP_HOTPLUGGABLE "hotpluggable" + +#define UPLPATH_MEMORY_MAP "/memory-map" +#define UPLN_MEMORY_MAP "memory-map" +#define UPLP_USAGE "usage" + +#define UPLN_MEMORY_RESERVED "reserved-memory" +#define UPLPATH_MEMORY_RESERVED "/reserved-memory" +#define UPLP_NO_MAP "no-map" + +#define UPLN_SERIAL "serial" +#define UPLP_REG "reg" +#define UPLP_COMPATIBLE "compatible" +#define UPLP_CLOCK_FREQUENCY "clock-frequency" +#define UPLP_CURRENT_SPEED "current-speed" +#define UPLP_REG_IO_SHIFT "reg-io-shift" +#define UPLP_REG_OFFSET "reg-offset" +#define UPLP_REG_IO_WIDTH "reg-io-width" +#define UPLP_VIRTUAL_REG "virtual-reg" +#define UPLP_ACCESS_TYPE "access-type" + +#define UPLN_GRAPHICS "framebuffer" +#define UPLC_GRAPHICS "simple-framebuffer" +#define UPLP_WIDTH "width" +#define UPLP_HEIGHT "height" +#define UPLP_STRIDE "stride" +#define UPLP_GRAPHICS_FORMAT "format" + +/** + * enum upl_boot_mode - Encodes the boot mode + * + * Each is a bit number from the boot_mode mask + */ +enum upl_boot_mode { + UPLBM_FULL, + UPLBM_MINIMAL, + UPLBM_FAST, + UPLBM_DIAG, + UPLBM_DEFAULT, + UPLBM_S2, + UPLBM_S3, + UPLBM_S4, + UPLBM_S5, + UPLBM_FACTORY, + UPLBM_FLASH, + UPLBM_RECOVERY, + + UPLBM_COUNT, +}; + +/** + * struct upl_image - UPL image informaiton + * + * @load: Address image was loaded to + * @size: Size of image in bytes + * @offset: Offset of the image in the FIT (0=none) + * @desc: Description of the iamge (taken from the FIT) + */ +struct upl_image { + ulong load; + ulong size; + uint offset; + const char *description; +}; + +/** + * struct memregion - Information about a region of memory + * + * @base: Base address + * @size: Size in bytes + */ +struct memregion { + ulong base; + ulong size; +}; + +/** + * struct upl_mem - Information about physical-memory layout + * + * TODO: Figure out initial-mapped-area + * + * @region: Memory region list (struct memregion) + * @hotpluggable: true if hotpluggable + */ +struct upl_mem { + struct alist region; + bool hotpluggable; +}; + +/** + * enum upl_usage - Encodes the usage + * + * Each is a bit number from the usage mask + */ +enum upl_usage { + UPLUS_ACPI_RECLAIM, + UPLUS_ACPI_NVS, + UPLUS_BOOT_CODE, + UPLUS_BOOT_DATA, + UPLUS_RUNTIME_CODE, + UPLUS_RUNTIME_DATA, + UPLUS_COUNT +}; + +/** + * struct upl_memmap - Information about logical-memory layout + * + * @name: Node name to use + * @region: Memory region list (struct memregion) + * @usage: Memory-usage mask (enum upl_usage) + */ +struct upl_memmap { + const char *name; + struct alist region; + uint usage; +}; + +/** + * struct upl_memres - Reserved memory + * + * @name: Node name to use + * @region: Reserved memory region list (struct memregion) + * @no_map: true to indicate that a virtual mapping must not be created + */ +struct upl_memres { + const char *name; + struct alist region; + bool no_map; +}; + +enum upl_serial_access_type { + UPLSAT_MMIO, + UPLSAT_IO, +}; + +/* serial defaults */ +enum { + UPLD_REG_IO_SHIFT = 0, + UPLD_REG_OFFSET = 0, + UPLD_REG_IO_WIDTH = 1, +}; + +/** + * enum upl_access_type - Access types + * + * @UPLAT_MMIO: Memory-mapped I/O + * @UPLAT_IO: Separate I/O + */ +enum upl_access_type { + UPLAT_MMIO, + UPLAT_IO, +}; + +/** + * struct upl_serial - Serial console + * + * @compatible: Compatible string (NULL if there is no serial console) + * @clock_frequency: Input clock frequency of UART + * @current_speed: Current baud rate of UART + * @reg: List of base address and size of registers (struct memregion) + * @reg_shift_log2: log2 of distance between each register + * @reg_offset: Offset of registers from the base address + * @reg_width: Register width in bytes + * @virtual_reg: Virtual register access (0 for none) + * @access_type: Register access type to use + */ +struct upl_serial { + const char *compatible; + uint clock_frequency; + uint current_speed; + struct alist reg; + uint reg_io_shift; + uint reg_offset; + uint reg_io_width; + ulong virtual_reg; + enum upl_serial_access_type access_type; +}; + +/** + * enum upl_graphics_format - Graphics formats + * + * @UPLGF_ARGB32: 32bpp format using 0xaarrggbb + * @UPLGF_ABGR32: 32bpp format using 0xaabbggrr + * @UPLGF_ARGB64: 64bpp format using 0xaaaabbbbggggrrrr + */ +enum upl_graphics_format { + UPLGF_ARGB32, + UPLGF_ABGR32, + UPLGF_ABGR64, +}; + +/** + * @reg: List of base address and size of registers (struct memregion) + * @width: Width of display in pixels + * @height: Height of display in pixels + * @stride: Number of bytes from one line to the next + * @format: Pixel format + */ +struct upl_graphics { + struct alist reg; + uint width; + uint height; + uint stride; + enum upl_graphics_format format; +}; + +/* + * Information about the UPL state + * + * @addr_cells: Number of address cells used in the handoff + * @size_cells: Number of size cells used in the handoff + * @bootmode: Boot-mode mask (enum upl_boot_mode) + * @fit: Address of FIT image that was loaded + * @conf_offset: Offset in FIT of the configuration that was selected + * @addr_width: Adress-bus width of machine, e.g. 46 for 46 bits + * @acpi_nvs_size: Size of the ACPI non-volatile-storage area in bytes + * @image: Information about each image (struct upl_image) + * @mem: Information about physical-memory regions (struct upl_mem) + * @nennap: Information about logical-memory regions (struct upl_memmap) + * @nennap: Information about reserved-memory regions (struct upl_memres) + */ +struct upl { + int addr_cells; + int size_cells; + + ulong smbios; + ulong acpi; + uint bootmode; + ulong fit; + uint conf_offset; + uint addr_width; + uint acpi_nvs_size; + + struct alist image; + struct alist mem; + struct alist memmap; + struct alist memres; + struct upl_serial serial; + struct upl_graphics graphics; +}; + +/** + * upl_write_handoff() - Write a Unversal Payload handoff structure + * + * upl: UPL state to write + * @root: root node to write it to + * @skip_existing: Avoid recreating any nodes which already exist in the + * devicetree. For example, if there is a serial node, just leave it alone, + * since don't need to create a new one + * Return: 0 on success, -ve on error + */ +int upl_write_handoff(const struct upl *upl, ofnode root, bool skip_existing); + +/** + * upl_create_handoff_tree() - Write a Unversal Payload handoff structure + * + * upl: UPL state to write + * @treep: Returns a new tree containing the handoff + * Return: 0 on success, -ve on error + */ +int upl_create_handoff_tree(const struct upl *upl, oftree *treep); + +/** + * upl_read_handoff() - Read a Unversal Payload handoff structure + * + * upl: UPL state to read into + * @tree: Devicetree containing the data to read + * Return: 0 on success, -ve on error + */ +int upl_read_handoff(struct upl *upl, oftree tree); +#endif /* USE_HOSTCC */ + +#if CONFIG_IS_ENABLED(UPL) && defined(CONFIG_SPL_BUILD) + +/** + * upl_set_fit_info() - Set up basic info about the FIT + * + * @fit: Address of FIT + * @conf_offset: Configuration node being used + * @entry_addr: Entry address for next phase + */ +void upl_set_fit_info(ulong fit, int conf_offset, ulong entry_addr); + +/** + * upl_set_fit_addr() - Set up the address of the FIT + * + * @fit: Address of FIT + */ +void upl_set_fit_addr(ulong fit); + +/** + * upl_add_image() - Add a new image to the UPL + * + * @node: Image node offset in FIT + * @load_addr: Address to which images was loaded + * @size: Image size in bytes + * @desc: Description of image + */ +int upl_add_image(int node, ulong load_addr, ulong size, const char *desc); + +#else +static inline void upl_set_fit_addr(ulong fit) {} +static inline void upl_set_fit_info(ulong fit, int conf_offset, + ulong entry_addr) {} +static inline int upl_add_image(int node, ulong load_addr, ulong size, + const char *desc) +{ + return 0; +} +#endif /* UPL && SPL */ + +/** upl_init() - Set up a UPL struct */ +void upl_init(struct upl *upl); + +#endif /* __UPL_WRITE_H */

Universal Payload provides a standard way of handing off control between two firmware phases. Add support for writing the handoff information from a structure.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
boot/Kconfig | 8 + boot/Makefile | 1 + boot/upl_write.c | 622 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 631 insertions(+) create mode 100644 boot/upl_write.c
diff --git a/boot/Kconfig b/boot/Kconfig index 6e907b45095..fa4f766054c 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -739,6 +739,7 @@ config BOOTMETH_SCRIPT config UPL bool "upl - Universal Payload Specification" imply UPL_READ + imply UPL_WRITE help Provides support for UPL payloads and handoff information. U-Boot supports generating and accepting handoff information. The mkimage @@ -753,6 +754,13 @@ config UPL_READ which can be used elsewhere in U-Boot. This is just the reading implementation, useful for trying it out.
+config UPL_WRITE + bool "upl - Support writing a Universal Payload handoff" + help + Provides support for encoding a UPL-format payload from a C structure + so it can be passed to another program. This is just the writing + implementation, useful for trying it out. + endif # UPL
endif # BOOTSTD diff --git a/boot/Makefile b/boot/Makefile index 97b52a3404c..b8b63d31e88 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_$(SPL_TPL_)FDT_SIMPLEFB) += fdt_simplefb.o
obj-$(CONFIG_$(SPL_TPL_)UPL) += upl_common.o obj-$(CONFIG_$(SPL_TPL_)UPL_READ) += upl_read.o +obj-$(CONFIG_$(SPL_TPL_)UPL_WRITE) += upl_write.o
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += image-fdt.o obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o diff --git a/boot/upl_write.c b/boot/upl_write.c new file mode 100644 index 00000000000..7d637c15ba0 --- /dev/null +++ b/boot/upl_write.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UPL handoff generation + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <log.h> +#include <upl.h> +#include <dm/ofnode.h> +#include "upl_common.h" + +/** + * write_addr() - Write an address + * + * Writes an address in the correct format, either 32- or 64-bit + * + * @upl: UPL state + * @node: Node to write to + * @prop: Property name to write + * @addr: Address to write + * Return: 0 if OK, -ve on error + */ +static int write_addr(const struct upl *upl, ofnode node, const char *prop, + ulong addr) +{ + int ret; + + if (upl->addr_cells == 1) + ret = ofnode_write_u32(node, prop, addr); + else + ret = ofnode_write_u64(node, prop, addr); + + return ret; +} + +/** + * write_size() - Write a size + * + * Writes a size in the correct format, either 32- or 64-bit + * + * @upl: UPL state + * @node: Node to write to + * @prop: Property name to write + * @size: Size to write + * Return: 0 if OK, -ve on error + */ +static int write_size(const struct upl *upl, ofnode node, const char *prop, + ulong size) +{ + int ret; + + if (upl->size_cells == 1) + ret = ofnode_write_u32(node, prop, size); + else + ret = ofnode_write_u64(node, prop, size); + + return ret; +} + +/** + * ofnode_write_bitmask() - Write a bit mask as a string list + * + * @node: Node to write to + * @prop: Property name to write + * @names: Array of names for each bit + * @count: Number of array entries + * @value: Bit-mask value to write + * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the + * string is too long for the (internal) buffer + */ +static int ofnode_write_bitmask(ofnode node, const char *prop, + const char *const names[], uint count, + uint value) +{ + char buf[128]; + char *ptr, *end = buf + sizeof(buf); + uint bit; + int ret; + + ptr = buf; + for (bit = 0; bit < count; bit++) { + if (value & BIT(bit)) { + const char *str = names[bit]; + uint len; + + if (!str) { + log_debug("Unnamed bit number %d\n", bit); + return log_msg_ret("bit", -EINVAL); + } + len = strlen(str) + 1; + if (ptr + len > end) { + log_debug("String array too long\n"); + return log_msg_ret("bit", -ENOSPC); + } + + memcpy(ptr, str, len); + ptr += len; + } + } + + ret = ofnode_write_prop(node, prop, buf, ptr - buf, true); + if (ret) + return log_msg_ret("wri", ret); + + return 0; +} + +/** + * ofnode_write_value() - Write an int as a string value using a lookup + * + * @node: Node to write to + * @prop: Property name to write + * @names: Array of names for each int value + * @count: Number of array entries + * @value: Int value to write + * Return: 0 if OK, -EINVAL if a bit number is not defined, -ENOSPC if the + * string is too long for the (internal) buffer + */ +static int ofnode_write_value(ofnode node, const char *prop, + const char *const names[], uint count, + uint value) +{ + const char *str; + int ret; + + if (value >= count) { + log_debug("Value of range %d\n", value); + return log_msg_ret("val", -ERANGE); + } + str = names[value]; + if (!str) { + log_debug("Unnamed value %d\n", value); + return log_msg_ret("val", -EINVAL); + } + ret = ofnode_write_string(node, prop, str); + if (ret) + return log_msg_ret("wri", ret); + + return 0; +} + +/** + * add_root_props() - Add root properties to the tree + * + * @node: Node to add to + * Return 0 if OK, -ve on error + */ +static int add_root_props(const struct upl *upl, ofnode node) +{ + int ret; + + ret = ofnode_write_u32(node, UPLP_ADDRESS_CELLS, upl->addr_cells); + if (!ret) + ret = ofnode_write_u32(node, UPLP_SIZE_CELLS, upl->size_cells); + if (ret) + return log_msg_ret("cel", ret); + + return 0; +} + +/** + * add_upl_params() - Add UPL parameters node + * + * @upl: UPL state + * @options: /options node to add to + * Return 0 if OK, -ve on error + */ +static int add_upl_params(const struct upl *upl, ofnode options) +{ + ofnode node; + int ret; + + ret = ofnode_add_subnode(options, UPLN_UPL_PARAMS, &node); + if (ret) + return log_msg_ret("img", ret); + + ret = write_addr(upl, node, UPLP_SMBIOS, upl->smbios); + if (!ret) + ret = write_addr(upl, node, UPLP_ACPI, upl->acpi); + if (!ret && upl->bootmode) + ret = ofnode_write_bitmask(node, UPLP_BOOTMODE, bootmode_names, + UPLBM_COUNT, upl->bootmode); + if (!ret) + ret = ofnode_write_u32(node, UPLP_ADDR_WIDTH, upl->addr_width); + if (!ret) + ret = ofnode_write_u32(node, UPLP_ACPI_NVS_SIZE, + upl->acpi_nvs_size); + if (ret) + return log_msg_ret("cnf", ret); + + return 0; +} + +/** + * add_upl_image() - Add /options/upl-image nodes and properties to the tree + * + * @upl: UPL state + * @node: /options node to add to + * Return 0 if OK, -ve on error + */ +static int add_upl_image(const struct upl *upl, ofnode options) +{ + ofnode node; + int ret, i; + + ret = ofnode_add_subnode(options, UPLN_UPL_IMAGE, &node); + if (ret) + return log_msg_ret("img", ret); + + if (upl->fit) + ret = ofnode_write_u32(node, UPLP_FIT, upl->fit); + if (!ret && upl->conf_offset) + ret = ofnode_write_u32(node, UPLP_CONF_OFFSET, + upl->conf_offset); + if (ret) + return log_msg_ret("cnf", ret); + + for (i = 0; i < upl->image.count; i++) { + const struct upl_image *img = alist_get(&upl->image, i, + struct upl_image); + ofnode subnode; + char name[10]; + + snprintf(name, sizeof(name), UPLN_IMAGE "-%d", i + 1); + ret = ofnode_add_subnode(node, name, &subnode); + if (ret) + return log_msg_ret("sub", ret); + + ret = write_addr(upl, subnode, UPLP_LOAD, img->load); + if (!ret) + ret = write_size(upl, subnode, UPLP_SIZE, img->size); + if (!ret && img->offset) + ret = ofnode_write_u32(subnode, UPLP_OFFSET, + img->offset); + ret = ofnode_write_string(subnode, UPLP_DESCRIPTION, + img->description); + if (ret) + return log_msg_ret("sim", ret); + } + + return 0; +} + +/** + * buffer_addr_size() - Generate a set of addr/size pairs + * + * Each base/size value from each region is written to the buffer in a suitable + * format to be written to the devicetree + * + * @upl: UPL state + * @buf: Buffer to write to + * @size: Buffer size + * @num_regions: Number of regions to process + * @region: List of regions to process (struct memregion) + * Returns: Number of bytes written, or -ENOSPC if the buffer is too small + */ +static int buffer_addr_size(const struct upl *upl, char *buf, int size, + uint num_regions, const struct alist *region) +{ + char *ptr, *end = buf + size; + int i; + + ptr = buf; + for (i = 0; i < num_regions; i++) { + const struct memregion *reg = alist_get(region, i, + struct memregion); + + if (upl->addr_cells == 1) + *(u32 *)ptr = cpu_to_fdt32(reg->base); + else + *(u64 *)ptr = cpu_to_fdt64(reg->base); + ptr += upl->addr_cells * sizeof(u32); + + if (upl->size_cells == 1) + *(u32 *)ptr = cpu_to_fdt32(reg->size); + else + *(u64 *)ptr = cpu_to_fdt64(reg->size); + ptr += upl->size_cells * sizeof(u32); + if (ptr > end) + return -ENOSPC; + } + + return ptr - buf; +} + +/** + * add_upl_memory() - Add /memory nodes to the tree + * + * @upl: UPL state + * @root: Parent node to contain the new /memory nodes + * Return 0 if OK, -ve on error + */ +static int add_upl_memory(const struct upl *upl, ofnode root) +{ + int i; + + for (i = 0; i < upl->mem.count; i++) { + const struct upl_mem *mem = alist_get(&upl->mem, i, + struct upl_mem); + char buf[mem->region.count * sizeof(64) * 2]; + const struct memregion *first; + char name[26]; + int ret, len; + ofnode node; + + if (!mem->region.count) { + log_debug("Memory %d has no regions\n", i); + return log_msg_ret("reg", -EINVAL); + } + first = alist_get(&mem->region, 0, struct memregion); + sprintf(name, UPLN_MEMORY "@0x%lx", first->base); + ret = ofnode_add_subnode(root, name, &node); + if (ret) + return log_msg_ret("mem", ret); + + len = buffer_addr_size(upl, buf, sizeof(buf), mem->region.count, + &mem->region); + if (len < 0) + return log_msg_ret("buf", len); + + ret = ofnode_write_prop(node, UPLP_REG, buf, len, true); + if (!ret && mem->hotpluggable) + ret = ofnode_write_bool(node, UPLP_HOTPLUGGABLE, + mem->hotpluggable); + if (ret) + return log_msg_ret("lst", ret); + } + + return 0; +} + +/** + * add_upl_memmap() - Add memory-map nodes to the tree + * + * @upl: UPL state + * @root: Parent node to contain the new /memory-map node and its subnodes + * Return 0 if OK, -ve on error + */ +static int add_upl_memmap(const struct upl *upl, ofnode root) +{ + ofnode mem_node; + int i, ret; + + if (!upl->memmap.count) + return 0; + ret = ofnode_add_subnode(root, UPLN_MEMORY_MAP, &mem_node); + if (ret) + return log_msg_ret("img", ret); + + for (i = 0; i < upl->memmap.count; i++) { + const struct upl_memmap *memmap = alist_get(&upl->memmap, i, + struct upl_memmap); + char buf[memmap->region.count * sizeof(64) * 2]; + const struct memregion *first; + char name[26]; + int ret, len; + ofnode node; + + if (!memmap->region.count) { + log_debug("Memory %d has no regions\n", i); + return log_msg_ret("reg", -EINVAL); + } + first = alist_get(&memmap->region, 0, struct memregion); + sprintf(name, "%s@0x%lx", memmap->name, first->base); + ret = ofnode_add_subnode(mem_node, name, &node); + if (ret) + return log_msg_ret("memmap", ret); + + len = buffer_addr_size(upl, buf, sizeof(buf), + memmap->region.count, &memmap->region); + if (len < 0) + return log_msg_ret("buf", len); + ret = ofnode_write_prop(node, UPLP_REG, buf, len, true); + if (!ret && memmap->usage) + ret = ofnode_write_bitmask(node, UPLP_USAGE, + usage_names, + UPLUS_COUNT, memmap->usage); + if (ret) + return log_msg_ret("lst", ret); + } + + return 0; +} + +/** + * add_upl_memres() - Add /memory-reserved nodes to the tree + * + * @upl: UPL state + * @root: Parent node to contain the new node + * Return 0 if OK, -ve on error + */ +static int add_upl_memres(const struct upl *upl, ofnode root, + bool skip_existing) +{ + ofnode mem_node; + int i, ret; + + if (!upl->memmap.count) + return 0; + ret = ofnode_add_subnode(root, UPLN_MEMORY_RESERVED, &mem_node); + if (ret) { + if (skip_existing && ret == -EEXIST) + return 0; + return log_msg_ret("img", ret); + } + + for (i = 0; i < upl->memres.count; i++) { + const struct upl_memres *memres = alist_get(&upl->memres, i, + struct upl_memres); + char buf[memres->region.count * sizeof(64) * 2]; + const struct memregion *first; + char name[26]; + int ret, len; + ofnode node; + + if (!memres->region.count) { + log_debug("Memory %d has no regions\n", i); + return log_msg_ret("reg", -EINVAL); + } + first = alist_get(&memres->region, 0, struct memregion); + sprintf(name, "%s@0x%lx", memres->name, first->base); + ret = ofnode_add_subnode(mem_node, name, &node); + if (ret) + return log_msg_ret("memres", ret); + + len = buffer_addr_size(upl, buf, sizeof(buf), + memres->region.count, &memres->region); + ret = ofnode_write_prop(node, UPLP_REG, buf, len, true); + if (!ret && memres->no_map) + ret = ofnode_write_bool(node, UPLP_NO_MAP, + memres->no_map); + if (ret) + return log_msg_ret("lst", ret); + } + + return 0; +} + +/** + * add_upl_serial() - Add serial node + * + * @upl: UPL state + * @root: Parent node to contain the new node + * Return 0 if OK, -ve on error + */ +static int add_upl_serial(const struct upl *upl, ofnode root, + bool skip_existing) +{ + const struct upl_serial *ser = &upl->serial; + const struct memregion *first; + char name[26]; + ofnode node; + int ret; + + if (!ser->compatible || skip_existing) + return 0; + if (!ser->reg.count) + return log_msg_ret("ser", -EINVAL); + first = alist_get(&ser->reg, 0, struct memregion); + sprintf(name, UPLN_SERIAL "@0x%lx", first->base); + ret = ofnode_add_subnode(root, name, &node); + if (ret) + return log_msg_ret("img", ret); + ret = ofnode_write_string(node, UPLP_COMPATIBLE, ser->compatible); + if (!ret) + ret = ofnode_write_u32(node, UPLP_CLOCK_FREQUENCY, + ser->clock_frequency); + if (!ret) + ret = ofnode_write_u32(node, UPLP_CURRENT_SPEED, + ser->current_speed); + if (!ret) { + char buf[16]; + int len; + + len = buffer_addr_size(upl, buf, sizeof(buf), 1, &ser->reg); + if (len < 0) + return log_msg_ret("buf", len); + + ret = ofnode_write_prop(node, UPLP_REG, buf, len, true); + } + if (!ret && ser->reg_io_shift != UPLD_REG_IO_SHIFT) + ret = ofnode_write_u32(node, UPLP_REG_IO_SHIFT, + ser->reg_io_shift); + if (!ret && ser->reg_offset != UPLD_REG_OFFSET) + ret = ofnode_write_u32(node, UPLP_REG_OFFSET, ser->reg_offset); + if (!ret && ser->reg_io_width != UPLD_REG_IO_WIDTH) + ret = ofnode_write_u32(node, UPLP_REG_IO_WIDTH, + ser->reg_io_width); + if (!ret && ser->virtual_reg) + ret = write_addr(upl, node, UPLP_VIRTUAL_REG, ser->virtual_reg); + if (!ret) { + ret = ofnode_write_value(node, UPLP_ACCESS_TYPE, access_types, + ARRAY_SIZE(access_types), + ser->access_type); + } + if (ret) + return log_msg_ret("ser", ret); + + return 0; +} + +/** + * add_upl_graphics() - Add graphics node + * + * @upl: UPL state + * @root: Parent node to contain the new node + * Return 0 if OK, -ve on error + */ +static int add_upl_graphics(const struct upl *upl, ofnode root) +{ + const struct upl_graphics *gra = &upl->graphics; + const struct memregion *first; + char name[36]; + ofnode node; + int ret; + + if (!gra->reg.count) + return log_msg_ret("gra", -ENOENT); + first = alist_get(&gra->reg, 0, struct memregion); + sprintf(name, UPLN_GRAPHICS "@0x%lx", first->base); + ret = ofnode_add_subnode(root, name, &node); + if (ret) + return log_msg_ret("gra", ret); + + ret = ofnode_write_string(node, UPLP_COMPATIBLE, UPLC_GRAPHICS); + if (!ret) { + char buf[16]; + int len; + + len = buffer_addr_size(upl, buf, sizeof(buf), 1, &gra->reg); + if (len < 0) + return log_msg_ret("buf", len); + + ret = ofnode_write_prop(node, UPLP_REG, buf, len, true); + } + if (!ret) + ret = ofnode_write_u32(node, UPLP_WIDTH, gra->width); + if (!ret) + ret = ofnode_write_u32(node, UPLP_HEIGHT, gra->height); + if (!ret) + ret = ofnode_write_u32(node, UPLP_STRIDE, gra->stride); + if (!ret) { + ret = ofnode_write_value(node, UPLP_GRAPHICS_FORMAT, + graphics_formats, + ARRAY_SIZE(graphics_formats), + gra->format); + } + if (ret) + return log_msg_ret("pro", ret); + + return 0; +} + +int upl_write_handoff(const struct upl *upl, ofnode root, bool skip_existing) +{ + ofnode options; + int ret; + + ret = add_root_props(upl, root); + if (ret) + return log_msg_ret("ad1", ret); + ret = ofnode_add_subnode(root, UPLN_OPTIONS, &options); + if (ret && ret != -EEXIST) + return log_msg_ret("opt", -EINVAL); + + ret = add_upl_params(upl, options); + if (ret) + return log_msg_ret("ad1", ret); + + ret = add_upl_image(upl, options); + if (ret) + return log_msg_ret("ad2", ret); + + ret = add_upl_memory(upl, root); + if (ret) + return log_msg_ret("ad3", ret); + + ret = add_upl_memmap(upl, root); + if (ret) + return log_msg_ret("ad4", ret); + + ret = add_upl_memres(upl, root, skip_existing); + if (ret) + return log_msg_ret("ad5", ret); + + ret = add_upl_serial(upl, root, skip_existing); + if (ret) + return log_msg_ret("ad6", ret); + + ret = add_upl_graphics(upl, root); + if (ret && ret != -ENOENT) + return log_msg_ret("ad6", ret); + + return 0; +} + +int upl_create_handoff_tree(const struct upl *upl, oftree *treep) +{ + ofnode root; + oftree tree; + int ret; + + ret = oftree_new(&tree); + if (ret) + return log_msg_ret("new", ret); + + root = oftree_root(tree); + if (!ofnode_valid(root)) + return log_msg_ret("roo", -EINVAL); + + ret = upl_write_handoff(upl, root, false); + if (ret) + return log_msg_ret("wr", ret); + + *treep = tree; + + return 0; +}

Add some unit tests to check that we can write a UPL handoff and read it back.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v2)
Changes in v2: - Put the upl tests under their own subcommand to avoid bootstd init
MAINTAINERS | 1 + include/test/suites.h | 1 + include/upl.h | 9 ++ test/boot/Makefile | 2 + test/boot/upl.c | 368 ++++++++++++++++++++++++++++++++++++++++++ test/cmd_ut.c | 3 + 6 files changed, 384 insertions(+) create mode 100644 test/boot/upl.c
diff --git a/MAINTAINERS b/MAINTAINERS index 070a766a76f..1c41fd30b2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1707,6 +1707,7 @@ S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git F: boot/upl* F: include/upl.h +F: test/boot/upl.c
USB M: Marek Vasut marex@denx.de diff --git a/include/test/suites.h b/include/test/suites.h index 365d5f20dfe..2ceef577f7f 100644 --- a/include/test/suites.h +++ b/include/test/suites.h @@ -63,5 +63,6 @@ int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_time(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_unicode(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_ut_upl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
#endif /* __TEST_SUITES_H__ */ diff --git a/include/upl.h b/include/upl.h index fca7e5fd49f..fa784e57f74 100644 --- a/include/upl.h +++ b/include/upl.h @@ -308,6 +308,15 @@ int upl_create_handoff_tree(const struct upl *upl, oftree *treep); * Return: 0 on success, -ve on error */ int upl_read_handoff(struct upl *upl, oftree tree); + +/** + * upl_get_test_data() - Fill a UPL with some test data + * + * @uts: Test state (can be uninited) + * @upl: Returns test data + * Return: 0 on success, 1 on error + */ +int upl_get_test_data(struct unit_test_state *uts, struct upl *upl); #endif /* USE_HOSTCC */
#if CONFIG_IS_ENABLED(UPL) && defined(CONFIG_SPL_BUILD) diff --git a/test/boot/Makefile b/test/boot/Makefile index 068522cb9e0..8ec5daa7bfe 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -13,3 +13,5 @@ ifdef CONFIG_OF_LIVE obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o endif obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o + +obj-$(CONFIG_UPL) += upl.o diff --git a/test/boot/upl.c b/test/boot/upl.c new file mode 100644 index 00000000000..e9d92efd80d --- /dev/null +++ b/test/boot/upl.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UPL handoff testing + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#include <abuf.h> +#include <mapmem.h> +#include <upl.h> +#include <dm/ofnode.h> +#include <test/suites.h> +#include <test/test.h> +#include <test/ut.h> +#include "bootstd_common.h" + +/* Declare a new upl test */ +#define UPL_TEST(_name, _flags) UNIT_TEST(_name, _flags, upl_test) + +static int add_region(struct unit_test_state *uts, struct alist *lst, + ulong base, ulong size) +{ + struct memregion region; + + region.base = base; + region.size = size; + ut_assertnonnull(alist_add(lst, region, struct memregion)); + + return 0; +} + +int upl_get_test_data(struct unit_test_state *uts, struct upl *upl) +{ + struct upl_memmap memmap; + struct upl_memres memres; + struct upl_image img; + struct upl_mem mem; + + upl_init(upl); + + upl->addr_cells = 1; + upl->size_cells = 1; + upl->smbios = 0x123; + upl->acpi = 0x456; + upl->bootmode = BIT(UPLBM_DEFAULT) | BIT(UPLBM_S3); + upl->fit = 0x789; + upl->conf_offset = 0x234; + upl->addr_width = 46; + upl->acpi_nvs_size = 0x100; + + /* image[0] */ + img.load = 0x1; + img.size = 0x2; + img.offset = 0x3; + img.description = "U-Boot"; + ut_assertnonnull(alist_add(&upl->image, img, struct upl_image)); + + /* image[1] */ + img.load = 0x4; + img.size = 0x5; + img.offset = 0x6; + img.description = "ATF"; + ut_assertnonnull(alist_add(&upl->image, img, struct upl_image)); + + /* mem[0] : 3 regions */ + memset(&mem, '\0', sizeof(mem)); + alist_init_struct(&mem.region, struct memregion); + ut_assertok(add_region(uts, &mem.region, 0x10, 0x20)); + ut_assertok(add_region(uts, &mem.region, 0x30, 0x40)); + ut_assertok(add_region(uts, &mem.region, 0x40, 0x50)); + ut_assertnonnull(alist_add(&upl->mem, mem, struct upl_mem)); + + /* mem[0] : 1 region */ + alist_init_struct(&mem.region, struct memregion); + ut_assertok(add_region(uts, &mem.region, 0x70, 0x80)); + mem.hotpluggable = true; + ut_assertnonnull(alist_add(&upl->mem, mem, struct upl_mem)); + mem.hotpluggable = false; + + /* memmap[0] : 5 regions */ + alist_init_struct(&memmap.region, struct memregion); + memmap.name = "acpi"; + memmap.usage = BIT(UPLUS_ACPI_RECLAIM); + ut_assertok(add_region(uts, &memmap.region, 0x11, 0x12)); + ut_assertok(add_region(uts, &memmap.region, 0x13, 0x14)); + ut_assertok(add_region(uts, &memmap.region, 0x15, 0x16)); + ut_assertok(add_region(uts, &memmap.region, 0x17, 0x18)); + ut_assertok(add_region(uts, &memmap.region, 0x19, 0x1a)); + ut_assertnonnull(alist_add(&upl->memmap, memmap, struct upl_memmap)); + + /* memmap[1] : 1 region */ + memmap.name = "u-boot"; + memmap.usage = BIT(UPLUS_BOOT_DATA); + alist_init_struct(&memmap.region, struct memregion); + ut_assertok(add_region(uts, &memmap.region, 0x21, 0x22)); + ut_assertnonnull(alist_add(&upl->memmap, memmap, struct upl_memmap)); + + /* memmap[2] : 1 region */ + alist_init_struct(&memmap.region, struct memregion); + memmap.name = "efi"; + memmap.usage = BIT(UPLUS_RUNTIME_CODE); + ut_assertok(add_region(uts, &memmap.region, 0x23, 0x24)); + ut_assertnonnull(alist_add(&upl->memmap, memmap, struct upl_memmap)); + + /* memmap[3]: 2 regions */ + alist_init_struct(&memmap.region, struct memregion); + memmap.name = "empty"; + memmap.usage = 0; + ut_assertok(add_region(uts, &memmap.region, 0x25, 0x26)); + ut_assertok(add_region(uts, &memmap.region, 0x27, 0x28)); + ut_assertnonnull(alist_add(&upl->memmap, memmap, struct upl_memmap)); + + /* memmap[4]: 1 region */ + alist_init_struct(&memmap.region, struct memregion); + memmap.name = "acpi-things"; + memmap.usage = BIT(UPLUS_RUNTIME_CODE) | BIT(UPLUS_ACPI_NVS); + ut_assertok(add_region(uts, &memmap.region, 0x29, 0x2a)); + ut_assertnonnull(alist_add(&upl->memmap, memmap, struct upl_memmap)); + + /* memres[0]: 1 region */ + alist_init_struct(&memres.region, struct memregion); + memset(&memres, '\0', sizeof(memres)); + memres.name = "mmio"; + ut_assertok(add_region(uts, &memres.region, 0x2b, 0x2c)); + ut_assertnonnull(alist_add(&upl->memres, memres, struct upl_memres)); + + /* memres[1]: 2 regions */ + alist_init_struct(&memres.region, struct memregion); + memres.name = "memory"; + ut_assertok(add_region(uts, &memres.region, 0x2d, 0x2e)); + ut_assertok(add_region(uts, &memres.region, 0x2f, 0x30)); + memres.no_map = true; + ut_assertnonnull(alist_add(&upl->memres, memres, struct upl_memres)); + + upl->serial.compatible = "ns16550a"; + upl->serial.clock_frequency = 1843200; + upl->serial.current_speed = 115200; + alist_init_struct(&upl->serial.reg, struct memregion); + ut_assertok(add_region(uts, &upl->serial.reg, 0xf1de0000, 0x100)); + upl->serial.reg_io_shift = 2; + upl->serial.reg_offset = 0x40; + upl->serial.reg_io_width = 1; + upl->serial.virtual_reg = 0x20000000; + upl->serial.access_type = UPLSAT_MMIO; + + alist_init_struct(&upl->graphics.reg, struct memregion); + ut_assertok(add_region(uts, &upl->graphics.reg, 0xd0000000, 0x10000000)); + upl->graphics.width = 1280; + upl->graphics.height = 1280; + upl->graphics.stride = upl->graphics.width * 4; + upl->graphics.format = UPLGF_ARGB32; + + return 0; +} + +static int compare_upl_image(struct unit_test_state *uts, + const struct upl_image *base, + const struct upl_image *cmp) +{ + ut_asserteq(base->load, cmp->load); + ut_asserteq(base->size, cmp->size); + ut_asserteq(base->offset, cmp->offset); + ut_asserteq_str(base->description, cmp->description); + + return 0; +} + +static int compare_upl_memregion(struct unit_test_state *uts, + const struct memregion *base, + const struct memregion *cmp) +{ + ut_asserteq(base->base, cmp->base); + ut_asserteq(base->size, cmp->size); + + return 0; +} + +static int compare_upl_mem(struct unit_test_state *uts, + const struct upl_mem *base, + const struct upl_mem *cmp) +{ + int i; + + ut_asserteq(base->region.count, cmp->region.count); + ut_asserteq(base->hotpluggable, cmp->hotpluggable); + for (i = 0; i < base->region.count; i++) { + ut_assertok(compare_upl_memregion(uts, + alist_get(&base->region, i, struct memregion), + alist_get(&cmp->region, i, struct memregion))); + } + + return 0; +} + +static int check_device_name(struct unit_test_state *uts, const char *base, + const char *cmp) +{ + const char *p; + + p = strchr(cmp, '@'); + if (p) { + ut_assertnonnull(p); + ut_asserteq_strn(base, cmp); + ut_asserteq(p - cmp, strlen(base)); + } else { + ut_asserteq_str(base, cmp); + } + + return 0; +} + +static int compare_upl_memmap(struct unit_test_state *uts, + const struct upl_memmap *base, + const struct upl_memmap *cmp) +{ + int i; + + ut_assertok(check_device_name(uts, base->name, cmp->name)); + ut_asserteq(base->region.count, cmp->region.count); + ut_asserteq(base->usage, cmp->usage); + for (i = 0; i < base->region.count; i++) + ut_assertok(compare_upl_memregion(uts, + alist_get(&base->region, i, struct memregion), + alist_get(&cmp->region, i, struct memregion))); + + return 0; +} + +static int compare_upl_memres(struct unit_test_state *uts, + const struct upl_memres *base, + const struct upl_memres *cmp) +{ + int i; + + ut_assertok(check_device_name(uts, base->name, cmp->name)); + ut_asserteq(base->region.count, cmp->region.count); + ut_asserteq(base->no_map, cmp->no_map); + for (i = 0; i < base->region.count; i++) + ut_assertok(compare_upl_memregion(uts, + alist_get(&base->region, i, struct memregion), + alist_get(&cmp->region, i, struct memregion))); + + return 0; +} + +static int compare_upl_serial(struct unit_test_state *uts, + struct upl_serial *base, struct upl_serial *cmp) +{ + int i; + + ut_asserteq_str(base->compatible, cmp->compatible); + ut_asserteq(base->clock_frequency, cmp->clock_frequency); + ut_asserteq(base->current_speed, cmp->current_speed); + for (i = 0; i < base->reg.count; i++) + ut_assertok(compare_upl_memregion(uts, + alist_get(&base->reg, i, struct memregion), + alist_get(&cmp->reg, i, struct memregion))); + ut_asserteq(base->reg_io_shift, cmp->reg_io_shift); + ut_asserteq(base->reg_offset, cmp->reg_offset); + ut_asserteq(base->reg_io_width, cmp->reg_io_width); + ut_asserteq(base->virtual_reg, cmp->virtual_reg); + ut_asserteq(base->access_type, cmp->access_type); + + return 0; +} + +static int compare_upl_graphics(struct unit_test_state *uts, + struct upl_graphics *base, + struct upl_graphics *cmp) +{ + int i; + + for (i = 0; i < base->reg.count; i++) + ut_assertok(compare_upl_memregion(uts, + alist_get(&base->reg, i, struct memregion), + alist_get(&cmp->reg, i, struct memregion))); + ut_asserteq(base->width, cmp->width); + ut_asserteq(base->height, cmp->height); + ut_asserteq(base->stride, cmp->stride); + ut_asserteq(base->format, cmp->format); + + return 0; +} + +static int compare_upl(struct unit_test_state *uts, struct upl *base, + struct upl *cmp) +{ + int i; + + ut_asserteq(base->addr_cells, cmp->addr_cells); + ut_asserteq(base->size_cells, cmp->size_cells); + + ut_asserteq(base->smbios, cmp->smbios); + ut_asserteq(base->acpi, cmp->acpi); + ut_asserteq(base->bootmode, cmp->bootmode); + ut_asserteq(base->fit, cmp->fit); + ut_asserteq(base->conf_offset, cmp->conf_offset); + ut_asserteq(base->addr_width, cmp->addr_width); + ut_asserteq(base->acpi_nvs_size, cmp->acpi_nvs_size); + + ut_asserteq(base->image.count, cmp->image.count); + for (i = 0; i < base->image.count; i++) + ut_assertok(compare_upl_image(uts, + alist_get(&base->image, i, struct upl_image), + alist_get(&cmp->image, i, struct upl_image))); + + ut_asserteq(base->mem.count, cmp->mem.count); + for (i = 0; i < base->mem.count; i++) + ut_assertok(compare_upl_mem(uts, + alist_get(&base->mem, i, struct upl_mem), + alist_get(&cmp->mem, i, struct upl_mem))); + + ut_asserteq(base->memmap.count, cmp->memmap.count); + for (i = 0; i < base->memmap.count; i++) + ut_assertok(compare_upl_memmap(uts, + alist_get(&base->memmap, i, struct upl_memmap), + alist_get(&cmp->memmap, i, struct upl_memmap))); + + ut_asserteq(base->memres.count, cmp->memres.count); + for (i = 0; i < base->memres.count; i++) + ut_assertok(compare_upl_memres(uts, + alist_get(&base->memres, i, struct upl_memres), + alist_get(&cmp->memres, i, struct upl_memres))); + + ut_assertok(compare_upl_serial(uts, &base->serial, &cmp->serial)); + ut_assertok(compare_upl_graphics(uts, &base->graphics, &cmp->graphics)); + + return 0; +} + +/* Basic test of writing and reading UPL handoff */ +static int upl_test_base(struct unit_test_state *uts) +{ + oftree tree, check_tree; + struct upl upl, check; + struct abuf buf; + + if (!CONFIG_IS_ENABLED(OFNODE_MULTI_TREE)) + return -EAGAIN; /* skip test */ + ut_assertok(upl_get_test_data(uts, &upl)); + + ut_assertok(upl_create_handoff_tree(&upl, &tree)); + ut_assertok(oftree_to_fdt(tree, &buf)); + + /* + * strings in check_tree and therefore check are only valid so long as + * buf stays around. As soon as we call abuf_uninit they go away + */ + check_tree = oftree_from_fdt(abuf_data(&buf)); + ut_assert(ofnode_valid(oftree_path(check_tree, "/"))); + + ut_assertok(upl_read_handoff(&check, check_tree)); + ut_assertok(compare_upl(uts, &upl, &check)); + abuf_uninit(&buf); + + return 0; +} +UPL_TEST(upl_test_base, 0); + +int do_ut_upl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct unit_test *tests = UNIT_TEST_SUITE_START(upl_test); + const int n_ents = UNIT_TEST_SUITE_COUNT(upl_test); + + return cmd_ut_category("cmd_upl", "cmd_upl_", tests, n_ents, argc, + argv); +} diff --git a/test/cmd_ut.c b/test/cmd_ut.c index 4e4aa8f1cb2..38ba89ee33e 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -133,6 +133,9 @@ static struct cmd_tbl cmd_ut_sub[] = { #ifdef CONFIG_CMD_SEAMA U_BOOT_CMD_MKENT(seama, CONFIG_SYS_MAXARGS, 1, do_ut_seama, "", ""), #endif +#ifdef CONFIG_CMD_UPL + U_BOOT_CMD_MKENT(upl, CONFIG_SYS_MAXARGS, 1, do_ut_upl, "", ""), +#endif };
static int do_ut_all(struct cmd_tbl *cmdtp, int flag, int argc,

Add a 'upl' command to work with Universal Payload features. For now it only supports reading and writing a handoff structure.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
MAINTAINERS | 2 + boot/Kconfig | 1 + cmd/Kconfig | 7 ++ cmd/Makefile | 1 + cmd/upl.c | 118 +++++++++++++++++++ doc/usage/cmd/upl.rst | 186 ++++++++++++++++++++++++++++++ doc/usage/index.rst | 1 + include/asm-generic/global_data.h | 15 +++ test/boot/upl.c | 40 +++++++ 9 files changed, 371 insertions(+) create mode 100644 cmd/upl.c create mode 100644 doc/usage/cmd/upl.rst
diff --git a/MAINTAINERS b/MAINTAINERS index 1c41fd30b2a..538d975748d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1706,6 +1706,8 @@ M: Simon Glass sjg@chromium.org S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git F: boot/upl* +F: cmd/upl.c +F: doc/usage/cmd/upl.rst F: include/upl.h F: test/boot/upl.c
diff --git a/boot/Kconfig b/boot/Kconfig index fa4f766054c..f9a44550ac4 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -738,6 +738,7 @@ config BOOTMETH_SCRIPT
config UPL bool "upl - Universal Payload Specification" + imply CMD_UPL imply UPL_READ imply UPL_WRITE help diff --git a/cmd/Kconfig b/cmd/Kconfig index 0cf0d8ad8ab..3c602bd5ba3 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -388,6 +388,13 @@ config CMD_SEAMA help Support reading NAND Seattle Image (SEAMA) images.
+config CMD_UPL + bool "upl - Universal Payload Specification" + help + Provides commands to deal with UPL payloads and handoff information. + U-Boot supports generating and accepting handoff information. The + mkimage tool will eventually support creating payloads. + config CMD_VBE bool "vbe - Verified Boot for Embedded" depends on BOOTMETH_VBE diff --git a/cmd/Makefile b/cmd/Makefile index 87133cc27a8..91227f1249c 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -189,6 +189,7 @@ obj-$(CONFIG_CMD_UBIFS) += ubifs.o obj-$(CONFIG_CMD_UNIVERSE) += universe.o obj-$(CONFIG_CMD_UNLZ4) += unlz4.o obj-$(CONFIG_CMD_UNZIP) += unzip.o +obj-$(CONFIG_CMD_UPL) += upl.o obj-$(CONFIG_CMD_VIRTIO) += virtio.o obj-$(CONFIG_CMD_WDT) += wdt.o obj-$(CONFIG_CMD_LZMADEC) += lzmadec.o diff --git a/cmd/upl.c b/cmd/upl.c new file mode 100644 index 00000000000..c9745886507 --- /dev/null +++ b/cmd/upl.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Commands for UPL handoff generation + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <abuf.h> +#include <alist.h> +#include <command.h> +#include <display_options.h> +#include <mapmem.h> +#include <string.h> +#include <upl.h> +#include <dm/ofnode.h> +#include <test/ut.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int do_upl_info(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const struct upl *upl = gd_upl(); + + printf("UPL state: %sactive\n", upl ? "" : "in"); + if (!upl) + return 0; + if (argc > 1 && !strcmp("-v", argv[1])) { + int i; + + printf("fit %lx\n", upl->fit); + printf("conf_offset %x\n", upl->conf_offset); + for (i = 0; i < upl->image.count; i++) { + const struct upl_image *img = + alist_get(&upl->image, i, struct upl_image); + + printf("image %d: load %lx size %lx offset %x: %s\n", i, + img->load, img->size, img->offset, + img->description); + } + } + + return 0; +} + +static int do_upl_write(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct upl s_upl, *upl = &s_upl; + struct unit_test_state uts; + struct abuf buf; + oftree tree; + ulong addr; + int ret; + + upl_get_test_data(&uts, upl); + + log_debug("Writing UPL\n"); + ret = upl_create_handoff_tree(upl, &tree); + if (ret) { + log_err("Failed to write (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + log_debug("Flattening\n"); + ret = oftree_to_fdt(tree, &buf); + if (ret) { + log_err("Failed to write (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + addr = map_to_sysmem(abuf_data(&buf)); + printf("UPL handoff written to %lx size %lx\n", addr, abuf_size(&buf)); + if (env_set_hex("upladdr", addr) || + env_set_hex("uplsize", abuf_size(&buf))) { + printf("Cannot set env var\n"); + return CMD_RET_FAILURE; + } + + log_debug("done\n"); + + return 0; +} + +static int do_upl_read(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + struct upl s_upl, *upl = &s_upl; + oftree tree; + ulong addr; + int ret; + + if (argc < 1) + return CMD_RET_USAGE; + addr = hextoul(argv[1], NULL); + + printf("Reading UPL at %lx\n", addr); + tree = oftree_from_fdt(map_sysmem(addr, 0)); + ret = upl_read_handoff(upl, tree); + if (ret) { + log_err("Failed to read (err=%dE)\n", ret); + return CMD_RET_FAILURE; + } + + return 0; +} + +U_BOOT_LONGHELP(upl, + "info [-v] - Check UPL status\n" + "upl read <addr> - Read handoff information\n" + "upl write - Write handoff information"); + +U_BOOT_CMD_WITH_SUBCMDS(upl, "Universal Payload support", upl_help_text, + U_BOOT_SUBCMD_MKENT(info, 2, 1, do_upl_info), + U_BOOT_SUBCMD_MKENT(read, 2, 1, do_upl_read), + U_BOOT_SUBCMD_MKENT(write, 1, 1, do_upl_write)); diff --git a/doc/usage/cmd/upl.rst b/doc/usage/cmd/upl.rst new file mode 100644 index 00000000000..a64063d42d5 --- /dev/null +++ b/doc/usage/cmd/upl.rst @@ -0,0 +1,186 @@ +.. SPDX-License-Identifier: GPL-2.0+: + +upl command +=========== + +Synopsis +-------- + +:: + + upl write + upl read <addr> + upl info [-v] + +Description +----------- + +The *upl* command is used to test U-Boot's support for the Universal Payload +Specification (UPL) firmware standard. It allows creation of a fake handoff for +use in testing. + + +upl write +~~~~~~~~~ + +Write a fake UPL handoff structure. The `upladdr` environment variable is set to +the address of this structure and `uplsize` is set to the size. + + +upl read +~~~~~~~~ + +Read a UPL handoff structure into internal state. This allows testing that the +handoff can be obtained. + +upl info +~~~~~~~~ + +Show basic information about usage of UPL: + + UPL state + active or inactive (indicates whether U-Boot booted from UPL or not) + + fit + Address of the FIT which was loaded + + conf_offset 2a4 + FIT offset of the chosen configuration + +For each image the following information is shown: + + Image number + Images are numbered from 0 + + load + Address to which the image was loaded + + size + Size of the loaded image + + offset + FIT offset of the image + + description + Description of the image + + +Example +------- + +This shows checking whether a UPL handoff was read at start-up:: + + => upl info + UPL state: active + +This shows how to use the command to write and display the handoff:: + + => upl write + UPL handoff written to bc8a5e0 size 662 + => print upladdr + upladdr=bc8a5e0 + => print uplsize + uplsize=662 + + > fdt addr ${upladdr} + Working FDT set to bc8a5e0 + => fdt print + / { + #address-cells = <0x00000001>; + #size-cells = <0x00000001>; + options { + upl-params { + smbios = <0x00000123>; + acpi = <0x00000456>; + bootmode = "default", "s3"; + addr-width = <0x0000002e>; + acpi-nvs-size = <0x00000100>; + }; + upl-image { + fit = <0x00000789>; + conf-offset = <0x00000234>; + image-1 { + load = <0x00000001>; + size = <0x00000002>; + offset = <0x00000003>; + description = "U-Boot"; + }; + image-2 { + load = <0x00000004>; + size = <0x00000005>; + offset = <0x00000006>; + description = "ATF"; + }; + }; + }; + memory@0x10 { + reg = <0x00000010 0x00000020 0x00000030 0x00000040 0x00000050 0x00000060>; + }; + memory@0x70 { + reg = <0x00000070 0x00000080>; + hotpluggable; + }; + memory-map { + acpi@0x11 { + reg = <0x00000011 0x00000012 0x00000013 0x00000014 0x00000015 0x00000016 0x00000017 0x00000018 0x00000019 0x0000001a>; + usage = "acpi-reclaim"; + }; + u-boot@0x21 { + reg = <0x00000021 0x00000022>; + usage = "boot-data"; + }; + efi@0x23 { + reg = <0x00000023 0x00000024>; + usage = "runtime-code"; + }; + empty@0x25 { + reg = <0x00000025 0x00000026 0x00000027 0x00000028>; + }; + acpi-things@0x2a { + reg = <0x0000002a 0x00000000>; + usage = "acpi-nvs", "runtime-code"; + }; + }; + reserved-memory { + mmio@0x2b { + reg = <0x0000002b 0x0000002c>; + }; + memory@0x2d { + reg = <0x0000002d 0x0000002e 0x0000002f 0x00000030>; + no-map; + }; + }; + serial@0xf1de0000 { + compatible = "ns16550a"; + clock-frequency = <0x001c2000>; + current-speed = <0x0001c200>; + reg = <0xf1de0000 0x00000100>; + reg-io-shift = <0x00000002>; + reg-offset = <0x00000040>; + virtual-reg = <0x20000000>; + access-type = "mmio"; + }; + framebuffer@0xd0000000 { + compatible = "simple-framebuffer"; + reg = <0xd0000000 0x10000000>; + width = <0x00000500>; + height = <0x00000500>; + stride = <0x00001400>; + format = "a8r8g8b8"; + }; + }; + => + +This showing reading the handoff into internal state:: + + => upl read bc8a5e0 + Reading UPL at bc8a5e0 + => + +This shows getting basic information about UPL: + + => upl info -v + UPL state: active + fit 1264000 + conf_offset 2a4 + image 0: load 200000 size 105f5c8 offset a4: U-Boot 2024.07-00770-g739ee12e8358 for sandbox board diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 49b354e6ffd..b34e388bfd1 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -113,6 +113,7 @@ Shell commands cmd/tftpput cmd/trace cmd/true + cmd/upl cmd/ums cmd/unbind cmd/ut diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h index aa336d63e3a..98d822530d8 100644 --- a/include/asm-generic/global_data.h +++ b/include/asm-generic/global_data.h @@ -30,6 +30,7 @@
struct acpi_ctx; struct driver_rt; +struct upl;
typedef struct global_data gd_t;
@@ -491,6 +492,12 @@ struct global_data { * @dmtag_list: List of DM tags */ struct list_head dmtag_list; +#if CONFIG_IS_ENABLED(UPL) + /** + * @upl: Universal Payload-handoff information + */ + struct upl *upl; +#endif }; #ifndef DO_DEPS_ONLY static_assert(sizeof(struct global_data) == GD_SIZE); @@ -590,6 +597,14 @@ static_assert(sizeof(struct global_data) == GD_SIZE); #define gd_malloc_ptr() 0L #endif
+#if CONFIG_IS_ENABLED(UPL) +#define gd_upl() gd->upl +#define gd_set_upl(_val) gd->upl = (_val) +#else +#define gd_upl() NULL +#define gd_set_upl(val) +#endif + /** * enum gd_flags - global data flags * diff --git a/test/boot/upl.c b/test/boot/upl.c index e9d92efd80d..c55f1d297d1 100644 --- a/test/boot/upl.c +++ b/test/boot/upl.c @@ -358,6 +358,46 @@ static int upl_test_base(struct unit_test_state *uts) } UPL_TEST(upl_test_base, 0);
+/* Test 'upl info' command */ +static int upl_test_info(struct unit_test_state *uts) +{ + gd_set_upl(NULL); + ut_assertok(run_command("upl info", 0)); + ut_assert_nextline("UPL state: inactive"); + ut_assert_console_end(); + + gd_set_upl((struct upl *)uts); /* set it to any non-zero value */ + ut_assertok(run_command("upl info", 0)); + ut_assert_nextline("UPL state: active"); + ut_assert_console_end(); + gd_set_upl(NULL); + + return 0; +} +UPL_TEST(upl_test_info, UT_TESTF_CONSOLE_REC); + +/* Test 'upl read' and 'upl_write' commands */ +static int upl_test_read_write(struct unit_test_state *uts) +{ + ulong addr; + + if (!CONFIG_IS_ENABLED(OFNODE_MULTI_TREE)) + return -EAGAIN; /* skip test */ + ut_assertok(run_command("upl write", 0)); + + addr = env_get_hex("upladdr", 0); + ut_assert_nextline("UPL handoff written to %lx size %lx", addr, + env_get_hex("uplsize", 0)); + ut_assert_console_end(); + + ut_assertok(run_command("upl read ${upladdr}", 0)); + ut_assert_nextline("Reading UPL at %lx", addr); + ut_assert_console_end(); + + return 0; +} +UPL_TEST(upl_test_read_write, UT_TESTF_CONSOLE_REC); + int do_ut_upl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { struct unit_test *tests = UNIT_TEST_SUITE_START(upl_test);

Add the basic code to create a handoff structure in SPL, so it can be passed to the next phase. For now this is not plumbed in.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v2)
Changes in v2: - Add a function to init the UPL process in SPL
MAINTAINERS | 1 + boot/Kconfig | 32 ++++++++ common/spl/Makefile | 2 + common/spl/spl_upl.c | 171 +++++++++++++++++++++++++++++++++++++++++++ include/spl.h | 16 ++++ 5 files changed, 222 insertions(+) create mode 100644 common/spl/spl_upl.c
diff --git a/MAINTAINERS b/MAINTAINERS index 538d975748d..efb557b2524 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1707,6 +1707,7 @@ S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git F: boot/upl* F: cmd/upl.c +F: common/spl/spl_upl.c F: doc/usage/cmd/upl.rst F: include/upl.h F: test/boot/upl.c diff --git a/boot/Kconfig b/boot/Kconfig index f9a44550ac4..1fdbb0755c5 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -741,6 +741,7 @@ config UPL imply CMD_UPL imply UPL_READ imply UPL_WRITE + imply SPL_UPL if SPL help Provides support for UPL payloads and handoff information. U-Boot supports generating and accepting handoff information. The mkimage @@ -757,11 +758,42 @@ config UPL_READ
config UPL_WRITE bool "upl - Support writing a Universal Payload handoff" + help + Provides support for encoding a UPL-format payload from a C structure + so it can be passed to another program. This is just the writing + implementation, useful for trying it out. See SPL_UPL_OUT + for how to tell U-Boot SPL to actually write it before jumping to + the next phase. + +if SPL + +config SPL_UPL + bool "Write a UPL handoff in SPL" + imply SPL_UPL_OUT + help + This tells SPL to write a UPL handoff and pass it to the next phase + (e.g. to U-Boot or another program which SPL loads and runs). THis + provides information to help that program run correctly and + efficiently on the machine. + +config SPL_UPL_WRITE + bool # upl - Support writing a Universal Payload handoff in SPL + select SPL_BLOBLIST help Provides support for encoding a UPL-format payload from a C structure so it can be passed to another program. This is just the writing implementation, useful for trying it out.
+config SPL_UPL_OUT + bool "upl - Support writing a Universal Payload handoff in SPL" + select SPL_UPL_WRITE + help + Provides support for encoding a UPL-format payload and passing it to + the next firmware phase. This allows U-Boot SPL to function as + Platform Init in the meaning of the specification. + +endif # SPL + endif # UPL
endif # BOOTSTD diff --git a/common/spl/Makefile b/common/spl/Makefile index 4809f9c3ec1..137b18428bd 100644 --- a/common/spl/Makefile +++ b/common/spl/Makefile @@ -37,3 +37,5 @@ obj-$(CONFIG_$(SPL_TPL_)SPI_LOAD) += spl_spi.o obj-$(CONFIG_$(SPL_TPL_)RAM_SUPPORT) += spl_ram.o obj-$(CONFIG_$(SPL_TPL_)USB_SDP_SUPPORT) += spl_sdp.o endif + +obj-$(CONFIG_$(SPL_TPL_)UPL) += spl_upl.o diff --git a/common/spl/spl_upl.c b/common/spl/spl_upl.c new file mode 100644 index 00000000000..12337a05cc0 --- /dev/null +++ b/common/spl/spl_upl.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * UPL handoff parsing + * + * Copyright 2024 Google LLC + * Written by Simon Glass sjg@chromium.org + */ + +#define LOG_CATEGORY UCLASS_BOOTSTD + +#include <bloblist.h> +#include <dm.h> +#include <image.h> +#include <mapmem.h> +#include <serial.h> +#include <spl.h> +#include <upl.h> +#include <video.h> +#include <asm/global_data.h> +#include <dm/read.h> +#include <dm/uclass-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct upl s_upl; + +void upl_set_fit_addr(ulong fit) +{ + struct upl *upl = &s_upl; + + upl->fit = fit; +} + +void upl_set_fit_info(ulong fit, int conf_offset, ulong entry_addr) +{ + struct upl *upl = &s_upl; + + upl->fit = fit; + upl->conf_offset = conf_offset; + log_debug("upl: add fit %lx conf %x\n", fit, conf_offset); +} + +int upl_add_image(int node, ulong load_addr, ulong size, const char *desc) +{ + struct upl *upl = &s_upl; + struct upl_image img; + + img.load = load_addr; + img.size = size; + img.offset = node; + img.description = desc; + if (!alist_add(&upl->image, img, struct upl_image)) + return -ENOMEM; + log_debug("upl: add image %s at %lx size %lx\n", desc, load_addr, size); + + return 0; +} + +static int write_serial(struct upl_serial *ser) +{ + struct udevice *dev = gd->cur_serial_dev; + struct serial_device_info info; + struct memregion region; + int ret; + + if (!dev) + return log_msg_ret("ser", -ENOENT); + ret = serial_getinfo(dev, &info); + if (ret) + return log_msg_ret("inf", ret); + + ser->compatible = ofnode_read_string(dev_ofnode(dev), "compatible"); + ser->clock_frequency = info.clock; + ser->current_speed = gd->baudrate; + region.base = info.addr; + region.size = info.size; + alist_init_struct(&ser->reg, struct memregion); + if (!alist_add(&ser->reg, region, struct memregion)) + return -ENOMEM; + ser->reg_io_shift = info.reg_shift; + ser->reg_offset = info.reg_offset; + ser->reg_io_width = info.reg_width; + ser->virtual_reg = 0; + ser->access_type = info.addr_space; + + return 0; +} + +static int write_graphics(struct upl_graphics *gra) +{ + struct video_uc_plat *plat; + struct video_priv *priv; + struct memregion region; + struct udevice *dev; + + alist_init_struct(&gra->reg, struct memregion); + uclass_find_first_device(UCLASS_VIDEO, &dev); + if (!dev || !device_active(dev)) + return log_msg_ret("vid", -ENOENT); + + plat = dev_get_uclass_plat(dev); + region.base = plat->base; + region.size = plat->size; + if (!alist_add(&gra->reg, region, struct memregion)) + return log_msg_ret("reg", -ENOMEM); + + priv = dev_get_uclass_priv(dev); + gra->width = priv->xsize; + gra->height = priv->ysize; + gra->stride = priv->line_length; /* private field */ + switch (priv->format) { + case VIDEO_RGBA8888: + case VIDEO_X8R8G8B8: + gra->format = UPLGF_ARGB32; + break; + case VIDEO_X8B8G8R8: + gra->format = UPLGF_ABGR32; + break; + case VIDEO_X2R10G10B10: + log_debug("device '%s': VIDEO_X2R10G10B10 not supported\n", + dev->name); + return log_msg_ret("for", -EPROTO); + case VIDEO_UNKNOWN: + log_debug("device '%s': Unknown video format\n", dev->name); + return log_msg_ret("for", -EPROTO); + } + + return 0; +} + +int spl_write_upl_handoff(struct spl_image_info *spl_image) +{ + struct upl *upl = &s_upl; + struct abuf buf; + ofnode root; + void *ptr; + int ret; + + log_debug("UPL: Writing handoff - image_count=%d\n", upl->image.count); + upl->addr_cells = IS_ENABLED(CONFIG_PHYS_64BIT) ? 2 : 1; + upl->size_cells = IS_ENABLED(CONFIG_PHYS_64BIT) ? 2 : 1; + upl->bootmode = UPLBM_DEFAULT; + ret = write_serial(&upl->serial); + if (ret) + return log_msg_ret("ser", ret); + ret = write_graphics(&upl->graphics); + if (ret && ret != -ENOENT) + return log_msg_ret("gra", ret); + + root = ofnode_root(); + ret = upl_write_handoff(upl, root, true); + if (ret) + return log_msg_ret("wr", ret); + + ret = oftree_to_fdt(oftree_default(), &buf); + if (ret) + return log_msg_ret("fdt", ret); + log_debug("FDT size %zx\n", abuf_size(&buf)); + + ptr = bloblist_add(BLOBLISTT_CONTROL_FDT, abuf_size(&buf), 0); + if (!ptr) + return log_msg_ret("blo", -ENOENT); + memcpy(ptr, abuf_data(&buf), abuf_size(&buf)); + + return 0; +} + +void spl_upl_init(void) +{ + upl_init(&s_upl); +} diff --git a/include/spl.h b/include/spl.h index 5dfdf778d2d..089b1864c57 100644 --- a/include/spl.h +++ b/include/spl.h @@ -1074,4 +1074,20 @@ static inline bool spl_decompression_enabled(void) { return IS_ENABLED(CONFIG_SPL_GZIP) || IS_ENABLED(CONFIG_SPL_LZMA); } + +/** + * spl_write_upl_handoff() - Write a Universal Payload hand-off structure + * + * @spl_image: Information about the image being booted + * Return: 0 if OK, -ve on error + */ +int spl_write_upl_handoff(struct spl_image_info *spl_image); + +/** + * spl_upl_init() - Get UPL ready for information to be added + * + * This must be called before upl_add_image(), etc. + */ +void spl_upl_init(void); + #endif

This flag is set for simple FIT, so set it for full FIT too.
Signed-off-by: Simon Glass sjg@chromium.org ---
Changes in v3: - Split out into its own commit
common/spl/spl_fit.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c index 2a097f4464c..527a5691ac6 100644 --- a/common/spl/spl_fit.c +++ b/common/spl/spl_fit.c @@ -941,6 +941,7 @@ int spl_load_fit_image(struct spl_image_info *spl_image, if (ret < 0) return ret; } + spl_image->flags |= SPL_FIT_FOUND;
return 0; }

Specify the FIT and include information about each loaded image, as required by the UPL handoff.
Write the UPL handoff into the bloblist before jumping to the next phase.
Control this using a runtime flag to avoid conflicting with other handoff mechanisms.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v2)
Changes in v2: - Hang when something goes wrong, to avoid a broken boot - Add a runtime flag to enable UPL - Put upl_image() calls in a common place
boot/image-fit.c | 4 ++++ common/spl/spl.c | 8 ++++++++ common/spl/spl_fit.c | 9 +++++++++ include/asm-generic/global_data.h | 4 ++++ 4 files changed, 25 insertions(+)
diff --git a/boot/image-fit.c b/boot/image-fit.c index 9253f81fff5..c38ccd6754f 100644 --- a/boot/image-fit.c +++ b/boot/image-fit.c @@ -36,6 +36,7 @@ DECLARE_GLOBAL_DATA_PTR; #include <bootm.h> #include <image.h> #include <bootstage.h> +#include <upl.h> #include <u-boot/crc.h>
/*****************************************************************************/ @@ -2294,6 +2295,9 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
bootstage_mark(bootstage_id + BOOTSTAGE_SUB_LOAD);
+ upl_add_image(noffset, load, len, + fdt_getprop(fit, noffset, FIT_DESC_PROP, NULL)); + *datap = load; *lenp = len; if (fit_unamep) diff --git a/common/spl/spl.c b/common/spl/spl.c index 7794ddccade..d6a364de6ee 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -810,6 +810,14 @@ void board_init_r(gd_t *dummy1, ulong dummy2) printf(SPL_TPL_PROMPT "SPL hand-off write failed (err=%d)\n", ret); } + if (CONFIG_IS_ENABLED(UPL_OUT) && (gd->flags & GD_FLG_UPL)) { + ret = spl_write_upl_handoff(&spl_image); + if (ret) { + printf(SPL_TPL_PROMPT + "UPL hand-off write failed (err=%d)\n", ret); + hang(); + } + } if (CONFIG_IS_ENABLED(BLOBLIST)) { ret = bloblist_finish(); if (ret) diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c index 527a5691ac6..03a2ad4ebe7 100644 --- a/common/spl/spl_fit.c +++ b/common/spl/spl_fit.c @@ -12,6 +12,7 @@ #include <memalign.h> #include <mapmem.h> #include <spl.h> +#include <upl.h> #include <sysinfo.h> #include <asm/global_data.h> #include <asm/io.h> @@ -336,6 +337,9 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset, image_info->entry_point = FDT_ERROR; }
+ upl_add_image(node, load_addr, length, + fdt_getprop(fit, node, FIT_DESC_PROP, NULL)); + return 0; }
@@ -847,6 +851,8 @@ int spl_load_simple_fit(struct spl_image_info *spl_image, spl_image->entry_point = spl_image->load_addr;
spl_image->flags |= SPL_FIT_FOUND; + upl_set_fit_info(map_to_sysmem(ctx.fit), ctx.conf_node, + spl_image->entry_point);
return 0; } @@ -943,5 +949,8 @@ int spl_load_fit_image(struct spl_image_info *spl_image, } spl_image->flags |= SPL_FIT_FOUND;
+ upl_set_fit_info(map_to_sysmem(header), conf_noffset, + spl_image->entry_point); + return 0; } diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h index 98d822530d8..37643a4bbb9 100644 --- a/include/asm-generic/global_data.h +++ b/include/asm-generic/global_data.h @@ -716,6 +716,10 @@ enum gd_flags { * @GD_FLG_HUSH_MODERN_PARSER: Use hush 2021 parser. */ GD_FLG_HUSH_MODERN_PARSER = 0x2000000, + /** + * @GD_FLG_UPL: Read/write a Universal Payload (UPL) handoff + */ + GD_FLG_UPL = 0x4000000, };
#endif /* __ASSEMBLY__ */

Read the UPL early in boot so that it is available. For now none of the information is used.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
boot/Kconfig | 12 +++++++++++- common/board_f.c | 22 ++++++++++++++++++++++ common/board_r.c | 2 ++ 3 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/boot/Kconfig b/boot/Kconfig index 1fdbb0755c5..ca14a614152 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -754,7 +754,9 @@ config UPL_READ help Provides support for decoding a UPL-format payload into a C structure which can be used elsewhere in U-Boot. This is just the reading - implementation, useful for trying it out. + implementation, useful for trying it out. See UPL_IN for how + to tell U-Boot to actually read it on startup and use it for memory + and device information, etc.
config UPL_WRITE bool "upl - Support writing a Universal Payload handoff" @@ -765,6 +767,14 @@ config UPL_WRITE for how to tell U-Boot SPL to actually write it before jumping to the next phase.
+config UPL_IN + bool "upl - Read the UPL handoff on startup" + select UPL_READ + help + Read an SPL handoff when U-Boot starts and use it to provide + devices, memory layout, etc. required by U-Boot. This allows U-Boot + to function as a payload in the meaning of the specification. + if SPL
config SPL_UPL diff --git a/common/board_f.c b/common/board_f.c index 22c180b2187..ebcc63f34dc 100644 --- a/common/board_f.c +++ b/common/board_f.c @@ -40,6 +40,7 @@ #include <sysreset.h> #include <timer.h> #include <trace.h> +#include <upl.h> #include <video.h> #include <watchdog.h> #include <asm/cache.h> @@ -862,6 +863,26 @@ __weak int clear_bss(void) return 0; }
+static int initf_upl(void) +{ + struct upl *upl; + int ret; + + if (!IS_ENABLED(CONFIG_UPL_IN) || !(gd->flags & GD_FLG_UPL)) + return 0; + + upl = malloc(sizeof(struct upl)); + if (upl) + ret = upl_read_handoff(upl, oftree_default()); + if (ret) { + printf("UPL handoff: read failure (err=%dE)\n", ret); + return ret; + } + gd_set_upl(upl); + + return 0; +} + static const init_fnc_t init_sequence_f[] = { setup_mon_len, #ifdef CONFIG_OF_CONTROL @@ -871,6 +892,7 @@ static const init_fnc_t init_sequence_f[] = { trace_early_init, #endif initf_malloc, + initf_upl, log_init, initf_bootstage, /* uses its own timer, so does not need DM */ event_init, diff --git a/common/board_r.c b/common/board_r.c index d4ba245ac69..f445803d7a4 100644 --- a/common/board_r.c +++ b/common/board_r.c @@ -521,6 +521,8 @@ static int dm_announce(void) uclass_count); if (CONFIG_IS_ENABLED(OF_REAL)) printf(", devicetree: %s", fdtdec_get_srcname()); + if (CONFIG_IS_ENABLED(UPL)) + printf(", universal payload active"); printf("\n"); if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE) && (gd->fdt_src == FDTSRC_SEPARATE ||

Use the sandbox_vpl build to test UPL since it supports a real devicetree in SPL. The sandbox_spl build uses OF_PLATDATA.
Enable writing the UPL handoff in SPL and reading it in U-Boot proper. Provide a test to check that this handoff works.
Note that the test uses the standard devicetree rather than the test one, since it is a lot smaller and fits in the existing bloblist.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
configs/sandbox_vpl_defconfig | 4 ++++ test/py/tests/test_upl.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 test/py/tests/test_upl.py
diff --git a/configs/sandbox_vpl_defconfig b/configs/sandbox_vpl_defconfig index 72483d8ba10..96e9211bd19 100644 --- a/configs/sandbox_vpl_defconfig +++ b/configs/sandbox_vpl_defconfig @@ -27,6 +27,9 @@ CONFIG_FIT=y CONFIG_FIT_VERBOSE=y CONFIG_FIT_BEST_MATCH=y CONFIG_SPL_LOAD_FIT=y +CONFIG_UPL=y +CONFIG_UPL_IN=y +CONFIG_SPL_UPL_OUT=y CONFIG_BOOTSTAGE=y CONFIG_BOOTSTAGE_REPORT=y CONFIG_BOOTSTAGE_FDT=y @@ -35,6 +38,7 @@ CONFIG_BOOTSTAGE_STASH_SIZE=0x4096 CONFIG_CONSOLE_RECORD=y CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000 CONFIG_DISPLAY_BOARDINFO_LATE=y +CONFIG_BLOBLIST_SIZE=0x5000 CONFIG_SPL_NO_BSS_LIMIT=y CONFIG_HANDOFF=y CONFIG_SPL_BOARD_INIT=y diff --git a/test/py/tests/test_upl.py b/test/py/tests/test_upl.py new file mode 100644 index 00000000000..171079f8dba --- /dev/null +++ b/test/py/tests/test_upl.py @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2024 Google LLC +# +# Test addition of Universal Payload + +import os + +import pytest +import u_boot_utils + +@pytest.mark.boardspec('sandbox_vpl') +def test_upl_handoff(u_boot_console): + cons = u_boot_console + ram = os.path.join(cons.config.build_dir, 'ram.bin') + fdt = os.path.join(cons.config.build_dir, 'u-boot.dtb') + + # Remove any existing RAM file, so we don't have old data present + if os.path.exists(ram): + os.remove(ram) + flags = ['-m', ram, '-d', fdt] + cons.restart_uboot_with_flags(flags, use_dtb=False) + + # Make sure that Universal Payload is detected in U-Boot proper + output = cons.run_command('upl info') + assert output == 'UPL state: active'

Add some documentation to explain the basic concept along with a link to the full spec.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v2)
Changes in v2: - Add a link to the spec - Add a link to the command
MAINTAINERS | 1 + doc/usage/cmd/upl.rst | 4 ++-- doc/usage/index.rst | 1 + doc/usage/upl.rst | 46 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 doc/usage/upl.rst
diff --git a/MAINTAINERS b/MAINTAINERS index efb557b2524..8842ff8ad91 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1708,6 +1708,7 @@ T: git https://source.denx.de/u-boot/custodians/u-boot-dm.git F: boot/upl* F: cmd/upl.c F: common/spl/spl_upl.c +F: doc/usage/upl.rst F: doc/usage/cmd/upl.rst F: include/upl.h F: test/boot/upl.c diff --git a/doc/usage/cmd/upl.rst b/doc/usage/cmd/upl.rst index a64063d42d5..8d6ea5daa37 100644 --- a/doc/usage/cmd/upl.rst +++ b/doc/usage/cmd/upl.rst @@ -16,8 +16,8 @@ Description -----------
The *upl* command is used to test U-Boot's support for the Universal Payload -Specification (UPL) firmware standard. It allows creation of a fake handoff for -use in testing. +Specification (UPL) firmware standard (see :doc:`../upl`). It allows creation of +a fake handoff for use in testing.
upl write diff --git a/doc/usage/index.rst b/doc/usage/index.rst index b34e388bfd1..d67fe355446 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -15,6 +15,7 @@ Use U-Boot cmdline semihosting measured_boot + upl
Shell commands -------------- diff --git a/doc/usage/upl.rst b/doc/usage/upl.rst new file mode 100644 index 00000000000..3c4a10c862c --- /dev/null +++ b/doc/usage/upl.rst @@ -0,0 +1,46 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Universal Payload +----------------- + +`Universal Payload (UPL) https://universalpayload.github.io/spec/index.html`_ +is an Industry Standard for firmware components. UPL +is designed to improve interoperability within the firmware industry, allowing +mixing and matching of projects with less friction and fewer project-specific +implementations. UPL is cross-platform, supporting ARM, x86 and RISC-V +initially. + +UPL is defined in termns of two firmware components: + +`Platform Init` + Perhaps initial setup of the hardware and jumps to the payload. + +`Payload` + Selects the OS to boot + +In practice UPL can be used to handle any number of handoffs through the +firmware startup process, with one program acting as platform init and another +acting as the payload. + +UPL provides a standard for three main pieces: + +- file format for the payload, which may comprise multiple images to load +- handoff format for the information the payload needs, such as serial port, + memory layout, etc. +- machine state and register settings at the point of handoff + +See also the :doc:`cmd/upl`. + +UPL in U-Boot +~~~~~~~~~~~~~ + +U-Boot supports: + +- writing a UPL handoff (devicetree) in SPL +- reading a UPL handoff in U-Boot proper +- creating a FIT + +There are some new FIT features in UPL which are not yet supported in U-Boot. + +.. sectionauthor:: Simon Glass sjg@chromium.org +.. July 2024

UPL significantly alters the boot flow for sandbox. Add a flag to enable this so that it can be enabled only on tests which need it.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
arch/sandbox/cpu/start.c | 13 ++++++++++++- arch/sandbox/include/asm/state.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c index 0920ed55965..9ad5d46271a 100644 --- a/arch/sandbox/cpu/start.c +++ b/arch/sandbox/cpu/start.c @@ -431,6 +431,14 @@ static int sandbox_cmdline_cb_autoboot_keyed(struct sandbox_state *state, } SANDBOX_CMDLINE_OPT(autoboot_keyed, 0, "Allow keyed autoboot");
+static int sandbox_cmdline_cb_upl(struct sandbox_state *state, const char *arg) +{ + state->upl = true; + + return 0; +} +SANDBOX_CMDLINE_OPT(upl, 0, "Enable Universal Payload (UPL)"); + static void setup_ram_buf(struct sandbox_state *state) { /* Zero the RAM buffer if we didn't read it, to keep valgrind happy */ @@ -540,6 +548,9 @@ int sandbox_main(int argc, char *argv[]) goto err; }
+ if (state->upl) + gd->flags |= GD_FLG_UPL; + #if CONFIG_IS_ENABLED(SYS_MALLOC_F) gd->malloc_base = CFG_MALLOC_F_ADDR; #endif @@ -558,7 +569,7 @@ int sandbox_main(int argc, char *argv[]) log_debug("debug: %s\n", __func__);
/* Do pre- and post-relocation init */ - board_init_f(0); + board_init_f(gd->flags);
board_init_r(gd->new_gd, 0);
diff --git a/arch/sandbox/include/asm/state.h b/arch/sandbox/include/asm/state.h index c84a1f7060f..6b50473ed41 100644 --- a/arch/sandbox/include/asm/state.h +++ b/arch/sandbox/include/asm/state.h @@ -97,6 +97,7 @@ struct sandbox_state { bool autoboot_keyed; /* Use keyed-autoboot feature */ bool disable_eth; /* Disable Ethernet devices */ bool disable_sf_bootdevs; /* Don't bind SPI flash bootdevs */ + bool upl; /* Enable Universal Payload (UPL) */
/* Pointer to information for each SPI bus/cs */ struct sandbox_spi_info spi[CONFIG_SANDBOX_SPI_MAX_BUS]

Add support for loading a UPL image from SPL. This uses the simple FIT implementation, but also loads the full FIT just to permit more testing.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
arch/sandbox/cpu/spl.c | 49 +++++++++++++++++++++++++++++++++- arch/sandbox/include/asm/spl.h | 1 + 2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/arch/sandbox/cpu/spl.c b/arch/sandbox/cpu/spl.c index e86193de419..bcb1ca10a51 100644 --- a/arch/sandbox/cpu/spl.c +++ b/arch/sandbox/cpu/spl.c @@ -3,14 +3,18 @@ * Copyright (c) 2016 Google, Inc */
+#define LOG_CATEGORY LOGC_BOOT + #include <dm.h> #include <hang.h> #include <handoff.h> #include <image.h> #include <init.h> #include <log.h> +#include <mapmem.h> #include <os.h> #include <spl.h> +#include <upl.h> #include <asm/global_data.h> #include <asm/spl.h> #include <asm/state.h> @@ -52,7 +56,8 @@ void board_init_f(ulong flag) void board_boot_order(u32 *spl_boot_list) { spl_boot_list[0] = BOOT_DEVICE_VBE; - spl_boot_list[1] = BOOT_DEVICE_BOARD; + spl_boot_list[1] = BOOT_DEVICE_UPL; + spl_boot_list[2] = BOOT_DEVICE_BOARD; }
static int spl_board_load_file(struct spl_image_info *spl_image, @@ -250,3 +255,45 @@ int sandbox_spl_load_fit(char *fname, int maxlen, struct spl_image_info *image)
return 0; } + +static int upl_load_from_image(struct spl_image_info *spl_image, + struct spl_boot_device *bootdev) +{ + long long size; + char *fname; + int ret, fd; + ulong addr; + + if (!CONFIG_IS_ENABLED(UPL_OUT)) + return -ENOTSUPP; + + spl_upl_init(); + fname = os_malloc(256); + + ret = sandbox_spl_load_fit(fname, 256, spl_image); + if (ret) + return log_msg_ret("fit", ret); + spl_image->flags = SPL_SANDBOXF_ARG_IS_BUF; + spl_image->arg = map_sysmem(spl_image->load_addr, 0); + /* size is set by load_simple_fit(), offset is left as 0 */ + + /* now read the whole FIT into memory */ + fd = os_open(fname, OS_O_RDONLY); + if (fd < 0) + return log_msg_ret("op2", -ENOENT); + if (os_get_filesize(fname, &size)) + return log_msg_ret("fis", -ENOENT); + + /* place it after the loaded image, allowing plenty of space */ + addr = ALIGN(spl_image->load_addr + size, 0x1000); + log_debug("Loading whole FIT to %lx\n", addr); + if (os_read(fd, map_sysmem(addr, 0), size) != size) + return log_msg_ret("rea", -EIO); + os_close(fd); + + /* tell UPL where it is */ + upl_set_fit_addr(addr); + + return 0; +} +SPL_LOAD_IMAGE_METHOD("upl", 4, BOOT_DEVICE_UPL, upl_load_from_image); diff --git a/arch/sandbox/include/asm/spl.h b/arch/sandbox/include/asm/spl.h index d50d9ad6b48..d824b2123a2 100644 --- a/arch/sandbox/include/asm/spl.h +++ b/arch/sandbox/include/asm/spl.h @@ -18,6 +18,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_SPI, BOOT_DEVICE_NAND, + BOOT_DEVICE_UPL, };
/**

Now that sandbox_vpl supports UPL, add a test that checks that the payload can be loaded by SPL and the handoff information passed through to U-Boot proper.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v2)
Changes in v2: - Support CI testing - Rework to use alist instead of fixed-length arrays
MAINTAINERS | 1 + test/boot/upl.c | 29 +++++++++++++++++++++++++++++ test/py/tests/test_upl.py | 17 +++++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS index 8842ff8ad91..7f988e0762f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1712,6 +1712,7 @@ F: doc/usage/upl.rst F: doc/usage/cmd/upl.rst F: include/upl.h F: test/boot/upl.c +F: test/py/tests/test_upl.py
USB M: Marek Vasut marex@denx.de diff --git a/test/boot/upl.c b/test/boot/upl.c index c55f1d297d1..707bc9c9065 100644 --- a/test/boot/upl.c +++ b/test/boot/upl.c @@ -398,6 +398,35 @@ static int upl_test_read_write(struct unit_test_state *uts) } UPL_TEST(upl_test_read_write, UT_TESTF_CONSOLE_REC);
+/* Test UPL passthrough */ +static int upl_test_info_norun(struct unit_test_state *uts) +{ + const struct upl_image *img; + struct upl *upl = gd_upl(); + const void *fit; + + ut_assertok(run_command("upl info -v", 0)); + ut_assert_nextline("UPL state: active"); + ut_assert_nextline("fit %lx", upl->fit); + ut_assert_nextline("conf_offset %x", upl->conf_offset); + ut_assert_nextlinen("image 0"); + ut_assert_nextlinen("image 1"); + ut_assert_console_end(); + + /* check the offsets */ + fit = map_sysmem(upl->fit, 0); + ut_asserteq_str("conf-1", fdt_get_name(fit, upl->conf_offset, NULL)); + + ut_asserteq(2, upl->image.count); + + img = alist_get(&upl->image, 1, struct upl_image); + ut_asserteq_str("firmware-1", fdt_get_name(fit, img->offset, NULL)); + ut_asserteq(CONFIG_TEXT_BASE, img->load); + + return 0; +} +UPL_TEST(upl_test_info_norun, UT_TESTF_CONSOLE_REC | UT_TESTF_MANUAL); + int do_ut_upl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { struct unit_test *tests = UNIT_TEST_SUITE_START(upl_test); diff --git a/test/py/tests/test_upl.py b/test/py/tests/test_upl.py index 171079f8dba..3164bda6b71 100644 --- a/test/py/tests/test_upl.py +++ b/test/py/tests/test_upl.py @@ -10,6 +10,15 @@ import u_boot_utils
@pytest.mark.boardspec('sandbox_vpl') def test_upl_handoff(u_boot_console): + """Test of UPL handoff + + This works by starting up U-Boot VPL, which gets to SPL and then sets up a + UPL handoff using the FIT containing U-Boot proper. It then jumps to U-Boot + proper and runs a test to check that the parameters are correct. + + The entire FIT is loaded into memory in SPL (in upl_load_from_image()) so + that it can be inpected in upl_test_info_norun + """ cons = u_boot_console ram = os.path.join(cons.config.build_dir, 'ram.bin') fdt = os.path.join(cons.config.build_dir, 'u-boot.dtb') @@ -17,9 +26,13 @@ def test_upl_handoff(u_boot_console): # Remove any existing RAM file, so we don't have old data present if os.path.exists(ram): os.remove(ram) - flags = ['-m', ram, '-d', fdt] + flags = ['-m', ram, '-d', fdt, '--upl'] cons.restart_uboot_with_flags(flags, use_dtb=False)
# Make sure that Universal Payload is detected in U-Boot proper output = cons.run_command('upl info') - assert output == 'UPL state: active' + assert 'UPL state: active' == output + + # Check the FIT offsets look correct + output = cons.run_command('ut upl -f upl_test_info_norun') + assert 'Failures: 0' in output

On Sun, Jul 21, 2024 at 11:09:18AM +0100, Simon Glass wrote:
Universal Payload (UPL) is an Industry Standard for firmware components[1]. UPL is designed to improve interoperability within the firmware industry, allowing mixing and matching of projects with less friction and fewer project-specific implementations. UPL is cross-platform, supporting ARM, x86 and RISC-V initially.
This series provides some initial support for this, targeting 0.9.1 and sandbox only.
Features still to come include:
- Support for architectures
- FIT validation
- Handoff validation
- Interoperability tests
This series is available at dm/uplb-working and requires the alist series at dm/alist-working[2]
[1] https://universalpayload.github.io/spec/ [2] https://patchwork.ozlabs.org/project/uboot/list/?series=414642
Please rebase and repost v4 on top of current next, which has v5 of alist. Also, the changes to load_simple_fit() aren't size-neutral in SPL since it looks like upl_add_image() doesn't get nop'd out when SPL_UPL isn't set. Thanks!
participants (2)
-
Simon Glass
-
Tom Rini