Merge patch series "Universal Payload initial series"

Simon Glass <sjg@chromium.org> says:

Universal Payload (UPL) is an Industry Standard for firmware
components[1]. UPL is designed to improve interoperability within the
firmware industry, allowing mixing and matching of projects with less
friction and fewer project-specific implementations. UPL is
cross-platform, supporting ARM, x86 and RISC-V initially.

This series provides some initial support for this, targeting 0.9.1 and
sandbox only.

Features still to come include:
- Support for architectures
- FIT validation
- Handoff validation
- Interoperability tests
This commit is contained in:
Tom Rini 2024-08-09 16:03:21 -06:00
commit f4f845b859
43 changed files with 3052 additions and 72 deletions

View file

@ -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

View file

@ -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)

View file

@ -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) {

View file

@ -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" */

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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)

60
boot/upl_common.c Normal file
View file

@ -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);
}

24
boot/upl_common.h Normal file
View file

@ -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 */

588
boot/upl_read.c Normal file
View file

@ -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;
}

622
boot/upl_write.c Normal file
View file

@ -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;
}

View file

@ -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

View file

@ -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

118
cmd/upl.c Normal file
View file

@ -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));

View file

@ -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,

View file

@ -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 ||

View file

@ -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

View file

@ -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)

View file

@ -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;
}

172
common/spl/spl_upl.c Normal file
View file

@ -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);
}

View file

@ -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

View file

@ -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

186
doc/usage/cmd/upl.rst Normal file
View file

@ -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

View file

@ -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

46
doc/usage/upl.rst Normal file
View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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__ */

View file

@ -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);

View file

@ -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

View file

@ -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__ */

382
include/upl.h Normal file
View file

@ -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 */

View file

@ -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;

View file

@ -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

437
test/boot/upl.c Normal file
View file

@ -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);
}

View file

@ -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,

View file

@ -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;
}

38
test/py/tests/test_upl.py Normal file
View file

@ -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