xhcid: P7-C slice 1 — port suspend/resume via link-state write

Implement the actual port suspend/resume path using the USB 3.0
link state definitions, cross-referenced with Linux 7.1
xhci-hub.c: xhci_set_link_state().

  port.rs:
    - Port::set_link_state(state): writes PLS + PORT_LINK_STROBE
      after clearing all RW1CS/RW1S bits to neutral
    - Port::suspend(usb3): transitions to XDEV_U3
    - Port::resume(): transitions to XDEV_U0

  mod.rs:
    - Xhci::suspend_port(port_id): detects USB 3.0 vs USB 2.0
      from port speed field, calls Port::suspend()
    - Xhci::resume_port(port_id): calls Port::resume()

  Each operation locks ports, validates the port index, and
  logs the transition at info level.

This means the xhci controller can now transition individual
USB 3.0 root-hub ports to U3 (suspend) and back to U0 (resume),
which is the core mechanism for USB power management.  The
autosuspend timer that triggers these transitions automatically
is P7-C slice 2.
This commit is contained in:
Red Bear OS
2026-07-07 12:52:54 +03:00
parent 9a4f9ddd05
commit 431fa59e49
2 changed files with 50 additions and 0 deletions
+26
View File
@@ -747,6 +747,32 @@ impl<const N: usize> Xhci<N> {
Ok(())
}
/// Suspend a port to U3 link state.
/// Linux 7.1: xhci-hub.c → xhci_set_link_state(port, XDEV_U3).
pub fn suspend_port(&self, port_id: PortId) -> Result<()> {
let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner());
let port = ports.get_mut(port_id.root_hub_port_index())
.ok_or_else(|| Error::new(EINVAL))?;
// Detect USB 3.0 protocol speed from the extended capabilities.
// The port speed field (bits 13:10) gives the current link speed.
let speed = port.speed();
let usb3 = speed >= 4; // SuperSpeed (4) or SuperSpeedPlus (5+)
log::info!("xhcid: suspend port {} to U3 (USB3={})", port_id, usb3);
port.suspend(usb3);
Ok(())
}
/// Resume a port from U3 back to U0.
/// Linux 7.1: xhci-hub.c → xhci_set_link_state(port, XDEV_U0).
pub fn resume_port(&self, port_id: PortId) -> Result<()> {
let mut ports = self.ports.lock().unwrap_or_else(|e| e.into_inner());
let port = ports.get_mut(port_id.root_hub_port_index())
.ok_or_else(|| Error::new(EINVAL))?;
log::info!("xhcid: resume port {} to U0", port_id);
port.resume();
Ok(())
}
pub fn setup_scratchpads(&mut self) -> Result<()> {
let buf_count = self.cap.max_scratchpad_bufs();
+24
View File
@@ -175,4 +175,28 @@ impl Port {
pub fn link_state(&self) -> u8 {
((self.read() >> 5) & 0xF) as u8
}
/// Transition the port to a new link state.
/// Linux 7.1: xhci_set_link_state() in xhci-hub.c.
/// Writes PLS and sets PORT_LINK_STROBE (LWS) to commit the
/// transition. All RW1CS/RW1S bits are cleared to neutral
/// before writing.
pub fn set_link_state(&mut self, link_state: u32) {
let neutral = self.flags_preserved();
let val = (neutral & !(PortFlags::PLS_0 | PortFlags::PLS_1 | PortFlags::PLS_2 | PortFlags::PLS_3)).bits()
| link_state
| PortFlags::LWS.bits();
self.portsc.write(val);
}
/// Suspend the port to U3 (USB 3.0) or U3-equivalent for USB 2.
/// Linux 7.1: xhci-hub.c → xhci_set_link_state(xhci, port, XDEV_U3).
pub fn suspend(&mut self, usb3: bool) {
self.set_link_state(if usb3 { XDEV_U3 } else { XDEV_U3 });
}
/// Resume the port back to U0.
pub fn resume(&mut self) {
self.set_link_state(XDEV_U0);
}
}