# The device manager **Status: the protocol and supervision are built** (M18.1, 2026-07-13): `hello` with its deadline, supervised spawn, restart with backoff, and the crash-loop cap are in — usb-xhci-bus is the first conforming driver, and the `driver-restart` scenario proves fault → backoff → re-claim → cap end to end. Tree reports are built too (M18.2, 2026-07-13): the xHCI driver scans its root-hub ports and reports each connected device (`child_added`); the manager mirrors them and prunes a dead reporter's children, and the `usb-report` scenario proves report → prune → respawn → re-report. The application surface is built (M18.3, 2026-07-13): `enumerate` and `subscribe` over IPC, with `device-list` as the first client — the manager is now the one answer to "what devices exist" for applications. The primitives underneath are real ([process-management.md](process-management.md): spawn/supervise/kill/exit-notification; [driver-model.md](driver-model.md): the device table as a capability system; [drivers.md](drivers.md): claim/map/IRQ), and the first per-device driver spawn works (the device manager matches the xHCI controller by PCI class and spawns `usb-xhci-bus` with the device id as argv[1]). This document designs the rest: the device manager as **the tree, the matcher, and the supervisor** — the policy process that turns [resilience.md](resilience.md)'s restart goal into practice for drivers. How processes stop, reload, and report their deaths is deliberately **not** in this document: that is the universal lifecycle every danos process speaks — [process-lifecycle.md](process-lifecycle.md), signals over IPC and the stable `runtime.process` interface. The device manager is that design's first serious customer, not its owner. Its own protocol contains nothing lifecycle-shaped; a driver is stopped, health-checked, and buried exactly like any other process. ## The tree: structure in the manager, authority in the kernel The device tree is two things fused: *information* (what exists, how it nests) and *authority* (a descriptor is a licence to map physical memory). They separate: - The **kernel keeps the capability system** — device, I/O-port, and interrupt claims, resource containment on `device_register`, the `mmio_map`/`irq_bind`/`msi_bind` gates — and **cleans all of it up when a process dies** (settled; it is increment 1 of [process-lifecycle.md](process-lifecycle.md)). The three invariants in [driver-model.md](driver-model.md) stay exactly where they are. A device manager that could mint MMIO mappings by its own say-so would be a second kernel, and a buggy one would un-earn everything the microkernel bought. - The **device manager owns the tree as data** — identity, topology, naming, driver matching, hotplug events, and being the one process everything else asks about devices. Firmware discovery seeds it (today via the kernel's snapshot); **bus drivers grow it** by reporting what they see; applications query and watch it. `device_enumerate` fades to a manager-internal (then deleted) seam. Long-term, discovery itself leaves the kernel — but not *into* the manager. PCI enumeration is a **pci-bus driver**: the manager spawns it against the host bridge (already a device with the ECAM window as a resource), it scans, it reports functions like any bus reports children. ACPI becomes an **acpi service** that interprets the tables and reports the namespace. The manager only orchestrates and merges. Moving AML interpretation out of ring 0 is its own project on its own track; nothing here depends on when it lands. ## The protocol A `device-manager-protocol` module (the vfs-protocol pattern): extern-struct messages, a version in the handshake, reserved fields everywhere. The manager is a well-known endpoint (`ipc.register(.device_manager)`); the badge tells it who is talking; the same endpoint receives its children's exit notifications — one loop, one world. | Direction | Message | Purpose | |---|---|---| | driver → manager | `hello { version, role, device_id }` | confirms the argv assignment, starts the deadline clock | | bus → manager | `child_added { parent, identity, resources }` | one node the bus discovered | | bus → manager | `child_removed { id }` | unplug, or the bus lost it | | app → manager | `enumerate` | snapshot of the tree (read-only) | | app → manager | `subscribe` | receive published add/remove events | `hello` is the one deadline the manager enforces itself: spawned and silent past the deadline means wrong binary, wrong protocol version, or wedged before main — apply the stop sequence and the restart policy. Everything else lifecycle-shaped (terminate, the common `ping` liveness call, exit reasons) arrives through [process-lifecycle.md](process-lifecycle.md)'s vocabulary, not this protocol. Assignment stays argv (`usb-xhci-bus `) for now — simple, and it works. The step after `hello` exists is delegation: the manager claims (or is granted) the devices and passes the claim to the driver over IPC (the M13 capability-transfer mechanism), replacing first-come-first-served `device_claim` with policy. Identity in `child_added` is per-bus: PCI children carry the class triple (`pci_class`, as the xHCI match already uses); USB children carry the (class, subclass, protocol) triple from usb-ids.zig — each bus's native language, decoded by the shared ids modules. ## Supervision and restart Every driver is spawned with the manager's exit endpoint (`spawnSupervised` — built). On a death notification: 1. **Read the reason** ([process-lifecycle.md](process-lifecycle.md) increment 2). Clean exit → it meant to; don't restart. Fault or missed `hello` deadline → restart with **backoff**, and a crash-loop cap (three fast deaths → mark failed, stop respawning, log loudly; a later `reload` to the manager can retry). 2. **Prune the subtree** the dead bus driver reported. Its children describe protocol state (xHCI slot ids, transfer rings) that died with the process; keeping the nodes would be keeping a lie. Watchers receive `child_removed` — the input service losing, then regaining, a keyboard is the *honest* description of what happened. The restarted instance rediscovers and re-reports. 3. **The claim is already free** because the kernel released it at death — the restarted instance claims the same controller and comes up. Who supervises the supervisor: **init** (PID 1), which already supervises the services it starts. If the manager dies, drivers keep running (they hold their claims; the kernel doesn't care who their supervisor was — though their exit notifications now dangle harmlessly). The restarted manager re-learns the world: kernel snapshot, then a re-`hello` round — drivers answer a broadcast or are stopped and respawned. Full state handoff is deliberately not attempted. ## Thin drivers, class protocols The [driver-model.md](driver-model.md) three-shape split, restated as processes: - A **bus driver** (usb-xhci-bus) owns its controller — claim, MMIO, IRQ/MSI, DMA rings — and offers a *transfer* protocol ("submit a control transfer to device N", built from the usb-abi request constructors) plus tree reports to the manager. - A **class driver** (usb-hid, usb-storage) owns nothing: it is matched to a reported child by its identity triple, speaks the bus's transfer protocol downward and its service's protocol upward — HID reports to the input service, blocks to the block service. It works unchanged over any controller. - **Services** (input, display, block) aggregate class drivers and face applications. Each arrow is a protocol module. The manager routes none of the data plane — it introduces the parties (matching), supervises them (lifecycle), and gets out of the way. ## Increments Increments 1–4 are the lifecycle prerequisites and live in [process-lifecycle.md](process-lifecycle.md) (claim cleanup on death, exit reasons, published exit events, signals + `runtime.process`). On top of those: 5. **device-manager-protocol**: `hello`, supervised spawn with restart policy; usb-xhci-bus becomes the first conforming driver. 6. **Tree reports**: `child_added`/`child_removed`; the manager mirrors; xHCI reports the mouse and keyboard QEMU already hangs off it. 7. **App surface**: `enumerate`/`subscribe` over IPC; `device_enumerate` retreats to a manager-internal seam. 8. **Discovery migration**: pci-bus driver first, acpi service second, kernel scan retired last. (AML-in-user-space is its own track.) ## Settled questions (2026-07-12) - **Stateful buses**: pruning the subtree on bus-driver death is right for USB. A future storage bus with in-flight writes wants drain-before-terminate — which is exactly the `deadline_ms` parameter `stop()` already has; a per-driver deadline is one value in the manager's policy table when such a bus arrives. No design change. - **Manager death**: drivers survive the manager; the restarted manager re-learns the world (above). Checkpointing driver state with the manager is deferred until something demonstrates the need. - **Matching stays code until the third bus.** `driverFor`/`pciDriverFor` are honest at two bus types; the third triggers the manifest (a driver declares what it binds: a PCI class triple, a USB class triple, an ACPI `_HID`).