usbhubd: fix status-bitmap off-by-one + add initial full port scan
Two latent bugs exposed by test-usb-hub-qemu.sh against a real hub topology (QEMU usb-hub with usb-kbd behind it): 1. Status-change bitmap was indexed off by one: the loop checked bit (port - 1), but USB 2.0 spec 11.12.4 defines bit 0 = hub status change and bit N = port N status change (Linux hub_irq uses bit i for port i). Every change was processed on the wrong port — a device on port N was never enumerated (its bit was read as port N+1). 2. The event loop's first action was a blocking EP1 interrupt read; when the hub delivers no immediate status-change interrupt the daemon hung before ever scanning a port. Linux hub_activate() performs a full port scan before arming the status-change endpoint; the first iteration now forces an all-ports mask with the same semantics. Also widened the fallback all-ports mask to bits 1..=ports (bit 0 = hub, skipped).
This commit is contained in:
@@ -390,16 +390,30 @@ fn main() {
|
||||
// only poll GetPortStatus for ports whose bit is set.
|
||||
// If EP1 is unavailable, fall back to polling all ports.
|
||||
//
|
||||
// Bitmap size: ceil(ports / 8) bytes.
|
||||
// Port N is bit (N-1) of byte (N-1)/8.
|
||||
// Bitmap size: ceil((ports + 1) / 8) bytes — bit 0 is the hub itself,
|
||||
// bit N is port N (USB 2.0 spec 11.12.4; Linux hub_irq uses bit i for
|
||||
// port i). The previous revision used bit (N-1) for port N, an
|
||||
// off-by-one that processed every event on the wrong port.
|
||||
const POLL_FALLBACK_MS: u64 = 200;
|
||||
let bitmap_size = (ports as usize + 7) / 8;
|
||||
let bitmap_size = (ports as usize + 8) / 8;
|
||||
let mut bitmap = vec![0u8; bitmap_size];
|
||||
|
||||
// Mask with bits 1..=ports set (every real port; bit 0 = hub, skipped).
|
||||
let all_ports_mask: u64 = ((1u64 << ports) - 1) << 1;
|
||||
|
||||
// Linux hub_activate() performs a full port scan before arming the
|
||||
// status-change endpoint — devices already connected at hub power-on
|
||||
// must be enumerated even if no interrupt ever arrives (a blocking
|
||||
// first EP1 read would otherwise hang the daemon before any scan).
|
||||
let mut first_scan = true;
|
||||
|
||||
loop {
|
||||
// Build a port-change mask.
|
||||
// Bit N set = port (N+1) needs processing.
|
||||
let changed: u64 = if let Some(ref mut ep) = intr_ep_handle {
|
||||
// Bit N set = port N needs processing (bit 0 = hub status change).
|
||||
let changed: u64 = if first_scan {
|
||||
first_scan = false;
|
||||
all_ports_mask
|
||||
} else if let Some(ref mut ep) = intr_ep_handle {
|
||||
match ep.transfer_read(&mut bitmap) {
|
||||
Ok(_) => {
|
||||
let mut mask = 0u64;
|
||||
@@ -412,16 +426,16 @@ fn main() {
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("usbhubd: interrupt transfer failed ({}), falling back to poll", e);
|
||||
(1u64 << ports) - 1
|
||||
all_ports_mask
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Polling mode: process all ports.
|
||||
thread::sleep(time::Duration::from_millis(POLL_FALLBACK_MS));
|
||||
(1u64 << ports) - 1
|
||||
all_ports_mask
|
||||
};
|
||||
for port in 1..=ports {
|
||||
let bit = 1u64 << (port - 1);
|
||||
let bit = 1u64 << port;
|
||||
if (changed & bit) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user