7 KiB
AArch64 EL2 paravirtual hypercall ABI (uos_hv_abi) — guest-OS surface
Status: EXTENDED (2026-07-11). Guest-verifiable end-to-end via linux_stub.
Naming: uos_/UOS_ throughout; no vendor prefix token anywhere.
Why this exists
The PikeOS guest-OS layer is, conceptually, a syscall/hypercall ABI that guests
(paravirt Linux today; an ARINC-653 APEX shim later) call into the kernel. On
the ARMv7 track that is uos_pv_abi.h (SVC 0xA0-0xAF) because Cortex-A9/A15
have no EL2. On the AArch64 EL2 track the analog is hvc #0x5500 routed
through el2_guest.cpp EC_HVC_AA64. Before this slice it had six calls
(PUTC/PUTS/GETTIME/YIELD/SHUTDOWN/ACK_VTIMER) — enough to print and halt, but
not enough for a real guest to (a) probe capabilities, (b) control its virtual
IRQs, or (c) read console input. This slice closes that gap with a small,
append-only, capability-discoverable surface.
Contract (stable — append only, never renumber)
hvc #0x5500, nr in x0, args x1..x4, return in x0. ELR_EL2 already points past
the hvc; the HV mutates x0 and erets (no ELR advance).
| nr | name | in | out (x0) |
|---|---|---|---|
| 0x00 | PUTC | x1=char | 0 |
| 0x01 | PUTS | x1=IPA(NUL str) | bytes written |
| 0x02 | GETTIME | - | ticks (CNTPCT) |
| 0x03 | YIELD | - | 0 (no-op today) |
| 0x04 | SHUTDOWN | - | no return |
| 0x05 | ACK_VTIMER | - | 0 |
| 0x06 | GETC | - | char, or -EAGAIN |
| 0x07 | IRQ_ACK | x1=vintid | 0 (clear LR + deactivate) |
| 0x08 | IRQ_ENABLE | x1=vintid | 0 |
| 0x09 | IRQ_DISABLE | x1=vintid | 0 |
| 0x0a | INFO | x1=IPA(info 32B) | 0 / -EFAULT |
| 0x0b | GET_VCPU_ID | - | vcpu id (0 today) |
| 0x0c | GET_PART_ID | - | partition id (0 today) |
| 0x0d | GET_TICK_HZ | - | CNTFRQ (62500000 on virt) |
Error codes (negative errno-style, stable): OK=0, EINVAL=-1, ENOSYS=-2, EPERM=-3, EBUSY=-4, EAGAIN=-5, ENOMEM=-6, EFAULT=-7, ENXIO=-8, ETIMEDOUT=-9.
INFO (capability discovery — the key addition)
Writes a 32-byte uos_hv_info_t to the guest IPA in x1:
abi_version=0x00010000 (1.0), capabilities bitmask, vcpu_id, partition_id,
tick_hz (CNTFRQ low 32), nr_vcpus, reserved[2].
Capability bits: CONSOLE(0), VTIMER(1), VIRQ(2), VIRTIO_BLK(3), STAGE2(4).
A guest calls INFO once at boot and gates its driver init on the cap bits —
exactly how a real OS probes a hypervisor instead of hardcoding assumptions.
IPA window guard: [QEMU_GUEST_BASE, +256MiB); out-of-window -> -EFAULT.
IRQ control (0x07-0x09)
Routes to the vGIC/GICv3 the HV already owns: ACK = vgic_clear_irq(vintid) + gicv3_irq_deactivate(vintid) (generalises the old ACK_VTIMER; vintid==pintid on the identity map) ENABLE = gicv3_irq_enable(intid) DISABLE = gicv3_irq_disable(intid) This is what the virtio-blk INTID48 injection (phase 6) needs from the guest side: the guest enables 48, the device injects 48, the guest acks 48.
Verification (ad-hoc, decisive-signal; NOT suite green)
make ARCH=aarch64 PLATFORM=qemu-aarch64-virt run-linux (clean rebuild; the
guest stub calls uosabi_probe() before the vblk probe):
[uosabi] INFO rc=0 ver=0x10000 caps=0x1f vcpu=0 part=0 tick_hz=0x3b9aca0 nr_vcpus=1
[uosabi] ids vcpu=0 part=0 tick_hz=0x3b9aca0 (matches INFO)
[uosabi] GETC rc=-5 (EAGAIN, expected)
[uosabi] IRQ en=0 dis=0 ack=0
[uosabi] unknown(0x7f) rc=-2 (ENOSYS, expected)
[uosabi] OK
[vblk] sector-0 READ OK (signature matches) (prior slice intact)
[linux-stub] Linux boot contract verified; shutting down.
No MISMATCH / FAILED / Stage-2 data abort / EL2 TRAP / virtual SError.
tick_hz=0x3b9aca0 = 62,500,000 = QEMU virt 62.5 MHz generic timer (sanity).
caps=0x1f = bits 0..4 all set (CONSOLE|VTIMER|VIRQ|VIRTIO_BLK|STAGE2).
Cross-arch: armv7, riscv, aarch64-classic all build clean.
Bugs fixed this slice
- EL2 data-abort (EC 0x25, FSC 0x21 = alignment fault) at FAR 0x50081904 on
INFO write: the guest's
static uos_hv_info_t infowas only 4-aligned; the HV's paired stores faulted under SCTLR_EL2.A=1. Fixed two ways: the type is now__attribute__((aligned(8)))AND the HV builds on stack then__builtin_memcpyto the guest IPA (alignment-safe regardless of guest alignment). Lesson: any HV write into guest memory must be memcpy, not a typed struct store — the guest controls the alignment.
PikeOS parity mapping (descriptive; no vendor token in code)
guest identity -> GET_VCPU_ID / GET_PART_ID / INFO virtual IRQ ctrl -> IRQ_ACK / IRQ_ENABLE / IRQ_DISABLE time -> GETTIME / GET_TICK_HZ console -> PUTC / PUTS / GETC control -> SHUTDOWN / YIELD / ACK_VTIMER
Open items
- U-abi-1: GET_VCPU_ID/GET_PART_ID return 0 today (single guest vCPU). When the EL2 track runs >1 vCPU, source these from the per-PE current-vcpu pointer (the dispatcher already runs per-PE; wire a current-vcpu accessor).
- U-abi-2: YIELD is a no-op. With the A/B scheduler present it could donate the slice to the next runnable vCPU — scheduler work, out of scope here.
- U-abi-3: IRQ_ENABLE/DISABLE operate on the physical INTID (vintid==pintid). A per-guest virtual mask (so a guest can't unmask an IRQ the HV didn't assign) is the PikeOS-faithful hardening; needs a vintid->owner map.
- U-abi-4: no bulk read/write (PUTS is NUL-only). A real guest wants CON_WRITE(buf,len) — mirror ARMv7 PV_SVC_CON_WRITE (0xA2).
Naming-boundary note (important)
A repo-wide p4/P4 scrub was done for pure comments (armv7/riscv/core source
audit references) — p4_ identifier token count is now 0. NOT migrated: the
VMIT/XSD health-monitor token contract — P4_HM_* XML values in
core/config/boot-simple.xml + the string parse-keys in core/config/cfg_enum.cpp
- the
xsd/p4/namespace URI +P4_TRAP_*/P4_hm_*/p4vmit_enum_gen.hlabels. These are the PikeOS config contract consumed by the mycelium codegen lane, not our identifiers; renaming them breaks config<->parser<->XSD agreement and is a separate mycelium<->universalisos XSD-namespacing migration (new uos: namespace under xsd/, per the workspace rule). Do not blind-edit.
Files
- kernel/src/arch/aarch64/inc/uos_hv_abi.h — calls 0x06-0x0d, error codes, uos_hv_info_t (aligned(8)), inlines
- kernel/src/arch/aarch64/el2_guest.cpp — EC_HVC_AA64 dispatcher cases
- kernel/src/arch/aarch64/guest_payload/linux_stub.c — uosabi_probe() + call
- (comment scrub) armv7/uos_mmu_walk.{cpp,h}, uos_adspace.cpp, uos_cmm.cpp; riscv/{kernel,memory,task_sched}.cpp + inc/{vm,task_sched,hm,memory}.h; core/{uos_fpu.{cpp,h},uos_smp.h,mm.cpp,ipc/uos_shmem.{h,cpp}, abi/uos_pv_abi.h,hm/uos_hm.cpp,platform/drivers/pci.h}