From d86bdb60b51f864d188edc5d27847db5d2dfe75d Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:17 -0700 Subject: spl: Add fields for VBE Add some fields to track the VBE state in SPL. Signed-off-by: Simon Glass --- include/spl.h | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/spl.h b/include/spl.h index 43b344dbc55..781e5a2d638 100644 --- a/include/spl.h +++ b/include/spl.h @@ -14,6 +14,7 @@ #include #include #include +#include #include struct blk_desc; @@ -265,6 +266,11 @@ enum spl_sandbox_flags { SPL_SANDBOXF_ARG_IS_BUF, }; +/** + * struct spl_image_info - Information about the SPL image being loaded + * + * @fdt_size: Size of the FDT for the image (0 if none) + */ struct spl_image_info { const char *name; u8 os; @@ -276,6 +282,7 @@ struct spl_image_info { u32 boot_device; u32 offset; u32 size; + ulong fdt_size; u32 flags; void *arg; #ifdef CONFIG_SPL_LEGACY_IMAGE_CRC_CHECK @@ -316,12 +323,18 @@ typedef ulong (*spl_load_reader)(struct spl_load_info *load, ulong sector, * @read: Function to call to read from the device * @priv: Private data for the device * @bl_len: Block length for reading in bytes + * @phase: Image phase to load + * @fit_loaded: true if the FIT has been loaded, except for external data */ struct spl_load_info { spl_load_reader read; void *priv; #if IS_ENABLED(CONFIG_SPL_LOAD_BLOCK) - int bl_len; + u16 bl_len; +#endif +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + u8 phase; + u8 fit_loaded; #endif }; @@ -344,6 +357,32 @@ static inline void spl_set_bl_len(struct spl_load_info *info, int bl_len) #endif } +static inline void xpl_set_phase(struct spl_load_info *info, + enum image_phase_t phase) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + info->phase = phase; +#endif +} + +static inline enum image_phase_t xpl_get_phase(struct spl_load_info *info) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + return info->phase; +#else + return IH_PHASE_NONE; +#endif +} + +static inline bool xpl_get_fit_loaded(struct spl_load_info *info) +{ +#if CONFIG_IS_ENABLED(BOOTMETH_VBE) + return info->fit_loaded; +#else + return false; +#endif +} + /** * spl_load_init() - Set up a new spl_load_info structure */ -- cgit v1.3.1 From bed7c4599d14c251021b82186ecf558689873486 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:18 -0700 Subject: spl: Add a type for the jumper function This function will be used by the relocating jumper too, so add a typedef to the header file to avoid mismatches. Signed-off-by: Simon Glass --- common/spl/spl.c | 3 +-- include/spl.h | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/common/spl/spl.c b/common/spl/spl.c index ad31a2f8b6c..09e6dc26f5e 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -671,8 +671,7 @@ void board_init_r(gd_t *dummy1, ulong dummy2) BOOT_DEVICE_NONE, BOOT_DEVICE_NONE, }; - typedef void __noreturn (*jump_to_image_t)(struct spl_image_info *); - jump_to_image_t jump_to_image = &jump_to_image_no_args; + spl_jump_to_image_t jump_to_image = &jump_to_image_no_args; struct spl_image_info spl_image; int ret, os; diff --git a/include/spl.h b/include/spl.h index 781e5a2d638..488adbeb1bc 100644 --- a/include/spl.h +++ b/include/spl.h @@ -292,6 +292,9 @@ struct spl_image_info { #endif }; +/* function to jump to an image from SPL */ +typedef void __noreturn (*spl_jump_to_image_t)(struct spl_image_info *); + static inline void *spl_image_fdt_addr(struct spl_image_info *info) { #if CONFIG_IS_ENABLED(LOAD_FIT) || CONFIG_IS_ENABLED(LOAD_FIT_FULL) -- cgit v1.3.1 From 20ad3fa0e1af3982ddfe824aae1b8221c1f1fa0c Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:19 -0700 Subject: spl: Add support for a relocating jump to the next phase When one xPL phase wants to jump to the next, the next phase must be loaded into its required address. This means that the TEXT_BASE for the two phases must be different and there cannot be any memory overlap between the code used by the two phases. It also can mean that phases need to be moved around to accommodate any size growth. Having two xPL phases in SRAM at the same time can be tricky if SRAM is limited, which it often is. It would be better if the second phase could be loaded somewhere else, then decompressed into place over the top of the first phase. Introduce a relocating jump for xPL to support this. This selects a suitable place to load the (typically compressed) next phase, copies some decompression code out of the first phase, then jumps to this code to decompress and start the next phase. This feature makes it much easier to support Verified Boot for Embedded (VBE) on RK3399 boards, which have 192KB of SRAM. Signed-off-by: Simon Glass --- common/spl/Kconfig | 8 +++ common/spl/Kconfig.tpl | 8 +++ common/spl/Kconfig.vpl | 8 +++ common/spl/Makefile | 1 + common/spl/spl_reloc.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++++ include/spl.h | 45 ++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 common/spl/spl_reloc.c (limited to 'include') diff --git a/common/spl/Kconfig b/common/spl/Kconfig index 4e56d9909c8..94e118f8465 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -983,6 +983,14 @@ config SPL_NAND_IDENT help SPL uses the chip ID list to identify the NAND flash. +config SPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config SPL_UBI bool "Support UBI" help diff --git a/common/spl/Kconfig.tpl b/common/spl/Kconfig.tpl index 92d4d43ec87..22ca7016453 100644 --- a/common/spl/Kconfig.tpl +++ b/common/spl/Kconfig.tpl @@ -268,6 +268,14 @@ config TPL_RAM_DEVICE be already in memory when TPL takes over, e.g. loaded by the boot ROM. +config TPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config TPL_RTC bool "Support RTC drivers" help diff --git a/common/spl/Kconfig.vpl b/common/spl/Kconfig.vpl index eb57dfabea5..97dfc630152 100644 --- a/common/spl/Kconfig.vpl +++ b/common/spl/Kconfig.vpl @@ -181,6 +181,14 @@ config VPL_PCI necessary driver support. This enables the drivers in drivers/pci as part of a VPL build. +config VPL_RELOC_LOADER + bool "Allow relocating the next phase" + help + In some cases multiple U-Boot phases need to run in SRAM, typically + at the same address. Enable this to support loading the next phase + to temporary memory, then copying it into place afterwards, then + jumping to it. + config VPL_RTC bool "Support RTC drivers" help diff --git a/common/spl/Makefile b/common/spl/Makefile index 75123eb666b..4c9482bd309 100644 --- a/common/spl/Makefile +++ b/common/spl/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_$(PHASE_)BOOTROM_SUPPORT) += spl_bootrom.o obj-$(CONFIG_$(PHASE_)LOAD_FIT) += spl_fit.o obj-$(CONFIG_$(PHASE_)BLK_FS) += spl_blk_fs.o obj-$(CONFIG_$(PHASE_)LEGACY_IMAGE_FORMAT) += spl_legacy.o +obj-$(CONFIG_$(PHASE_)RELOC_LOADER) += spl_reloc.o obj-$(CONFIG_$(PHASE_)NOR_SUPPORT) += spl_nor.o obj-$(CONFIG_$(PHASE_)XIP_SUPPORT) += spl_xip.o obj-$(CONFIG_$(PHASE_)YMODEM_SUPPORT) += spl_ymodem.o diff --git a/common/spl/spl_reloc.c b/common/spl/spl_reloc.c new file mode 100644 index 00000000000..be8349b535b --- /dev/null +++ b/common/spl/spl_reloc.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2024 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* provide a way to jump straight into the relocation code, for debugging */ +#define DEBUG_JUMP 0 + +enum { + /* margin to allow for stack growth */ + RELOC_STACK_MARGIN = 0x800, + + /* align base address for DMA controllers which require it */ + BASE_ALIGN = 0x200, + + STACK_PROT_VALUE = 0x51ce4697, +}; + +typedef int (*rcode_func)(struct spl_image_info *image); + +static int setup_layout(struct spl_image_info *image, ulong *addrp) +{ + ulong base, fdt_size; + ulong limit, rcode_base; + uint rcode_size; + int buf_size, margin; + char *rcode_buf; + + limit = ALIGN(map_to_sysmem(&limit) - RELOC_STACK_MARGIN, 8); + image->stack_prot = map_sysmem(limit, sizeof(uint)); + *image->stack_prot = STACK_PROT_VALUE; + + fdt_size = fdt_totalsize(gd->fdt_blob); + base = ALIGN(map_to_sysmem(gd->fdt_blob) + fdt_size + BASE_ALIGN - 1, + BASE_ALIGN); + + rcode_size = _rcode_end - _rcode_start; + rcode_base = limit - rcode_size; + buf_size = rcode_base - base; + uint need_size = image->size + image->fdt_size; + margin = buf_size - need_size; + log_debug("spl_reloc %s->%s: margin%s%lx limit %lx fdt_size %lx base %lx avail %x image %x fdt %lx need %x\n", + spl_phase_name(spl_phase()), spl_phase_name(spl_phase() + 1), + margin >= 0 ? " " : " -", abs(margin), limit, fdt_size, base, + buf_size, image->size, image->fdt_size, need_size); + if (margin < 0) { + log_err("Image size %x but buffer is only %x\n", need_size, + buf_size); + return -ENOSPC; + } + + rcode_buf = map_sysmem(rcode_base, rcode_size); + log_debug("_rcode_start %p: %x -- func %p %x\n", _rcode_start, + *(uint *)_rcode_start, setup_layout, *(uint *)setup_layout); + + image->reloc_offset = rcode_buf - _rcode_start; + log_debug("_rcode start %lx base %lx size %x offset %lx\n", + (ulong)map_to_sysmem(_rcode_start), rcode_base, rcode_size, + image->reloc_offset); + + memcpy(rcode_buf, _rcode_start, rcode_size); + + image->buf = map_sysmem(base, need_size); + image->fdt_buf = image->buf + image->size; + image->rcode_buf = rcode_buf; + *addrp = base; + + return 0; +} + +int spl_reloc_prepare(struct spl_image_info *image, ulong *addrp) +{ + int ret; + + ret = setup_layout(image, addrp); + if (ret) + return ret; + + return 0; +} + +typedef void __noreturn (*image_entry_noargs_t)(uint crc, uint unc_len); + +/* this is the relocation + jump code that is copied to the top of memory */ +__rcode int rcode_reloc_and_jump(struct spl_image_info *image) +{ + image_entry_noargs_t entry = (image_entry_noargs_t)image->entry_point; + u32 *dst; + ulong image_len; + size_t unc_len; + int ret, crc; + uint magic; + + dst = map_sysmem(image->load_addr, image->size); + unc_len = (void *)image->rcode_buf - (void *)dst; + image_len = image->size; + if (*image->stack_prot != STACK_PROT_VALUE) + return -EFAULT; + magic = get_unaligned_le32(image->buf); + if (CONFIG_IS_ENABLED(LZMA)) { + SizeT lzma_len = unc_len; + + ret = lzmaBuffToBuffDecompress((u8 *)dst, &lzma_len, + image->buf, image_len); + unc_len = lzma_len; + } else if (CONFIG_IS_ENABLED(GZIP)) { + ret = gunzip(dst, unc_len, image->buf, &image_len); + } else if (CONFIG_IS_ENABLED(LZ4) && magic == LZ4F_MAGIC) { + ret = ulz4fn(image->buf, image_len, dst, &unc_len); + if (ret) + return ret; + } else { + u32 *src, *end, *ptr; + + unc_len = image->size; + for (src = image->buf, end = (void *)src + image->size, + ptr = dst; src < end;) + *ptr++ = *src++; + } + if (*image->stack_prot != STACK_PROT_VALUE) + return -EFAULT; + + /* copy in the FDT if needed */ + if (image->fdt_size) + memcpy(image->fdt_start, image->fdt_buf, image->fdt_size); + + crc = crc8(0, (u8 *)dst, unc_len); + + /* jump to the entry point */ + entry(crc, unc_len); +} + +int spl_reloc_jump(struct spl_image_info *image, spl_jump_to_image_t jump) +{ + rcode_func loader; + int ret; + + log_debug("malloc usage %lx bytes (%ld KB of %d KB)\n", gd->malloc_ptr, + gd->malloc_ptr / 1024, CONFIG_VAL(SYS_MALLOC_F_LEN) / 1024); + + if (*image->stack_prot != STACK_PROT_VALUE) { + log_err("stack busted, cannot continue\n"); + return -EFAULT; + } + loader = (rcode_func)(void *)rcode_reloc_and_jump + image->reloc_offset; + log_debug("Jumping via %p to %lx - image %p size %x load %lx\n", loader, + image->entry_point, image, image->size, image->load_addr); + + log_debug("unc_len %lx\n", + image->rcode_buf - map_sysmem(image->load_addr, image->size)); + if (DEBUG_JUMP) { + rcode_reloc_and_jump(image); + } else { + /* + * Must disable LOG_DEBUG since the decompressor cannot call + * log functions, printf(), etc. + */ + _Static_assert(DEBUG_JUMP || !_DEBUG, + "Cannot have debug output from decompressor"); + ret = loader(image); + } + + return -EFAULT; +} diff --git a/include/spl.h b/include/spl.h index 488adbeb1bc..9cfba98db55 100644 --- a/include/spl.h +++ b/include/spl.h @@ -270,6 +270,16 @@ enum spl_sandbox_flags { * struct spl_image_info - Information about the SPL image being loaded * * @fdt_size: Size of the FDT for the image (0 if none) + * @buf: Buffer where the image should be loaded + * @fdt_buf: Buffer where the FDT will be copied by spl_reloc_jump(), only used + * if @fdt_size is non-zero + * @fdt_start: Pointer to the FDT to be copied (must be set up before calling + * spl_reloc_jump() + * @rcode_buf: Buffer to hold the relocating-jump code + * @stack_prot: Pointer to the stack-protection value, used to ensure the stack + * does not overflow + * @reloc_offset: offset between the relocating-jump code and its place in the + * currently running image */ struct spl_image_info { const char *name; @@ -290,6 +300,14 @@ struct spl_image_info { ulong dcrc_length; ulong dcrc; #endif +#if CONFIG_IS_ENABLED(RELOC_LOADER) + void *buf; + void *fdt_buf; + void *fdt_start; + void *rcode_buf; + uint *stack_prot; + ulong reloc_offset; +#endif }; /* function to jump to an image from SPL */ @@ -1155,4 +1173,31 @@ int spl_write_upl_handoff(struct spl_image_info *spl_image); */ void spl_upl_init(void); +/** + * spl_reloc_prepare() - Prepare the relocating loader ready for use + * + * Sets up the relocating loader ready for use. This must be called before + * spl_reloc_jump() can be used. + * + * The memory layout is figured out, making use of the space between the top of + * the current image and the top of memory. + * + * Once this is done, the relocating-jump code is copied into place at + * image->rcode_buf + * + * @image: SPL image containing information. This is updated with various + * necessary values. On entry, the size and fdt_size fields must be valid + * @addrp: Returns the address to which the image should be loaded into memory + * Return 0 if OK, -ENOSPC if there is not enough memory available + */ +int spl_reloc_prepare(struct spl_image_info *image, ulong *addrp); + +/** + * spl_reloc_jump() - Jump to an image, via a 'relocating-jump' region + * + * @image: SPL image to jump to + * @func: Function to call in the final image + */ +int spl_reloc_jump(struct spl_image_info *image, spl_jump_to_image_t func); + #endif -- cgit v1.3.1 From ca055155f4179e92caae47ff21ee858884b47b21 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Wed, 15 Jan 2025 18:27:20 -0700 Subject: spl: Plumb in the relocating loader This is fairly easy to use. The SPL loader sets up some fields in the spl_image_info struct and calls spl_reloc_prepare(). When SPL is ready to do the jump it must call spl_reloc_jump() instead of jump_to_image(). Add this logic. Signed-off-by: Simon Glass --- common/spl/spl.c | 12 ++++++++++++ include/spl.h | 1 + 2 files changed, 13 insertions(+) (limited to 'include') diff --git a/common/spl/spl.c b/common/spl/spl.c index 09e6dc26f5e..7cfbab06419 100644 --- a/common/spl/spl.c +++ b/common/spl/spl.c @@ -826,6 +826,18 @@ void board_init_r(gd_t *dummy1, ulong dummy2) } spl_board_prepare_for_boot(); + + if (CONFIG_IS_ENABLED(RELOC_LOADER)) { + int ret; + + ret = spl_reloc_jump(&spl_image, jump_to_image); + if (ret) { + if (xpl_phase() == PHASE_VPL) + printf("jump failed %d\n", ret); + hang(); + } + } + jump_to_image(&spl_image); } diff --git a/include/spl.h b/include/spl.h index 9cfba98db55..7155e9c67aa 100644 --- a/include/spl.h +++ b/include/spl.h @@ -414,6 +414,7 @@ static inline void spl_load_init(struct spl_load_info *load, load->read = h_read; load->priv = priv; spl_set_bl_len(load, bl_len); + xpl_set_phase(load, IH_PHASE_NONE); } /* -- cgit v1.3.1