AArch64 EL2 hypervisor for QEMU virt that boots at EL2
  • C 87.9%
  • Makefile 4.2%
  • Assembly 3.5%
  • Python 3.4%
  • Shell 0.5%
  • Other 0.5%
Find a file
2026-05-25 01:32:36 +00:00
arch/aarch64 Use EL2 memory ordering obligations for trap handlers 2026-05-22 09:07:19 +00:00
core Use EL2 exception telemetry to expose guest trap behaviour through the UART monitor 2026-05-25 01:32:36 +00:00
drivers Instrument timer mediation paths for virtual counter drift 2026-05-24 18:47:31 +00:00
guest Initial commit 2026-03-20 14:37:18 +00:00
include/hv Add canonical IPA interval validation for monitor memory access 2026-05-24 22:46:31 +00:00
mm Normalise guest abort recovery through stage two fault intents 2026-05-24 22:05:56 +00:00
scripts Verify release artefact topology against expected build products 2026-05-20 10:18:25 +00:00
tests Add canonical IPA interval validation for monitor memory access 2026-05-24 22:46:31 +00:00
tools Initial commit 2026-03-20 14:37:18 +00:00
.gitignore Initial commit 2026-03-20 14:37:18 +00:00
linker.ld Initial commit 2026-03-20 14:37:18 +00:00
Makefile Validate release topology before archive materialisation 2026-04-27 11:34:44 +00:00
README.md Initial commit 2026-03-20 14:37:18 +00:00

ariel

AArch64 EL2 hypervisor for QEMU virt that boots at EL2, sets up exception vectors, writes a stage 2 guest IPA space and loads a bundled EL1 diagnostic guest/an external linux image and traps privileged guest activity as well as applying compiled policy tables and exposes a UART monitor over QEMU stdio

Build/Run

make clean
make
make run

Equivalent QEMU shape

qemu-system-aarch64 \
  -M virt,virtualization=on,gic-version=3 \
  -cpu cortex-a57 \
  -m 1G \
  -nographic \
  -serial mon:stdio \
  -kernel build/hypervisor.elf

Run a Linux arm64 Image with a QEMU virt DTB

make run-linux \
  LINUX_IMAGE=/path/Image \
  LINUX_DTB=/path/qemu-virt.dtb

Or just do it with an initrd

make run-linux \
  LINUX_IMAGE=/path/Image \
  LINUX_DTB=/path/qemu-virt.dtb \
  LINUX_INITRD=/path/initrd.gz

Fetch the tested Debian installer artifacts and smoke test the Linux path

make test-linux-smoke

and complete the local gate

make test
make release

Memory

hypervisor link PA       0x40080000
guest IPA base           0x40000000
guest backing PA         0x42000000
guest RAM size           0x30000000  768 MiB
diagnostic entry IPA     0x40000000
diagnostic stack top     0x40100000
Linux Image load PA      0x42000000
Linux Image entry IPA    Image text_offset + 0x40000000
Linux initrd PA          0x46000000
Linux initrd IPA         0x44000000
Linux DTB PA             0x71e00000
Linux DTB IPA            0x6fe00000
PL011 UART PA            0x09000000
GICD PA                  0x08000000
GICR PA                  0x080a0000

S2 uses 2 MiB block mappings for guest RAM. The guest sees the IPA range 0x40000000..0x70000000 which EL2 maps onto the PA range 0x42000000..0x72000000. MMIO isn't passed through by default andkKnown device ranges are handled through policy or emulation layers

Trap Path

EL1 traps enter the EL2 vector table and the assembly path saves general registers and exception state into struct trap_frame

x0-x30
sp_el0
sp_el1
elr_el2
spsr_el2
esr_el2
far_el2
hpfar_el2

Hypercalls

enum {
    HVC_GET_HV_ID = 0,
    HVC_DUMP_LOG = 1,
    HVC_GET_STATUS = 2,
    HVC_PAUSE = 3,
    HVC_GUEST_CONSOLE_WRITE = 4,
    HVC_REPORT_EXCEPTION = 5,
};

Guest side call pattern

static inline unsigned long hvc(unsigned long op,
                                unsigned long a1,
                                unsigned long a2,
                                unsigned long a3)
{
    register unsigned long x0 asm("x0") = op;
    register unsigned long x1 asm("x1") = a1;
    register unsigned long x2 asm("x2") = a2;
    register unsigned long x3 asm("x3") = a3;

    asm volatile("hvc #0"
                 : "+r"(x0)
                 : "r"(x1), "r"(x2), "r"(x3)
                 : "memory");
    return x0;
}

Console write from guest IPA memory

const char msg[] = "guest online";
hvc(HVC_GUEST_CONSOLE_WRITE, (unsigned long)msg, sizeof(msg) - 1, 0);

Pause into the EL2 monitor:

hvc(HVC_PAUSE, 0, 0, 0);

Logs

make run 2>&1 | tee uart.log
python3 tools/log_decode.py --summary < uart.log