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
make clean
makemake runEquivalent QEMU shape
qemu-system-aarch64 \
-M virt,virtualization=on,gic-version=3 \
-cpu cortex-a57 \
-m 1G \
-nographic \
-serial mon:stdio \
-kernel build/hypervisor.elfRun a Linux arm64 Image with a QEMU virt DTB
make run-linux \
LINUX_IMAGE=/path/Image \
LINUX_DTB=/path/qemu-virt.dtbOr just do it with an initrd
make run-linux \
LINUX_IMAGE=/path/Image \
LINUX_DTB=/path/qemu-virt.dtb \
LINUX_INITRD=/path/initrd.gzFetch the tested Debian installer artifacts and smoke test the Linux path
make test-linux-smokeand complete the local gate
make test
make releasehypervisor 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
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
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);make run 2>&1 | tee uart.log
python3 tools/log_decode.py --summary < uart.log