universalisos/.hermes/plans/2025-07-10_230000-aarch64-vgicd.md

3.3 KiB

AArch64 GICv3 Distributor Virtualization (vGICD)

Goal: Trap guest accesses to the GIC distributor (GICD @ 0x08000000) and emulate a per-vCPU virtual distributor, so a guest (today the C payload; eventually Linux) programs its own interrupt controller without touching the physical GICD. This is the next Linux-boot step after PL011 trap-emulate.

Why now: Today the whole low 4 GiB is mapped device at stage-2, so the guest sees the real GICD. A Linux guest would reprogram the physical distributor and fight the HV. GICv3's hardware virtualizes the CPU interface (ICV_* via ICH_*), but NOT the distributor — that must be emulated in software (KVM vgic-v3, Xen vgic, Bao vgicd).

Scope (bounded, MVP):

  • Stage-2: leave a trap hole over the GICD 64 KiB window (same mechanism as the existing PL011 hole in s2_fill_l2).
  • New kernel/src/arch/aarch64/vgicd.{cpp,h}: per-vCPU GICD shadow state + read/write emulation for the registers a GICv3 driver touches at probe/bring-up:
    • ID/config (read-only): GICD_CTLR, GICD_TYPER, GICD_IIDR
    • per-IRQ: IGROUPRn, ISENABLERn/ICENABLERn, ISPENDRn/ICPENDRn, ISACTIVERn/ICACTIVERn, IPRIORITYRn, ICFGRn, IROUTERn
  • Wire ISENABLER writes into the existing aarch64_vcpu_irq_enable() bitmap (already consulted by el2_guest_irq_handler for physical→virtual routing).
  • Extend el2_guest.cpp EC_DATA_ABORT_LEL path: dispatch GICD-window faults to vgicd_emulate() (alongside the existing PL011 path).
  • Guest payload: add a probe sequence that reads GICD_TYPER/IIDR, writes GICD_CTLR, enables a PPI via the virtual ISENABLER0-equivalent on the distributor, and reads it back — printing the values so the verify script can confirm the shadow round-trips.

Out of scope (deferred): GICR virtualization (GICv3 uses ICC/ICV sysregs, hardware-virtualized), ITS/LPI, full SPI routing to other vCPUs, priority-based preemption between virtual IRQs, GICD_CTLR.ARE/DIS security semantics.

Files:

  • Modify: kernel/src/arch/aarch64/stage2.cpp — second leave_*_hole for the GICD window.
  • Create: kernel/src/arch/aarch64/vgicd.h — vGICD state struct + emulate() decl.
  • Create: kernel/src/arch/aarch64/vgicd.cpp — shadow + read/write emulation.
  • Modify: kernel/src/arch/aarch64/el2_guest.cpp — dispatch GICD faults to vgicd_emulate.
  • Modify: kernel/src/arch/aarch64/objects.mk — add vgicd.o.
  • Modify: kernel/src/arch/aarch64/guest_payload/guest_main.c — GICD probe sequence.
  • Modify: kernel/src/arch/aarch64/vcpu.h — add a vgicd state pointer/field to aarch64_vcpu (or keep a single global vgicd for the one-vCPU MVP).

Verification (ad-hoc):

  • make ARCH=aarch64 PLATFORM=qemu-aarch64-virt clean.
  • -smp 4 boot UART shows the guest reading back the exact TYPER/IIDR/CTLR values it expects, and the ISENABLER write round-tripping — proving the trap-emulate shadow works and the guest no longer touches the physical GICD.
  • ARMv7 build clean.

Risks: GICD_IROUTERn are 64-bit registers the guest may access with a single 64-bit store (SAS=3) or two 32-bit stores — handle both. GICD access decode (ISS_ISV) must be valid; if a guest uses an addressing mode without syndrome, we log + inject vSError (already plumbed). Linux also reads GICR_TYPER via sysreg-free MMIO on some paths — out of scope; if a future guest faults on GICR we'll add it then.