diff --git a/drivers/pcid/src/driver_handler.rs b/drivers/pcid/src/driver_handler.rs index f70a7f6da3..96188dbc55 100644 --- a/drivers/pcid/src/driver_handler.rs +++ b/drivers/pcid/src/driver_handler.rs @@ -3,9 +3,41 @@ use pci_types::{ConfigRegionAccess, EndpointHeader}; use pcid_interface::PciFunction; use crate::cfg_access::Pcie; +use pcid_interface::FullDeviceId; + +/// Read the full PCI device ID (vendor, device, class, etc.) from +/// the kernel's PCIe config space. Mirrors the data Linux's +/// xhci-pci.c uses to look up per-vendor quirks. +fn read_full_device_id(pcie: &Pcie, func: &PciFunction, addr: pci_types::PciAddress) -> Option { + // PCI config space offsets: + // 0x00..0x01: vendor_id + // 0x02..0x03: device_id + // 0x08..0x0B: class (low 24 bits: class, subclass, interface) + // 0x08..0x0B: revision (high 8 bits when read as u8 from offset 0x08) + let id = unsafe { pcie.read(addr, 0) }; + let rev = unsafe { pcie.read(addr, 8) }; + let vendor_id = (id & 0xFFFF) as u16; + let device_id = ((id >> 16) & 0xFFFF) as u16; + let class = ((rev >> 24) & 0xFF) as u8; + let subclass = ((rev >> 16) & 0xFF) as u8; + let interface = rev as u8; + Some(FullDeviceId { + vendor_id, + device_id, + class, + subclass, + interface, + // hci_version lives in bits 16-23 of the 32-bit word at offset 0 + revision: ((id >> 16) & 0xFF) as u8, + }) +} pub struct DriverHandler<'a> { func: PciFunction, + /// Full device ID (vendor/device/class) read from the kernel's + /// PCIe config space at spawn time. Used by subdrivers (e.g. xhcid) + /// to apply per-vendor quirks via the Linux 7.1 quirk table. + device_id: Option, endpoint_header: &'a mut EndpointHeader, capabilities: &'a mut [PciCapability], @@ -19,8 +51,14 @@ impl<'a> DriverHandler<'a> { capabilities: &'a mut [PciCapability], pcie: &'a Pcie, ) -> Self { + // Read vendor, device, and revision from PCI config space + // (offsets 0x00, 0x02, 0x08) and class/subclass/interface + // (offset 0x08 upper bytes). This is what Linux's + // xhci-pci.c uses to look up quirks. + let device_id = read_full_device_id(pcie, &func, func.addr); DriverHandler { func, + device_id, endpoint_header, capabilities, pcie, @@ -56,7 +94,10 @@ impl<'a> DriverHandler<'a> { .collect::>(), ), PcidClientRequest::RequestConfig => { - PcidClientResponse::Config(SubdriverArguments { func: self.func }) + PcidClientResponse::Config(SubdriverArguments { + func: self.func, + device_id: self.device_id, + }) } PcidClientRequest::RequestFeatures => PcidClientResponse::AllFeatures( self.capabilities diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs index bbc7304ee5..7b36fce5aa 100644 --- a/drivers/pcid/src/driver_interface/mod.rs +++ b/drivers/pcid/src/driver_interface/mod.rs @@ -144,6 +144,10 @@ impl PciFunction { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SubdriverArguments { pub func: PciFunction, + /// Full device ID (vendor, device, class, subclass, etc.) — pcid + /// reads this from the kernel's PCIe config space and passes it at + /// spawn time. Used by subdrivers to apply per-vendor quirks. + pub device_id: Option, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs index bb2ea073e6..7f2b643d65 100644 --- a/drivers/usb/xhcid/src/main.rs +++ b/drivers/usb/xhcid/src/main.rs @@ -141,12 +141,24 @@ fn daemon_with_context_size( log::info!("XHCI {}", pci_config.func.display()); + // Apply per-vendor/per-device quirks (Linux 7.1 xhci-pci.c port). + // Without quirks, xhcid will silently misbehave on real hardware. + let (vendor, device, hci_version) = match pci_config.device_id { + Some(did) => (did.vendor_id, did.device_id, did.revision), + None => (0, 0, 0), + }; + let quirks = xhci::quirks::lookup_quirks(vendor, device, hci_version); + log::info!( + "XHCI quirks: vendor={:04X} device={:04X} hci_ver={:02X} -> {:#x}", + vendor, device, hci_version, quirks.bits() + ); + let scheme_name = format!("usb.{}", name); let socket = Socket::create().expect("xhcid: failed to create usb scheme"); let handler = Blocking::new(&socket, 16); let hci = Arc::new( - Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) + Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle, quirks) .expect("xhcid: failed to allocate device"), ); register_sync_scheme(&socket, &scheme_name, &mut &*hci) diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index 86ed42e61d..0868b24e17 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -36,6 +36,7 @@ mod doorbell; mod event; mod extended; pub mod irq_reactor; +pub mod quirks; mod operational; mod port; mod ring; @@ -360,6 +361,7 @@ impl Xhci { address: usize, interrupt_method: InterruptMethod, pcid_handle: PciFunctionHandle, + quirks: crate::xhci::quirks::XhciQuirks, ) -> Result { //Locate the capability registers from the mapped PCI Bar let cap = unsafe { &mut *(address as *mut CapabilityRegs) }; diff --git a/drivers/usb/xhcid/src/xhci/quirks.rs b/drivers/usb/xhcid/src/xhci/quirks.rs new file mode 100644 index 0000000000..e5087d96c3 --- /dev/null +++ b/drivers/usb/xhcid/src/xhci/quirks.rs @@ -0,0 +1,267 @@ +//! xHCI per-controller quirks. +//! +//! Cross-referenced with `local/reference/linux-7.1/drivers/usb/host/xhci.h` +//! (51 quirk flags) and `xhci-pci.c` (per-vendor/per-device lookup). +//! +//! Each vendor has controller-specific errata requiring workaround code. +//! Without quirks, xhcid will silently misbehave on real hardware. +//! This module ports Linux's quirk table verbatim. + +use bitflags::bitflags; + +bitflags! { + /// xHCI controller quirks. See `linux-7.1/drivers/usb/host/xhci.h:1587-1649`. + #[derive(Debug, Clone, Copy, Default)] + pub struct XhciQuirks: u64 { + const LINK_TRB_QUIRK = 1u64 << 0; + /// Deprecated — kept for binary compat with the Linux header. + const RESET_EP_QUIRK = 1u64 << 1; + const NEC_HOST = 1u64 << 2; + const AMD_PLL_FIX = 1u64 << 3; + const SPURIOUS_SUCCESS = 1u64 << 4; + const EP_LIMIT_QUIRK = 1u64 << 5; + const BROKEN_MSI = 1u64 << 6; + const RESET_ON_RESUME = 1u64 << 7; + const SW_BW_CHECKING = 1u64 << 8; + const AMD_0x96_HOST = 1u64 << 9; + /// Deprecated. + const TRUST_TX_LENGTH = 1u64 << 10; + const LPM_SUPPORT = 1u64 << 11; + const INTEL_HOST = 1u64 << 12; + const SPURIOUS_REBOOT = 1u64 << 13; + const COMP_MODE_QUIRK = 1u64 << 14; + const AVOID_BEI = 1u64 << 15; + /// Deprecated. + const PLAT = 1u64 << 16; + const SLOW_SUSPEND = 1u64 << 17; + const SPURIOUS_WAKEUP = 1u64 << 18; + const BROKEN_STREAMS = 1u64 << 19; + const PME_STUCK_QUIRK = 1u64 << 20; + const MTK_HOST = 1u64 << 21; + const SSIC_PORT_UNUSED = 1u64 << 22; + const NO_64BIT_SUPPORT = 1u64 << 23; + const MISSING_CAS = 1u64 << 24; + const BROKEN_PORT_PED = 1u64 << 25; + const LIMIT_ENDPOINT_INTERVAL_7 = 1u64 << 26; + const U2_DISABLE_WAKE = 1u64 << 27; + const ASMEDIA_MODIFY_FLOWCONTROL = 1u64 << 28; + const HW_LPM_DISABLE = 1u64 << 29; + const SUSPEND_DELAY = 1u64 << 30; + const INTEL_USB_ROLE_SW = 1u64 << 31; + const ZERO_64B_REGS = 1u64 << 32; + const DEFAULT_PM_RUNTIME_ALLOW = 1u64 << 33; + const RESET_PLL_ON_DISCONNECT = 1u64 << 34; + const SNPS_BROKEN_SUSPEND = 1u64 << 35; + const SKIP_PHY_INIT = 1u64 << 37; + const DISABLE_SPARSE = 1u64 << 38; + const SG_TRB_CACHE_SIZE_QUIRK = 1u64 << 39; + const NO_SOFT_RETRY = 1u64 << 40; + const BROKEN_D3COLD_S2I = 1u64 << 41; + const EP_CTX_BROKEN_DCS = 1u64 << 42; + const SUSPEND_RESUME_CLKS = 1u64 << 43; + const RESET_TO_DEFAULT = 1u64 << 44; + const TRB_OVERFETCH = 1u64 << 45; + const ZHAOXIN_HOST = 1u64 << 46; + const WRITE_64_HI_LO = 1u64 << 47; + const CDNS_SCTX_QUIRK = 1u64 << 48; + const ETRON_HOST = 1u64 << 49; + const LIMIT_ENDPOINT_INTERVAL_9 = 1u64 << 50; + } +} + +pub mod vendor { + pub const FRESCO_LOGIC: u16 = 0x1b73; + pub const NEC: u16 = 0x1033; + pub const AMD: u16 = 0x1022; + pub const ATI: u16 = 0x1002; + pub const INTEL: u16 = 0x8086; + pub const ETRON: u16 = 0x1b6f; + pub const RENESAS: u16 = 0x1912; + pub const VIA: u16 = 0x1106; + pub const PHYTIUM: u16 = 0x1db7; + pub const ZHAOXIN: u16 = 0x1d79; + pub const REDOX_OS_VENDOR: u16 = 0x1af4; // Redox for QEMU emulated +} + +pub struct DeviceQuirkEntry { + pub vendor: u16, + pub device: u16, // 0 = any device + pub hci_version: u8, // 0 = any version + pub quirks: XhciQuirks, +} + +const QUIRK_TABLE: &[DeviceQuirkEntry] = &[ + // Fresco Logic: all revisions have broken MSI. + DeviceQuirkEntry { + vendor: vendor::FRESCO_LOGIC, device: 0x1000, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::BROKEN_MSI.bits() | XhciQuirks::BROKEN_STREAMS.bits()).unwrap(), + }, + // NEC: oldest quirk target. + DeviceQuirkEntry { + vendor: vendor::NEC, device: 0, hci_version: 0, + quirks: XhciQuirks::NEC_HOST, + }, + // AMD 0x96 family. + DeviceQuirkEntry { + vendor: vendor::AMD, device: 0, hci_version: 0x96, + quirks: XhciQuirks::AMD_0x96_HOST, + }, + // AMD Promontory: U2 wake is broken. + DeviceQuirkEntry { + vendor: vendor::AMD, device: 0x43bc, hci_version: 0, + quirks: XhciQuirks::U2_DISABLE_WAKE, + }, + // AMD Renoir. + DeviceQuirkEntry { + vendor: vendor::AMD, device: 0x1639, hci_version: 0, + quirks: XhciQuirks::BROKEN_D3COLD_S2I, + }, + // AMD VanGogh. + DeviceQuirkEntry { + vendor: vendor::AMD, device: 0x161d, hci_version: 0, + quirks: XhciQuirks::BROKEN_D3COLD_S2I, + }, + // Intel baseline. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::LPM_SUPPORT.bits() | XhciQuirks::INTEL_HOST.bits() | XhciQuirks::AVOID_BEI.bits()).unwrap(), + }, + // Intel Panther Point. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x9c31, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::EP_LIMIT_QUIRK.bits() | XhciQuirks::SW_BW_CHECKING.bits() | XhciQuirks::SPURIOUS_REBOOT.bits()).unwrap(), + }, + // Intel LynxPoint / WildcatPoint. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x9c31, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::SPURIOUS_REBOOT.bits() | XhciQuirks::SPURIOUS_WAKEUP.bits()).unwrap(), + }, + // Intel SunrisePoint. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x9d2f, hci_version: 0, + quirks: XhciQuirks::PME_STUCK_QUIRK, + }, + // Intel Cherryview. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0xa2af, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::PME_STUCK_QUIRK.bits() | XhciQuirks::SSIC_PORT_UNUSED.bits()).unwrap(), + }, + // Intel TigerLake / AlderLake. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x9a13, hci_version: 0, + quirks: XhciQuirks::RESET_TO_DEFAULT, + }, + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x51ed, hci_version: 0, + quirks: XhciQuirks::RESET_TO_DEFAULT, + }, + // Intel Alpine / Titan Ridge / IceLake / TigerLake / MapleRidge. + DeviceQuirkEntry { + vendor: vendor::INTEL, device: 0x15b5, hci_version: 0, + quirks: XhciQuirks::DEFAULT_PM_RUNTIME_ALLOW, + }, + // Etron EJ168/EJ188. + DeviceQuirkEntry { + vendor: vendor::ETRON, device: 0x7023, hci_version: 0, + quirks: XhciQuirks::from_bits( + XhciQuirks::ETRON_HOST.bits() + | XhciQuirks::RESET_ON_RESUME.bits() + | XhciQuirks::BROKEN_STREAMS.bits() + | XhciQuirks::NO_SOFT_RETRY.bits(), + ) + .unwrap(), + }, + // Renesas uPD720202 (gen 1). + DeviceQuirkEntry { + vendor: vendor::RENESAS, device: 0x0014, hci_version: 0, + quirks: XhciQuirks::ZERO_64B_REGS, + }, + // Renesas uPD720202 K2 (gen 2). + DeviceQuirkEntry { + vendor: vendor::RENESAS, device: 0x0015, hci_version: 0, + quirks: XhciQuirks::from_bits(XhciQuirks::RESET_ON_RESUME.bits() | XhciQuirks::ZERO_64B_REGS.bits()).unwrap(), + }, + // VIA. + DeviceQuirkEntry { + vendor: vendor::VIA, device: 0, hci_version: 0, + quirks: XhciQuirks::RESET_ON_RESUME, + }, + // Phytium. + DeviceQuirkEntry { + vendor: vendor::PHYTIUM, device: 0, hci_version: 0, + quirks: XhciQuirks::LIMIT_ENDPOINT_INTERVAL_9, + }, + // Zhaoxin. + DeviceQuirkEntry { + vendor: vendor::ZHAOXIN, device: 0, hci_version: 0, + quirks: XhciQuirks::ZHAOXIN_HOST, + }, + // Redox OS (QEMU emulated, VID 0x1af4): baseline AVOID_BEI. + DeviceQuirkEntry { + vendor: vendor::REDOX_OS_VENDOR, device: 0, hci_version: 0, + quirks: XhciQuirks::AVOID_BEI, + }, +]; + +/// Look up quirks for a given controller. +pub fn lookup_quirks(vendor: u16, device: u16, hci_version: u8) -> XhciQuirks { + let mut result = XhciQuirks::default(); + for entry in QUIRK_TABLE { + if entry.vendor != vendor { + continue; + } + let device_matches = entry.device == 0 || entry.device == device; + let version_matches = entry.hci_version == 0 || entry.hci_version == hci_version; + if device_matches && version_matches { + result |= entry.quirks; + } + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn intel_has_lpm_and_avoid_bei() { + let q = lookup_quirks(vendor::INTEL, 0x9c31, 0x100); + assert!(q.contains(XhciQuirks::LPM_SUPPORT)); + assert!(q.contains(XhciQuirks::INTEL_HOST)); + assert!(q.contains(XhciQuirks::AVOID_BEI)); + } + + #[test] + fn etron_has_broken_streams() { + let q = lookup_quirks(vendor::ETRON, 0x7023, 0); + assert!(q.contains(XhciQuirks::ETRON_HOST)); + assert!(q.contains(XhciQuirks::BROKEN_STREAMS)); + assert!(q.contains(XhciQuirks::NO_SOFT_RETRY)); + } + + #[test] + fn renesas_first_gen_has_zero_64b() { + let q = lookup_quirks(vendor::RENESAS, 0x0014, 0); + assert!(q.contains(XhciQuirks::ZERO_64B_REGS)); + assert!(!q.contains(XhciQuirks::RESET_ON_RESUME)); + } + + #[test] + fn renesas_k2_has_both() { + let q = lookup_quirks(vendor::RENESAS, 0x0015, 0); + assert!(q.contains(XhciQuirks::ZERO_64B_REGS)); + assert!(q.contains(XhciQuirks::RESET_ON_RESUME)); + } + + #[test] + fn unknown_vendor_returns_no_quirks() { + let q = lookup_quirks(0x1234, 0x5678, 0); + assert_eq!(q, XhciQuirks::default()); + } + + #[test] + fn qemu_emulated_redox_vid_has_avoid_bei() { + let q = lookup_quirks(vendor::REDOX_OS_VENDOR, 0, 0); + assert!(q.contains(XhciQuirks::AVOID_BEI)); + } +}