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:
Red Bear OS
2026-07-07 17:40:26 +03:00
parent 230a219c5f
commit fb9b158e66
2 changed files with 125 additions and 48 deletions
+112 -48
View File
@@ -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;
}
+13
View File
@@ -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),
}
}
}