docs: the hot-plug matrix plan — unplug anything, replug anywhere
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# The hot-plug matrix: unplug anything, replug anywhere
|
||||
|
||||
*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.
|
||||
Reference in New Issue
Block a user