diff options
43 files changed, 3052 insertions, 72 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index ddcb7128db4..44e9c2f03f3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1709,6 +1709,19 @@ 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: 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 +F: test/py/tests/test_upl.py + USB M: Marek Vasut <marex@denx.de> S: Maintained @@ -1473,8 +1473,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) 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) { diff --git a/arch/sandbox/cpu/os.c b/arch/sandbox/cpu/os.c index 46ff305b536..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) @@ -808,7 +826,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 +877,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 @@ -964,7 +982,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" */ diff --git a/arch/sandbox/cpu/spl.c b/arch/sandbox/cpu/spl.c index 9ad9da686c6..bcb1ca10a51 100644 --- a/arch/sandbox/cpu/spl.c +++ b/arch/sandbox/cpu/spl.c @@ -3,13 +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> @@ -51,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, @@ -179,3 +185,115 @@ 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\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\n", size, res); + return log_msg_ret("osr", res); + } + + 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 %d\n", sizeof(*header), + ret); + return log_msg_ret("rea", ret); + } + 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; +} + +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/cpu/start.c b/arch/sandbox/cpu/start.c index dce80416529..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 */ @@ -483,6 +491,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 +512,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(); @@ -539,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 @@ -557,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/spl.h b/arch/sandbox/include/asm/spl.h index 4fab24cd156..d824b2123a2 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, @@ -16,6 +18,7 @@ enum { BOOT_DEVICE_NOR, BOOT_DEVICE_SPI, BOOT_DEVICE_NAND, + BOOT_DEVICE_UPL, }; /** @@ -31,4 +34,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/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] diff --git a/boot/Kconfig b/boot/Kconfig index 940389d4882..7ac34574079 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -745,6 +745,76 @@ 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 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 + 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. 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" + 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. + +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 + 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 config LEGACY_IMAGE_FORMAT diff --git a/boot/Makefile b/boot/Makefile index dff6f99081a..f4675d6ffd5 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -43,6 +43,10 @@ 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_)UPL_WRITE) += upl_write.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/image-fit.c b/boot/image-fit.c index 9253f81fff5..7d56f0b5e6e 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,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr, bootstage_mark(bootstage_id + BOOTSTAGE_SUB_LOAD); + upl_add_image(fit, noffset, load, len); + *datap = load; *lenp = len; if (fit_unamep) 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..5063897a132 --- /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)) + 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)) + 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)) + 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)) + 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)) + 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/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; +} diff --git a/cmd/Kconfig b/cmd/Kconfig index 978f44eda42..8af136d2bd4 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/common/board_f.c b/common/board_f.c index 29e185137ad..d71005d9f83 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> @@ -859,6 +860,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 @@ -868,6 +889,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 || 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.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 2a097f4464c..1ad5a69d807 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,8 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset, image_info->entry_point = FDT_ERROR; } + upl_add_image(fit, node, load_addr, length); + return 0; } @@ -847,6 +850,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; } @@ -941,6 +946,10 @@ int spl_load_fit_image(struct spl_image_info *spl_image, if (ret < 0) return ret; } + 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/common/spl/spl_upl.c b/common/spl/spl_upl.c new file mode 100644 index 00000000000..067d437150f --- /dev/null +++ b/common/spl/spl_upl.c @@ -0,0 +1,172 @@ +// 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 <alist.h> +#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)) + 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)) + 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)) + 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/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/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/doc/usage/cmd/upl.rst b/doc/usage/cmd/upl.rst new file mode 100644 index 00000000000..8d6ea5daa37 --- /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 (see :doc:`../upl`). 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 1f6518b77c4..b058c2254fb 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 -------------- @@ -114,6 +115,7 @@ Shell commands cmd/tftpput cmd/trace cmd/true + cmd/upl cmd/ums cmd/unbind cmd/ut 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 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/asm-generic/global_data.h b/include/asm-generic/global_data.h index 27aa75e7036..19c66e1fe5d 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 * @@ -701,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__ */ 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); diff --git a/include/spl.h b/include/spl.h index 1eebea3f981..f92089b69ea 100644 --- a/include/spl.h +++ b/include/spl.h @@ -1073,4 +1073,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 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 new file mode 100644 index 00000000000..2ec5ef1b5cf --- /dev/null +++ b/include/upl.h @@ -0,0 +1,382 @@ +/* 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 <image.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); + +/** + * 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) + +/** + * 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); + +#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) {} +#endif /* UPL && SPL */ + +/** + * _upl_add_image() - Internal function to 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 + * Return: 0 if OK, -ENOMEM if out of memory + */ +int _upl_add_image(int node, ulong load_addr, ulong size, const char *desc); + +/** + * upl_add_image() - Add a new image to the UPL + * + * @fit: Pointer to FIT + * @node: Image node offset in FIT + * @load_addr: Address to which images was loaded + * @size: Image size in bytes + * Return: 0 if OK, -ENOMEM if out of memory + */ +static inline int upl_add_image(const void *fit, int node, ulong load_addr, + ulong size) +{ + if (CONFIG_IS_ENABLED(UPL) && IS_ENABLED(CONFIG_SPL_BUILD)) { + const char *desc = fdt_getprop(fit, node, FIT_DESC_PROP, NULL); + + return _upl_add_image(node, load_addr, size, desc); + } + + return 0; +} + +/** upl_init() - Set up a UPL struct */ +void upl_init(struct upl *upl); + +#endif /* __UPL_WRITE_H */ diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 6865f78c70d..5edc8dd2f9f 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -1685,6 +1685,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; 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..364fb0526e4 --- /dev/null +++ b/test/boot/upl.c @@ -0,0 +1,437 @@ +// 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)); + + 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)); + + /* image[1] */ + img.load = 0x4; + img.size = 0x5; + img.offset = 0x6; + img.description = "ATF"; + ut_assertnonnull(alist_add(&upl->image, img)); + + /* 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)); + + /* 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)); + 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)); + + /* 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)); + + /* 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)); + + /* 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)); + + /* 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)); + + /* 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)); + + /* 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)); + + 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); + +/* 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); + +/* 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); + 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, 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; } diff --git a/test/py/tests/test_upl.py b/test/py/tests/test_upl.py new file mode 100644 index 00000000000..3164bda6b71 --- /dev/null +++ b/test/py/tests/test_upl.py @@ -0,0 +1,38 @@ +# 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): + """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') + + # 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, '--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 '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 |