211 lines
12 KiB
Markdown
211 lines
12 KiB
Markdown
# M17–M18 execution plan: process lifecycle + device manager
|
||
|
||
**Archived — completed 2026-07-13** (every item checked; suite ended 54/54).
|
||
Kept as the record of how M17–M18 landed; the successor is
|
||
[m19-m20-plan.md](m19-m20-plan.md).
|
||
|
||
The operational plan for building [process-lifecycle.md](process-lifecycle.md)
|
||
(M17) and [device-manager.md](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.
|
||
|
||
- [x] **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).
|
||
- [x] **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)
|
||
- [x] **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)
|
||
- [x] **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)
|
||
- [x] **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)
|
||
- [x] **merge** `feat/process-lifecycle` → main, push (merged 2026-07-13)
|
||
- [x] **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)
|
||
- [x] **merge** `feat/device-manager` → main, push (merged 2026-07-13)
|
||
- [x] **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)
|
||
- [x] **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)
|
||
- [x] **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 notifications** — `timer_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.
|