AArch64 EL2 hypervisor for QEMU virt that boots at EL2
1

Configure Feed

Select the types of activity you want to include in your feed.

23 1 0

Clone this repository

https://vm.fail/vm.fail/ariel https://vm.fail/did:plc:hryycbe6si3daxcdvs6uqn7l
ssh://git@knot.vm.fail:2222/vm.fail/ariel ssh://git@knot.vm.fail:2222/did:plc:hryycbe6si3daxcdvs6uqn7l

For self-hosted knots, clone URLs may differ based on your setup.


README.md

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