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:
Daniel Samson
2026-07-21 22:44:46 +01:00
parent 7ee6033fa4
commit 27f87cb5ba
2 changed files with 17 additions and 0 deletions
@@ -951,8 +951,18 @@ pub const Controller = struct {
/// bits (so it can signal again). Returns the wPortStatus word.
pub fn hubPortStatusAck(self: *Controller, hub: *Device, port: u16) ?u32 {
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_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;
}