diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index b4c2cf9..edf0b1f 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -212,16 +212,41 @@ fn addChild(parent: u64, bus_address: u64, identity: u64, device_id: u64, report /// protocol state (slots, rings) that died with the process — keeping the nodes /// would be keeping a lie. The restarted instance rediscovers and re-reports. /// Watchers hear the honest story: removed now, added again on rediscovery. +/// +/// **A reporter's death reaps its subtree.** The class drivers spawned for the +/// pruned children hold channels into the dead process; they cannot observe +/// the death themselves (an HID driver blocks on interrupt reports that will +/// simply never come — a silent zombie), and their still-`used` entries would +/// make the matcher's dedupe refuse the respawn when the re-report arrives. +/// So each is killed and its entry cleared: the re-report spawns a fresh +/// instance, whose hello fetches the successor's channel. That is the restart +/// story working — kill a bus driver and only its subtree blinks +/// (docs/establishment-planes-plan.md P3). fn pruneChildrenOf(reporter: u32) void { for (&children) |*child| { if (child.used and child.reporter == reporter) { std.log.info("child removed (device {d} port {d})", .{ child.parent, child.bus_address }); + reapDriverBoundTo(child.device_id); child.used = false; Serve.publish(.child_removed, 0, .{ .parent = child.parent, .bus_address = child.bus_address }); } } } +/// Kill the driver bound to a pruned child and clear its entry — the exit +/// notification that follows finds no entry and is ignored, so the death is +/// never double-counted, and the freed entry is what lets the re-report +/// respawn. The device it was given returns to us by the kernel's loan rule. +fn reapDriverBoundTo(device_id: u64) void { + const driver = driverEntryForDevice(device_id) orelse return; + if (driver.process_id != 0) { + std.log.info("reaping {s} (its provider died)", .{driver.name()}); + _ = process.kill(driver.process_id); + } + if (driver.endpoint) |endpoint| _ = ipc.close(endpoint); + driver.* = .{}; +} + /// How many children a driver instance has reported (the test-usb-restart /// trigger counts these). fn childCountOf(reporter: u32) u32 { diff --git a/test/qemu_test.py b/test/qemu_test.py index 1fcc6ad..402d9ca 100644 --- a/test/qemu_test.py +++ b/test/qemu_test.py @@ -606,9 +606,17 @@ CASES = [ "smp": 4, "expect": r"DANOS-TEST-RESULT: PASS", "fail": r"DANOS-TEST-RESULT: FAIL"}, - # M18.2: bus tree reports — the xHCI driver scans its root-hub ports and - # reports both QEMU devices; the manager mirrors, prunes on the reporter's - # death, and the respawned driver re-reports (docs/device-manager.md). + # M18.2 + establishment P3: bus tree reports, and the restart REBUILDING + # the subtree. The manager prunes on the reporter's death — reaping the + # class drivers bound to the pruned children, which hold channels into the + # dead process and cannot observe the death themselves — and when the + # respawned instance re-reports, the matcher spawns fresh class drivers + # whose hellos fetch the successor's channel. The tail asserts the subtree + # WORKS again: the respawned usb-storage opens its device on the NEW bus + # instance and reads block 0. (The HID "ok" lines never appear in this + # scenario — it boots no input service — so storage is the functional + # proof.) Before the reap existed, the stale entries blocked the respawn + # and the survivors were silent zombies, so this tail could not match. {"name": "usb-report", "smp": 4, "timeout": 150, @@ -617,9 +625,13 @@ CASES = [ "expect": r"device-manager: child added[\s\S]*" r"device-manager: child added[\s\S]*" r"device-manager: test mode: killing the reporter[\s\S]*" + r"device-manager: reaping \S*usb-storage[\s\S]*" r"device-manager: child removed[\s\S]*" r"device-manager: restarting \S*usb-xhci-bus[\s\S]*" - r"device-manager: child added", + r"device-manager: child added[\s\S]*" + r"device-manager: delegated device \d+ to /system/drivers/usb-storage[\s\S]*" + r"usb-storage: ready[\s\S]*" + r"usb-storage: block 0 signature 0x55aa", "fail": r"DANOS-TEST-RESULT: FAIL"}, # USB HID end to end: boot the full tree, enumerate the xHCI, and let the # manager spawn the USB keyboard driver, which opens its device over the