AHCI (SATA) Block Driver
Status: Design doc, non-normative (trails
CLAUDE.md). Records the AHCI backend forblock-driver, the modern-hardware disk path.
1. Why AHCI
The ATA PIO backend (docs/persistence.md §5) works in QEMU's legacy IDE but the
T630's SSD is AHCI-only - its firmware exposes no legacy IDE, so a read-only
probe of ports 0x1F0/0x170 returned status 0xFF (no drive) on real hardware.
AHCI is the standard SATA interface: it works on the T630 and in QEMU
(ich9-ahci), so it is the portable, modern path.
AHCI replaces only block-driver's bottom half - how sectors move. The block
IPC (ReadBlock/WriteBlock), the on-disk format, mkfs, fs, and reboot
survival all live above the block layer and are device-independent.
2. Shape of the driver (DMA + MMIO)
Unlike ATA PIO (port I/O), AHCI is MMIO + DMA, exactly like the USB drivers:
- The kernel finds the AHCI controller in the PCI scan (class 0x01, subclass
0x06, progif 0x01), records its ABAR (BAR5) + BDF (
pci.rs), and at spawn maps the ABAR (HBA registers) intoblock-driverand grants it a small physically-contiguous DMA arena (task/mod.rs, the same path as xhci/ehci). The driver takes the window withgs::driver::registersand the arena withgs::driver::arena, laid out in four named parts (docs/driver-library.mdstep 3). - Per-port DMA structures the driver builds in its arena: a command list (32 command headers), a received-FIS area, and per-command a command table (a command FIS + a PRDT - physical region descriptors pointing at the data buffer). A transfer = build a Register-Host-to-Device FIS (READ/WRITE DMA EXT), point the PRDT at the data buffer, set the command-issue bit, wait for completion, check the task-file for errors.
3. IOMMU (H1)
AHCI is DMA-capable, so on a machine with an IOMMU it should be confined to its arena like xhci (§6.4). The T630 has a working IOMMU. The driver was brought up in passthrough first (get it correct), on the "earned confinement" rule the USB drivers follow (docs/iommu.md).
It is still in passthrough, and that is now a decision rather than a stage. The
T630's BIOS hands the SATA controller over carrying a stale firmware DMA pointer, and
confining the device makes the controller's first access fault - the same quirk that
keeps ehci in passthrough. The kernel states it at the policy site:
kernel/src/task/mod.rs, the confine flag on HwClass::Pci (read by iommu_confine), "Not ehci + block-driver, which keep a stale firmware DMA pointer
that confinement would fault, so they stay in passthrough". xhci and audio-driver are the only confined
in-tree drivers (both confined on the T630; CLAUDE.md 6.4, note 2026-10-08); an installed driver
package given a PCI device is spawned asking for confinement too (services/supervisor/src/installed.rs). So block-driver is trust-critical on every machine,
IOMMU or not, exactly as §6.4 says an unconfined DMA driver must be.
4. Build steps (incremental, against QEMU ich9-ahci)
Developed behind the block-driver/ahci cargo feature so the ATA PIO tests stay
green during the migration; becomes the default (retiring ATA PIO + the IDE probe)
once read/write/fs/reboot are verified on it. Test: osdev test blockdev.
- Step A - detect + HBA init. ✅ done. Map ABAR, enable AHCI mode (GHC.AE), read CAP/VS/PI, enumerate implemented ports, report which carry a SATA disk (DET=3, sig 0x00000101). Verified: 6 ports, disks on ports 0/1.
- Step B - port init + IDENTIFY. ✅ done (
osdev test blockdev, AHCI.B). Stop the port (clear ST/FRE, wait CR/FR), plant the command list + received-FIS base in the arena, restart (FRE then ST), then issue IDENTIFY DEVICE via a command header + H2D Register FIS + single-PRDT command table, wait on PxCI, and parse the result. Verified: modelQEMU HARDDISK, 131072 sectors (64 MiB). The full DMA command path (command list / FIS / PRDT / command-issue / completion) now works. - Step C - read. ✅ done (
osdev test blockdev, AHCI.C). READ DMA EXT (0x25) viaissue()into the PRDT data buffer; block IPCReadBlockrestored. Verified:fsmounts the AHCI disk over IPC (fs: mounted). - Step D - write + integrate. ✅ done (AHCI.D). WRITE DMA EXT (0x35) + FLUSH EXT
(0xEA); the block-IPC serve loop runs in the AHCI backend;
fsspawns and the full file round-trip works over AHCI (fs: file round-trip OK). The whole filesystem stack now runs on AHCI. Harness: boot on legacy IDE, the persist disk ALONE onich9-ahci(→ port 0), mirroring the T630 (SSD is the only SATA disk). - Step E - promote. ✅ done. Confinement NOT taken. AHCI is now the only backend
(the
ahcicargo feature is gone; ATA PIO + thehw_piocapability + the IDE probe are retired - the T630's SSD is AHCI-only). The confinement half of this step was not completed and is not pending: block-driver is spawnedhwclass::pci(0x01_06_01, 5, false)(theblock-driverrow of the supervisor spawn table) and stays in IOMMU passthrough for the firmware stale-pointer reason in §3. Its DMA is arena-resident in practice, but nothing enforces that, so block-driver stays trust-critical on every machine (§6.4). Verified:osdev test blockdev3/3 + identity 23/23; unsafe audit back to 413/27 (thehw_pioarch wrappers +capability/hw_pio.rs+ SDKpio.rsremoved). - Step F - port recovery (COMRESET). ✅ done (
f069c85, hardware-found by the million-round soak). A soak that killsblock-drivermid-command can leave the port stuckBSY(the kernel clears PCI bus-master on kill before the in-flight command drains), and the soft port-restart of Step B cannot clear it - only re-running the SATA OOB sequence does.port_comreset(ahci.rs) does what a cold boot does: idle the port keeping FIS-receive armed (so the device's post-reset initial D2H FIS is captured and clearsPxTFD.BSY, AHCI 10.1.2), pulsePxSCTL.DET1 -> hold -> 0, wait for the PHY (PxSSTS.DET == 3), clearPxSERR/PxIS, restart the engine.init_portruns it on every (re)start;recover_port(the Phase H per-I/O retry path) escalates to it when a soft recovery leaves the port wedged, so a running driver self-heals a wedged port with no restart. This is what letsfs's mount-on-truth get a truthful answer:block-driver's reply reflects a settled controller, never a wedged-BSYfalse 0 (Commandment VIII). - Step G - CI-stuck escalation. ✅ done (
2026-07-09, hardware-found on the T630 by a rapid doublekill all-services). Step F escalated to COMRESET only on a stuckPxTFD.BSY. But a command against a nonsense LBA (anfsmount that derived a backup-superblock LBA from a garbage capacity - seedocs/persistence.md§6.16) can wedge the controller with the command-issue bitPxCIstuck set andBSYclear. A soft recovery (clearingPxSERR/PxIS) cannot clear an ownedPxCI- only a port COMRESET does - so the BSY-only gate never fired and the driver retried into the same stuck state forever.recover_portnow escalates onPxTFD.BSY != 0 || PxCI != 0, covering both wedge shapes. The fs-side half of the fix (rejecting the mis-shaped capacity reply, and a bounded failure-truth exit from the mount loop) isdocs/persistence.md§6.16; together they complete Commandment VIII in the storage stack. The live CI-stuck condition is T630-only - QEMU's emulated AHCI never raises it.
5. Register cheat-sheet
HBA (from ABAR): CAP 0x00, GHC 0x04 (bit31 AE), IS 0x08, PI 0x0C, VS 0x10.
Port n (base 0x100 + n·0x80): PxCLB 0x00, PxFB 0x08, PxIS 0x10, PxIE 0x14,
PxCMD 0x18 (bit0 ST, bit4 FRE, bit14 FR, bit15 CR), PxTFD 0x20, PxSIG 0x24,
PxSSTS 0x28 (DET in 3:0), PxSCTL 0x2C (DET in 3:0, the COMRESET control), PxSERR 0x30,
PxCI 0x38 (command-issue bitmask).