Skip to content

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

Open
dgarske wants to merge 1 commit into
wolfSSL:masterfrom
dgarske:versal_fit_gzip
Open

versal: report EL2 exceptions, fix FIT ramdisk fixup and the EL2->EL1 cache handoff#879
dgarske wants to merge 1 commit into
wolfSSL:masterfrom
dgarske:versal_fit_gzip

Conversation

@dgarske

@dgarske dgarske commented Aug 28, 2026

Copy link
Copy Markdown
Member

Found on a VMK180 (VM1802) booting a signed FIT with a gzip-compressed PetaLinux kernel. Four problems, all on the shared AArch64 FIT boot path.

1. A fatal exception at EL2 halted silently

wolfBoot runs at EL2 on Versal (EL2_HYPERVISOR=1 in hal/versal.h). The EL2 exception handlers in src/boot_aarch64.c were compiled only when the build defined DEBUG_HARDFAULT, which has no options.mk or Makefile plumbing and is not referenced by any AArch64 example config. Every real Versal and ZynqMP build therefore fell through to the bare while (1) wfi stub, so a data abort or SError produced no output at all and the boot simply stopped after whatever line it had last printed. That reads as a hang in the preceding operation rather than as a CPU fault. The EL3 path already prints its syndrome; the EL2 block is now gated on DEBUG_UART to match, and reports ESR_EL2, ELR_EL2 and FAR_EL2.

2. FIT_RAMDISK could never install the initrd properties

fdt_fixup_initrd() runs while the FIT is being unpacked, before do_boot() calls hal_dts_fixup() and that grows the blob by WOLFBOOT_FDT_FIXUP_HEADROOM. A DTB emitted by mkimage carries no slack, so both fdt_setprop() calls returned -FDT_ERR_NOSPACE and the kernel booted with no initramfs. The failure was logged and the boot continued, so the only visible symptom was a payload that behaves differently under wolfBoot than under a U-Boot bootm of the same file. fdt_fixup_initrd() now takes its own headroom, mirroring the HAL fixups.

3. No cache maintenance on the EL2 -> EL1 handoff

The BOOT_EL1 branch of do_boot() called el2_to_el1_boot() directly; the existing el2_flush_and_disable_mmu() helper was only reachable from the other branch. The sole clean was in hal_prepare_boot(), over a fixed one-megabyte window at WOLFBOOT_LOAD_ADDRESS -- which on a FIT boot is the staging buffer, not the kernel or DTB destinations, so neither was cleaned. Linux enters at EL1 with SCTLR_EL1.M and .C clear and reads memory uncached, and the ARM64 boot protocol requires the image to be clean to the Point of Coherency. This also affects ZynqMP.

4. The gzip output bound did not account for its own input

fit_load_image_inner() passed WOLFBOOT_FIT_MAX_DECOMP (256 MB) to wolfBoot_gunzip() as the output ceiling. With the Versal kernel destination at 0x200000 that window extends to 0x10200000 and so covers the compressed input staged at WOLFBOOT_LOAD_ADDRESS, letting a large enough payload overwrite the bytes it is still reading. The bound is now derived from the gap between destination and input.

Also checks the fdt_fixup_str() return value in hal/versal.c, matching hal/zynq.c, and enables FIT_RAMDISK=1 in both Versal example configs now that the ramdisk path works.

Hardware / test status

Hardware-validated on a VMK180 through the production chain (PLM -> PSM -> BL31 at EL3 -> wolfBoot at EL2 -> Linux at EL1). A signed FIT holding a gzip-compressed PetaLinux 2024.2 kernel plus an initramfs decompresses to 0x200000, installs both initrd properties, and boots Linux 6.6.40 to a login prompt. Problem 1 was verified separately by taking a deliberate translation fault and confirming the syndrome is printed. Build-tested against versal_vmk180, versal_vmk180_sdcard, zynqmp, zynqmp_sdcard and zynqmp_fsbl.

ZD 20634

@dgarske dgarske self-assigned this Aug 28, 2026
Copilot AI lite review requested due to automatic review settings August 28, 2026 21:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes multiple reliability and correctness issues in the shared AArch64 FIT boot flow (Versal/ZynqMP), including initrd DT fixups, gzip decompression safety, EL2 exception visibility, and EL2→EL1 handoff cache/MMU maintenance.

Changes:

  • Make fdt_fixup_initrd() self-grow the DTB and add unit tests for headroom + oversize rejection.
  • Prevent gzip decompression from overwriting its staged input by bounding output by the destination→input gap.
  • Ensure EL2→EL1 boot path performs cache clean + MMU disable, and enable/diagnose initrd usage in configs/docs.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/unit-tests/unit-fdt.c Adds tests validating initrd DT fixup growth and oversize handling.
src/fdt.c Grows DTB for initrd properties; tightens gzip output bound to avoid self-overwrite.
src/boot_aarch64.c Adds EL2→EL1 cache/MMU handoff and enables EL2 exception reporting under DEBUG_UART.
hal/versal.its Documents how to include a ramdisk subimage and enable it in the FIT config node.
hal/versal.c Checks/propagates fdt_fixup_str() failures when setting bootargs.
docs/Targets.md Documents FIT ramdisk usage and adds troubleshooting/output examples.
config/examples/versal_vmk180_sdcard.config Enables FIT_RAMDISK=1 by default for Versal SD card example.
config/examples/versal_vmk180.config Enables FIT_RAMDISK=1 by default for Versal example.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/fdt.c
return -1;
}

end = start + size;
Comment thread src/fdt.c
Comment on lines +1105 to +1110
total = fdt_totalsize(fdt);
if (total > WOLFBOOT_DTS_MAX_SIZE - (2U * WOLFBOOT_FDT_FIXUP_HEADROOM)) {
return -1;
}
memset((uint8_t*)fdt + total, 0, WOLFBOOT_FDT_FIXUP_HEADROOM);
fdt_set_totalsize(fdt, total + WOLFBOOT_FDT_FIXUP_HEADROOM);
* and the kernel silently boots with no initramfs. */
START_TEST(test_fdt_fixup_initrd_grows_and_sets_props)
{
static uint8_t buf[0x1000];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants