ddb40deac5
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.
326 lines
14 KiB
Rust
326 lines
14 KiB
Rust
use pci_types::capability::{MultipleMessageSupport, PciCapability};
|
|
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],
|
|
|
|
pcie: &'a Pcie,
|
|
}
|
|
|
|
impl<'a> DriverHandler<'a> {
|
|
pub fn new(
|
|
func: PciFunction,
|
|
endpoint_header: &'a mut EndpointHeader,
|
|
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,
|
|
}
|
|
}
|
|
|
|
pub fn respond(
|
|
&mut self,
|
|
request: pcid_interface::PcidClientRequest,
|
|
) -> pcid_interface::PcidClientResponse {
|
|
use pcid_interface::*;
|
|
|
|
#[forbid(non_exhaustive_omitted_patterns)]
|
|
match request {
|
|
PcidClientRequest::EnableDevice => {
|
|
self.func.legacy_interrupt_line = crate::enable_function(
|
|
&self.pcie,
|
|
&mut self.endpoint_header,
|
|
&mut self.capabilities,
|
|
);
|
|
|
|
PcidClientResponse::EnabledDevice
|
|
}
|
|
PcidClientRequest::RequestVendorCapabilities => PcidClientResponse::VendorCapabilities(
|
|
self.capabilities
|
|
.iter()
|
|
.filter_map(|capability| match capability {
|
|
PciCapability::Vendor(addr) => unsafe {
|
|
Some(VendorSpecificCapability::parse(*addr, self.pcie))
|
|
},
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<_>>(),
|
|
),
|
|
PcidClientRequest::RequestConfig => {
|
|
PcidClientResponse::Config(SubdriverArguments {
|
|
func: self.func,
|
|
device_id: self.device_id,
|
|
})
|
|
}
|
|
PcidClientRequest::RequestFeatures => PcidClientResponse::AllFeatures(
|
|
self.capabilities
|
|
.iter()
|
|
.filter_map(|capability| match capability {
|
|
PciCapability::Msi(_) => Some(PciFeature::Msi),
|
|
PciCapability::MsiX(_) => Some(PciFeature::MsiX),
|
|
_ => None,
|
|
})
|
|
.collect(),
|
|
),
|
|
PcidClientRequest::EnableFeature(feature) => {
|
|
match feature {
|
|
PciFeature::Msi => {
|
|
if let Some(msix_capability) =
|
|
self.capabilities
|
|
.iter_mut()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::MsiX(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
// If MSI-X is supported disable it before enabling MSI as they can't be
|
|
// active at the same time.
|
|
msix_capability.set_enabled(false, self.pcie);
|
|
}
|
|
|
|
let capability = match self.capabilities.iter_mut().find_map(|capability| {
|
|
match capability {
|
|
PciCapability::Msi(cap) => Some(cap),
|
|
_ => None,
|
|
}
|
|
}) {
|
|
Some(capability) => capability,
|
|
None => {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(feature),
|
|
)
|
|
}
|
|
};
|
|
capability.set_enabled(true, self.pcie);
|
|
PcidClientResponse::FeatureEnabled(feature)
|
|
}
|
|
PciFeature::MsiX => {
|
|
if let Some(msi_capability) =
|
|
self.capabilities
|
|
.iter_mut()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::Msi(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
// If MSI is supported disable it before enabling MSI-X as they can't be
|
|
// active at the same time.
|
|
msi_capability.set_enabled(false, self.pcie);
|
|
}
|
|
|
|
let capability = match self.capabilities.iter_mut().find_map(|capability| {
|
|
match capability {
|
|
PciCapability::MsiX(cap) => Some(cap),
|
|
_ => None,
|
|
}
|
|
}) {
|
|
Some(capability) => capability,
|
|
None => {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(feature),
|
|
)
|
|
}
|
|
};
|
|
capability.set_enabled(true, self.pcie);
|
|
PcidClientResponse::FeatureEnabled(feature)
|
|
}
|
|
}
|
|
}
|
|
PcidClientRequest::FeatureInfo(feature) => PcidClientResponse::FeatureInfo(
|
|
feature,
|
|
match feature {
|
|
PciFeature::Msi => {
|
|
if let Some(info) =
|
|
self.capabilities
|
|
.iter()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::Msi(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
PciFeatureInfo::Msi(msi::MsiInfo {
|
|
log2_multiple_message_capable: info.multiple_message_capable()
|
|
as u8,
|
|
is_64bit: info.is_64bit(),
|
|
has_per_vector_masking: info.has_per_vector_masking(),
|
|
})
|
|
} else {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(feature),
|
|
);
|
|
}
|
|
}
|
|
PciFeature::MsiX => {
|
|
if let Some(info) =
|
|
self.capabilities
|
|
.iter()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::MsiX(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
PciFeatureInfo::MsiX(msi::MsixInfo {
|
|
table_bar: info.table_bar(),
|
|
table_offset: info.table_offset(),
|
|
table_size: info.table_size(),
|
|
pba_bar: info.pba_bar(),
|
|
pba_offset: info.pba_offset(),
|
|
})
|
|
} else {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(feature),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
PcidClientRequest::SetFeatureInfo(info_to_set) => match info_to_set {
|
|
SetFeatureInfo::Msi(info_to_set) => {
|
|
if let Some(info) =
|
|
self.capabilities
|
|
.iter_mut()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::Msi(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
if let Some(mme) = info_to_set.multi_message_enable {
|
|
if (info.multiple_message_capable() as u8) < mme {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::InvalidBitPattern,
|
|
);
|
|
}
|
|
info.set_multiple_message_enable(
|
|
match mme {
|
|
0 => MultipleMessageSupport::Int1,
|
|
1 => MultipleMessageSupport::Int2,
|
|
2 => MultipleMessageSupport::Int4,
|
|
3 => MultipleMessageSupport::Int8,
|
|
4 => MultipleMessageSupport::Int16,
|
|
5 => MultipleMessageSupport::Int32,
|
|
_ => {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::InvalidBitPattern,
|
|
)
|
|
}
|
|
},
|
|
self.pcie,
|
|
);
|
|
}
|
|
if let Some(message_addr_and_data) = info_to_set.message_address_and_data {
|
|
let message_addr = message_addr_and_data.addr;
|
|
if message_addr & 0b11 != 0 {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::InvalidBitPattern,
|
|
);
|
|
}
|
|
if message_addr_and_data.data
|
|
& ((1 << info.multiple_message_enable(self.pcie) as u8) - 1)
|
|
!= 0
|
|
{
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::InvalidBitPattern,
|
|
);
|
|
}
|
|
info.set_message_info(
|
|
message_addr,
|
|
message_addr_and_data
|
|
.data
|
|
.try_into()
|
|
.expect("pcid: MSI message data too big"),
|
|
self.pcie,
|
|
);
|
|
}
|
|
if let Some(mask_bits) = info_to_set.mask_bits {
|
|
info.set_message_mask(mask_bits, self.pcie);
|
|
}
|
|
PcidClientResponse::SetFeatureInfo(PciFeature::Msi)
|
|
} else {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(PciFeature::Msi),
|
|
);
|
|
}
|
|
}
|
|
SetFeatureInfo::MsiX { function_mask } => {
|
|
if let Some(info) =
|
|
self.capabilities
|
|
.iter_mut()
|
|
.find_map(|capability| match capability {
|
|
PciCapability::MsiX(cap) => Some(cap),
|
|
_ => None,
|
|
})
|
|
{
|
|
if let Some(mask) = function_mask {
|
|
info.set_function_mask(mask, self.pcie);
|
|
}
|
|
PcidClientResponse::SetFeatureInfo(PciFeature::MsiX)
|
|
} else {
|
|
return PcidClientResponse::Error(
|
|
PcidServerResponseError::NonexistentFeature(PciFeature::MsiX),
|
|
);
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
},
|
|
PcidClientRequest::ReadConfig(offset) => {
|
|
let value = unsafe { self.pcie.read(self.func.addr, offset) };
|
|
return PcidClientResponse::ReadConfig(value);
|
|
}
|
|
PcidClientRequest::WriteConfig(offset, value) => {
|
|
unsafe {
|
|
self.pcie.write(self.func.addr, offset, value);
|
|
}
|
|
return PcidClientResponse::WriteConfig;
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
}
|