docs: flip stale status markers across the tracks (audit found 23)

An all-tracks docs-vs-code audit (the same method that caught the storage
drift) found 23 confirmed inaccuracies where a doc's build-status claim no
longer matches the source — status markers that were never flipped after a
track landed, and a few paths left over from completed flag-days. All verified
against the code before editing; docs only, no behavior change.

The systemic ones:
- IOMMU enforcement (driver-model.md, drivers.md): docs said enforcement was
  not built and "device_claim = ring 0" / "memory-safe is not true yet". It is
  built (per-device VT-d/AMD-Vi domains programmed at device_claim, -ECONFINE
  rollback, dma_alloc buffers bound and torn down at death; fail-open only with
  no IOMMU). Restated; M16 marker flipped to done.
- The FHS flag-day paths: /etc/devices.csv -> /system/configuration/devices.csv
  (devices-csv.md, new-driver-checklist.md, device-manager.md), /var/log ->
  /system/logs (logging.md, new-driver-checklist.md), /mnt/usb -> /volumes/usb
  (process-management.md). Following the old paths silently breaks driver match.
- protocol-namespace P4 "remaining" -> landed (only P5 remains); shared-fate
  fan-out "not yet enforced" -> enforced; wall_clock "not built" -> built;
  SMP affinity + fault-recovery "left" -> built; process_enumerate raw-pointer
  trust model -> checked copyToUser/EFAULT; bounds.md maximum_devices static
  hole -> dynamic per-registrar quota; init spawns fat -> volume-manager;
  config "hardcoded, move to /etc" -> already CSV data files; vdso.md three-
  value call; zig-self-hosting library/ layout; python argv "new" -> built.

Found and fixed by a multi-agent audit across 12 doc clusters, each finding
adversarially verified against the source.
This commit is contained in:
Daniel Samson
2026-08-09 22:04:17 +01:00
parent 8216be991d
commit bf0595763e
17 changed files with 134 additions and 105 deletions
@@ -136,7 +136,7 @@ xHCI match already uses); USB children carry the (class, subclass, protocol) tri
from usb-ids.zig — each bus's native language, decoded by the shared ids modules.
(Since the registry landed, `child_added` also carries a `bus` discriminator and
the numeric `vendor`/`device`/`subsystem` ids the finer match levels need —
see [/etc/devices.csv](devices-csv.md).)
see [/system/configuration/devices.csv](devices-csv.md).)
## Supervision and restart
@@ -227,7 +227,7 @@ published exit events, signals + `process`). On top of those:
class triple alone, so a virtio-gpu could only be matched as a generic display
function and the driver had to re-confirm its `1AF4:1050` identity from config
space after being spawned. The manifest the earlier note anticipated landed as a
human-readable registry: **[/etc/devices.csv](devices-csv.md)**, parsed by the
human-readable registry: **[/system/configuration/devices.csv](devices-csv.md)**, parsed by the
pure `device-registry` module and read by the manager at boot. A row binds a
driver to a device by any of base / subclass / prog-IF / vendor / device /
subsystem / `_HID`, most-specific match winning; it is authoritative (no
@@ -1,6 +1,6 @@
# /etc/devices.csv — the device registry
# /system/configuration/devices.csv — the device registry
**Status: built (2026-07-26).** The device manager reads `/etc/devices.csv` at
**Status: built (2026-07-26).** The device manager reads `/system/configuration/devices.csv` at
boot and binds every device a bus driver reports to the driver the registry
names. It replaces the three hand-written `switch` tables that used to live in
the manager (`pciDriverForIdentity`, `hidDriverFor`, `usbDriverForIdentity`) —
@@ -73,15 +73,15 @@ the first, so the shadowed rule is visible rather than silently dropped.
There is no compiled-in default table behind the registry. A device that no row
matches goes **unbound** and is logged; the manager never guesses. A missing or
empty `/etc/devices.csv` therefore means nothing matches — which is loud at boot,
empty `/system/configuration/devices.csv` therefore means nothing matches — which is loud at boot,
not a silent half-working system.
## How the manager reads it
`/etc/devices.csv` is bundled into the initial ramdisk (`build.zig`'s `bundled`
list). The kernel serves the initrd's `/etc` tree directly — the `fat` service is
spawned *after* the device manager and is irrelevant to `/etc` — so the manager
reads the file with a plain `fs.open("/etc/devices.csv")` + `read`, with no
`/system/configuration/devices.csv` is bundled into the initial ramdisk (`build.zig`'s `bundled`
list). The kernel serves the initrd's `/system/configuration` tree directly — the `fat` service is
spawned *after* the device manager and is irrelevant to `/system/configuration` — so the manager
reads the file with a plain `fs.open("/system/configuration/devices.csv")` + `read`, with no
filesystem service running and no boot-ordering dependency. It parses the bytes
once in `initialise`, before any bus driver can report a device to match.
@@ -105,6 +105,6 @@ in different namespaces — against the right `bus` column.
`build_support.programModule`). Then bundle it at `/system/drivers/<name>`:
one dependency + one bundled entry in the root `build.zig`, one line in the
root `build.zig.zon`.
2. Add a row to `etc/devices.csv` naming the identity it binds and its full path.
2. Add a row to `system/configuration/devices.csv` naming the identity it binds and its full path.
No device-manager change is required — the registry is the seam.
+27 -21
View File
@@ -193,12 +193,14 @@ class driver, the device manager, or the kernel may share them freely.
a PS/2 or 16550 driver possible; the low-rate legacy hardware that needs it is fine with
a syscall per access. `io_port` resources were recorded by discovery and ignored — now
they're used.
- **M16 (detection)** — the IOMMU is now *found*: discovery parses the ACPI DMAR table,
maps the first VT-d unit, and reads its version + capabilities (`iommu_present` in the
platform info). This is detection only — **no translation domains are programmed, so
DMA is still unprotected** (the caveat below). Enforcement lands with the first DMA
driver, which is what there is to protect and test against. Proven in the `iommu` test,
booted with an emulated `intel-iommu`.
- **M16 (enforcement)** — the IOMMU is now *found and used*: discovery parses the ACPI
DMAR table, maps the first VT-d unit, and reads its version + capabilities
(`iommu_present` in the platform info), and **`device_claim` programs a private
per-device translation domain** for the claimed function — rolling the claim back with
`-ECONFINE` if it cannot confine it — so `dma_alloc` buffers are bound into that domain
and torn down at process death. DMA is protected on any IOMMU-equipped machine; the
system fails open only when there is no IOMMU at all. Proven in the `iommu` test, booted
with an emulated `intel-iommu`.
- **`system_spawn`** — a user-space supervisor starts a driver:
`system_spawn(name, arguments)` loads a binary bundled in the initial-ramdisk as a
fresh ring-3 process; `name` becomes the child's argv[0] and the optional
@@ -369,25 +371,29 @@ capability walk (MSI, MSI-X, PCIe extended caps) without any new syscall.
Note QEMU's HPET reports `Tn_FSB_INT_DEL_CAP = 0` — no MSI — so an HPET timer could never
exercise this path. The first MSI driver will be the first PCI driver.
## M16 — the IOMMU, and the honest caveat ◑ detection done, enforcement pending
## M16 — the IOMMU, and the honest caveat ✅ done
*The IOMMU is now detected (DMAR parsed, VT-d unit mapped and read — see the `iommu`
test), but **enforcement is not built**: no translation domains are programmed, so the
caveat below still holds in full. Detection can't be taken further usefully until there
is a DMA driver to protect and QEMU's `intel-iommu` to test the protection against —
building the per-device domains alongside that first driver is both the natural order
and the only way to verify them. The rest of this section is the original caveat.*
test) **and enforced**: `device_claim` programs a private per-device VT-d/AMD-Vi
translation domain for the claimed function and rolls the claim back with `-ECONFINE` if
it cannot confine it, `dma_alloc` buffers are bound into that domain and torn down at
process death, and the machine fails open only when it has no IOMMU at all. So the caveat
below no longer holds except on IOMMU-less hardware. The rest of this section is the
original caveat, kept for the reasoning.*
Everything above is capability-gated at the *CPU*. None of it is gated at the *device*.
A driver that can program a bus-mastering engine can make that device write to any
physical address, because page tables sit between the CPU and RAM, not between a device
and RAM. Until VT-d/DMAR (or SMMU on ARM) is programmed from the DMAR table, **`device_claim`
on any DMA-capable device is equivalent to granting ring 0.**
Everything above is capability-gated at the *CPU*. CPU page tables alone do not gate the
*device*: a driver that can program a bus-mastering engine could make that device write to
any physical address, because those page tables sit between the CPU and RAM, not between a
device and RAM. That is exactly what the IOMMU closes. Now that VT-d/DMAR (and AMD-Vi; SMMU
on ARM) is programmed, **`device_claim` confines the function into a private translation
domain** and rolls the claim back with `-ECONFINE` if it cannot — so a claimed DMA-capable
device is no longer equivalent to granting ring 0.
This does not make the model useless — it's the same position Linux is in with the
IOMMU off, and every other guarantee (crash isolation, restart, no shared address
space) still holds. But "user-space drivers are memory-safe" is not true yet, and the
gap should be named rather than implied.
This puts the model ahead of Linux-with-the-IOMMU-off: with an IOMMU present,
"user-space drivers are memory-safe" now holds, alongside every other guarantee (crash
isolation, restart, no shared address space). The one remaining gap — a machine with no
IOMMU at all, where the system deliberately fails open — should be named rather than
implied.
## Ordering
+16 -15
View File
@@ -32,7 +32,7 @@ kernel ──spawns──► init (PID 1) ──spawns──► device-manag
spawns only init, the service supervisor: the driver supervisor: enumerates
publishes the starts the system /system/devices, matches each device
initial-ramdisk services (device-manager, to a driver, and system_spawn's it
so user space can fat, logger, ...). Its
so user space can volume-manager, logger, ...). Its
system_spawn from it list is init policy.
```
@@ -43,7 +43,7 @@ the optional arguments its argv[1..], on a SysV entry stack, see sysv.md). Every
- **init** ([system/services/init](system/services/init/init.zig)) is the **service
supervisor**. It spawns the system services danos brings up at boot — today `input`,
the `device-manager`, `fat`, `display`, `display-demo`, and the `logger` — from a
the `device-manager`, `volume-manager`, `display`, `display-demo`, and the `logger` — from a
small list. Drivers are deliberately *not* its job. (An earlier draft listed a `vfs`
service here; that service is retired — the router moved into the kernel as
`fs_resolve`.)
@@ -66,9 +66,10 @@ the optional arguments its argv[1..], on a SysV entry stack, see sysv.md). Every
So "how is a driver discovered and configured" has two halves: **discovery** is the
kernel's device table, read by anyone; **configuration** is two user-space policies —
init's service list and the device-manager's match table. Both are hardcoded in their
respective programs today; the natural next step is to move them into `/etc` (see the
milestone notes in [driver-model.md](driver-model.md)). `system_spawn` is currently
init's service list and the device-manager's match table. Both are now data, not code —
init reads `/system/configuration/init.csv` and the device manager reads
`/system/configuration/devices.csv`, each parsed at startup (the compiled-in switch
tables are gone). `system_spawn` is currently
ungated — any process may spawn any bundled binary — because there is no spawn
capability yet.
@@ -301,12 +302,13 @@ process releases its claims and IRQ/MSI bindings — `releaseAllOwnedBy`,
- **Page granularity.** `mmio_map` rounds to 4 KiB. Two devices sharing a page means
granting one grants the other. A `device_register`ed child's *resource* can be narrower
than a page, but its *mapping* can't.
- **DMA is not contained.** A driver that can program a bus-mastering device can make
that device write to *any* physical address — page tables don't sit between a device
and RAM; an IOMMU does. The IOMMU is now *detected* (M16), but no translation domains
are programmed, so `device_claim` on a DMA-capable device is still effectively
equivalent to granting ring 0. This is the largest gap between the design's promise and
what it delivers; enforcement lands with the first DMA driver.
- **DMA is contained — except on a machine with no IOMMU.** A driver that can program a
bus-mastering device could make that device write to *any* physical address — page
tables don't sit between a device and RAM; an IOMMU does. `device_claim` now confines
each claimed PCI function into its own VT-d/AMD-Vi translation domain and rolls the
claim back with `-ECONFINE` if it can't (`system/kernel/process.zig`); DMA buffers are
bound into that domain and torn down at process death. The residual gap is fail-open:
where the machine exposes **no IOMMU at all**, a DMA-capable claim still reaches RAM.
- **No voluntary `dev_release`.** A *live* driver can't drop a claim — only exit
releases it (any path out of a process runs `releaseAllOwnedBy`) — so handing a
device between running drivers still means exiting.
@@ -365,10 +367,9 @@ $ python3 test/qemu_test.py device-manager acpi-ps2 pci-scan containment irqfree
## What's next (not done here)
The big driver-model pieces — capability passing (class drivers), DMA + barriers, MSI,
and IOMMU detection — are **now done** ([driver-model.md](driver-model.md), M13–M16), as
is **port I/O** (`io_read`/`io_write`, the claim-gated syscalls that make a PS/2 or 16550
driver possible). What's left is IOMMU *enforcement* (per-device domains — it waits on
the first DMA driver to protect and test against) and these smaller items:
and per-device IOMMU confinement — are **now done** ([driver-model.md](driver-model.md),
M13–M16), as is **port I/O** (`io_read`/`io_write`, the claim-gated syscalls that make a
PS/2 or 16550 driver possible). What's left is a handful of smaller items:
- **Releasing a claim** — half done. The kernel now drops *all* of a dead driver's
claims on every path out of a process (`releaseAllOwnedBy`, called from process
@@ -162,7 +162,7 @@ Without the boot-tree row the binary never reaches the image and the
device-manager has nothing to spawn. (The package also builds standalone:
`cd system/drivers/intel-uhd-graphics-750 && zig build`.)
## 3. Add the match rule to `etc/devices.csv`
## 3. Add the match rule to `system/configuration/devices.csv`
One row: bus, class triplet, vendor/device, driver path. **Copy the class
triplet from the pci-bus boot log line, not from another row** — for the iGPU
@@ -195,9 +195,9 @@ mapping — with zero risk to the hardware.
## 5. Verify the plumbing
- `zig build test` still passes.
- On the image: `/var/log/<boot-stamp>/system/services/device-manager.log`
- On the image: `/system/logs/<boot-stamp>/system/services/device-manager.log`
shows `spawned <name> for device <N>`, and
`/var/log/<boot-stamp>/system/drivers/<name>.log` holds the resource list and
`/system/logs/<boot-stamp>/system/drivers/<name>.log` holds the resource list and
your first read.
- If the driver did not spawn, diagnose in this order: binary on the image
(step 2) → CSV row matches the log line exactly (step 3) → path identical in