usb: don't spin on a SuperSpeed hub's change bits; log hub port status
Real-hardware finding: a SuperSpeed hub (the user's Genesys, 4 SS downstream ports) HUNG the bus driver at boot — it stopped logging with no fault, and the hub's port power dropped. Cause: a SuperSpeed hub has change bits a USB2 hub lacks (link-state, BH-reset), and the B4 servicing — written and tested against QEMU's USB2 hub — never cleared them, so the hub's status-change endpoint re-reported the same port forever and the driver spun servicing it, never reaching the USB2 companion hub where the full-speed keyboard actually lives. hubPortStatusAck now clears the SuperSpeed-only change features too (gated on hub speed; a USB2 hub is unaffected), and the tick services a bounded batch of hub changes (32) before yielding — so even if a hub's change bits misbehave, the driver cannot spin the machine. A per-port status log line surfaces exactly what a hub reports, so the next real boot shows the SS hub's port states and whether the USB2 companion enumerates. QEMU usb-hub cases still green (the SS clears are gated off for QEMU's USB2 hub).
This commit is contained in:
parent
7ee6033fa4
commit
27f87cb5ba
|
|
@ -256,6 +256,7 @@ fn hubPortKey(hub_slot: u8, port: u16) u32 {
|
||||||
fn bringUpBehindHub(manager: runtime.ipc.Handle, engine: *library.Controller, hub: *library.Device, port: u16) void {
|
fn bringUpBehindHub(manager: runtime.ipc.Handle, engine: *library.Controller, hub: *library.Device, port: u16) void {
|
||||||
const status = engine.hubPortStatusAck(hub, port) orelse return;
|
const status = engine.hubPortStatusAck(hub, port) orelse return;
|
||||||
const connected = library.Controller.hubPortConnected(status);
|
const connected = library.Controller.hubPortConnected(status);
|
||||||
|
std.log.info("hub slot {d} port {d} status 0x{x:0>8} ({s})", .{ hub.slot_id, port, status, if (connected) "connected" else "empty" });
|
||||||
const existing = engine.deviceOnHubPort(hub, port);
|
const existing = engine.deviceOnHubPort(hub, port);
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
|
|
@ -501,9 +502,15 @@ fn onNotification(badge: u64) void {
|
||||||
// Downstream hub-port changes (docs/usb-hub.md): a device connected on a
|
// Downstream hub-port changes (docs/usb-hub.md): a device connected on a
|
||||||
// hub's downstream port is enumerated and registered here, so a keyboard
|
// hub's downstream port is enumerated and registered here, so a keyboard
|
||||||
// behind a hub reaches its class driver like one on a root port.
|
// behind a hub reaches its class driver like one on a root port.
|
||||||
|
// Cap per tick: even if a hub's change bits refuse to clear, the driver
|
||||||
|
// must not spin here — it services a bounded batch and yields to the
|
||||||
|
// next tick (and to storage, input, everything else).
|
||||||
|
var serviced: u32 = 0;
|
||||||
while (engine.takeHubChange()) |change| {
|
while (engine.takeHubChange()) |change| {
|
||||||
const manager = manager_handle orelse break;
|
const manager = manager_handle orelse break;
|
||||||
bringUpBehindHub(manager, engine, change.hub, change.port);
|
bringUpBehindHub(manager, engine, change.hub, change.port);
|
||||||
|
serviced += 1;
|
||||||
|
if (serviced >= 32) break;
|
||||||
}
|
}
|
||||||
while (engine.takeReport()) |report| {
|
while (engine.takeReport()) |report| {
|
||||||
var message = transfer.InterruptReport{
|
var message = transfer.InterruptReport{
|
||||||
|
|
|
||||||
|
|
@ -951,8 +951,18 @@ pub const Controller = struct {
|
||||||
/// bits (so it can signal again). Returns the wPortStatus word.
|
/// bits (so it can signal again). Returns the wPortStatus word.
|
||||||
pub fn hubPortStatusAck(self: *Controller, hub: *Device, port: u16) ?u32 {
|
pub fn hubPortStatusAck(self: *Controller, hub: *Device, port: u16) ?u32 {
|
||||||
const status = self.readHubPortStatus(hub, port) orelse return null;
|
const status = self.readHubPortStatus(hub, port) orelse return null;
|
||||||
|
// Acknowledge every latched change bit. A SuperSpeed hub has extra ones
|
||||||
|
// (link-state, BH-reset) beyond a USB2 hub's connection/reset — leaving
|
||||||
|
// any set makes the hub's status-change endpoint re-report the same port
|
||||||
|
// forever, spinning the driver (a real SuperSpeed hub hung boot here;
|
||||||
|
// QEMU's USB2 hub has none of these). Clearing an inapplicable feature
|
||||||
|
// is harmless (the hub STALLs it and we move on).
|
||||||
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_port_connection, port), &.{}, false);
|
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_port_connection, port), &.{}, false);
|
||||||
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_port_reset, port), &.{}, false);
|
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_port_reset, port), &.{}, false);
|
||||||
|
if (hub.speed >= 4) {
|
||||||
|
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_port_link_state, port), &.{}, false);
|
||||||
|
_ = self.controlTransfer(hub, hubreq.clearPortFeature(hubreq.feature_c_bh_port_reset, port), &.{}, false);
|
||||||
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue