mirror of
https://github.com/u-boot/u-boot.git
synced 2025-05-08 19:11:53 +00:00
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:
commit
f4f845b859
43 changed files with 3052 additions and 72 deletions
13
MAINTAINERS
13
MAINTAINERS
|
@ -1709,6 +1709,19 @@ M: Neha Malcom Francis <n-francis@ti.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/ufs/
|
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
|
USB
|
||||||
M: Marek Vasut <marex@denx.de>
|
M: Marek Vasut <marex@denx.de>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -1473,8 +1473,10 @@ u-boot.bin.lzma: u-boot.bin FORCE
|
||||||
u-boot-lzma.img: u-boot.bin.lzma FORCE
|
u-boot-lzma.img: u-boot.bin.lzma FORCE
|
||||||
$(call if_changed,mkimage)
|
$(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: \
|
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) \
|
$(if $(CONFIG_OF_SEPARATE)$(CONFIG_OF_EMBED)$(CONFIG_SANDBOX),dts/dt.dtb) \
|
||||||
,$(UBOOT_BIN)) FORCE
|
,$(UBOOT_BIN)) FORCE
|
||||||
$(call if_changed,mkimage)
|
$(call if_changed,mkimage)
|
||||||
|
|
|
@ -340,6 +340,8 @@ void *board_fdt_blob_setup(int *ret)
|
||||||
int err;
|
int err;
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
|
if (gd->fdt_blob)
|
||||||
|
return (void *)gd->fdt_blob;
|
||||||
blob = map_sysmem(CONFIG_SYS_FDT_LOAD_ADDR, 0);
|
blob = map_sysmem(CONFIG_SYS_FDT_LOAD_ADDR, 0);
|
||||||
*ret = 0;
|
*ret = 0;
|
||||||
if (!state->fdt_fname) {
|
if (!state->fdt_fname) {
|
||||||
|
|
|
@ -47,12 +47,24 @@ struct os_mem_hdr {
|
||||||
|
|
||||||
ssize_t os_read(int fd, void *buf, size_t count)
|
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)
|
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, ...)
|
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 os_lseek(int fd, off_t offset, int whence)
|
||||||
{
|
{
|
||||||
|
off_t ret;
|
||||||
|
|
||||||
if (whence == OS_SEEK_SET)
|
if (whence == OS_SEEK_SET)
|
||||||
whence = SEEK_SET;
|
whence = SEEK_SET;
|
||||||
else if (whence == OS_SEEK_CUR)
|
else if (whence == OS_SEEK_CUR)
|
||||||
|
@ -77,7 +91,11 @@ off_t os_lseek(int fd, off_t offset, int whence)
|
||||||
whence = SEEK_END;
|
whence = SEEK_END;
|
||||||
else
|
else
|
||||||
os_exit(1);
|
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)
|
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
|
* @count: Number of arguments in @add_args
|
||||||
* Return: 0 if OK, -ENOMEM if out of memory
|
* 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;
|
char **argv, **ap;
|
||||||
int argc;
|
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();
|
struct sandbox_state *state = state_get_current();
|
||||||
char mem_fname[30];
|
char mem_fname[30];
|
||||||
int fd, err;
|
int fd, err;
|
||||||
char *extra_args[5];
|
const char *extra_args[5];
|
||||||
char **argv = state->argv;
|
char **argv = state->argv;
|
||||||
int argc;
|
int argc;
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
|
@ -964,7 +982,7 @@ int os_find_u_boot(char *fname, int maxlen, bool use_img,
|
||||||
p = strstr(fname, subdir);
|
p = strstr(fname, subdir);
|
||||||
if (p) {
|
if (p) {
|
||||||
if (*next_prefix)
|
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));
|
memcpy(p + 1, next_prefix, strlen(next_prefix));
|
||||||
else
|
else
|
||||||
/* e.g. ".../spl/u-boot" to ".../u-boot" */
|
/* e.g. ".../spl/u-boot" to ".../u-boot" */
|
||||||
|
|
|
@ -3,13 +3,18 @@
|
||||||
* Copyright (c) 2016 Google, Inc
|
* Copyright (c) 2016 Google, Inc
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define LOG_CATEGORY LOGC_BOOT
|
||||||
|
|
||||||
#include <dm.h>
|
#include <dm.h>
|
||||||
#include <hang.h>
|
#include <hang.h>
|
||||||
#include <handoff.h>
|
#include <handoff.h>
|
||||||
|
#include <image.h>
|
||||||
#include <init.h>
|
#include <init.h>
|
||||||
#include <log.h>
|
#include <log.h>
|
||||||
|
#include <mapmem.h>
|
||||||
#include <os.h>
|
#include <os.h>
|
||||||
#include <spl.h>
|
#include <spl.h>
|
||||||
|
#include <upl.h>
|
||||||
#include <asm/global_data.h>
|
#include <asm/global_data.h>
|
||||||
#include <asm/spl.h>
|
#include <asm/spl.h>
|
||||||
#include <asm/state.h>
|
#include <asm/state.h>
|
||||||
|
@ -51,7 +56,8 @@ void board_init_f(ulong flag)
|
||||||
void board_boot_order(u32 *spl_boot_list)
|
void board_boot_order(u32 *spl_boot_list)
|
||||||
{
|
{
|
||||||
spl_boot_list[0] = BOOT_DEVICE_VBE;
|
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,
|
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;
|
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);
|
||||||
|
|
|
@ -431,6 +431,14 @@ static int sandbox_cmdline_cb_autoboot_keyed(struct sandbox_state *state,
|
||||||
}
|
}
|
||||||
SANDBOX_CMDLINE_OPT(autoboot_keyed, 0, "Allow keyed autoboot");
|
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)
|
static void setup_ram_buf(struct sandbox_state *state)
|
||||||
{
|
{
|
||||||
/* Zero the RAM buffer if we didn't read it, to keep valgrind happy */
|
/* 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();
|
text_base = os_find_text_base();
|
||||||
|
|
||||||
|
memset(&data, '\0', sizeof(data));
|
||||||
|
gd = &data;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This must be the first invocation of os_malloc() to have
|
* This must be the first invocation of os_malloc() to have
|
||||||
* state->ram_buf in the low 4 GiB.
|
* state->ram_buf in the low 4 GiB.
|
||||||
|
@ -501,8 +512,6 @@ int sandbox_main(int argc, char *argv[])
|
||||||
os_exit(1);
|
os_exit(1);
|
||||||
memcpy(os_argv, argv, size);
|
memcpy(os_argv, argv, size);
|
||||||
|
|
||||||
memset(&data, '\0', sizeof(data));
|
|
||||||
gd = &data;
|
|
||||||
gd->arch.text_base = text_base;
|
gd->arch.text_base = text_base;
|
||||||
|
|
||||||
state = state_get_current();
|
state = state_get_current();
|
||||||
|
@ -539,6 +548,9 @@ int sandbox_main(int argc, char *argv[])
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state->upl)
|
||||||
|
gd->flags |= GD_FLG_UPL;
|
||||||
|
|
||||||
#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
|
#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
|
||||||
gd->malloc_base = CFG_MALLOC_F_ADDR;
|
gd->malloc_base = CFG_MALLOC_F_ADDR;
|
||||||
#endif
|
#endif
|
||||||
|
@ -557,7 +569,7 @@ int sandbox_main(int argc, char *argv[])
|
||||||
log_debug("debug: %s\n", __func__);
|
log_debug("debug: %s\n", __func__);
|
||||||
|
|
||||||
/* Do pre- and post-relocation init */
|
/* Do pre- and post-relocation init */
|
||||||
board_init_f(0);
|
board_init_f(gd->flags);
|
||||||
|
|
||||||
board_init_r(gd->new_gd, 0);
|
board_init_r(gd->new_gd, 0);
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#ifndef __asm_spl_h
|
#ifndef __asm_spl_h
|
||||||
#define __asm_spl_h
|
#define __asm_spl_h
|
||||||
|
|
||||||
|
struct spl_image_info;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
BOOT_DEVICE_MMC1,
|
BOOT_DEVICE_MMC1,
|
||||||
BOOT_DEVICE_MMC2,
|
BOOT_DEVICE_MMC2,
|
||||||
|
@ -16,6 +18,7 @@ enum {
|
||||||
BOOT_DEVICE_NOR,
|
BOOT_DEVICE_NOR,
|
||||||
BOOT_DEVICE_SPI,
|
BOOT_DEVICE_SPI,
|
||||||
BOOT_DEVICE_NAND,
|
BOOT_DEVICE_NAND,
|
||||||
|
BOOT_DEVICE_UPL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,4 +34,16 @@ enum {
|
||||||
*/
|
*/
|
||||||
int sandbox_find_next_phase(char *fname, int maxlen, bool use_img);
|
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
|
#endif
|
||||||
|
|
|
@ -97,6 +97,7 @@ struct sandbox_state {
|
||||||
bool autoboot_keyed; /* Use keyed-autoboot feature */
|
bool autoboot_keyed; /* Use keyed-autoboot feature */
|
||||||
bool disable_eth; /* Disable Ethernet devices */
|
bool disable_eth; /* Disable Ethernet devices */
|
||||||
bool disable_sf_bootdevs; /* Don't bind SPI flash bootdevs */
|
bool disable_sf_bootdevs; /* Don't bind SPI flash bootdevs */
|
||||||
|
bool upl; /* Enable Universal Payload (UPL) */
|
||||||
|
|
||||||
/* Pointer to information for each SPI bus/cs */
|
/* Pointer to information for each SPI bus/cs */
|
||||||
struct sandbox_spi_info spi[CONFIG_SANDBOX_SPI_MAX_BUS]
|
struct sandbox_spi_info spi[CONFIG_SANDBOX_SPI_MAX_BUS]
|
||||||
|
|
70
boot/Kconfig
70
boot/Kconfig
|
@ -745,6 +745,76 @@ config BOOTMETH_SCRIPT
|
||||||
This provides a way to try out standard boot on an existing boot flow.
|
This provides a way to try out standard boot on an existing boot flow.
|
||||||
It is not enabled by default to save space.
|
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
|
endif # BOOTSTD
|
||||||
|
|
||||||
config LEGACY_IMAGE_FORMAT
|
config LEGACY_IMAGE_FORMAT
|
||||||
|
|
|
@ -43,6 +43,10 @@ endif
|
||||||
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += fdt_support.o
|
obj-$(CONFIG_$(SPL_TPL_)OF_LIBFDT) += fdt_support.o
|
||||||
obj-$(CONFIG_$(SPL_TPL_)FDT_SIMPLEFB) += fdt_simplefb.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_)OF_LIBFDT) += image-fdt.o
|
||||||
obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
|
obj-$(CONFIG_$(SPL_TPL_)FIT_SIGNATURE) += fdt_region.o
|
||||||
obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o
|
obj-$(CONFIG_$(SPL_TPL_)FIT) += image-fit.o
|
||||||
|
|
|
@ -36,6 +36,7 @@ DECLARE_GLOBAL_DATA_PTR;
|
||||||
#include <bootm.h>
|
#include <bootm.h>
|
||||||
#include <image.h>
|
#include <image.h>
|
||||||
#include <bootstage.h>
|
#include <bootstage.h>
|
||||||
|
#include <upl.h>
|
||||||
#include <u-boot/crc.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);
|
bootstage_mark(bootstage_id + BOOTSTAGE_SUB_LOAD);
|
||||||
|
|
||||||
|
upl_add_image(fit, noffset, load, len);
|
||||||
|
|
||||||
*datap = load;
|
*datap = load;
|
||||||
*lenp = len;
|
*lenp = len;
|
||||||
if (fit_unamep)
|
if (fit_unamep)
|
||||||
|
|
60
boot/upl_common.c
Normal file
60
boot/upl_common.c
Normal 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
24
boot/upl_common.h
Normal 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
588
boot/upl_read.c
Normal 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
622
boot/upl_write.c
Normal 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;
|
||||||
|
}
|
|
@ -388,6 +388,13 @@ config CMD_SEAMA
|
||||||
help
|
help
|
||||||
Support reading NAND Seattle Image (SEAMA) images.
|
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
|
config CMD_VBE
|
||||||
bool "vbe - Verified Boot for Embedded"
|
bool "vbe - Verified Boot for Embedded"
|
||||||
depends on BOOTMETH_VBE
|
depends on BOOTMETH_VBE
|
||||||
|
|
|
@ -189,6 +189,7 @@ obj-$(CONFIG_CMD_UBIFS) += ubifs.o
|
||||||
obj-$(CONFIG_CMD_UNIVERSE) += universe.o
|
obj-$(CONFIG_CMD_UNIVERSE) += universe.o
|
||||||
obj-$(CONFIG_CMD_UNLZ4) += unlz4.o
|
obj-$(CONFIG_CMD_UNLZ4) += unlz4.o
|
||||||
obj-$(CONFIG_CMD_UNZIP) += unzip.o
|
obj-$(CONFIG_CMD_UNZIP) += unzip.o
|
||||||
|
obj-$(CONFIG_CMD_UPL) += upl.o
|
||||||
obj-$(CONFIG_CMD_VIRTIO) += virtio.o
|
obj-$(CONFIG_CMD_VIRTIO) += virtio.o
|
||||||
obj-$(CONFIG_CMD_WDT) += wdt.o
|
obj-$(CONFIG_CMD_WDT) += wdt.o
|
||||||
obj-$(CONFIG_CMD_LZMADEC) += lzmadec.o
|
obj-$(CONFIG_CMD_LZMADEC) += lzmadec.o
|
||||||
|
|
118
cmd/upl.c
Normal file
118
cmd/upl.c
Normal 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));
|
|
@ -40,6 +40,7 @@
|
||||||
#include <sysreset.h>
|
#include <sysreset.h>
|
||||||
#include <timer.h>
|
#include <timer.h>
|
||||||
#include <trace.h>
|
#include <trace.h>
|
||||||
|
#include <upl.h>
|
||||||
#include <video.h>
|
#include <video.h>
|
||||||
#include <watchdog.h>
|
#include <watchdog.h>
|
||||||
#include <asm/cache.h>
|
#include <asm/cache.h>
|
||||||
|
@ -859,6 +860,26 @@ __weak int clear_bss(void)
|
||||||
return 0;
|
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[] = {
|
static const init_fnc_t init_sequence_f[] = {
|
||||||
setup_mon_len,
|
setup_mon_len,
|
||||||
#ifdef CONFIG_OF_CONTROL
|
#ifdef CONFIG_OF_CONTROL
|
||||||
|
@ -868,6 +889,7 @@ static const init_fnc_t init_sequence_f[] = {
|
||||||
trace_early_init,
|
trace_early_init,
|
||||||
#endif
|
#endif
|
||||||
initf_malloc,
|
initf_malloc,
|
||||||
|
initf_upl,
|
||||||
log_init,
|
log_init,
|
||||||
initf_bootstage, /* uses its own timer, so does not need DM */
|
initf_bootstage, /* uses its own timer, so does not need DM */
|
||||||
event_init,
|
event_init,
|
||||||
|
|
|
@ -521,6 +521,8 @@ static int dm_announce(void)
|
||||||
uclass_count);
|
uclass_count);
|
||||||
if (CONFIG_IS_ENABLED(OF_REAL))
|
if (CONFIG_IS_ENABLED(OF_REAL))
|
||||||
printf(", devicetree: %s", fdtdec_get_srcname());
|
printf(", devicetree: %s", fdtdec_get_srcname());
|
||||||
|
if (CONFIG_IS_ENABLED(UPL))
|
||||||
|
printf(", universal payload active");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE) &&
|
if (IS_ENABLED(CONFIG_OF_HAS_PRIOR_STAGE) &&
|
||||||
(gd->fdt_src == FDTSRC_SEPARATE ||
|
(gd->fdt_src == FDTSRC_SEPARATE ||
|
||||||
|
|
|
@ -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_)RAM_SUPPORT) += spl_ram.o
|
||||||
obj-$(CONFIG_$(SPL_TPL_)USB_SDP_SUPPORT) += spl_sdp.o
|
obj-$(CONFIG_$(SPL_TPL_)USB_SDP_SUPPORT) += spl_sdp.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
obj-$(CONFIG_$(SPL_TPL_)UPL) += spl_upl.o
|
||||||
|
|
|
@ -810,6 +810,14 @@ void board_init_r(gd_t *dummy1, ulong dummy2)
|
||||||
printf(SPL_TPL_PROMPT
|
printf(SPL_TPL_PROMPT
|
||||||
"SPL hand-off write failed (err=%d)\n", ret);
|
"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)) {
|
if (CONFIG_IS_ENABLED(BLOBLIST)) {
|
||||||
ret = bloblist_finish();
|
ret = bloblist_finish();
|
||||||
if (ret)
|
if (ret)
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <memalign.h>
|
#include <memalign.h>
|
||||||
#include <mapmem.h>
|
#include <mapmem.h>
|
||||||
#include <spl.h>
|
#include <spl.h>
|
||||||
|
#include <upl.h>
|
||||||
#include <sysinfo.h>
|
#include <sysinfo.h>
|
||||||
#include <asm/global_data.h>
|
#include <asm/global_data.h>
|
||||||
#include <asm/io.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;
|
image_info->entry_point = FDT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upl_add_image(fit, node, load_addr, length);
|
||||||
|
|
||||||
return 0;
|
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->entry_point = spl_image->load_addr;
|
||||||
|
|
||||||
spl_image->flags |= SPL_FIT_FOUND;
|
spl_image->flags |= SPL_FIT_FOUND;
|
||||||
|
upl_set_fit_info(map_to_sysmem(ctx.fit), ctx.conf_node,
|
||||||
|
spl_image->entry_point);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -941,6 +946,10 @@ int spl_load_fit_image(struct spl_image_info *spl_image,
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
spl_image->flags |= SPL_FIT_FOUND;
|
||||||
|
|
||||||
|
upl_set_fit_info(map_to_sysmem(header), conf_noffset,
|
||||||
|
spl_image->entry_point);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
172
common/spl/spl_upl.c
Normal file
172
common/spl/spl_upl.c
Normal 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);
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ CONFIG_FIT_RSASSA_PSS=y
|
||||||
CONFIG_FIT_CIPHER=y
|
CONFIG_FIT_CIPHER=y
|
||||||
CONFIG_FIT_VERBOSE=y
|
CONFIG_FIT_VERBOSE=y
|
||||||
CONFIG_BOOTMETH_ANDROID=y
|
CONFIG_BOOTMETH_ANDROID=y
|
||||||
|
CONFIG_UPL=y
|
||||||
CONFIG_LEGACY_IMAGE_FORMAT=y
|
CONFIG_LEGACY_IMAGE_FORMAT=y
|
||||||
CONFIG_MEASURED_BOOT=y
|
CONFIG_MEASURED_BOOT=y
|
||||||
CONFIG_BOOTSTAGE=y
|
CONFIG_BOOTSTAGE=y
|
||||||
|
|
|
@ -27,6 +27,9 @@ CONFIG_FIT=y
|
||||||
CONFIG_FIT_VERBOSE=y
|
CONFIG_FIT_VERBOSE=y
|
||||||
CONFIG_FIT_BEST_MATCH=y
|
CONFIG_FIT_BEST_MATCH=y
|
||||||
CONFIG_SPL_LOAD_FIT=y
|
CONFIG_SPL_LOAD_FIT=y
|
||||||
|
CONFIG_UPL=y
|
||||||
|
CONFIG_UPL_IN=y
|
||||||
|
CONFIG_SPL_UPL_OUT=y
|
||||||
CONFIG_BOOTSTAGE=y
|
CONFIG_BOOTSTAGE=y
|
||||||
CONFIG_BOOTSTAGE_REPORT=y
|
CONFIG_BOOTSTAGE_REPORT=y
|
||||||
CONFIG_BOOTSTAGE_FDT=y
|
CONFIG_BOOTSTAGE_FDT=y
|
||||||
|
@ -35,6 +38,7 @@ CONFIG_BOOTSTAGE_STASH_SIZE=0x4096
|
||||||
CONFIG_CONSOLE_RECORD=y
|
CONFIG_CONSOLE_RECORD=y
|
||||||
CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000
|
CONFIG_CONSOLE_RECORD_OUT_SIZE=0x1000
|
||||||
CONFIG_DISPLAY_BOARDINFO_LATE=y
|
CONFIG_DISPLAY_BOARDINFO_LATE=y
|
||||||
|
CONFIG_BLOBLIST_SIZE=0x5000
|
||||||
CONFIG_SPL_NO_BSS_LIMIT=y
|
CONFIG_SPL_NO_BSS_LIMIT=y
|
||||||
CONFIG_HANDOFF=y
|
CONFIG_HANDOFF=y
|
||||||
CONFIG_SPL_BOARD_INIT=y
|
CONFIG_SPL_BOARD_INIT=y
|
||||||
|
|
186
doc/usage/cmd/upl.rst
Normal file
186
doc/usage/cmd/upl.rst
Normal 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
|
|
@ -15,6 +15,7 @@ Use U-Boot
|
||||||
cmdline
|
cmdline
|
||||||
semihosting
|
semihosting
|
||||||
measured_boot
|
measured_boot
|
||||||
|
upl
|
||||||
|
|
||||||
Shell commands
|
Shell commands
|
||||||
--------------
|
--------------
|
||||||
|
@ -114,6 +115,7 @@ Shell commands
|
||||||
cmd/tftpput
|
cmd/tftpput
|
||||||
cmd/trace
|
cmd/trace
|
||||||
cmd/true
|
cmd/true
|
||||||
|
cmd/upl
|
||||||
cmd/ums
|
cmd/ums
|
||||||
cmd/unbind
|
cmd/unbind
|
||||||
cmd/ut
|
cmd/ut
|
||||||
|
|
46
doc/usage/upl.rst
Normal file
46
doc/usage/upl.rst
Normal 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
|
|
@ -25,7 +25,7 @@ static unsigned long host_block_read(struct udevice *dev,
|
||||||
struct udevice *host_dev = dev_get_parent(dev);
|
struct udevice *host_dev = dev_get_parent(dev);
|
||||||
struct host_sb_plat *plat = dev_get_plat(host_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);
|
printf("ERROR: Invalid block %lx\n", start);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ static unsigned long host_block_write(struct udevice *dev,
|
||||||
struct udevice *host_dev = dev_get_parent(dev);
|
struct udevice *host_dev = dev_get_parent(dev);
|
||||||
struct host_sb_plat *plat = dev_get_plat(host_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);
|
printf("ERROR: Invalid block %lx\n", start);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,7 +196,7 @@ static int handle_ufi_command(struct sandbox_flash_priv *priv, const void *buff,
|
||||||
priv->fd != -1) {
|
priv->fd != -1) {
|
||||||
offset = os_lseek(priv->fd, info->seek_block * info->block_size,
|
offset = os_lseek(priv->fd, info->seek_block * info->block_size,
|
||||||
OS_SEEK_SET);
|
OS_SEEK_SET);
|
||||||
if (offset == (off_t)-1)
|
if (offset < 0)
|
||||||
setup_fail_response(priv);
|
setup_fail_response(priv);
|
||||||
else
|
else
|
||||||
setup_response(priv);
|
setup_response(priv);
|
||||||
|
|
|
@ -28,7 +28,7 @@ int sandbox_fs_read_at(const char *filename, loff_t pos, void *buffer,
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return fd;
|
return fd;
|
||||||
ret = os_lseek(fd, pos, OS_SEEK_SET);
|
ret = os_lseek(fd, pos, OS_SEEK_SET);
|
||||||
if (ret == -1) {
|
if (ret < 0) {
|
||||||
os_close(fd);
|
os_close(fd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -65,14 +65,14 @@ int sandbox_fs_write_at(const char *filename, loff_t pos, void *buffer,
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return fd;
|
return fd;
|
||||||
ret = os_lseek(fd, pos, OS_SEEK_SET);
|
ret = os_lseek(fd, pos, OS_SEEK_SET);
|
||||||
if (ret == -1) {
|
if (ret < 0) {
|
||||||
os_close(fd);
|
os_close(fd);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
size = os_write(fd, buffer, towrite);
|
size = os_write(fd, buffer, towrite);
|
||||||
os_close(fd);
|
os_close(fd);
|
||||||
|
|
||||||
if (size == -1) {
|
if (size < 0) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
} else {
|
} else {
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
struct acpi_ctx;
|
struct acpi_ctx;
|
||||||
struct driver_rt;
|
struct driver_rt;
|
||||||
|
struct upl;
|
||||||
|
|
||||||
typedef struct global_data gd_t;
|
typedef struct global_data gd_t;
|
||||||
|
|
||||||
|
@ -491,6 +492,12 @@ struct global_data {
|
||||||
* @dmtag_list: List of DM tags
|
* @dmtag_list: List of DM tags
|
||||||
*/
|
*/
|
||||||
struct list_head dmtag_list;
|
struct list_head dmtag_list;
|
||||||
|
#if CONFIG_IS_ENABLED(UPL)
|
||||||
|
/**
|
||||||
|
* @upl: Universal Payload-handoff information
|
||||||
|
*/
|
||||||
|
struct upl *upl;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#ifndef DO_DEPS_ONLY
|
#ifndef DO_DEPS_ONLY
|
||||||
static_assert(sizeof(struct global_data) == GD_SIZE);
|
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
|
#define gd_malloc_ptr() 0L
|
||||||
#endif
|
#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
|
* 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: Use hush 2021 parser.
|
||||||
*/
|
*/
|
||||||
GD_FLG_HUSH_MODERN_PARSER = 0x2000000,
|
GD_FLG_HUSH_MODERN_PARSER = 0x2000000,
|
||||||
|
/**
|
||||||
|
* @GD_FLG_UPL: Read/write a Universal Payload (UPL) handoff
|
||||||
|
*/
|
||||||
|
GD_FLG_UPL = 0x4000000,
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __ASSEMBLY__ */
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
|
@ -29,7 +29,7 @@ int os_printf(const char *format, ...);
|
||||||
* @fd: File descriptor as returned by os_open()
|
* @fd: File descriptor as returned by os_open()
|
||||||
* @buf: Buffer to place data
|
* @buf: Buffer to place data
|
||||||
* @count: Number of bytes to read
|
* @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);
|
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()
|
* @fd: File descriptor as returned by os_open()
|
||||||
* @buf: Buffer containing data to write
|
* @buf: Buffer containing data to write
|
||||||
* @count: Number of bytes 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);
|
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()
|
* @fd: File descriptor as returned by os_open()
|
||||||
* @offset: File offset (based on whence)
|
* @offset: File offset (based on whence)
|
||||||
* @whence: Position offset is relative to (see below)
|
* @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);
|
off_t os_lseek(int fd, off_t offset, int whence);
|
||||||
|
|
||||||
|
|
|
@ -1073,4 +1073,20 @@ static inline bool spl_decompression_enabled(void)
|
||||||
{
|
{
|
||||||
return IS_ENABLED(CONFIG_SPL_GZIP) || IS_ENABLED(CONFIG_SPL_LZMA);
|
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
|
#endif
|
||||||
|
|
|
@ -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_time(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
|
||||||
int do_ut_unicode(struct cmd_tbl *cmdtp, int flag, int argc,
|
int do_ut_unicode(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||||
char *const argv[]);
|
char *const argv[]);
|
||||||
|
int do_ut_upl(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
|
||||||
|
|
||||||
#endif /* __TEST_SUITES_H__ */
|
#endif /* __TEST_SUITES_H__ */
|
||||||
|
|
382
include/upl.h
Normal file
382
include/upl.h
Normal 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 */
|
|
@ -1685,6 +1685,7 @@ int fdtdec_setup(void)
|
||||||
gd->fdt_src = FDTSRC_BLOBLIST;
|
gd->fdt_src = FDTSRC_BLOBLIST;
|
||||||
log_debug("Devicetree is in bloblist at %p\n",
|
log_debug("Devicetree is in bloblist at %p\n",
|
||||||
gd->fdt_blob);
|
gd->fdt_blob);
|
||||||
|
ret = 0;
|
||||||
} else {
|
} else {
|
||||||
log_debug("No FDT found in bloblist\n");
|
log_debug("No FDT found in bloblist\n");
|
||||||
ret = -ENOENT;
|
ret = -ENOENT;
|
||||||
|
|
|
@ -13,3 +13,5 @@ ifdef CONFIG_OF_LIVE
|
||||||
obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o
|
obj-$(CONFIG_BOOTMETH_VBE_SIMPLE) += vbe_simple.o
|
||||||
endif
|
endif
|
||||||
obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o
|
obj-$(CONFIG_BOOTMETH_VBE) += vbe_fixup.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_UPL) += upl.o
|
||||||
|
|
437
test/boot/upl.c
Normal file
437
test/boot/upl.c
Normal 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);
|
||||||
|
}
|
|
@ -133,6 +133,9 @@ static struct cmd_tbl cmd_ut_sub[] = {
|
||||||
#ifdef CONFIG_CMD_SEAMA
|
#ifdef CONFIG_CMD_SEAMA
|
||||||
U_BOOT_CMD_MKENT(seama, CONFIG_SYS_MAXARGS, 1, do_ut_seama, "", ""),
|
U_BOOT_CMD_MKENT(seama, CONFIG_SYS_MAXARGS, 1, do_ut_seama, "", ""),
|
||||||
#endif
|
#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,
|
static int do_ut_all(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||||
|
|
|
@ -10,63 +10,12 @@
|
||||||
#include <test/spl.h>
|
#include <test/spl.h>
|
||||||
#include <test/ut.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)
|
static int spl_test_load(struct unit_test_state *uts)
|
||||||
{
|
{
|
||||||
struct spl_image_info image;
|
struct spl_image_info image;
|
||||||
struct legacy_img_hdr *header;
|
|
||||||
struct text_ctx text_ctx;
|
|
||||||
struct spl_load_info load;
|
|
||||||
char fname[256];
|
char fname[256];
|
||||||
int ret;
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
memset(&load, '\0', sizeof(load));
|
ut_assertok(sandbox_spl_load_fit(fname, sizeof(fname), &image));
|
||||||
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));
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
38
test/py/tests/test_upl.py
Normal file
38
test/py/tests/test_upl.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue