docs: document the supervision hierarchy and driver discovery

drivers.md gains a "How a driver gets started" section — the doc explained what a
running driver does but never who starts it. It lays out the three-level supervision
hierarchy (kernel spawns init; init spawns the services; the device-manager discovers,
matches, and spawns the drivers) and names the two user-space policies that "configure"
drivers today: init's service list and the device-manager's match table.

driver-model.md's "what exists today" adds system_spawn and the supervision model, and
the drivers.md restart bullet is refreshed: a spawning supervisor now exists, a
restarting one still doesn't.
This commit is contained in:
Daniel Samson
2026-07-10 18:48:26 +01:00
parent 56110b0019
commit 8eb4210251
2 changed files with 57 additions and 5 deletions
+8 -2
View File
@@ -132,9 +132,15 @@ If a class driver needs `mmio`, it has become an HCD and should be one.
- **M11** — `irq_bind` / `irq_ack`. IRQ delivered as an IPC notification; mask before
EOI; `irq_ack` is the unmask.
- **M12** — `parent` in `DeviceDesc`, `device_register` with resource containment.
- **`system_spawn`** — a user-space supervisor starts a driver: `system_spawn(name)`
loads a binary bundled in the initial-ramdisk as a fresh ring-3 process. This is what
turned the device manager from "log the match" into "run the driver": the kernel now
spawns only `init`, `init` spawns the services, and the **device-manager** discovers
the hardware and spawns each driver ([drivers.md](drivers.md)). Ungated for now — a
spawn capability is future work.
So: **bus drivers work now.** HCDs and class drivers do not. Here is exactly why, and
exactly what would fix it.
So: **bus drivers work now, and they're started by the device manager, not the kernel.**
HCDs and class drivers do not work yet. Here is exactly why, and exactly what would fix it.
---
+49 -3
View File
@@ -20,6 +20,49 @@ them for itself:
A driver is, in one sentence, *a process that sleeps until its device has something to
say.*
## How a driver gets started: discover, match, spawn
Nothing in the kernel decides that the HPET needs the `hpet` driver — that is policy,
and policy lives in user space. Boot brings user space up as a three-level supervision
hierarchy, each level owning one job:
```
kernel ──spawns──► init (PID 1) ──spawns──► device-manager ──spawns──► hpet
| | |
spawns only init, the service supervisor: the driver supervisor: enumerates
publishes the starts the system /system/devices, matches each device
initial-ramdisk services (vfs, the to a driver, and system_spawn's it
so user space can device-manager). Its
system_spawn from it list is init policy.
```
The kernel launches exactly one process — `init` — and hands it nothing but the raw
ability to start more (`system_spawn(name)`, which loads a binary bundled in the
initial-ramdisk as a fresh ring-3 process). Everything else is a user-space decision:
- **init** ([system/services/init](system/services/init/init.zig)) is the **service
supervisor**. It spawns the system services danos brings up at boot — today `vfs` and
the `device-manager` — from a small list. Drivers are deliberately *not* its job.
- **device-manager** ([system/services/device-manager](system/services/device-manager/device-manager.zig))
is the **driver supervisor**. It does the three steps a monolithic kernel would do in
its probe path, entirely from ring 3:
1. **Discover** — `device_enumerate` snapshots the device table the kernel built from
ACPI/PCI ([discovery](discovery.md)).
2. **Match** — for each device it looks up a driver by `DeviceClass`. The match policy
is a table (`driverFor`): today a static `timer → hpet` map; a fuller system reads
what each driver *binds* (a manifest under `/system/drivers`, or the driver
describing its own match).
3. **Spawn** — `system_spawn(driver_name)` starts the matched driver, which then claims
its device and runs the event loop below.
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
ungated — any process may spawn any bundled binary — because there is no spawn
capability yet.
## The capability: claim before touch
The driver syscall numbers (`system/abi.zig`) with the device types they carry
@@ -312,9 +355,12 @@ controller drivers), and the IOMMU — have proposed signatures in
- **Unregistering children.** `device_register` only appends. A USB device that is
unplugged cannot be removed, and a bus driver in a loop can exhaust the 64-entry
table.
- **Restart.** A driver that dies should release its claim, have its device quiesced,
and be respawned by a supervisor. Some pieces (`releaseIrqs`, `device_grant`
teardown, the claim table) exist; the policy doesn't.
- **Restart.** A supervisor that *spawns* drivers now exists — the device-manager starts
them with `system_spawn` — but a supervisor that *restarts* them does not. A driver that
dies should release its claim, have its device quiesced, and be respawned; today nothing
notices the death. Some pieces (`releaseIrqs`, `device_grant` teardown, the claim table)
exist, and `dev_release` (below) is the missing mechanism; the restart policy is the
resilience track ([resilience.md](resilience.md)).
- **Interrupt priority / threaded IRQ latency.** `notifyFromIsr` enqueues the woken
driver but doesn't preempt (`wakeLocked` deliberately leaves that to the caller), so
a woken driver waits for the next scheduling point.