xv6 §6, "Interrupts and device drivers". "A driver is the code in an operating system that manages a particular device: it configures the device hardware, tells the device to perform operations, handles the resulting interrupts, and interacts with processes using the device." The chapter's "Real world" section adds: "Programmed I/O is simple, but too slow to be used at high data rates."
Phase 6 is where the plan's Concept first comes together. Glint takes real interrupts, drives its own devices and delivers virtual interrupts to a guest. That is enough for an unmodified Linux kernel to boot. The phase also turns the Zephyr seam that Track Z built for the console into the plan's answer to the "Real world" sentence above: the console driver becomes interrupt-driven (6.3), and the first Zephyr storage provider follows (6.10). Firmware drivers poll; Zephyr's are interrupt- and DMA-driven. Three of its slices extend Track Z (6.3, 6.8 and 6.9), as Track Z's What Track Z leaves sets out. The phase starts from Phase 5's emulated devices and Phase 4's run loop, on boots that are native by default since 2.10. It leaves a single-vCPU Linux guest; Phase 8 (8.6) gives it more vCPUs.
Where the two architectures differ substantially, a slice is split into an aarch64 check-in (a) followed by an x86_64 one (b), and both land within the phase.
Refined on Nov 10, 2026 (rule 9). Phase 6 starts with Phases 1–5 done on both architectures, Track S's S.1 and Track U's U.1 merged. The decisions taken in refining it:
- Order. 6.1a, 6.1b, 6.2, 6.3 (split into 6.3a and 6.3b if its two halves don't fit one check-in), 6.4a, 6.4b, 6.5a (devicetree), 6.5b (ACPI), 6.6, 6.7a, 6.7b, then Track U's U.2–U.5 (USB, Glint's primary peripheral, which needs 6.4a's virtual interrupts and 6.7a's Linux guest), then 6.8, 6.9 and 6.10.
- GICv3 on Arm. QEMU's
virtpicks GICv2 when no version is given and there are at most 8 CPUs (QEMU 11.1.2hw/arm/virt.clines 2624–2634). The Beagle boards' SoCs carry GICv3:arm,gic-v3in U-Boot 2026.07'sk3-am62-main.dtsiandk3-am62p-j722s-common-main.dtsi. So Glint drives GICv3 and its boots ask for it (gic-version=3). GICv3's CPU interface is system registers (ICC_*), and its virtualization is theICH_*list registers, also system registers, so 6.4a needs no memory-mapped hypervisor interface. The project's QEMU hasCONFIG_ARM_GICV3, andvirt,virtualization=on,gic-version=3starts. - Each slice's passage is the one named in it below. Each task quotes it if the text is in the workspace and otherwise paraphrases it and says so, as Phases 4 and 5 did.
- Open question 1 (Phase 6's first Zephyr provider, and the Zephyr pin) must be answered before 6.8 is refined. It is asked when 6.7b lands, so the maintainer isn't asked about something weeks away.
Slices
6.1a The Arm interrupt controller. Glint drives the GIC (Arm's Generic Interrupt Controller, version 3 or 2): its distributor, which routes device interrupts, and its per-CPU interface, which delivers them. Physical interrupts then reach handlers through Phase 4's dispatcher (4.1). It opens the phase because every later slice here either takes an interrupt or injects one. Routing every device through the one dispatcher, never through ad hoc paths, is Parsimony in Tambassi's sense, as in 4.1. Its passage is MOS4/5 §5.1.5 ("Interrupts Revisited").
6.1b The x86 interrupt controllers. This is the same job for x86: each CPU's local APIC and the IOAPIC that routes device interrupts to CPUs, wired into the same dispatcher. It follows 6.1a because the two controllers share nothing but the dispatcher they feed. Keeping them as separate check-ins keeps each small enough to read in one sitting, which is Parsimony in the plan's sense.
6.2 Timer interrupts. The Arm generic timer and the LAPIC timer each deliver a periodic tick through 6.1a/6.1b, and Glint keeps time from it. The two timers differ only in a few registers, so this is one check-in. It comes right after 6.1a and 6.1b because a tick needs a controller to arrive through, and Phase 8's preemption (8.3) depends on it. Its passage is xv6 §6.4 ("Timer interrupts"). A tick that specs and a boot test can count serves Demonstrability: Glint's sense of time becomes something that can be shown.
6.3 An interrupt-driven console. The console since 2.6 is Zephyr's own
UART driver (PL011 or NS16550), compiled in by Track Z and run in poll mode.
Here it stops polling: it is rebuilt with CONFIG_UART_INTERRUPT_DRIVEN,
and reads and writes when the UART raises an interrupt through 6.1a or 6.1b.
That needs the first interrupt pieces of the Zephyr seam: the devicetree
generator learns interrupts cells (and pc-com1.dts gains COM1's
interrupt, which Z.3 left out), the shim implements IRQ_CONNECT and
irq_enable on 4.1's dispatcher (on x86-64 through
arch_irq_connect_dynamic; on arm64 the drivers' .intList entries become
a generated table), and Z.9's console provider gains a receive ring, since
the driver's callback runs in interrupt context and keeps no buffer
(survey section 2). The two architectures register interrupts differently,
so this slice may split into 6.3a and 6.3b when refined. It comes after
6.1–6.2 because it is the smallest real use of them, the phase's whole theme
(from polling to interrupts) on one device. That keeps the first
interrupt-driven driver small enough to check by eye, which is Parsimony.
Its passages are xv6 §6.1–6.2 (console input and output).
6.4a Virtual interrupts on Arm. Glint injects interrupts into a guest through the vGIC's list registers (slots the hypervisor fills with pending virtual interrupts), and gives the guest a virtual timer. It is built on 6.1a's controller: a physical interrupt arrives there, and Glint decides which guest it belongs to. Emulated devices from Phase 5 (5.3–5.6) can now raise interrupts instead of being polled. It comes after 6.3 because Glint must handle its own interrupts before it delivers them to anyone else. The guest sees the same interrupt controller it would see on hardware, which serves Loose Coupling: the guest never learns it is talking to Glint.
6.4b Virtual interrupts on x86. On AMD x86, Glint injects interrupts by writing an event into the VMCB (the per-guest control block SVM reads on each entry), built on 6.1b's APICs. It follows 6.4a for the same reasons, and is split from it because the two mechanisms share no code. Keeping VMCB injection behind the same injection call as 6.4a is Loose Coupling.
6.5 Guest firmware. Glint gives the guest a small EFI environment: a
system table, a subset of boot services, a memory map and runtime services,
plus a description of the guest's machine. Linux's own EFI stub (the small
EFI program at the front of every Linux kernel image) boots from this, as
What Linux sees in the plan describes. On Arm the description is a
devicetree, written at run time by Glint's own allocation-free FDT writer,
since Cleave::Fdt cannot run inside the image (Build constraints); a
spec checks the writer's output by reading it back with Cleave::Fdt, as
2.4a's reader is checked. On x86 it is a set of ACPI tables, the minimum a
Linux guest needs for the machine What Linux sees describes: an RSDP
pointing to an XSDT; a FADT with its DSDT and FACS; a MADT listing the
guest's local APIC and IOAPIC; and an MCFG giving the configuration space of
the PCI bus that carries virtio-pci. The two describe different things and
share only the writer's pattern, so the slice may split into 6.5a
(devicetree) and 6.5b (ACPI) when refined. The environment is built from
the same protocol definitions Phase 1 wrote (1.3, 1.5). Each definition
exists once and serves both Glint and its guests, which is Parsimony
(Tambassi's rule [39] that each thing should appear only
once). It comes before 6.7a and 6.7b because Linux cannot start without it.
6.6 A virtio-blk device for guests. Guests get a virtio block device,
built on 5.4's virtio-mmio transport, and on 5.5's virtio-pci on x86. It is
backed by whichever BLOCK_IO provider Glint has. In native mode the
firmware's provider is gone, so the first backing is a disk image held in
memory, preloaded from the ESP (2.9). Swapping that backing later (6.10,
Phase 10) needs no change to the device, which is Loose Coupling. It
comes before 6.7a and 6.7b because Linux needs a disk to find its initial
files on. Its passage is MOS4/5 §7.7 on
guest disks as files or regions.
6.7a Linux boots on Arm. An unmodified arm64 kernel Image, with an
initramfs (a small in-memory file system holding BusyBox, a single-binary
set of basic Unix tools), boots through its EFI stub to a shell on one vCPU.
It needs 6.4a's interrupts, 6.5's firmware and 6.6's disk, which is why it
comes after all three. dmesg (Linux's kernel log) shows Glint's SMCCC
hypervisor UID, the identifier Glint answers with when Linux asks which
hypervisor it is running on. This is the phase's central Demonstrability
claim, and MOS4/5 §7.2's fidelity requirement is its acceptance test: Linux
behaves as it does on bare hardware.
6.7b Linux boots on x86_64. The same demonstration on QEMU's q35
machine with SVM, booting a bzImage (the x86 kernel image format) through
6.4b, with 6.5's firmware and 6.6's disk, as in 6.7a. It comes after 6.7a
because the plan brings aarch64 up first in every phase. It is a slice of
its own because a demonstration that only one architecture passes would not
show what the plan claims. Showing the same guest ABI working on both
architectures is the Demonstrability behind the plan's central claim, that
to Linux on either architecture Glint looks like a hypervisor.
6.8 Zephyr's build data, extended. Track Z already generates Zephyr's
three kinds of build input in Crystal: devicetree_generated.h from the
devicetree and Zephyr's YAML bindings (Z.3–Z.5), autoconf.h from a checked
symbol table (Z.6), and the syscall stub headers (Z.7), limited to what two
poll-mode UARTs reach. This slice extends them to what the first block
provider needs: ranges, PCI and chosen in the devicetree generator,
beyond the interrupt cells 6.3 added (aliases and node labels only if the
driver reaches them), and the provider's Kconfig symbols in the table.
Pinctrl, clocks and resets are not among them; What Track Z leaves says
where they stand. The provider's driver then compiles from generated
headers, and Z.2's hand-written fixtures for it are retired. It comes before
6.9 and 6.10 because the provider cannot compile without them. The
acceptance test stays Track Z's: Zephyr's own headers accept the output and
the driver compiles with -Werror, which serves Demonstrability.
6.9 The Zephyr shim, extended. Track Z's shim (Z.8) has a device table,
an init runner and no kernel object of its own (the NS16550's spinlock is
Zephyr's inline one). This slice
adds the kernel objects a block driver uses: k_sem, k_mutex, k_work
and k_timer on 6.2's timer, with logging routed to Glint's console through
CONFIG_LOG_CUSTOM_HEADER. C headers declare them and Crystal implements
them, through the C seam proved in 1.4. Its lock objects start as stand-ins
that simply mask interrupts on the one CPU running drivers, and a blocking
k_sem_take spins with interrupts enabled until Phase 8 brings threads
(survey risk 4). 7.5 maps the locks onto Glint's real ones once those exist.
It follows 6.8 because the shim's headers include 6.8's generated ones.
Keeping Zephyr behind this single seam is Loose Coupling: Glint can replace
any Zephyr driver without touching the rest.
6.10 The first Zephyr provider. An upstream Zephyr block driver is built
unmodified with clang against 6.8's generated headers and 6.9's shim. It is
then wrapped as a BLOCK_IO provider behind 6.6's device. Which driver is
the plan's open question 1. The benchmark that plan principle 8 requires
compares the firmware's BLOCK_IO in hosted mode (a boot asked to stay
hosted, 2.10) with this provider in native mode, reading the same QEMU
disk. It comes last in the phase because it needs 6.8, 6.9 and a
running guest to serve. Measuring the "Real world" sentence's claim, rather
than assuming it, is Demonstrability.
Phase demo: an unmodified Linux kernel boots to a BusyBox shell on Glint (one vCPU, both architectures), and the benchmark shows the Zephyr provider against firmware.
References
- xv6 §6: "A driver is the code in an operating system that manages a particular device"; §6.4 "Timer interrupts"; §6.5: "Programmed I/O is simple, but too slow to be used at high data rates."
- Hailperin §2.5 "Preemptive Multitasking" (timer interrupts drive preemption). Hailperin has no device-driver chapter, which is the main gap in using it as the spine.
- Tanenbaum: MOS4/5 §5.1.4 "Direct Memory Access"; MOS4/5 §5.2.2–5.2.4 "Programmed I/O", "Interrupt-Driven I/O", "I/O Using DMA"; MOS4/5 §5.3.2 "Device Drivers"; MOS4/5 §5.5 "Clocks"; MOS4/5 §7.7 "I/O Virtualization", subsections on the I/O MMU and on Device Domains [one VM runs a standard OS and serves other guests' I/O, as Xen's domain 0 does; a type 1 hypervisor must otherwise contain every driver itself].
- Track Z, whose generators and shim 6.3, 6.8 and 6.9
extend (What Track Z leaves), and
docs/zephyr-survey.md(sections 3, 5 and 6 list what Phase 6 adds). - Rationale: xv6's real-world passage states Glint's firmware-to-Zephyr thesis in one line. Tanenbaum's §5.2.2–5.2.4 lay out the same ladder from programmed I/O to DMA that Glint climbs from firmware to Zephyr providers. Its §7.7 "Device Domains" names Glint's Linux driver domain and the reason for it.