versal: report EL2 exceptions, fix FIT ramdisk fixup and the EL2->EL1 cache handoff

pull/864/merge
David Garske 2026-09-02 14:09:26 -07:00 committed by Daniele Lacamera
parent 24c23e767b
commit a2639e88f8
10 changed files with 163 additions and 8 deletions

View File

@ -62,6 +62,11 @@ ELF?=1
# Native gzip decompression for FIT subimages (set GZIP=0 to disable)
GZIP?=1
# FIT ramdisk (initramfs) extraction plus the /chosen/linux,initrd-{start,end}
# fixup. A stock PetaLinux image.ub carries a ramdisk sub-image that a U-Boot
# "bootm" loads; without this wolfBoot ignores it.
FIT_RAMDISK?=1
# Toolchain
USE_GCC=1
CROSS_COMPILE=aarch64-none-elf-

View File

@ -36,6 +36,11 @@ ELF?=1
# Native gzip decompression for FIT subimages (set GZIP=0 to disable)
GZIP?=1
# FIT ramdisk (initramfs) extraction plus the /chosen/linux,initrd-{start,end}
# fixup. A stock PetaLinux image.ub carries a ramdisk sub-image that a U-Boot
# "bootm" loads; without this wolfBoot ignores it.
FIT_RAMDISK?=1
# Boot Benchmarking (optional)
BOOT_BENCHMARK?=1

View File

@ -4968,6 +4968,12 @@ sf erase 0x800000 +${filesize}
sf write ${loadaddr} 0x800000 ${filesize}
```
##### Ramdisk (initramfs)
A stock PetaLinux `image.ub` carries a `ramdisk` sub-image that `bootm` passes to the kernel via `/chosen/linux,initrd-{start,end}`. wolfBoot does the same with `FIT_RAMDISK=1`, enabled by default in both Versal example configs. Add the node plus a `ramdisk = "ramdisk-1";` reference to your configuration node; `hal/versal.its` carries a commented example.
`WOLFBOOT_LOAD_RAMDISK_ADDRESS` defaults to 0, which uses the ramdisk in place inside the staged FIT. Set it to a DDR address clear of the kernel, DTB and staging area if the payload needs a fixed location.
**DTB Fixup for Root Filesystem**
wolfBoot automatically modifies the device tree to set the kernel command line (`bootargs`). The default configuration mounts the root filesystem from SD card partition 2:
@ -5030,6 +5036,39 @@ PetaLinux 2024.2 xilinx-vmk180 ttyAMA0
xilinx-vmk180 login:
```
**Example Linux Boot Output (`GZIP=1` + `FIT_RAMDISK=1`)**
```
Decompressing Image kernel-1 (gzip): 0x100000E4 -> 0x200000 (10623118 bytes)
Decompressed kernel-1: 24617472 bytes (7363 ms)
Loading DTS: 0x1000 -> 0x1000 (39384 bytes)
Loaded ramdisk: 0x10A2B540 (5647828 bytes)
FDT: Set chosen (28076), linux,initrd-start=279098688
FDT: Set chosen (28076), linux,initrd-end=284746516
Booting at 0x200000
do_boot: EL2->EL1 via ERET
[ 0.856319] Freeing initrd memory: 5512K
[ 3.315791] Run /init as init process
xilinx-vmk180-20242 login:
```
Inflating a 24 MB kernel takes about 7 s on the A72, spent entirely between the `Decompressing` and `Decompressed` lines with no intermediate output. Do not mistake that gap for a hang.
**Diagnosing a boot that stops with no message**
wolfBoot runs at EL2 on Versal, where any abort is fatal. With `DEBUG_UART=1` (the default in both Versal example configs) the handlers print the syndrome:
```
*** SYNCHRONOUS EXCEPTION ***
ESR_EL2: 0x0000000096000006
ELR_EL2: 0x0000000008005910
FAR_EL2: 0x00000000F9200000
*** SYSTEM HALTED ***
```
`ESR_EL2[31:26]` is the exception class, `[5:0]` the fault status, and `FAR_EL2` the faulting address. A stop with no such banner is not an exception - check the PLM's `PMC EAM` output on the same UART. Error IDs `0xA`/`0xB` are `DDRMB_CR`/`DDRMB_NCR`, DDR controller correctable and uncorrectable errors, which the PLM logs without acting on.
**Boot Performance**
Typical boot timing with ECC384/SHA384 signing:

View File

@ -1180,6 +1180,18 @@ void hal_init(void)
#endif
"========================================\n");
wolfBoot_printf("Current EL: %d\n", current_el());
/* BL31 enters wolfBoot with all of DAIF masked (SPSR 0x3c9), so an
* asynchronous external abort - e.g. a DDR uncorrectable ECC error
* returned to the A72 - stays pending and invisible while the boot
* dies downstream. Unmask SError now that the console is up so it is
* taken at EL2 and reported by SErrorInterrupt() instead. */
#if defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1
if (current_el() == 2) {
__asm__ volatile("msr daifclr, #4");
__asm__ volatile("isb");
}
#endif
#endif
#ifdef EXT_FLASH
@ -1306,14 +1318,19 @@ int hal_dts_fixup(void* dts_addr, uint32_t capacity)
off = fdt_add_subnode(&ctx, 0, "chosen");
}
if (off >= 0) {
/* Set bootargs property */
fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS);
} else {
if (off < 0) {
wolfBoot_printf("FDT: Failed to find/create chosen node (%d)\n", off);
return off;
}
/* Set bootargs property - overrides the PetaLinux default root= with
* the wolfBoot partition layout. */
ret = fdt_fixup_str(&ctx, off, "chosen", "bootargs", LINUX_BOOTARGS);
if (ret < 0) {
wolfBoot_printf("FDT: Failed to set bootargs (%d)\n", ret);
return ret;
}
return 0;
}
#endif /* __WOLFBOOT */

View File

@ -29,6 +29,23 @@
algo = "sha256";
};
};
/* Ramdisk (initramfs) sub-image; requires FIT_RAMDISK=1 (default in
* the Versal example configs). wolfBoot uses it in place and patches
* /chosen/linux,initrd-{start,end}. Add the "ramdisk = ..." reference
* to the configuration node below to enable it.
*
* ramdisk-1 {
* description = "petalinux-image-minimal";
* data = /incbin/("../rootfs.cpio.gz");
* type = "ramdisk";
* arch = "arm64";
* os = "linux";
* compression = "none";
* hash-1 {
* algo = "sha256";
* };
* };
*/
/* FPGA bitstream sub-image (requires FPGA_BITSTREAM=1). Add the
* "fpga = ..." reference to the configuration node below. The
* bitstream must be a bootgen .bin staged to its load address.
@ -60,6 +77,7 @@
description = "Linux kernel and FDT blob";
kernel = "kernel-1";
fdt = "fdt-1";
/* ramdisk = "ramdisk-1"; */
/* fpga = "fpga-1"; */
hash-1 {
algo = "sha256";

View File

@ -36,6 +36,10 @@ SECTIONS
PROVIDE (_MEMORY_SIZE = LENGTH(psu_ocm_ram_0_MEM_0));
.text : {
/* Exported for the gzip inflate bound in src/fdt.c (weak _start_text):
* marks the start of wolfBoot's own image so a runaway decompression
* fails closed instead of overwriting the bootloader. */
_start_text = .;
KEEP (*(.vectors))
*(.boot)
*(.text)

View File

@ -37,6 +37,10 @@ SECTIONS
PROVIDE (_MEMORY_SIZE = LENGTH(psu_ocm_ram_0_MEM_0));
.text : {
/* Exported for the gzip inflate bound in src/fdt.c (weak _start_text):
* marks the start of wolfBoot's own image so a runaway decompression
* fails closed instead of overwriting the bootloader. */
_start_text = .;
KEEP (*(.vectors))
*(.boot)
*(.text)

View File

@ -186,6 +186,13 @@ void RAMFUNCTION do_boot(const uint32_t *app_offset)
uintptr_t dts = 0;
#endif
wolfBoot_printf("do_boot: EL2->EL1 via ERET\n");
/* Clean the EL2 D-cache and drop the MMU before the ERET: Linux
* enters at EL1 with SCTLR_EL1.M/C clear and reads memory uncached,
* so the kernel and DTB must be clean to PoC (ARM64 booting.rst).
* hal_prepare_boot() cleans only a fixed window at
* WOLFBOOT_LOAD_ADDRESS, which on a FIT boot is the staging buffer,
* not the load destinations. */
el2_flush_and_disable_mmu();
el2_to_el1_boot((uintptr_t)app_offset, dts);
}
#else
@ -273,7 +280,7 @@ void RAMFUNCTION arch_reboot(void)
#endif
/* ============================================================================
* Exception Handlers for EL2 (optional DEBUG_HARDFAULT)
* Exception Handlers for EL2 / EL3
* ============================================================================
*/
@ -307,7 +314,11 @@ void FIQInterrupt(void)
void SErrorInterrupt(void)
{ print_exception_info_el3("SERROR"); while (1) { __asm__ volatile("wfi"); } }
#elif defined(DEBUG_HARDFAULT) && defined(DEBUG_UART) && defined(EL2_HYPERVISOR)
#elif defined(DEBUG_UART) && defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1
/* EL2 counterpart of the EL3 block above. Gated on DEBUG_UART alone: without
* it an abort taken at EL2 (ZynqMP, Versal) lands in the silent wfi stub and
* the boot just stops with no output. */
#define READ_SYSREG(_out, _reg) __asm__ volatile("mrs %0, " #_reg : "=r"(_out))
@ -374,4 +385,4 @@ void SynchronousInterrupt(void) { while (1) { __asm__ volatile("wfi"); } }
void IRQInterrupt(void) { while (1) { __asm__ volatile("wfi"); } }
void FIQInterrupt(void) { while (1) { __asm__ volatile("wfi"); } }
void SErrorInterrupt(void) { while (1) { __asm__ volatile("wfi"); } }
#endif /* DEBUG_HARDFAULT && DEBUG_UART && EL2_HYPERVISOR */
#endif /* exception handler variants */

View File

@ -62,6 +62,12 @@
#define WOLFBOOT_FIT_MAX_DECOMP (256U * 1024U * 1024U)
#endif
/* Start of wolfBoot's own image (linker script _start_text). Weak so hosted
* builds (sim, unit tests) without the symbol resolve it to NULL and skip the
* bound derived from it. */
extern char _start_text[] __attribute__((weak));
#define wolfboot_start_text ((void*)_start_text)
/* ------------------------------------------------------------------ */
/* Byte order */
/* ------------------------------------------------------------------ */
@ -1299,6 +1305,10 @@ int fdt_fixup_initrd(fdt_ctx* ctx, uint64_t start, uint64_t size)
if (ret < 0) {
return ret;
}
if (start + size < start) {
/* Wrapped initrd end: linux,initrd-end would precede -start. */
return -FDT_ERR_BADARG;
}
ret = fdt_fixup_val64(ctx, off, "chosen", "linux,initrd-end",
start + size);
if (ret < 0) {
@ -1609,12 +1619,40 @@ static void* fit_load_image_inner(fdt_ctx* ctx, const char* image, int* lenp,
if (is_gzip) {
#ifdef WOLFBOOT_GZIP
uint32_t out_len = 0;
uint32_t gz_max = out_max;
uintptr_t gap;
int rc;
/* out_max is only a sanity ceiling. Cap the output below
* anything above the destination that must survive the
* inflate: the staged compressed input, and wolfBoot's own
* image (a corrupted stream can emit garbage at full match
* speed, and without this cap the window reaches straight
* through the bootloader before the decoder trips on an
* invalid code). Conservative lower bounds. */
if ((uintptr_t)data > (uintptr_t)load) {
gap = (uintptr_t)data - (uintptr_t)load;
if (gap < (uintptr_t)gz_max) {
gz_max = (uint32_t)gap;
}
}
if ((uintptr_t)wolfboot_start_text > (uintptr_t)load) {
gap = (uintptr_t)wolfboot_start_text - (uintptr_t)load;
if (gap < (uintptr_t)gz_max) {
gz_max = (uint32_t)gap;
}
}
else if (wolfboot_start_text == NULL) {
/* Linker script does not export _start_text: the
* bootloader-image bound cannot be applied. Say so
* rather than skipping it silently. */
wolfBoot_printf("gzip: no _start_text, output not "
"bounded by the wolfBoot image\n");
}
wolfBoot_printf("Decompressing Image %s (gzip): "
"%p -> %p (%d bytes)\n", image, data, load, len);
BENCHMARK_START();
rc = wolfBoot_gunzip((const uint8_t*)data,
(uint32_t)len, (uint8_t*)load, out_max, &out_len);
(uint32_t)len, (uint8_t*)load, gz_max, &out_len);
if (rc != 0) {
wolfBoot_printf("FIT gunzip failed for %s: rc=%d "
"(wrote %u bytes)\n", image, rc, out_len);

View File

@ -801,6 +801,19 @@ START_TEST(test_fdt_fixup_initrd)
}
END_TEST
/* A start+size that wraps must be rejected: linux,initrd-end would
* otherwise precede linux,initrd-start. */
START_TEST(test_fdt_fixup_initrd_rejects_wrapped_end)
{
static uint8_t buf[0x800];
fdt_ctx ctx;
(void)build_compat_fdt(buf, sizeof(buf), (const uint8_t *)"abc\0", 4);
ck_assert_int_eq(fdt_open(&ctx, buf, (uint32_t)sizeof(buf)), 0);
ck_assert_int_lt(fdt_fixup_initrd(&ctx, ~0ULL - 1U, 0x1000ULL), 0);
}
END_TEST
/* ------------------------------------------------------------------ */
/* fdt_peek_size */
/* ------------------------------------------------------------------ */
@ -935,6 +948,7 @@ static Suite *fdt_suite(void)
tcase_add_test(tc, test_fdt_add_subnode_bounded_by_capacity);
tcase_add_test(tc, test_fdt_setprop_resizes_existing_property);
tcase_add_test(tc, test_fdt_fixup_initrd);
tcase_add_test(tc, test_fdt_fixup_initrd_rejects_wrapped_end);
tcase_add_test(tc, test_fdt_peek_size_header_only);
tcase_add_test(tc, test_fit_find_images_rejects_unterminated_image_name);
suite_add_tcase(s, tc);