diff --git a/drivers/usb/usbhubd/src/main.rs b/drivers/usb/usbhubd/src/main.rs index 4bef273b78..a439012746 100644 --- a/drivers/usb/usbhubd/src/main.rs +++ b/drivers/usb/usbhubd/src/main.rs @@ -1,8 +1,8 @@ use std::{env, thread, time}; use xhcid_interface::{ - plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, PortId, PortReqRecipient, PortReqTy, - XhciClientHandle, + plain, usb, ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpointTy, EndpDesc, + EndpDirection, PortId, PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, }; fn main() { @@ -143,6 +143,32 @@ fn main() { log::info!("usbhubd: SET_HUB_DEPTH to {}", hub_depth); } + // Find the hub interrupt endpoint for status-change detection. + // Linux 7.1 hub_irq() reads the status-change bitmap from EP1. + // For USB 2.x hubs: EP1, bInterval=12 (255ms max polling). + // For USB 3 hubs: may not have a dedicated interrupt EP — fall back to polling. + let intr_desc = if_desc + .endpoints + .iter() + .find(|ep| ep.ty() == EndpointTy::Interrupt && ep.direction() == EndpDirection::In) + .cloned(); + + let mut intr_ep_handle: Option = if intr_desc.is_some() { + match handle.open_endpoint(1u8) { + Ok(handle) => { + log::info!("usbhubd: interrupt endpoint opened for change detection"); + Some(handle) + } + Err(e) => { + log::warn!("usbhubd: interrupt endpoint open failed ({}), falling back to polling", e); + None + } + } + } else { + log::info!("usbhubd: no interrupt endpoint found, using polling"); + None + }; + // Initialize states struct PortState { port_id: PortId, @@ -188,13 +214,49 @@ fn main() { // Linux uses 100ms minimum; follow the same floor. let power_on_delay_ms = core::cmp::max(power_on_delay_ms, 100); - // Main event loop. Linux 7.1 uses interrupt-driven change - // detection; we poll at 250ms as an intermediate step until - // hub interrupt endpoints are integrated. - const POLL_INTERVAL_MS: u64 = 250; + // Main event loop with interrupt-driven change detection. + // Linux 7.1 hub_irq(): reads status-change bitmap from EP1, + // then kicks hub_wq to process only the changed ports. + // We do the same: read the bitmap, build a port mask, and + // 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. + const POLL_FALLBACK_MS: u64 = 200; + let bitmap_size = (ports as usize + 7) / 8; + let mut bitmap = vec![0u8; bitmap_size]; 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 { + 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); + (1u64 << ports) - 1 + } + } + } else { + // Polling mode: process all ports. + thread::sleep(time::Duration::from_millis(POLL_FALLBACK_MS)); + (1u64 << ports) - 1 + }; for port in 1..=ports { + let bit = 1u64 << (port - 1); + if (changed & bit) == 0 { + continue; + } + let port_idx: usize = port.checked_sub(1).unwrap().into(); let state = states.get_mut(port_idx).unwrap(); @@ -287,7 +349,5 @@ fn main() { state.ensure_attached(true); } - - thread::sleep(time::Duration::from_millis(POLL_INTERVAL_MS)); } }