universalisos/.hermes/plans/2025-07-10_233000-aarch64-boot-linux.md

4.3 KiB

AArch64: boot a real Linux Image as the EL1 guest

North star: Replace the hand-written guest_payload with a stock Linux arm64 Image booted under UniversalisOS as a type-1 guest at EL1, printing to earlycon and reaching the virtual timer/GIC. Staged as independently-verifiable milestones.

Linux arm64 boot contract (what the HV must provide)

  • Entry at EL1h, MMU off, D-cache off, I-cache on/off ok.
  • x0 = PA of a flattened DTB (IPA==PA here); x1=x2=x3 = 0.
  • Image loaded at guest-DRAM base + 0x80000 (text_offset from the Image header).
  • Stage-2 identity-maps the guest RAM so Linux's stage-1 page tables Just Work.
  • Devices described in the DTB must match what the HV emulates/passes through: PL011 @ 0x09000000 (trapped), GICv3 @ 0x08000000/0x080A0000 (vGICD + vCPU IF), ARMv8 generic timer (vTimer PPI 27 injected), PSCI 0.2 (SMC conduit present).

What's already in place

  • Stage-2 identity map + PL011 + GICD trap-emulate windows.
  • vGICD (CTLR/TYPER/IIDR/IGROUPR/ISENABLER/IPRIORITYR/ICFGR/IROUTER shadow).
  • Virtual CPU interface (ICH_, ICV_), vTimer PPI 27 inject + linked HW deact.
  • PSCI SMC conduit (CPU_ON already used to wake MP1).
  • vCPU entry at EL1h with x0=arg0; EL1 sysreg save/restore; SCTLR_EL1 MMU off.

Gaps to close

  1. Guest DTB: author + build a guest.dtb describing the virt subset Linux needs (memory, chosen/bootargs+stdout-path, intc=gic-v3, pl011, timer, psci). Place its IPA in x0 at vmentry.
  2. Image loading: load an external Linux Image + the DTB into guest RAM instead of the embedded guest_payload.bin. QEMU path: -device loader,file=Image,addr=<ipa> + -device loader,file=guest.dtb,addr=<ipa>; HV just sets ELR=Image IPA and x0=DTB IPA. (Keep the embedded-payload path as a build-time fallback so the demo still boots without an Image.)
  3. Stage-2 sizing for the Image: a real Image is ~30 MB; guest RAM window is 256 MB (0x50000000..0x60000000) — fine. Ensure stage-2 covers the whole guest RAM (it does: 0x40000000..0x60000000).
  4. PL011 RX (M2): milestone 1 is TX-only earlycon; RX needed once Linux opens a console/login.
  5. SPI routing in vGICD (M2): PL011 IRQ (INTID 33) and any virtio IRQ need IROUTER/ISENABLER for SPIs > 31 and injection through ICH_LR with HW link.
  6. Virtio block (M3): rootfs. Out of scope for "banner" but needed for login.

Milestones

  • M1 — Linux-protocol plumbing + DTB (this session). Add a guest DTB built by dtc, teach the HV to hand x0=DTB-IPA and to load an external Image via the QEMU loader device (with the embedded blob as fallback). Validate the HV side with a tiny freestanding "Image-shaped" blob placed at 0x50080000 that reads x0 (DTB PA), checks the FDT magic, and prints the model string through the PL011 trap. Prove: DTB handoff + Image entry + PL011 earlycon path.
  • M2 — Real Image earlycon. Obtain a Linux arm64 Image (download prebuilt or build defconfig+tiny), boot it, target: Booting Linux on physical CPU, earlycon banner, GICv3 probe succeeds, "Timer: ..." lines. Fix whatever vGICD/timer/PL011 gaps Linux actually exercises (SPI routing, RX, ICV writes).
  • M3 — virtio-mmio + rootfs + login. Larger; own plan.

Why M1 stops short of a real Image

A real arm64 Image is ~30 MB and is not in the repo; building one is a large, networked toolchain job. M1 delivers and verifies every hypervisor behavior a real Image relies on for early boot (DTB in x0, entry at text_offset, PL011 trap-emulate, GICv3 + vTimer) using a protocol-faithful stub, so M2 becomes "drop in the Image and debug" rather than "build the whole loader blind."

Resume steps for M2

  1. Put a Linux arm64 Image at kernel/guest/Image (or set LINUX_IMAGE=).
  2. make ARCH=aarch64 PLATFORM=qemu-aarch64-virt run-linux boots it.
  3. Capture UART; iterate on vGICD SPI routing / PL011 RX as Linux probes them.

Files (M1)

  • kernel/src/arch/aarch64/guest_payload/guest.dts (new; DTB source)
  • kernel/src/arch/aarch64/guest_payload/Makefile (build guest.dtb)
  • kernel/src/arch/aarch64/guest_payload/linux_stub.S (new; Image-shaped stub)
  • kernel/src/arch/aarch64/kernel_aarch64.cpp (x0=DTB IPA, entry=Image)
  • kernel/Makefile (run-linux target, loader devs)
  • kernel/src/arch/aarch64/inc/board_qemu_virt.h (QEMU_GUEST_DTB addr)