xv6 §4, "Traps and system calls". "There are three kinds of event which cause the CPU to set aside ordinary execution of instructions and force a transfer of control to special kernel code that handles the event." Those three kinds are a system call, an exception and a device interrupt.
Phase 4 makes Glint a hypervisor. It enters a guest, takes control back when the guest traps (a VM exit: the CPU leaving the guest and returning to Glint with a reason), and serves the guest's calls. It starts from Phase 3's guest address space (3.5) and loaded image (3.6). Work is divided between phases as follows:
- Phase 4: the path from any trap or VM exit to a handler, and the guest's calls to Glint;
- Phase 5: the handlers for guest memory faults and device accesses;
- Phase 6: interrupt delivery.
Every boot already leaves boot services and runs native by default (2.10),
so nothing in this phase is typed at a prompt to get there. Hypervisor mode
joins Phase 2's mode choice (2.6) in 4.3a, as one more request: a boot asks
for it with the word hypervisor in the ESP file glint-mode.txt, which
glint-run --mode hypervisor writes (2.10), and gets it when 2.6's checks
for native mode pass and 2.2 found the virtualization level (EL2, or SVM).
Otherwise the boot stays native, or falls back to hosted, and its Mode:
line says why. Slices whose code is shared across architectures land as one
check-in; the run loop, which is different machinery on Arm and on AMD x86,
is split into an aarch64 check-in followed by an x86_64 one.
Provisional slices. Each is refined, with its quoted sentence, when the phase starts.
Order while x86-1 can't build aarch64. Until BQ2's toolchain with AArch64 is x86-1's system, slices that land both architectures together land their x86_64 half first, and the aarch64 half follows as its own check-in when the toolchain arrives. 4.3b may precede 4.3a. The maintainer asked for the book first (ocx-console 363063f81e), and this is the route that keeps it moving; the plan's aarch64-first rule resumes once both build.
Phase 3, as refined, constrains these slices, and their refinement must
honour it. On aarch64, 3.3 runs Glint on SP_EL0 and takes exceptions on
SP_ELx, so 4.1's vectors and 4.3a's guest entry and exit save and restore
Glint's SP_EL0 with the rest of its state. 3.6 loads guest images as PE32+
for Glint's own machine, so 4.2's test guest is built as Glint's images are
(tools/glint_build.cr, boot/efi.lds), and the guest firmware of 6.5 is
loaded through 3.6's parser too. On x86_64, 3.3's TSS with an IST stack for
#DF is the one 4.1 extends.
Slices
4.1 Full exception vectors. Glint gets complete exception vectors on Arm, at the level it runs at (EL2 for hypervisor mode, EL1 in native mode without virtualization), and an IDT (the x86 table of interrupt and exception handlers). They save the interrupted state into a frame and dispatch on the cause, replacing 2.7's panic-only vectors. It opens the phase because every later slice handles some kind of trap. One dispatcher for every trap, rather than one per cause, is Parsimony in Tambassi's sense [39]. Its passage is xv6 §4.1 (trap machinery). Both architectures land together: the dispatcher is shared and only the vector-table format differs.
4.2 A bare-metal test guest. guest/test/ holds a tiny program that
runs as a guest. It knows no Linux; it can only print through calls to Glint
and power itself off. It comes before the run loop (4.3a, 4.3b) so the run
loop has something small to enter. A guest this small isolates Glint's
behaviour from a large guest's, so any failure in this phase points at
Glint, which is Demonstrability. It is built for both architectures in one
check-in.
4.3a The run loop, aarch64. Glint loads a guest's state, enters it at
EL1 with eret (the instruction that returns to a lower exception level),
takes the VM exit through 4.1's vectors and decodes its reason. It also
tracks the guest's virtual kernel and user mode, as Hailperin §7.5.2
describes: "the VMM keeps track of each virtual machine's simulated mode."
It comes after 4.1 and 4.2 because it needs both a dispatcher and a guest to
run. Hypervisor mode becomes selectable in 2.6's mode choice here: the
request file gains hypervisor, and the decision gains the virtualization
level as a requirement for it. Keeping all knowledge of guest entry and exit
in this one loop, behind a vCPU interface the rest of Glint calls, is Loose
Coupling. Its passage is xv6
§4.2 ("Traps from user space").
4.3b The run loop, x86_64. The same vCPU interface on AMD x86: Glint
fills the VMCB (the per-guest control block AMD's SVM uses), enters the
guest with VMRUN and decodes the exit code SVM returns, including port I/O
exits, which Phase 5's UART (5.3) relies on. It follows 4.3a because the
plan's architecture rule lands aarch64 first. Putting SVM behind the same
interface as 4.3a, so nothing above the run loop knows which CPU it is on,
is Loose Coupling.
4.4 The guest's system calls. Glint dispatches HVC (Arm) and VMMCALL
(x86) by SMCCC function number (the standard numbering of calls a guest can
make to a hypervisor), then implements PSCI VERSION and SYSTEM_OFF. It
comes after 4.3a/b because a call arrives as a decoded VM exit. Using the
standard numbering that Linux already speaks, rather than a Glint-specific
call interface, keeps the guest ABI replaceable and documented by others,
which is Loose Coupling. Its passage is xv6 §4.3 ("Code: Calling system
calls"); MOS4/5 §7.2 explains why such calls trap at all. Both architectures
land together; only the trapping instruction differs.
4.5 Checking guest arguments. Before Glint uses any address or length a guest passes in 4.4's calls, it checks them against the guest address space built in 3.5. Specs try the hostile values the threat model (2.8) anticipates. It comes after 4.4 because there are now calls with arguments to check, and it is where the threat model starts being enforced. Checking every argument against a written boundary, with specs that show the hostile cases rejected, is Demonstrability. Its passage is xv6 §4.4 ("Code: System call arguments").
4.6 Traps from Glint itself. A fault in Glint's own code now produces a report with a backtrace (the chain of calls that led to the fault), through 4.1's dispatcher, extending 2.7's panic. It comes last because the guest-facing paths (4.3a–4.5) are the phase's purpose, and a good report of Glint's own faults is what makes debugging them practical. A backtrace turns a crash into evidence, which is Demonstrability. Its passage is xv6 §4.5 ("Traps from kernel space"); both architectures land together.
Phase demo: booted with bin/glint-run aarch64 --mode hypervisor (and
x86_64), with the test guest's image on the ESP for the preload (2.9),
Glint reports Mode: hypervisor, and the test guest calls PSCI VERSION,
prints the answer through Glint and powers off with SYSTEM_OFF, on both
architectures. Nothing is typed.
References
- xv6 §4: "There are three kinds of event which cause the CPU to set aside ordinary execution of instructions"; §4.4 "Code: System call arguments".
- Hailperin §7.5.2 "Protection of Entire Simulated Machines": "the VMM keeps track of each virtual machine's simulated mode, that is, whether the virtual machine is in simulated kernel mode or simulated user mode."
- Tanenbaum: MOS4/5 §1.6 "System Calls"; MOS4/5 §5.1.5 "Interrupts Revisited"; MOS4/5 §7.2 "Requirements for Virtualization" [the hypervisor requirements of safety, fidelity and efficiency; Popek and Goldberg's condition that sensitive instructions be privileged; why x86 needed VT/SVM]; MOS4/5 §7.4.1 "Virtualizing the Unvirtualizable" [the guest kernel runs in a virtual kernel mode, and kernel-only instructions trap to the hypervisor].
- Rationale: xv6 provides the trap path that Glint's EL2 vectors follow. Hailperin is the direct source for trap-and-emulate of a guest's privileged operations. Tanenbaum adds the formal virtualizability requirements, which explain why EL2 and SVM exist at all. Its fidelity requirement is the acceptance test for Glint's guest ABI: an unmodified Linux kernel behaves as it does on bare hardware.