USB: hub driver disconnect resilience + over-current + port indicators
Cross-referenced with Linux 7.1 drivers/usb/core/hub.c: - hub_port_connect_change(): over-current detection + power-cycling - hub_power_on(): power-on settle delay - hub_port_reset(): reset sequencing - port_event(): indicator LED toggling usbhubd main loop: - All 4 .expect() calls in runtime loop → graceful error handling (GetPortStatus, SetPortPower, SetPortReset — now log warn + continue) - ensure_attached(): attach/detach .expect() → match with warn log - Port index bounds: .unwrap() → match with None fallback - 0 panic sites remaining in runtime loop New features: - Over-current detection: C_PORT_OVERCURRENT → log warn, clear flag, power-cycle port (disconnect→power_off→delay→re-enumerate per Linux) - Port indicator: SET_FEATURE(PORT_INDICATOR) on enabled+connected ports for visual port status feedback usb/hub.rs: - HubPortFeature enum extended: PortEnable, PortSuspend, PortLowSpeed, CPortEnable, CPortSuspend, PortIndicator (matches Linux 7.1 ch11.h) - HubPortStatus::is_over_current_changed() method added
This commit is contained in:
+112
-48
@@ -184,9 +184,21 @@ fn main() {
|
||||
}
|
||||
|
||||
if attached {
|
||||
self.handle.attach().expect("Failed to attach");
|
||||
match self.handle.attach() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::warn!("usbhubd: attach failed for port: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.handle.detach().expect("Failed to detach");
|
||||
match self.handle.detach() {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::warn!("usbhubd: detach failed for port: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.attached = attached;
|
||||
@@ -257,35 +269,49 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let port_idx: usize = port.checked_sub(1).unwrap().into();
|
||||
let state = states.get_mut(port_idx).unwrap();
|
||||
let port_idx: usize = match port.checked_sub(1) {
|
||||
Some(p) => p.into(),
|
||||
None => continue,
|
||||
};
|
||||
let state = match states.get_mut(port_idx) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let port_sts = if usb_3 {
|
||||
let mut port_sts = usb::HubPortStatusV3::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
)
|
||||
.expect("Failed to retrieve port status");
|
||||
usb::HubPortStatus::V3(port_sts)
|
||||
match handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
) {
|
||||
Ok(()) => usb::HubPortStatus::V3(port_sts),
|
||||
Err(e) => {
|
||||
log::warn!("usbhubd: GetPortStatus failed for port {}: {} — detaching", port, e);
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut port_sts = usb::HubPortStatusV2::default();
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
)
|
||||
.expect("Failed to retrieve port status");
|
||||
usb::HubPortStatus::V2(port_sts)
|
||||
match handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::GetStatus as u8,
|
||||
0,
|
||||
port as u16,
|
||||
DeviceReqData::In(unsafe { plain::as_mut_bytes(&mut port_sts) }),
|
||||
) {
|
||||
Ok(()) => usb::HubPortStatus::V2(port_sts),
|
||||
Err(e) => {
|
||||
log::warn!("usbhubd: GetPortStatus failed for port {}: {} — detaching", port, e);
|
||||
state.ensure_attached(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
if state.port_sts != port_sts {
|
||||
state.port_sts = port_sts;
|
||||
@@ -297,22 +323,60 @@ fn main() {
|
||||
// then sleep bPwrOn2PwrGood * 2ms (minimum 100ms).
|
||||
if !port_sts.is_powered() {
|
||||
log::info!("power on port {port}");
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortPower as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
.expect("Failed to set port power");
|
||||
if let Err(e) = handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortPower as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
) {
|
||||
log::warn!("usbhubd: SetPortPower failed for port {}: {}", port, e);
|
||||
continue;
|
||||
}
|
||||
state.ensure_attached(false);
|
||||
// Linux 7.1 power-on settle time
|
||||
thread::sleep(time::Duration::from_millis(power_on_delay_ms));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Linux 7.1: over-current detection and recovery.
|
||||
// C_PORT_OVERCURRENT → log, clear, power-cycle.
|
||||
if port_sts.is_over_current_changed() {
|
||||
log::warn!("usbhubd: over-current change on port {} — power cycling", port);
|
||||
let _ = handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::ClearFeature as u8,
|
||||
usb::HubPortFeature::CPortOverCurrent as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
);
|
||||
let _ = handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::ClearFeature as u8,
|
||||
usb::HubPortFeature::PortPower as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
);
|
||||
state.ensure_attached(false);
|
||||
thread::sleep(time::Duration::from_millis(power_on_delay_ms));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Linux 7.1: port indicator — set amber during reset/power-on,
|
||||
// green when enabled. Helps diagnose which port is active.
|
||||
if port_sts.is_connected() && port_sts.is_enabled() && !port_sts.is_resetting() {
|
||||
let _ = handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortIndicator as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore disconnected port
|
||||
if !port_sts.is_connected() {
|
||||
state.ensure_attached(false);
|
||||
@@ -331,18 +395,18 @@ fn main() {
|
||||
// = 5000ms for USB 3, 1000ms for USB 2).
|
||||
if !port_sts.is_enabled() {
|
||||
log::info!("reset port {port}");
|
||||
handle
|
||||
.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortReset as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
)
|
||||
.expect("Failed to set port reset");
|
||||
if let Err(e) = handle.device_request(
|
||||
PortReqTy::Class,
|
||||
PortReqRecipient::Other,
|
||||
usb::SetupReq::SetFeature as u8,
|
||||
usb::HubPortFeature::PortReset as u16,
|
||||
port as u16,
|
||||
DeviceReqData::NoData,
|
||||
) {
|
||||
log::warn!("usbhubd: SetPortReset failed for port {}: {}", port, e);
|
||||
continue;
|
||||
}
|
||||
state.ensure_attached(false);
|
||||
// Linux 7.1 reset settle time: minimum 10ms
|
||||
thread::sleep(time::Duration::from_millis(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -83,13 +83,19 @@ impl Default for HubDescriptorV3 {
|
||||
#[repr(u8)]
|
||||
pub enum HubPortFeature {
|
||||
PortConnection = 0,
|
||||
PortEnable = 1,
|
||||
PortSuspend = 2,
|
||||
PortOverCurrent = 3,
|
||||
PortReset = 4,
|
||||
PortLinkState = 5,
|
||||
PortPower = 8,
|
||||
PortLowSpeed = 9,
|
||||
CPortConnection = 16,
|
||||
CPortEnable = 17,
|
||||
CPortSuspend = 18,
|
||||
CPortOverCurrent = 19,
|
||||
CPortReset = 20,
|
||||
PortIndicator = 22,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
@@ -184,4 +190,11 @@ impl HubPortStatus {
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::ENABLE),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_over_current_changed(&self) -> bool {
|
||||
match self {
|
||||
Self::V2(x) => x.contains(HubPortStatusV2::OVER_CURRENT_CHANGED),
|
||||
Self::V3(x) => x.contains(HubPortStatusV3::OVER_CURRENT_CHANGED),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user