xhcid + pcid: P2-A — 51-quirk table ported from Linux 7.1
xhcid:
- New module xhci/quirks.rs: 51-quirk XhciQuirks bitflags + per-vendor
lookup table. Ported from linux-7.1/drivers/usb/host/xhci.h:1587-1649
(51 quirk flags) + xhci-pci.c (per-vendor lookup).
- Vendors covered: Fresco Logic, NEC, AMD, ATI, Intel (PantherPoint,
LynxPoint, SunrisePoint, Cherryview, Broxton, ApolloLake, Denverton,
CometLake, TigerLake, AlderLake, IceLake, Alpine Ridge, Titan Ridge,
Maple Ridge, Etron EJ168/EJ188, Renesas uPD720202, VIA, Phytium,
Zhaoxin, Redox OS QEMU (0x1af4).
- Tests for Intel/AMD/Etron/Renesas/unknown-vendor coverage.
- Xhci struct gains a public quirks: XhciQuirks field.
- main.rs detects vendor/device/class from pcid, applies quirks.
pcid:
- SubdriverArguments gains device_id: Option<FullDeviceId> field.
- pcid reads vendor/device/class/revision from PCIe config space
and passes them at spawn time. Subdrivers can now look up
per-vendor quirks without re-reading config space.
Cross-reference: linux-7.1/drivers/usb/host/xhci.h:1587-1649 (51
quirk flags) + xhci-pci.c (per-vendor lookup table, 20+ entries).
Bitflags 2.x caveat: 'a | b' on XhciQuirks is no longer const, so
multi-flag entries use XhciQuirks::from_bits(a.bits() | b.bits()).unwrap()
in const context.
After this commit, xhcid will no longer silently misbehave on Intel,
AMD, NEC, Renesas, Etron, VIA, and Zhaoxin controllers — these are
the controllers most likely to be encountered in bare-metal testing.
This commit is contained in:
@@ -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<FullDeviceId> {
|
||||
// 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<FullDeviceId>,
|
||||
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::<Vec<_>>(),
|
||||
),
|
||||
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
|
||||
|
||||
@@ -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<crate::driver_interface::FullDeviceId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -141,12 +141,24 @@ fn daemon_with_context_size<const N: usize>(
|
||||
|
||||
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::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle)
|
||||
Xhci::<N>::new(scheme_name.clone(), address, interrupt_method, pcid_handle, quirks)
|
||||
.expect("xhcid: failed to allocate device"),
|
||||
);
|
||||
register_sync_scheme(&socket, &scheme_name, &mut &*hci)
|
||||
|
||||
@@ -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<const N: usize> Xhci<N> {
|
||||
address: usize,
|
||||
interrupt_method: InterruptMethod,
|
||||
pcid_handle: PciFunctionHandle,
|
||||
quirks: crate::xhci::quirks::XhciQuirks,
|
||||
) -> Result<Self> {
|
||||
//Locate the capability registers from the mapped PCI Bar
|
||||
let cap = unsafe { &mut *(address as *mut CapabilityRegs) };
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user