establishment: unplug reaps like death, so a replug rebinds
The hot-unplug path (onChildRemoved, a report from a live bus) cleared the child but left the bound class driver: a process blocked on reports that will never come, whose stale entry made the matcher's dedupe refuse the respawn when the device was plugged back in — the same wall the restart zombie hit, one path over. Unplug now reaps exactly like reporter death. The usb-hub-unplug case grows the replug: device_del the hub keyboard, then device_add it back (qmp_sequence); the ordered tail — child removed, reaping, delegated, ok — can only be satisfied by the second generation, since every boot keyboard's ok precedes the unplug. Discrimination: without the reap the replug never rebinds and the case times out (verified by stash run). Hub family, restart drill, and the two-controller proof all green (8/8).
This commit is contained in:
@@ -706,6 +706,12 @@ fn onChildRemoved(_: void, invocation: Invocation(device_manager_protocol.ChildR
|
|||||||
for (&children) |*child| {
|
for (&children) |*child| {
|
||||||
if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == invocation.sender) {
|
if (child.used and child.parent == report.parent and child.bus_address == report.bus_address and child.reporter == invocation.sender) {
|
||||||
std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
|
std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address });
|
||||||
|
// Unplug reaps exactly like reporter death (pruneChildrenOf): the
|
||||||
|
// bound driver's device is gone and it cannot observe that — it
|
||||||
|
// blocks on reports that will never come — and its stale entry
|
||||||
|
// would make the dedupe refuse the respawn when the device is
|
||||||
|
// PLUGGED BACK IN. Reap now, and a replug's re-report rebinds.
|
||||||
|
reapDriverBoundTo(child.device_id);
|
||||||
child.used = false;
|
child.used = false;
|
||||||
status = 0;
|
status = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-6
@@ -955,10 +955,16 @@ CASES = [
|
|||||||
"expect": r"(?s)(?=.*hub slot \d+ port \d+ device:.*0x0409)"
|
"expect": r"(?s)(?=.*hub slot \d+ port \d+ device:.*0x0409)"
|
||||||
r"(?=.*usb-hid-keyboard: ok \(device 3)",
|
r"(?=.*usb-hid-keyboard: ok \(device 3)",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# Hub-downstream disconnect (B4c): device_del the keyboard behind the hub;
|
# Hub-downstream disconnect AND replug (B4c + establishment): device_del the
|
||||||
# the hub's status-change endpoint reports it, the device is torn down
|
# keyboard behind the hub — the hub's status-change endpoint reports it, the
|
||||||
# (ChildRemoved + Disable Slot). QEMU's hub DOES raise downstream changes
|
# device is torn down (ChildRemoved + Disable Slot), and the manager REAPS
|
||||||
# (unlike root-port hot-plug).
|
# the bound class driver (it blocks on reports that will never come, and its
|
||||||
|
# stale entry would make the dedupe refuse the respawn). Then device_add
|
||||||
|
# plugs it back: the re-report re-registers the same identity and the
|
||||||
|
# matcher spawns a fresh driver, which binds — the ordered tail (reaping →
|
||||||
|
# child removed → delegated → ok) can only be satisfied by the SECOND
|
||||||
|
# generation, since the boot keyboards' ok lines all precede the unplug.
|
||||||
|
# QEMU's hub DOES raise downstream changes (unlike root-port hot-plug).
|
||||||
{"name": "usb-hub-unplug",
|
{"name": "usb-hub-unplug",
|
||||||
"build_case": "usb-hid",
|
"build_case": "usb-hid",
|
||||||
"smp": 4,
|
"smp": 4,
|
||||||
@@ -966,9 +972,17 @@ CASES = [
|
|||||||
"qemu_extra": ["-device", "qemu-xhci,id=xhci2",
|
"qemu_extra": ["-device", "qemu-xhci,id=xhci2",
|
||||||
"-device", "usb-hub,bus=xhci2.0,port=1",
|
"-device", "usb-hub,bus=xhci2.0,port=1",
|
||||||
"-device", "usb-kbd,bus=xhci2.0,port=1.1,id=dkbd"],
|
"-device", "usb-kbd,bus=xhci2.0,port=1.1,id=dkbd"],
|
||||||
"qmp_after": {"delay": 8, "command": "device_del", "arguments": {"id": "dkbd"}},
|
"qmp_sequence": [
|
||||||
|
{"delay": 8, "command": "device_del", "arguments": {"id": "dkbd"}},
|
||||||
|
{"delay": 14, "command": "device_add",
|
||||||
|
"arguments": {"driver": "usb-kbd", "bus": "xhci2.0", "port": "1.1", "id": "dkbd2"}},
|
||||||
|
],
|
||||||
"expect": r"(?s)(?=.*usb-hid-keyboard: ok \(device 3)"
|
"expect": r"(?s)(?=.*usb-hid-keyboard: ok \(device 3)"
|
||||||
r"(?=.*slot \d+ disconnected)",
|
r"(?=.*slot \d+ disconnected"
|
||||||
|
r"[\s\S]*device-manager: child removed"
|
||||||
|
r"[\s\S]*device-manager: reaping \S*usb-hid-keyboard"
|
||||||
|
r"[\s\S]*device-manager: delegated device \d+ to /system/drivers/usb-hid-keyboard"
|
||||||
|
r"[\s\S]*usb-hid-keyboard: ok)",
|
||||||
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
"fail": r"DANOS-TEST-RESULT: FAIL"},
|
||||||
# The kernel VFS root (M-F): the mount table serves the initrd at /system —
|
# The kernel VFS root (M-F): the mount table serves the initrd at /system —
|
||||||
# path resolution, node status/read (an ELF magic), and directory listing,
|
# path resolution, node status/read (an ELF magic), and directory listing,
|
||||||
|
|||||||
Reference in New Issue
Block a user