diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs index 5edab889dc..516a60cfc4 100644 --- a/drivers/usb/usbhubd/src/main.rs +++ b/drivers/usb/usbhubd/src/main.rs @@ -396,7 +396,6 @@ fn main() { // off-by-one that processed every event on the wrong port. const POLL_FALLBACK_MS: u64 = 200; 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; @@ -407,27 +406,54 @@ fn main() { // first EP1 read would otherwise hang the daemon before any scan). let mut first_scan = true; + // EP1 status-change events are produced on a worker thread so the + // main loop can bound its wait: the blocking transfer_read has no + // timeout, and after a synchronous action (power-on, reset) the port + // state changes WITHOUT a new interrupt — a pure blocking read would + // never wake to observe it. The worker forwards each interrupt bitmap + // as a u64 mask; the main loop falls back to an all-ports poll on + // timeout, so interrupt delivery is a fast path, never a requirement. + let (event_tx, event_rx) = std::sync::mpsc::channel::(); + let intr_ep_available = intr_ep_handle.is_some(); + if let Some(mut ep) = intr_ep_handle.take() { + thread::spawn(move || { + let mut buf = vec![0u8; bitmap_size]; + loop { + match ep.transfer_read(&mut buf) { + Ok(_) => { + let mut mask = 0u64; + for (byte_idx, &byte) in buf.iter().enumerate() { + if byte != 0 { + mask |= (byte as u64) << (byte_idx * 8); + } + } + buf.iter_mut().for_each(|b| *b = 0); + if event_tx.send(mask).is_err() { + return; + } + } + Err(_) => { + if event_tx.send(u64::MAX).is_err() { + return; + } + } + } + } + }); + } + loop { // Build a port-change mask. // 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; - for (byte_idx, &byte) in bitmap.iter().enumerate() { - if byte != 0 { - mask |= (byte as u64) << (byte_idx * 8); - } - } - mask - } - Err(e) => { - log::warn!("usbhubd: interrupt transfer failed ({}), falling back to poll", e); - all_ports_mask - } + } else if intr_ep_available { + match event_rx.recv_timeout(time::Duration::from_millis(POLL_FALLBACK_MS)) { + // A real interrupt bitmap: process only the changed ports. + Ok(mask) if mask != u64::MAX => mask, + // Error sentinel, timeout, or dead worker: poll everything. + _ => all_ports_mask, } } else { // Polling mode: process all ports.