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:
Red Bear OS
2026-07-07 09:19:14 +03:00
parent d3b8d08420
commit ddb40deac5
5 changed files with 328 additions and 2 deletions
+42 -1
View File
@@ -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
+4
View File
@@ -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)]
+13 -1
View File
@@ -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)
+2
View File
@@ -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) };
+267
View File
@@ -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));
}
}