usb: poll root ports on the tick — catch a late USB2 connection (edge-triggered)
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.
This commit is contained in:
parent
10956c6660
commit
0b10a637ae
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue