child_added/child_removed join the device-manager protocol. The driver maps its register BAR (resource 0 is the ECAM config space; the walk starts at 1), reads CAPLENGTH and HCSPARAMS1, and reads one PORTSC per port: the connect bit and speed class come straight from hardware, no rings needed to see the devices. The manager mirrors reported children keyed by (parent, port), remembers which instance reported each, and prunes a dead reporter's children before deciding the restart — the children describe protocol state that died with the process. The usb-report scenario drives the whole loop: two QEMU devices reported, reporter killed, children pruned, driver respawned with backoff, and the new instance re-claims, re-scans, and re-reports.
11 KiB
M17–M18 execution plan: process lifecycle + device manager
The operational plan for building process-lifecycle.md (M17) and device-manager.md increments 5–7 (M18). Design is settled in those documents; this file is the build order — one phase at a time, each phase green before the next starts. Delete or archive this file when M18 lands.
Definition of green, every phase: zig build clean, zig build test clean,
python3 test/qemu_test.py passes (existing scenarios plus the phase's new one),
and the relevant design doc's "known gaps" / status lines updated. Commit per
green phase (no co-author trailers).
Workflow (settled 2026-07-12): work happens in a dedicated git worktree, on
feature branches cut from main — feat/process-lifecycle (M17.1–17.4),
feat/device-manager (M18.1), feat/usb-xhci-bus (M18.2–18.3). When a branch's
phases are all green it is auto-merged into main; branches are kept after
merge, not deleted. Merges and branches are pushed to origin. Phase 0 (once):
commit the design docs, merge the outstanding feat/usb work into main, and
run the existing QEMU suite green before any new work starts.
Numbering note: continues the milestone sequence (driver track ended at M16).
Status
The loop marks a phase [x] in the same commit that lands it. A phase is marked
only when its definition of green holds.
- Phase 0 — baseline: docs committed, feat/usb merged to main, pushed;
usb-xhci-libary.zigrenamed tousb-xhci-library.zig; existing QEMU suite green from the worktree (48/48, 2026-07-12). - M17.1 — kernel releases claims/MSI on death (claims:
releaseAllOwnedByin the reap; MSI was already swept byirq.releaseOwner;claim-releasetest; suite 49/49) - M17.2 — exit reasons (
ExitReasonrecorded at exit/fault/kill before the notification;process_exit_reasonsupervisor-gated;runtime.process.exitReason; kernel + ring-3 assertions; suite 49/49) - M17.3 — published exit events + VFS subscriber (
process_subscribe, bounded ref-counted table, publish on every death;runtime.process.subscribeExits; VFS handles carry owners and are swept on the owner's death;vfs-client-deathtest; suite 50/50) - M17.4 — signals, timer notifications,
runtime.process, the service harness (signal_bind/process_signal + coalescing pending mask; timer_bind on the tick; bindSignals/signalsFrom/sendSignal/stop + timerOnce; runtime.service.run with the zero-length ping; VFS converted;signalsscenario; suite 51/51) - merge
feat/process-lifecycle→ main, push (merged 2026-07-13) - M18.1 — device-manager protocol: hello + restart policy
(device-manager-protocol module; the manager as a harness service:
supervised spawns, hello deadline via timer sweep, restart with
300/600/1200ms backoff, exit reasons deciding restart-vs-stopped,
crash-loop cap; usb-xhci-bus first conforming driver; crash-test fixture
re-proving claim release each respawn;
driver-restartscenario; maximum_tasks 16→32 — the sweep was overflowing the pool; suite 52/52) - merge
feat/device-manager→ main, push (merged 2026-07-13) - M18.2 — xHCI port scan + tree reports (child_added/child_removed in
the protocol; the manager's child mirror with death-pruning; xHCI maps the
register BAR — resource 0 is ECAM — reads CAPLENGTH/HCSPARAMS1, scans
PORTSC, reports connected ports with speed-class identity;
usb-reportscenario proves report → prune → respawn → re-report; suite 53/53) - M18.3 — app surface: enumerate/subscribe + device-list
- merge
feat/usb-xhci-bus→ main, push — loop ends here
M17.1 — the kernel releases a dead process's claims
The cleanup half of iron rule 1; the prerequisite for every restart story.
system/kernel/devices-broker.zig:releaseAllOwnedBy(owner: u32)— clear everyclaimed[]slot holding this task id.system/kernel/process.zig: call it from the reap path, alongside the existing IRQ-binding release (the ordering comment there says why IRQs go first — claims slot in after them, before the exit notification).- MSI vectors: find where
msi_bindrecords per-device vectors (interrupts module) and release those by owner in the same pass. - Docs: remove the claims bullet from process-management.md "Known gaps".
Test: new QEMU scenario claim-release — a test child claims an unclaimed
device, is killed, is respawned, and claims the same device again successfully;
assert both claims in the serial log. Kernel-side unit coverage in
system/kernel/tests.zig for releaseAllOwnedBy (claim two devices as two owners,
release one owner, verify exactly its claims freed).
M17.2 — exit reasons
system/abi.zig:ExitReason(exited, aborted, segmentation_fault, illegal_instruction, arithmetic_fault, killed).- Kernel: record the reason at every death site — clean exit path, each fault
class in
onException, the kill path. Bounded recent-exits table (ids are never reused, so a small ring keyed by id is enough). - New system call
process_exit_reason(id)— supervisor-gated, like kill; returns the recorded reason or-ESRCHonce evicted. library/runtime/process.zig:ExitReason+exitReason(id: u32).- Docs: remove the no-exit-status bullet from process-management.md.
Test: extend the supervision scenario — three children: one exits cleanly,
one faults (the fault-recovery pattern), one is killed; the supervisor asserts all
three reasons.
M17.3 — published exit events
- Kernel: bounded subscriber table (endpoints); new system call
process_subscribe(endpoint)(ungated, likeprocess_enumerate); every death postsnotify_exit_bit | idto each subscriber — the same post the supervisor path already uses. library/runtime/process.zig:subscribeExits(endpoint).- VFS becomes the first subscriber: on an exit event, release every handle keyed by that task id (badges already are task ids). Log the release.
- Docs: note the convention in ipc.md (exit events reuse the exit-notification badge encoding).
Test: new QEMU scenario vfs-client-death — a client opens a file and is
killed without closing; assert the VFS logs the handle release and its open-handle
count returns to baseline.
M17.4 — signals and the service harness
- Kernel: per-task pending mask + bound endpoint; system calls
signal_bind(endpoint)andprocess_signal(id, signal)(supervisor-or-self gated); delivery postsnotify_signal_bit | pending mask, coalescing; pending signals with no bound endpoint pend silently. library/runtime/process.zig:Signal,SignalSet,bindSignals,signalsFrom,sendSignal,stop(id, deadline_ms)(terminate → wait for exit notification → kill). Implementterminate,reload,user_1,user_2;interrupt/quitare enum members with no sender yet;alarmstays unbuilt.- Kernel: one-shot timer notifications —
timer_bind(endpoint, ms)posts a notification badge when the deadline lands (IRQ-as-IPC again, on the timer wheelsleepalready uses). This is the missing timed-wait primitive:replyWaitblocks forever andsleepblocks the whole process, butstop()'s escalation, the device manager'shellodeadline (M18.1), and restart backoff all need a deadline while staying responsive. It is also the mechanismalarmgets for free later. - New
library/runtime/service.zig: the harness —run(callbacks)owning the replyWait loop, folding protocol messages, signals, and child-exit notifications intoinit/on_message/on_reload/on_terminate; answers the commonpingautomatically. Define the reservedpingrequest encoding here and document it in ipc.md (one obvious encoding; smallest that cannot collide with existing protocols). - Convert one existing service (input-source or hpet) to the harness as proof it subtracts code rather than adding it.
Test: extend supervision — a harness-built child: sendSignal(reload)
observed in its log, ping answered, stop() produces a clean exit with reason
exited; a second child that ignores signals (no bind) is killed by stop()'s
deadline with reason killed.
M18.1 — device-manager protocol: hello + restart policy
- New
system/services/device-manager/device-manager-protocol.zigmodule (vfs-protocol pattern):hello { version, role, device_id }; version constant; reserved fields. - Device manager: register the
.device_managerendpoint; spawn drivers with its exit endpoint; enforce the hello deadline; restart policy — backoff, crash-loop cap (three fast deaths → mark failed, log, stop), reasons from M17.2 deciding restart vs not. - usb-xhci-bus: adopt the harness + send hello. hpet/ps2-bus follow only if the conversion is mechanical; otherwise they keep working unconverted (the manager only enforces hello on drivers spawned with an assignment).
- build.zig: test-loop entry for the protocol module if it grows pure logic.
Test: new QEMU scenario driver-restart — the xHCI driver takes a test-only
argv flag to fault after hello on its first run; assert: fault, exit reason
recorded, manager respawns with backoff, second run claims the controller
(M17.1) and hellos clean. Assert the crash-loop cap by a driver that always
faults (a tiny test driver, not xhci).
M18.2 — bus tree reports
- Protocol:
child_added { parent, identity, resources }/child_removed { id }. - usb-xhci-bus: bring-up to port scan only — map the MMIO window (claimed in
M16-era work), controller reset/start per xHCI spec, walk the port registers,
report one
child_addedper connected port with speed + port number as identity. No transfer rings, no descriptors — reading device/interface descriptors (and therefore USB class triples for matching) is the follow-on USB track, not this plan. - Device manager: mirror reports into its tree; prune the subtree (emitting
child_removed) when a bus driver dies; assert re-report on restart.
Test: QEMU already attaches usb-kbd + usb-mouse on xhci.0 — assert two
child_added events reach the manager and appear in its tree dump; kill the
driver, assert two child_removed then two fresh child_added after respawn.
M18.3 — the application surface
- Protocol:
enumerate(tree snapshot) +subscribe(published add/remove events, input-service pattern). - A small client (
device-list, thepsanalog) exercising both; the manager becomes the one answer to "what devices exist" for user space.device_enumeratestays for drivers/kernel seeding — its retreat is tied to the discovery migration, out of this plan.
Test: QEMU scenario — device-list shows the tree including USB children;
during a driver restart the subscribing client logs remove + add events.
Explicitly out of scope (own tracks, after M18): discovery migration (pci-bus
driver, acpi service, retiring the kernel scan), USB control transfers +
descriptors + class-driver matching, the musl layer, interrupt/quit senders
(needs a console), job control.