how a from-scratch PC emulator learned to boot Windows 2000

CHAPTER 3 · INTERRUPT DELIVERY

EDGES & WIRES

A Windows 2000 install is millions of disk writes, and every one of them ends the same way: a wire goes high, a controller latches an edge, a processor is interrupted. The emulator had the letter of that sequence right and the physics wrong — at layer after layer. This chapter is how interrupts really work, and how an emulator can quietly lose them.

3.64M
guest disk write-commits the install reached after the edge-destruction fix — up from 2.03M (11 Jul)
ms vs µs
how long an interrupt sits latched-but-masked under NT versus DOS — every destruction window thousands of times wider
0x0C00
the ELCR Windows 2000 programs: IRQ 10/11 level, everything else — including IRQ 14 — edge
4
divergence-counter firings in Civ (0 in DOOM/F1GP) before the PIC contract change — every one a correctly retained timer tick
2
unit tests found to have encoded the wrong behaviour — rewritten, not weakened
5,544
mddosem-hw tests green when the BMISTA latch-at-assert fix landed, 01:09 on 16 Jul

How a disk becomes code

When Windows 2000 writes a sector, the driver programs the drive and then puts the calling thread to sleep. Nothing about the write is synchronous: the drive works alone, and when it finishes it announces the fact electrically — it raises a wire called INTRQ, the interrupt request line it shares with the interrupt controller.

That controller is the Intel 8259 — two of them, in the classic PC arrangement, a master and a slave chained together; the disk's line, IRQ 14, arrives at the slave. Inside the 8259 sits the IRR, the Interrupt Request Register, and on an edge-triggered line the chip's job is to capture the transition: the instant INTRQ rises, the IRR bit is latched. A second register, the IMR, is a mask — a request can be latched yet held back. When a latched request is unmasked, the controller asserts INT to the processor; the processor acknowledges, receives a vector, and jumps through the interrupt table into the driver's interrupt service routine.

The ISR's first meaningful act is to read the drive's Status register at port 0x1F7 — and that read is itself part of the protocol. Per the ATA specification (ATA-4 §6.3.5), reading Status tells the drive its interrupt has been noticed, and the drive answers by deasserting INTRQ. There are two acknowledgements at two layers: the Status read quenches the drive, and the EOI later releases the controller. Between them sits the hardware truth this whole chapter turns on: the falling wire cannot reach back into the 8259 and erase the edge it already captured. By the time any software can read Status, the request is the controller's property.

A latch with too many erasers

mddosem does not deliver interrupts in the middle of an instruction. Its device models latch a one-shot pending-edge flag — irq_pic_edge_pending — and a delivery pump collects those flags and presents them to the emulated 8259 at quantum boundaries, roughly once per emulated millisecond. Deferred delivery is a respectable emulator architecture; QEMU's timing is similarly coarse. It has one absolute requirement: between assertion and delivery, the latch is sacred.

It was not. The code paths that deassert INTRQ on a real drive — a guest read of the Status port, the issue of a new command, and, in the ATAPI model, the issue of a packet — also cleared the flag. So a completion whose Status the driver happened to poll before the pump ran was silently destroyed: the sector was on the disk, no system call had failed, the drive looked healthy — and the interrupt announcing all of this had never existed. Which completions died was decided by host-scheduling jitter, so the failure moved every time anyone looked at it. And it reproduced on both execution engines, because the storage models are shared.

A Fable 5 consult on 11 July pinned the mechanism; the reasoning was verified in the code and confirmed by reproduction before the fix was applied. The fix itself is almost an anticlimax: the pending edge may be consumed by the delivery pump or by a device reset, and by nothing else. Two existing unit tests turned out to have encoded the wrong behaviour — one solemnly asserting that a Status read revokes the latch, the other that nIEN does — and were rewritten rather than weakened. The effect was immediate: the install, which had been dying at around 2.03 million guest disk write-commits, ran on to 3.64 million — straight into a new wall, but that is the shape of this whole story.

A sibling fix the same evening closed the last eraser. nIEN is a bit in the drive's Device Control register that masks its interrupt output, and the ATA-6 standard (§7.9) is precise about what masking means: the drive tri-states INTRQ — electrically disconnects the wire. A disconnected wire is a falling edge, and a falling edge cannot unlatch a captured request. The old code revoked the latched edge whenever the guest set nIEN; the fix deletes that branch and keeps only the legitimate behaviour, re-driving the line when nIEN falls while a request is still pending.

The NT amplifier

Why had years of DOS software never tripped any of this? Because DOS closes the window almost before it opens: a DOS handler runs with the request already delivered, and the interval in which a latched edge sits waiting is measured in microseconds. NT is different by design. Its Standard PC HAL implements interrupt priority — IRQL — by rewriting the 8259's mask register on every KfRaiseIrql and KfLowerIrql, and it sends EOI at ISR entry. Under NT, requests routinely sit latched-but-masked in the IRR for whole ISR-plus-DPC windows — the handler and the deferred work it queues — which is to say milliseconds. As the wire-conformance analysis put it, every destruction bug in this class has a window thousands of times wider under NT than under DOS.

The emulator's own deferral stacked on top: the delivery pump stretches the vulnerable interval from near-zero instructions to as much as a full quantum. Deferral turns a latent inconsistency into a live bug — and Windows 2000, the first guest to hold requests masked for so long, was the first workload wide enough to catch it.

One more piece of ground truth was dumped from the guest itself before the seam was rebuilt. On PCI-era chipsets, edge versus level is programmable per line through the ELCR, the Edge/Level Control Register. On 14 July the team read back what Windows 2000 actually programs: 0x0C00. Only IRQ 10 and 11 — the PCI interrupt lines — are level-triggered; IRQ 3, 4, 6 and 14 are all edge. The disk interrupt really is an edge, with everything that implies.

THE INTRQ WIRE — ONE COMPLETION, TWO PHYSICS step ▶ · ← → with the diagram focused · toggle the model
1 · completion The drive commits the sector, drops BSY, sets DRDY — and raises INTRQ, the interrupt-request wire it shares with the interrupt controller.
latched request masked (IMR) destroyed / lost wire asserted
step 1 / 7
Buggy path: the mddosem storage layer before 11 July 2026 — the Status-read and nIEN erasers of a latched completion (HLD 2026.07.11, bug ledger MDD-BUG-KILN-00004). Real path: the capture-at-assert contract adopted on 14 July. Hand-stepped recreation from the HLD state machines, not a trace.

Three oracles, one hybrid

The ATA fix repaired one device; the disease was more general. The emulated 8259's own lower_irq cleared the latched IRR bit whenever a device's line fell — "line-follows" semantics — so any device whose wire dropped before the pump ran could lose an interrupt. Serial resync, the real-time clock's Status-C read and the ATA soft reset were all members of the same lost-interrupt class. Before changing a contract that the entire DOS-era corpus had been passing against, the team asked the references. They disagreed.

What happens to a latched edge when the request line falls
ReferenceOn deassert of an edge lineWhy it gets away with it
QEMU (hw/intc/i8259.c) IRR bit retained — capture-at-assert Coarse timing: it cannot guarantee the CPU samples INT before the line drops, so the latch must survive
DOSBox-X (pic.cpp) Request cleared — line-follows Delivery in effectively zero time: the edge is consumed before anything can clear it
Real 8259A silicon Nearest DOSBox-X — a request that drops before acknowledge becomes the classic spurious IRQ 7 Nanosecond electronics; the race window barely exists
mddosem, pre-fix Cleared — line-follows, with delivery deferred to the next quantum It didn't. The hybrid opened a window of up to a full quantum in which a latched edge could die

The disagreement is decisive rather than confusing once timing enters the picture. DOSBox-X survives line-follows because it delivers interrupts at once; QEMU retains the IRR bit because its timing is too coarse to promise that. mddosem's timing is QEMU-shaped, so QEMU's contract — capture-at-assert — is the correct one for it. And the verdict on the pre-fix emulator was the sharpest line of the campaign: Windows 2000 installs on both QEMU and DOSBox-X. "What W2K cannot survive is our hybrid" — line-follows semantics bolted to quantum-deferred delivery.

Adopting capture-at-assert initially broke sixteen tests, which looked like evidence the old semantics were load-bearing. A five-analyst read-only review, a synthesis, and an adversarial refutation pass — "cannot refute" was the required standard — established the opposite: the collateral was self-inflicted. The spurious-IRQ 7 path that appeared to depend on line-follows had no production caller at all, and the failing tests were asserting the emulator's habits, not the hardware's. All sixteen were updated, not weakened; two were rewritten outright because their premise was void. The cheapest decisive check was a divergence counter placed on the exact state where the two models differ: across DOOM and F1GP it fired zero times, in Civilization four — every firing an IRQ 0 timer tick the new model correctly retained — and a wider fourteen-game sweep found only a handful more, all equally benign. The change landed ELCR-aware on 14 July (mddosem-hw 0.41.0): edge lines keep their captured requests; level lines still follow the wire, as real level lines do.

The same work left behind a permanent instrument: a wire-conformance oracle that drives the production PIC and a deliberately simple reference 8259 through adversarial pump-boundary schedules, with calibration rows that must re-find each already-fixed bug — "an oracle that misses the bugs we already found is worthless". The reflection on method belongs to chapter 7; the machinery lives on in mddosem-hw's test suite.

The southbridge that lied by omission

Between the drive and the 8259 sits one more chip this chapter owes an account of: the Intel PIIX3, the southbridge of the emulated board, whose IDE function answers PCI enumeration as device 8086:7010. When the Windows 2000 campaign began, mddosem's PIIX3 IDE was a configuration-space stub: programming-interface byte 0x00, no BAR4 — no bus-master register window at all. DOS never noticed, because DOS talks to the legacy ports. NT interrogates the controller. Its IDE drivers — intelide, pciide — bind to the PIIX3 and require the bus-master window to initialise the storage stack; without it, initialisation fails and the kernel bugchecks STOP 0x0000007B INACCESSIBLE_BOOT_DEVICE — which is exactly what a transplanted QEMU-installed disk hit during the July transplant experiments (the boot chain is chapter 1's subject; the transplant end-run itself later died of a transplant-inherent wall and was rejected as doctrine-violating anyway).

So on 13 July the PIIX3 grew a real engine: bus-master command, status and descriptor-pointer registers in a sixteen-byte I/O window at BAR4 (placed at 0xC000, QEMU's conventional base); a walker for the PRD scatter-gather tables that describe bus-master transfers; READ DMA and WRITE DMA command support; an IDENTIFY block honestly advertising the DMA modes. The irony is complete: the installed Windows 2000 ran its disk transfers in PIO mode throughout. The active ingredient was the window's existence — BAR4 being there let intelide initialise, and the storage stack came up.

Fidelity kept mattering after the engine worked. During a bus-master transfer the emulation briefly exposed two states no real hardware ever produces: the drive reported idle-and-ready with a DMA command still outstanding, and the controller's bus-master Active bit was set and cleared inside a single pump call, so no guest could ever observe it as 1. Windows' IdePort tick handler polls both. The 15 July fix holds BSY from command acceptance to completion, per the ATA-6 command model, and ties Active to the Start bit for the whole in-flight window — a deliberate, documented divergence from modern QEMU (≥ 2.8), which uses BSY=0/DRQ=1 during transfer.

And then the status bit itself. On a real PIIX3, the moment a drive asserts INTRQ the controller latches that channel's Interrupt bit in BMISTA, its bus-master status register — whether or not a DMA transfer is running. mddosem raised IRQ 14 with BMISTA reading zero, and the secondary channel's status register was hardwired to zero, so IRQ 15 could never appear there at all. A PCI IDE interrupt service routine is entitled to look down at its controller, see "no channel interrupt", and dismiss the IRQ as not-mine — the strongest concrete explanation on record for setup dismissing its own final completions, though the exact terminal IRQ was never caught in the act. The diagnosis came out of the RAM-dump census that chapter 4 recounts. An independent review sharpened the fix before it landed: latching at the quantum-end pump was still too late, because the interpreter can execute another I/O instruction in the same batch and read stale status — so the latch must be visible before the I/O instruction that provoked it returns. The final engine keeps two independent channel banks, write-one-to-clear semantics and Start/Stop cancellation, and 5,544 mddosem-hw tests were green when it landed at 01:09 on 16 July. This is the blue pane of the four-fix flag :

The other three panes — the interrupt shadow, the parked timer and the four-megabyte page — are CPU-level stories, and they belong to chapter 6.

The wall moves downstream

Notice the drumbeat. The edge-destruction fix was real hardware truth, and the wall moved from 2.03 to 3.64 million write-commits. Capture-at-assert was real, and by its own commit message unblocked nothing. The bus-master engine was real, and carried the kernel from STOP 0x7B into the boot splash and driver load. Each correction removed a genuine lie the emulator had been telling, and each time Windows walked further before stopping somewhere new — until it stopped somewhere no log, no trace and no oracle could see: every thread waiting, on a screen that stayed black for four days. Making that silence speak took a kernel debugger with no symbols and a census of a 128 MiB RAM dump — chapter 4.

Sources for this chapter — the ATA/ATAPI completion-IRQ edge-destruction HLD (2026.07.11, mechanism pinned by a Fable 5 consult) and the PIC wire-conformance oracle HLD (2026.07.13–14, authored by Fable 5, verified by Claude); the bug ledger MDD-BUG-KILN-00004; the commit trail of 11–16 July 2026 (mddosem-hw 0.34.0 → 0.42.0, capture-at-assert at 0.41.0, BMISTA latch-at-assert e0e9fe9a5); the guest ELCR dump of 14 July. Diagram states are recreations from the HLDs, not traces. Full evidence: how we know.