Files
danos/docs/hot-plug-matrix-plan.md
T

3.7 KiB
Raw Blame History

The hot-plug matrix: unplug anything, replug anywhere

Status: COMPLETE, 2026-08-09, same day — H1..H5 all green, full suite 124/124. The predicted cascade bug was real and found by H2: tearDownPort did not recurse into a hub yanked from a root port, so its subtree stayed live against vanished hardware and the replugged hub found its port occupied; fixed by mirroring tearDownHubDevice's children-first recursion (77e7001). Every other cell passed against the establishment-track machinery unchanged.

2026-08-09. The requirement, verbatim: "i want to be able to unplug devices and plug them back in, in any order and on any hub." The machinery exists end to end — per-device driver processes, cascading teardown (tearDownHubDevice recurses a departing hub's subtree, children first), reap-on-removal in the manager, idempotent per-port registration identity, lineage rebinding — but only one cell of the matrix is verified: a leaf keyboard behind a hub, unplugged and replugged on the same port (usb-hub-unplug). This plan verifies the rest and fixes what verification flushes out. The cascade path has never executed; expect it to carry at least one bug.

Out of scope, stated up front: unplugging the BOOT stick (the drivers handle it; fat holds a dead block channel until M21 remount gives it re-acquisition), and real-hardware root-port timing (the Ryzen bench is the acceptance run for that, as always — QEMU proves the logic, not the silicon).

The discrimination lever

Every case's rebind tail (— reaping → delegated → second-generation ok) is impossible without the manager's reap: stashing the reap out of onChildRemoved/pruneChildrenOf makes every matrix case fail at the dedupe wall. That is the standing discrimination check for the whole matrix — run it once per case shape, not per commit.

The matrix

Case Topology Event sequence What it proves
H1 usb-root-replug keyboard on a 2nd controller's ROOT port del @8s, add @14s (same port) the tearDownPort path + reap + rebind; root-port changes arrive via the 250 ms reconcile tick (QEMU raises no root-port events)
H2 usb-hub-yank hub on 2nd controller; keyboard AND mouse behind it del the HUB @8s; re-add hub @14s, kbd @17s, mouse @18s the recursive cascade: one event removes the subtree, EVERY bound driver is reaped, and the rebuilt hub re-binds both
H3 usb-hub-nested-yank hub → hub → keyboard del the OUTER hub @8s; re-add all three @14–18s the cascade recursion depth ≥ 2, and a nested rebuild
H4 usb-replug-moved keyboard behind hub port 1.1 del @8s; add on port 1.2 @14s a replug on a DIFFERENT port is simply a new device: new port identity, new id, fresh match — no stale state ties a driver to the old port
H5 usb-replug-cycles keyboard behind hub del/add three times (6 hooks) no state leaks across generations: slots, the bus's open table, driver entries (reaped entries must be reusable)

Ordered expects follow the usb-hub-unplug shape: the boot devices' ok lines all precede the first unplug, so a tail of removed → reaping → delegated → ok can only be satisfied by the generation the sequence created. H2/H3 require one reaping per bound child; H5 requires three.

Order and discipline

H1 → H2 → H3 → H4 → H5, one commit per case (plus its fix if it finds a bug — the case and the fix land together, the case having failed first). The standing discrimination run once for H1 and once for H2's shape. Full suite green at the end, docs touched where behavior was corrected, memory updated. Fixes stay within the settled design: teardown order (children first), reap-on-removal, identity per port — anything design-shaped that surfaces stops the loop and asks.