danos/docs/m17-m18-plan.md

12 KiB
Raw Blame History

M17M18 execution plan: process lifecycle + device manager

Archived — completed 2026-07-13 (every item checked; suite ended 54/54). Kept as the record of how M17M18 landed; the successor is m19-m20-plan.md.

The operational plan for building process-lifecycle.md (M17) and device-manager.md increments 57 (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 mainfeat/process-lifecycle (M17.117.4), feat/device-manager (M18.1), feat/usb-xhci-bus (M18.218.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.zig renamed to usb-xhci-library.zig; existing QEMU suite green from the worktree (48/48, 2026-07-12).
  • M17.1 — kernel releases claims/MSI on death (claims: releaseAllOwnedBy in the reap; MSI was already swept by irq.releaseOwner; claim-release test; suite 49/49)
  • M17.2 — exit reasons (ExitReason recorded at exit/fault/kill before the notification; process_exit_reason supervisor-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-death test; 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; signals scenario; 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-restart scenario; 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-report scenario proves report → prune → respawn → re-report; suite 53/53)
  • M18.3 — app surface: enumerate/subscribe over IPC (subscriber endpoint rides as the call's capability; events are the same structs the buses send); device-list first client; protocol capped at the kernel's IPC MESSAGE_MAXIMUM (256); the startUserTask debug print removed — it sheared concurrent serial lines and was the scenario-flake root cause; device-list scenario; suite 54/54)
  • merge feat/usb-xhci-bus → main, push (merged 2026-07-13) — plan complete

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 every claimed[] 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_bind records 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 -ESRCH once 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, like process_enumerate); every death posts notify_exit_bit | id to 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) and process_signal(id, signal) (supervisor-or-self gated); delivery posts notify_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). Implement terminate, reload, user_1, user_2; interrupt/quit are enum members with no sender yet; alarm stays unbuilt.
  • Kernel: one-shot timer notificationstimer_bind(endpoint, ms) posts a notification badge when the deadline lands (IRQ-as-IPC again, on the timer wheel sleep already uses). This is the missing timed-wait primitive: replyWait blocks forever and sleep blocks the whole process, but stop()'s escalation, the device manager's hello deadline (M18.1), and restart backoff all need a deadline while staying responsive. It is also the mechanism alarm gets for free later.
  • New library/runtime/service.zig: the harness — run(callbacks) owning the replyWait loop, folding protocol messages, signals, and child-exit notifications into init / on_message / on_reload / on_terminate; answers the common ping automatically. Define the reserved ping request 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.zig module (vfs-protocol pattern): hello { version, role, device_id }; version constant; reserved fields.
  • Device manager: register the .device_manager endpoint; 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_added per 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, the ps analog) exercising both; the manager becomes the one answer to "what devices exist" for user space. device_enumerate stays 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.