From 0b10a637ae1df2b741bef51a3e54a5c650f76bc7 Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:06:49 +0100 Subject: [PATCH] =?UTF-8?q?usb:=20poll=20root=20ports=20on=20the=20tick=20?= =?UTF-8?q?=E2=80=94=20catch=20a=20late=20USB2=20connection=20(edge-trigge?= =?UTF-8?q?red)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real-hardware root-cause: the user's keyboard works in the firmware boot menu but dies under danos. The port dump shows why — danos scans the root ports ~3 ms after the controller reset, but a USB2 connection needs ~100 ms to debounce, so the boot scan sees the USB2 companion hub's port empty (all USB2 ports ccs=0), while the SuperSpeed devices (hub, storage) train instantly and DO show up. danos then relied on a Port Status Change EVENT to catch the late USB2 connection, which does not fire reliably on this hardware. The tick now polls every root port and reconciles on the empty->connected EDGE (previous-state tracked per port, seeded from the boot scan so already-up ports never re-fire), bringing up a device that appears after the scan without depending on the event. Edge-triggered so a port that fails to enumerate is not retried every 8 ms. Branch-only until the keyboard is confirmed on real hardware; the diagnostic dumps stay for the next boot. --- system/drivers/usb-xhci-bus/usb-xhci-bus.zig | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig index eef7f91..c2c5f8b 100644 --- a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig +++ b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig @@ -189,6 +189,10 @@ fn speedName(speed: u32) []const u8 { /// class driver against. var manager_handle: ?runtime.ipc.Handle = null; +// Per-root-port connected state from the previous tick, so the poll acts on +// empty->connected transitions (edge), never re-attempting a level every tick. +var prev_connected: [64]bool = [_]bool{false} ** 64; + fn scanPorts(manager: runtime.ipc.Handle) void { const engine = if (controller) |*c| c else { _ = runtime.system.write("/system/drivers/usb-xhci-bus: controller not initialised\n"); @@ -204,6 +208,7 @@ fn scanPorts(manager: runtime.ipc.Handle) void { var connected: u32 = 0; while (port <= engine.max_ports) : (port += 1) { if (!engine.portConnected(port)) continue; + if (port < prev_connected.len) prev_connected[port] = true; // don't re-fire the poll for these connected += 1; bringUpPort(manager, engine, port); } @@ -495,6 +500,29 @@ fn onNotification(badge: u64) void { if (badge & runtime.ipc.notify_timer_bit == 0) return; if (controller) |*engine| { engine.pump(); + // Poll every root port and reconcile — a device present but not yet + // enumerated is brought up; a device gone is torn down. This does NOT + // depend on a Port Status Change EVENT firing: the boot scan runs ~3 ms + // after the controller reset, far too early for a USB2 connection to + // debounce (~100 ms), and the SuperSpeed devices that DO show up early + // proved the event path unreliable for the late USB2 companion hub on + // real hardware. Polling catches it on the next tick regardless. + if (manager_handle) |manager| { + var port: u32 = 1; + while (port <= engine.max_ports and port <= prev_connected.len) : (port += 1) { + const connected = engine.portConnected(port); + const was = prev_connected[port]; + prev_connected[port] = connected; + if (connected and !was and engine.deviceOnPort(port) == null) { + // Rising edge the boot scan missed (it ran before the USB2 + // connection debounced): bring the device up now. + std.log.info("root port {d}: device appeared (PORTSC 0x{x:0>8})", .{ port, engine.portStatus(port) }); + bringUpPort(manager, engine, port); + } else if (!connected and was and engine.deviceOnPort(port) != null) { + tearDownPort(manager, engine, port); + } + } + } while (engine.takePortChange()) |port| { const manager = manager_handle orelse break; const connected = engine.portConnected(port);