diff --git a/config/examples/versal_vmk180.config b/config/examples/versal_vmk180.config index e65d9e6500..a6c328ef4a 100644 --- a/config/examples/versal_vmk180.config +++ b/config/examples/versal_vmk180.config @@ -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- diff --git a/config/examples/versal_vmk180_sdcard.config b/config/examples/versal_vmk180_sdcard.config index 53fde6b84b..50db74a57d 100644 --- a/config/examples/versal_vmk180_sdcard.config +++ b/config/examples/versal_vmk180_sdcard.config @@ -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 diff --git a/docs/Targets.md b/docs/Targets.md index be98482de0..f0f8c2ab08 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -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: diff --git a/hal/versal.c b/hal/versal.c index af82dbc050..ce0c5ce472 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -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 */ diff --git a/hal/versal.its b/hal/versal.its index f2aa203495..69e958f8ff 100644 --- a/hal/versal.its +++ b/hal/versal.its @@ -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"; diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index c3d04c906e..f60e9805fd 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -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 */ diff --git a/src/fdt.c b/src/fdt.c index 5972e70da3..d09769be98 100644 --- a/src/fdt.c +++ b/src/fdt.c @@ -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,33 @@ 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; + } + } 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); diff --git a/tools/unit-tests/unit-fdt.c b/tools/unit-tests/unit-fdt.c index 8ffd6effdf..83044cf115 100644 --- a/tools/unit-tests/unit-fdt.c +++ b/tools/unit-tests/unit-fdt.c @@ -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); diff --git a/tools/unit-tests/unit-update-disk-fs.c b/tools/unit-tests/unit-update-disk-fs.c index 287b23eb13..98f06ac8f5 100644 --- a/tools/unit-tests/unit-update-disk-fs.c +++ b/tools/unit-tests/unit-update-disk-fs.c @@ -682,9 +682,10 @@ uint32_t wolfBoot_get_blob_version(uint8_t *blob) return version; } -int wolfBoot_get_dts_size(void *dts_addr) +int wolfBoot_get_dts_size(void *dts_addr, uint32_t capacity) { (void)dts_addr; + (void)capacity; return -1; }