A research Type-1 ARM64 hypervisor targeting QEMU virt (AArch64) first, then
Rockchip RK3588. Inspired by ACRN, Xvisor, and bao-hypervisor; the directory
layout mirrors ACRN's hypervisor/ structure.
M10 — Multi-VM foundation is done (gate-verified 2026-07-20). Two unmodified
Linux guests run side by side on QEMU virt, each with its own Stage-2 page
table, VMID, vGICv3 and vuart, statically partitioned 2+2 across four pCPUs.
EL2 owns the physical PL011 exclusively; both VMs get a trap-and-emulate vuart,
and Ctrl-T opens an EL2 shell to switch console focus between them.
Each VM is built from the same guest address map (RAM at IPA 0x40000000,
backed by different PAs), so one DTB template and one load-address scheme serve
both. There is still no scheduler: vCPUs are pinned 1:1 to pCPUs.
Next: M11 (vCPU scheduler) — full context switch including FP/SIMD state, time slicing, and vCPU count greater than pCPU count. The long-term target is the full ACRN model (Service VM + userspace Device Model); the RK3588 port is last, at M15.
See the multi-VM design spec and ADR-0014 for the current architecture.
Note on milestone numbers: the roadmap was renumbered to consecutive integers on 2026-08-10 (the old scheme had
M1.5,M2.5,M3.0–M3.5and an unusedM4). Historical specs, ADRs and debug notes keep their original numbering — e.g. this spec's filename saysm5for what is now M10. CLAUDE.md has the full old→new mapping table.
| Milestone | Status | Goal |
|---|---|---|
| M0 — Hello EL2 | done | Enter EL2, print banner |
| M1 — Bare-metal guest | done | Stage-2 MMU, minimal vCPU |
| M2 — PSCI | done | PSCI over HVC (no GIC) |
| M3 — vGIC software injection | done | HVC → vgic_inject_sw → guest EL1 IRQ handler |
| M4 — Physical timer + GIC + HW-forwarding | done | timer PPI → EL2 → vgic_inject_hw → guest |
| M5 — Linux alive | done | Load Image + DTB, arm64 boot protocol, PL011 earlycon |
| M6 — MMIO trap framework | done | Stage-2 data-abort decode + trap-and-emulate dispatch |
| M7 — vGICv3 emulation | done | GICD/GICR trap-and-emulate; timer-PPI injection |
| M8 — Boot to shell | done | initramfs → interactive busybox shell |
| M9 — SMP | done | 2-vCPU Linux, PSCI CPU_ON, per-pCPU vCPU, SGI virtualization |
| M10 — Multi-VM foundation | done | 2 VMs × 2 vCPUs, per-VM Stage-2/VMID/vGIC/vuart, EL2-owned console |
| M11 — vCPU scheduler | next | Context switch incl. FP/SIMD, time slicing, vCPU overcommit |
| M12 — Hypercall ABI + VM lifecycle | planned | HVC hypercall namespace, VM create/start/pause/destroy |
| M13 — HSM kernel driver + io_req ring | planned | Service VM kernel module, MMIO exits forwarded to userspace |
| M14 — Device Model + virtio backends | planned | Userspace dm, virtio-mmio console and blk |
| M15 — RK3588 port | planned | Runtime FDT parsing, real UART/GIC/storage |
make defconfig # copy configs/qemu_virt_defconfig → .config
make # build build/hypervisor.elf + build/hypervisor.bin
LINUX_IMAGE=/path/to/Image LINUX_INITRD=/path/to/initramfs.cpio.gz make runEarliest banner (within ~3 s):
[hv] Hello from EL2 on qemu_virt, CurrentEL=0x8
LINUX_INITRD is optional. With an initramfs configured, both VMs boot Linux to
a busybox shell prompt. Exit QEMU with Ctrl-A x.
Two constraints are easy to trip over:
-m 4Gis required (already set inscripts/run-qemu.sh). VM0's RAM is backed at PA0x80000000and VM1's at0xC0000000, so both 1 GB-aligned blocks only fall inside QEMUvirtDRAM with 4 GB present.- The kernel
Imagemust fit the address budget (~31 MB). A full arm64defconfigImage overruns the DTB; trim unused subsystems, keeping PL011 + virtio + devtmpfs + initramfs.
Both guests share one physical UART, so EL2 mediates console input. Press
Ctrl-T at any time to drop into the hypervisor shell:
hv> help
hv> vm_list # VMs, their pCPUs, state, and current console focus
hv> vm_console 1 # attach console input to VM 1
Ctrl-T again returns to the guest. Guest output is unaffected by focus — only
input is routed. Each VM's initramfs sets a distinct prompt (vm0:~ # /
vm1:~ #) so it is obvious which guest you are typing to.
GDB attach:
LINUX_IMAGE=/path/to/Image QEMU_EXTRA_ARGS="-s -S" make run
aarch64-none-linux-gnu-gdb build/hypervisor.elf -ex 'target remote :1234'aarch64-none-linux-gnu-gcc>= 10aarch64-none-linux-gnu-binutilsqemu-system-aarch64>= 6.0dtc(device-tree-compiler)
There is no CI; verification is local and automated.
- Automated suite:
make testchecks C/assembly struct offsets, then runs five QEMU integration scenarios — M1, M3 and M4 on a single-VM SVM build (build/test-svm/), plus the dual-VM (svm4) and EL2-shell scenarios on aNR_VMS=2build (build/test-svm-dual/). The shell scenario drives real keystrokes into QEMU's serial stdin (Ctrl-T,vm_list,help,vm_console, error paths, backspace editing) and uses bare-metal guests, so it finishes in seconds rather than waiting on a Linux boot. - Build check:
makemust succeed with zero warnings (-Werroris on). - Linux SMP boot: run with
LINUX_IMAGE(and optionallyLINUX_INITRD) and verifyCPU1: Booted secondary processor,smp: Brought up 1 node, 2 CPUs, and0-1in/sys/devices/system/cpu/online. - IPI/timer check: inspect
/proc/interrupts; both CPU columns should show timer interrupts and increasing IPI counts.
- Design specs:
docs/superpowers/specs/ - Architecture decisions:
docs/adr/ - Reference knowledge base:
docs/reference/— split by architecture intoarm/(the implementation baseline: Stage-2, vGIC, world switch, exit dispatch) andx86/(an x86-64 VT-x contrast document for mapping concepts when reading ACRN) - Debug walkthroughs:
docs/debug/ - Project guidance for contributors and agents:
CLAUDE.md
See LICENSE.