Update redox-driver-sys PCI and quirk support
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -12,6 +12,7 @@ thiserror = "2"
|
||||
bitflags = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
bincode = "1"
|
||||
toml = "0.8"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -33,6 +33,7 @@ pub mod irq;
|
||||
pub mod memory;
|
||||
pub mod pci;
|
||||
pub mod pcid_client;
|
||||
pub mod quirks;
|
||||
|
||||
use syscall as redox_syscall;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -134,6 +134,8 @@ pub struct PciDeviceInfo {
|
||||
pub location: PciLocation,
|
||||
pub vendor_id: u16,
|
||||
pub device_id: u16,
|
||||
pub subsystem_vendor_id: u16,
|
||||
pub subsystem_device_id: u16,
|
||||
pub revision: u8,
|
||||
pub class_code: u8,
|
||||
pub subclass: u8,
|
||||
@@ -171,6 +173,14 @@ impl PciDeviceInfo {
|
||||
pub fn find_memory_bar(&self, index: usize) -> Option<&PciBarInfo> {
|
||||
self.bars.iter().find(|b| b.index == index && b.is_memory())
|
||||
}
|
||||
|
||||
pub fn quirks(&self) -> crate::quirks::PciQuirkFlags {
|
||||
crate::quirks::lookup_pci_quirks(self)
|
||||
}
|
||||
|
||||
pub fn has_quirk(&self, flag: crate::quirks::PciQuirkFlags) -> bool {
|
||||
self.quirks().contains(flag)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PciDevice {
|
||||
@@ -351,10 +361,21 @@ impl PciDevice {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let (subsystem_vendor_id, subsystem_device_id) = if header_type == PCI_HEADER_TYPE_NORMAL {
|
||||
(
|
||||
self.read_config_word(0x2C).unwrap_or(0xFFFF),
|
||||
self.read_config_word(0x2E).unwrap_or(0xFFFF),
|
||||
)
|
||||
} else {
|
||||
(0xFFFF, 0xFFFF)
|
||||
};
|
||||
|
||||
Ok(PciDeviceInfo {
|
||||
location: self.location,
|
||||
vendor_id,
|
||||
device_id,
|
||||
subsystem_vendor_id,
|
||||
subsystem_device_id,
|
||||
revision,
|
||||
class_code,
|
||||
subclass,
|
||||
@@ -546,16 +567,19 @@ impl PciDevice {
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable MSI-X by setting the MSI-X Enable bit (bit 14) in Message Control.
|
||||
/// Per PCI spec §6.8.2, bit 14 enables MSI-X; bit 15 is Function Mask.
|
||||
pub fn enable_msix(&mut self, cap_offset: u8) -> Result<()> {
|
||||
let msg_ctrl = self.read_config_word(cap_offset as u64 + 2)?;
|
||||
let new_ctrl = msg_ctrl | 0x8000;
|
||||
let new_ctrl = msg_ctrl | 0x4000;
|
||||
self.write_config_word(cap_offset as u64 + 2, new_ctrl)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disable MSI-X by clearing the MSI-X Enable bit (bit 14) in Message Control.
|
||||
pub fn disable_msix(&mut self, cap_offset: u8) -> Result<()> {
|
||||
let msg_ctrl = self.read_config_word(cap_offset as u64 + 2)?;
|
||||
let new_ctrl = msg_ctrl & !0x8000;
|
||||
let new_ctrl = msg_ctrl & !0x4000;
|
||||
self.write_config_word(cap_offset as u64 + 2, new_ctrl)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -622,10 +646,22 @@ fn enumerate_pci_filtered(class: Option<u8>) -> Result<Vec<PciDeviceInfo>> {
|
||||
let header_type = data[0x0e] & 0x7F;
|
||||
let irq_line = data[0x3c];
|
||||
|
||||
let (subsystem_vendor_id, subsystem_device_id) =
|
||||
if header_type == PCI_HEADER_TYPE_NORMAL && data.len() > 0x2F {
|
||||
(
|
||||
u16::from_le_bytes([data[0x2c], data[0x2d]]),
|
||||
u16::from_le_bytes([data[0x2e], data[0x2f]]),
|
||||
)
|
||||
} else {
|
||||
(0xFFFF, 0xFFFF)
|
||||
};
|
||||
|
||||
devices.push(PciDeviceInfo {
|
||||
location,
|
||||
vendor_id,
|
||||
device_id,
|
||||
subsystem_vendor_id,
|
||||
subsystem_device_id,
|
||||
revision,
|
||||
class_code,
|
||||
subclass,
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
use super::{toml_loader, PciQuirkFlags, PCI_QUIRK_ANY_ID};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// DMI/SMBIOS field identifiers for system matching.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DmiMatchRule {
|
||||
pub sys_vendor: Option<Cow<'static, str>>,
|
||||
pub board_vendor: Option<Cow<'static, str>>,
|
||||
pub board_name: Option<Cow<'static, str>>,
|
||||
pub board_version: Option<Cow<'static, str>>,
|
||||
pub product_name: Option<Cow<'static, str>>,
|
||||
pub product_version: Option<Cow<'static, str>>,
|
||||
pub bios_version: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl DmiMatchRule {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.sys_vendor.is_none()
|
||||
&& self.board_vendor.is_none()
|
||||
&& self.board_name.is_none()
|
||||
&& self.board_version.is_none()
|
||||
&& self.product_name.is_none()
|
||||
&& self.product_version.is_none()
|
||||
&& self.bios_version.is_none()
|
||||
}
|
||||
|
||||
pub fn matches(&self, info: &DmiInfo) -> bool {
|
||||
if let Some(ref val) = self.sys_vendor {
|
||||
if info
|
||||
.sys_vendor
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.board_vendor {
|
||||
if info
|
||||
.board_vendor
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.board_name {
|
||||
if info
|
||||
.board_name
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.board_version {
|
||||
if info
|
||||
.board_version
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.product_name {
|
||||
if info
|
||||
.product_name
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.product_version {
|
||||
if info
|
||||
.product_version
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref val) = self.bios_version {
|
||||
if info
|
||||
.bios_version
|
||||
.as_deref()
|
||||
.map_or(true, |v| v != val.as_ref())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DmiInfo {
|
||||
pub sys_vendor: Option<String>,
|
||||
pub board_vendor: Option<String>,
|
||||
pub board_name: Option<String>,
|
||||
pub board_version: Option<String>,
|
||||
pub product_name: Option<String>,
|
||||
pub product_version: Option<String>,
|
||||
pub bios_version: Option<String>,
|
||||
}
|
||||
|
||||
/// A DMI-based quirk rule: if the system matches the DMI rule, apply PCI
|
||||
/// quirk flags to matching devices.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DmiPciQuirkRule {
|
||||
pub dmi_match: DmiMatchRule,
|
||||
pub vendor: u16,
|
||||
pub device: u16,
|
||||
pub flags: PciQuirkFlags,
|
||||
}
|
||||
|
||||
/// Read DMI/SMBIOS data from the ACPI scheme.
|
||||
///
|
||||
/// Returns `Err(())` if DMI data is not available (e.g., early boot,
|
||||
/// no SMBIOS table, or acpid not running).
|
||||
pub fn read_dmi_info() -> Result<DmiInfo, ()> {
|
||||
let dmi_path = "/scheme/acpi/dmi";
|
||||
match std::fs::read_to_string(dmi_path) {
|
||||
Ok(data) => parse_dmi_data(&data),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dmi_data(data: &str) -> Result<DmiInfo, ()> {
|
||||
let mut info = DmiInfo::default();
|
||||
for line in data.lines() {
|
||||
let Some((key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
let key = key.trim();
|
||||
let value = value.trim().to_string();
|
||||
match key {
|
||||
"sys_vendor" => info.sys_vendor = Some(value),
|
||||
"board_vendor" => info.board_vendor = Some(value),
|
||||
"board_name" => info.board_name = Some(value),
|
||||
"board_version" => info.board_version = Some(value),
|
||||
"product_name" => info.product_name = Some(value),
|
||||
"product_version" => info.product_version = Some(value),
|
||||
"bios_version" => info.bios_version = Some(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// Compiled-in DMI-based PCI quirk rules.
|
||||
const F_NO_MSIX_NO_ASPM: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NO_MSIX.bits() | PciQuirkFlags::NO_ASPM.bits(),
|
||||
);
|
||||
const F_NO_ASPM_NEED_FW: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NO_ASPM.bits() | PciQuirkFlags::NEED_FIRMWARE.bits(),
|
||||
);
|
||||
const F_NEED_IOMMU_NO_ASPM: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NEED_IOMMU.bits() | PciQuirkFlags::NO_ASPM.bits(),
|
||||
);
|
||||
|
||||
pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("LENOVO")),
|
||||
product_name: Some(Cow::Borrowed("ThinkPad X1 Carbon")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x8086,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::NO_ASPM,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Dell Inc.")),
|
||||
product_name: Some(Cow::Borrowed("OptiPlex 7090")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: PCI_QUIRK_ANY_ID,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::NO_MSI,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Dell Inc.")),
|
||||
product_name: Some(Cow::Borrowed("PowerEdge R740")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x14E4,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: F_NO_MSIX_NO_ASPM,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("HP")),
|
||||
product_name: Some(Cow::Borrowed("HP ProBook")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x8086,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::NO_D3COLD,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("ASUSTeK COMPUTER INC.")),
|
||||
board_name: Some(Cow::Borrowed("PRIME X570-PRO")),
|
||||
board_vendor: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x1002,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: F_NO_ASPM_NEED_FW,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
product_name: Some(Cow::Borrowed("Laptop 16")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x1002,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: F_NEED_IOMMU_NO_ASPM,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Microsoft Corporation")),
|
||||
product_name: Some(Cow::Borrowed("Surface Pro")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: PCI_QUIRK_ANY_ID,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::NO_USB3,
|
||||
},
|
||||
DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Gigabyte Technology Co., Ltd.")),
|
||||
board_name: Some(Cow::Borrowed("X570 AORUS MASTER")),
|
||||
board_vendor: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x1022,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::RESET_DELAY_MS,
|
||||
},
|
||||
];
|
||||
|
||||
pub(crate) fn apply_dmi_pci_quirk_rules(
|
||||
info: &PciDeviceInfo,
|
||||
dmi_info: Option<&DmiInfo>,
|
||||
rules: &[DmiPciQuirkRule],
|
||||
) -> PciQuirkFlags {
|
||||
let Some(dmi_info) = dmi_info else {
|
||||
return PciQuirkFlags::empty();
|
||||
};
|
||||
|
||||
let mut flags = PciQuirkFlags::empty();
|
||||
for rule in rules {
|
||||
if !rule.dmi_match.matches(dmi_info) {
|
||||
continue;
|
||||
}
|
||||
if rule.vendor != super::PCI_QUIRK_ANY_ID && rule.vendor != info.vendor_id {
|
||||
continue;
|
||||
}
|
||||
if rule.device != super::PCI_QUIRK_ANY_ID && rule.device != info.device_id {
|
||||
continue;
|
||||
}
|
||||
flags |= rule.flags;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
/// Look up DMI-based PCI quirks for the given device.
|
||||
///
|
||||
/// Checks if the current system matches any DMI rules and if so, applies
|
||||
/// PCI quirk flags to matching devices.
|
||||
pub fn load_dmi_pci_quirks(info: &PciDeviceInfo) -> Result<PciQuirkFlags, ()> {
|
||||
let dmi_info = read_dmi_info()?;
|
||||
|
||||
let mut flags = apply_dmi_pci_quirk_rules(info, Some(&dmi_info), DMI_PCI_QUIRK_RULES);
|
||||
|
||||
if let Ok(toml_flags) = toml_loader::load_dmi_pci_quirks(info, &dmi_info) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dmi_match_all_fields() {
|
||||
let rule = DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
product_name: Some(Cow::Borrowed("Laptop 16")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
let info = DmiInfo {
|
||||
sys_vendor: Some("Framework".to_string()),
|
||||
product_name: Some("Laptop 16".to_string()),
|
||||
board_name: Some("FRANMECP01".to_string()),
|
||||
board_vendor: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
assert!(rule.matches(&info));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_no_match_wrong_vendor() {
|
||||
let rule = DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
let info = DmiInfo {
|
||||
sys_vendor: Some("Lenovo".to_string()),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
assert!(!rule.matches(&info));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_match_missing_field_fails() {
|
||||
let rule = DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
let info = DmiInfo {
|
||||
sys_vendor: None,
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_name: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
assert!(!rule.matches(&info));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_dmi_rules_requires_live_dmi_info() {
|
||||
let info = PciDeviceInfo {
|
||||
location: crate::pci::PciLocation {
|
||||
segment: 0,
|
||||
bus: 0,
|
||||
device: 0,
|
||||
function: 0,
|
||||
},
|
||||
vendor_id: 0x1002,
|
||||
device_id: 0x73BF,
|
||||
subsystem_vendor_id: 0,
|
||||
subsystem_device_id: 0,
|
||||
revision: 0,
|
||||
class_code: 0,
|
||||
subclass: 0,
|
||||
prog_if: 0,
|
||||
header_type: 0,
|
||||
irq: None,
|
||||
bars: Vec::new(),
|
||||
capabilities: Vec::new(),
|
||||
};
|
||||
let rules = [DmiPciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
product_name: Some(Cow::Borrowed("Laptop 16")),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
},
|
||||
vendor: 0x1002,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
flags: PciQuirkFlags::DISABLE_ACCEL,
|
||||
}];
|
||||
|
||||
let flags = apply_dmi_pci_quirk_rules(&info, None, &rules);
|
||||
assert!(flags.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
//! Hardware quirks system for Red Bear OS.
|
||||
//!
|
||||
//! Provides data-driven device quirk tables inspired by Linux's PCI/USB/DMI
|
||||
//! quirk infrastructure, adapted for Redox's userspace driver architecture.
|
||||
//!
|
||||
//! # Design
|
||||
//!
|
||||
//! Quirks are loaded in layers:
|
||||
//! 1. **Compiled-in** (`pci_table`, `usb_table`): Critical quirks always available.
|
||||
//! 2. **TOML files** (`toml_loader`): Extensible quirks from `/etc/quirks.d/*.toml`.
|
||||
//! 3. **DMI rules** (`dmi`): System-level overrides matched by SMBIOS data.
|
||||
//!
|
||||
//! Each layer accumulates flags via bitwise OR, so broader rules can set
|
||||
//! baseline flags and narrower rules add more.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use redox_driver_sys::pci::PciDeviceInfo;
|
||||
//! use redox_driver_sys::quirks::PciQuirkFlags;
|
||||
//!
|
||||
//! fn probe(info: &PciDeviceInfo) {
|
||||
//! let quirks = info.quirks();
|
||||
//! if quirks.contains(PciQuirkFlags::NO_MSIX) {
|
||||
//! // fall back to MSI or legacy IRQ
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
pub mod dmi;
|
||||
pub mod pci_table;
|
||||
pub mod toml_loader;
|
||||
pub mod usb_table;
|
||||
|
||||
use crate::pci::PciDeviceInfo;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for PCI device quirks.
|
||||
///
|
||||
/// Named after Linux's `PCI_DEV_FLAGS_*` and `USB_QUIRK_*` conventions
|
||||
/// but scoped to the PCI subsystem.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PciQuirkFlags: u64 {
|
||||
const NO_MSI = 1 << 0;
|
||||
const NO_MSIX = 1 << 1;
|
||||
const FORCE_LEGACY_IRQ = 1 << 2;
|
||||
const NO_PM = 1 << 3;
|
||||
const NO_D3COLD = 1 << 4;
|
||||
const NO_ASPM = 1 << 5;
|
||||
const NEED_IOMMU = 1 << 6;
|
||||
const NO_IOMMU = 1 << 7;
|
||||
const DMA_32BIT_ONLY = 1 << 8;
|
||||
const RESIZE_BAR = 1 << 9;
|
||||
const DISABLE_BAR_SIZING = 1 << 10;
|
||||
const NEED_FIRMWARE = 1 << 11;
|
||||
const DISABLE_ACCEL = 1 << 12;
|
||||
const FORCE_VRAM_ONLY = 1 << 13;
|
||||
const NO_USB3 = 1 << 14;
|
||||
const RESET_DELAY_MS = 1 << 15;
|
||||
const NO_STRING_FETCH = 1 << 16;
|
||||
const BAD_EEPROM = 1 << 17;
|
||||
const BUS_MASTER_DELAY = 1 << 18;
|
||||
const WRONG_CLASS = 1 << 19;
|
||||
const BROKEN_BRIDGE = 1 << 20;
|
||||
const NO_RESOURCE_RELOC = 1 << 21;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for USB device quirks.
|
||||
///
|
||||
/// Mirrors Linux's `USB_QUIRK_*` defines.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct UsbQuirkFlags: u64 {
|
||||
const NO_STRING_FETCH = 1 << 0;
|
||||
const RESET_DELAY = 1 << 1;
|
||||
const NO_USB3 = 1 << 2;
|
||||
const NO_SET_CONFIG = 1 << 3;
|
||||
const NO_SUSPEND = 1 << 4;
|
||||
const NEED_RESET = 1 << 5;
|
||||
const BAD_DESCRIPTOR = 1 << 6;
|
||||
const NO_LPM = 1 << 7;
|
||||
const NO_U1U2 = 1 << 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// Wildcard value for PCI ID matching.
|
||||
pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF;
|
||||
|
||||
/// Compiled-in PCI quirk entry. All matching entries' flags accumulate via OR.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PciQuirkEntry {
|
||||
/// PCI vendor ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub vendor: u16,
|
||||
/// PCI device ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub device: u16,
|
||||
/// PCI subsystem vendor ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub subvendor: u16,
|
||||
/// PCI subsystem device ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub subdevice: u16,
|
||||
/// Bitmask applied to the 24-bit class code before comparison.
|
||||
pub class_mask: u32,
|
||||
/// 24-bit class code value to match after masking. Ignored when `class_mask` is 0.
|
||||
pub class_match: u32,
|
||||
/// Lower bound of revision ID (inclusive).
|
||||
pub revision_lo: u8,
|
||||
/// Upper bound of revision ID (inclusive).
|
||||
pub revision_hi: u8,
|
||||
/// Quirk flags to apply when this entry matches.
|
||||
pub flags: PciQuirkFlags,
|
||||
}
|
||||
|
||||
impl PciQuirkEntry {
|
||||
/// Convenience constant for an all-wildcard entry (matches everything).
|
||||
///
|
||||
/// Useful as a base when constructing entries with `..PciQuirkEntry::WILDCARD`.
|
||||
pub const WILDCARD: Self = Self {
|
||||
vendor: PCI_QUIRK_ANY_ID,
|
||||
device: PCI_QUIRK_ANY_ID,
|
||||
subvendor: PCI_QUIRK_ANY_ID,
|
||||
subdevice: PCI_QUIRK_ANY_ID,
|
||||
class_mask: 0,
|
||||
class_match: 0,
|
||||
revision_lo: 0x00,
|
||||
revision_hi: 0xFF,
|
||||
flags: PciQuirkFlags::empty(),
|
||||
};
|
||||
|
||||
fn matches_with_subsystem(&self, info: &PciDeviceInfo, match_subsystem: bool) -> bool {
|
||||
if self.vendor != PCI_QUIRK_ANY_ID && self.vendor != info.vendor_id {
|
||||
return false;
|
||||
}
|
||||
if self.device != PCI_QUIRK_ANY_ID && self.device != info.device_id {
|
||||
return false;
|
||||
}
|
||||
if info.revision < self.revision_lo || info.revision > self.revision_hi {
|
||||
return false;
|
||||
}
|
||||
if self.class_mask != 0 {
|
||||
let class24 = ((info.class_code as u32) << 16)
|
||||
| ((info.subclass as u32) << 8)
|
||||
| (info.prog_if as u32);
|
||||
if (class24 & self.class_mask) != (self.class_match & self.class_mask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if match_subsystem {
|
||||
if self.subvendor != PCI_QUIRK_ANY_ID && self.subvendor != info.subsystem_vendor_id {
|
||||
return false;
|
||||
}
|
||||
if self.subdevice != PCI_QUIRK_ANY_ID && self.subdevice != info.subsystem_device_id {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Check whether this quirk entry matches the given PCI device info.
|
||||
pub fn matches(&self, info: &PciDeviceInfo) -> bool {
|
||||
self.matches_with_subsystem(info, true)
|
||||
}
|
||||
|
||||
pub(crate) fn matches_toml(&self, info: &PciDeviceInfo) -> bool {
|
||||
self.matches_with_subsystem(info, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PciQuirkEntry {
|
||||
fn default() -> Self {
|
||||
Self::WILDCARD
|
||||
}
|
||||
}
|
||||
|
||||
/// A single compiled-in USB quirk entry.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UsbQuirkEntry {
|
||||
/// USB vendor ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub vendor: u16,
|
||||
/// USB product ID to match, or `PCI_QUIRK_ANY_ID` for any.
|
||||
pub product: u16,
|
||||
/// Quirk flags to apply when this entry matches.
|
||||
pub flags: UsbQuirkFlags,
|
||||
}
|
||||
|
||||
impl UsbQuirkEntry {
|
||||
/// Convenience constant for an all-wildcard entry.
|
||||
pub const WILDCARD: Self = Self {
|
||||
vendor: PCI_QUIRK_ANY_ID,
|
||||
product: PCI_QUIRK_ANY_ID,
|
||||
flags: UsbQuirkFlags::empty(),
|
||||
};
|
||||
|
||||
pub fn matches(&self, vendor: u16, product: u16) -> bool {
|
||||
(self.vendor == PCI_QUIRK_ANY_ID || self.vendor == vendor)
|
||||
&& (self.product == PCI_QUIRK_ANY_ID || self.product == product)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UsbQuirkEntry {
|
||||
fn default() -> Self {
|
||||
Self::WILDCARD
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up accumulated PCI quirk flags for the given device.
|
||||
///
|
||||
/// Checks all available quirk sources in order:
|
||||
/// 1. Compiled-in table (always available)
|
||||
/// 2. TOML quirk files from `/etc/quirks.d/` (if filesystem is mounted)
|
||||
/// 3. DMI-based system quirk overrides (if SMBIOS data is available)
|
||||
///
|
||||
/// All matching entries' flags are ORed together.
|
||||
pub fn lookup_pci_quirks(info: &PciDeviceInfo) -> PciQuirkFlags {
|
||||
let mut flags = PciQuirkFlags::empty();
|
||||
|
||||
// Layer 1: Compiled-in table
|
||||
for entry in pci_table::PCI_QUIRK_TABLE {
|
||||
if entry.matches(info) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
|
||||
// Layer 2: TOML quirk files (best-effort; may not be available early in boot)
|
||||
if let Ok(toml_flags) = toml_loader::load_pci_quirks(info) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
// Layer 3: DMI-based system quirks (best-effort)
|
||||
if let Ok(dmi_flags) = dmi::load_dmi_pci_quirks(info) {
|
||||
flags |= dmi_flags;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
/// Look up accumulated USB quirk flags for the given vendor/product pair.
|
||||
pub fn lookup_usb_quirks(vendor: u16, product: u16) -> UsbQuirkFlags {
|
||||
let mut flags = UsbQuirkFlags::empty();
|
||||
|
||||
for entry in usb_table::USB_QUIRK_TABLE {
|
||||
if entry.matches(vendor, product) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(toml_flags) = toml_loader::load_usb_quirks(vendor, product) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::pci::{PciDeviceInfo, PciLocation};
|
||||
|
||||
fn make_info(vendor: u16, device: u16, class: u8, subclass: u8, revision: u8) -> PciDeviceInfo {
|
||||
PciDeviceInfo {
|
||||
location: PciLocation {
|
||||
segment: 0,
|
||||
bus: 0,
|
||||
device: 0,
|
||||
function: 0,
|
||||
},
|
||||
vendor_id: vendor,
|
||||
device_id: device,
|
||||
subsystem_vendor_id: 0,
|
||||
subsystem_device_id: 0,
|
||||
revision,
|
||||
class_code: class,
|
||||
subclass,
|
||||
prog_if: 0,
|
||||
header_type: 0,
|
||||
irq: None,
|
||||
bars: Vec::new(),
|
||||
capabilities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_entry_matches_everything() {
|
||||
let entry = PciQuirkEntry {
|
||||
flags: PciQuirkFlags::NO_PM,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
};
|
||||
let info = make_info(0x8086, 0x1234, 0x03, 0x00, 0x01);
|
||||
assert!(entry.matches(&info));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vendor_only_match() {
|
||||
let entry = PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
flags: PciQuirkFlags::NEED_FIRMWARE,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
};
|
||||
assert!(entry.matches(&make_info(0x1002, 0x73BF, 0x03, 0x00, 0x00)));
|
||||
assert!(!entry.matches(&make_info(0x8086, 0x46A6, 0x03, 0x00, 0x00)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revision_range_match() {
|
||||
let entry = PciQuirkEntry {
|
||||
vendor: 0x8086,
|
||||
device: 0x46A6,
|
||||
revision_lo: 0x00,
|
||||
revision_hi: 0x04,
|
||||
flags: PciQuirkFlags::DISABLE_ACCEL,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
};
|
||||
assert!(entry.matches(&make_info(0x8086, 0x46A6, 0x03, 0x00, 0x03)));
|
||||
assert!(!entry.matches(&make_info(0x8086, 0x46A6, 0x03, 0x00, 0x05)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_mask_match() {
|
||||
let entry = PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
class_mask: 0xFF0000,
|
||||
class_match: 0x030000,
|
||||
flags: PciQuirkFlags::NO_D3COLD,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
};
|
||||
assert!(entry.matches(&make_info(0x1002, 0x7310, 0x03, 0x00, 0x00)));
|
||||
assert!(!entry.matches(&make_info(0x1002, 0x7310, 0x02, 0x00, 0x00)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flags_accumulate() {
|
||||
let table = &[
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
flags: PciQuirkFlags::NEED_FIRMWARE,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
device: 0x7310,
|
||||
flags: PciQuirkFlags::NO_MSIX,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
];
|
||||
|
||||
let info = make_info(0x1002, 0x7310, 0x03, 0x00, 0x00);
|
||||
let mut flags = PciQuirkFlags::empty();
|
||||
for entry in table {
|
||||
if entry.matches(&info) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
assert!(flags.contains(PciQuirkFlags::NEED_FIRMWARE));
|
||||
assert!(flags.contains(PciQuirkFlags::NO_MSIX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usb_quirk_lookup_works() {
|
||||
let flags = lookup_usb_quirks(0x0000, 0x0000);
|
||||
assert!(!flags.contains(UsbQuirkFlags::NO_STRING_FETCH));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use super::{PciQuirkEntry, PciQuirkFlags};
|
||||
|
||||
const F_NEED_FW_NO_D3: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NEED_FIRMWARE.bits() | PciQuirkFlags::NO_D3COLD.bits(),
|
||||
);
|
||||
const F_NEED_FW_NO_PM: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NEED_FIRMWARE.bits() | PciQuirkFlags::NO_PM.bits(),
|
||||
);
|
||||
const F_FULL_AMD_GPU: PciQuirkFlags = PciQuirkFlags::from_bits_truncate(
|
||||
PciQuirkFlags::NEED_FIRMWARE.bits()
|
||||
| PciQuirkFlags::NO_D3COLD.bits()
|
||||
| PciQuirkFlags::NO_ASPM.bits(),
|
||||
);
|
||||
|
||||
pub const PCI_QUIRK_TABLE: &[PciQuirkEntry] = &[
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
class_mask: 0xFF0000,
|
||||
class_match: 0x030000,
|
||||
flags: F_NEED_FW_NO_D3,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
device: 0x7310,
|
||||
revision_lo: 0x00,
|
||||
revision_hi: 0x01,
|
||||
flags: PciQuirkFlags::NO_ASPM,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
device: 0x73BF,
|
||||
flags: F_NEED_FW_NO_D3,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1002,
|
||||
device: 0x7480,
|
||||
flags: F_FULL_AMD_GPU,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x8086,
|
||||
class_mask: 0xFF0000,
|
||||
class_match: 0x030000,
|
||||
revision_lo: 0x00,
|
||||
revision_hi: 0x04,
|
||||
flags: PciQuirkFlags::NO_MSI,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x8086,
|
||||
device: 0x46A6,
|
||||
flags: PciQuirkFlags::NO_MSI,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x8086,
|
||||
device: 0x2725,
|
||||
class_mask: 0xFF0000,
|
||||
class_match: 0x028000,
|
||||
flags: F_NEED_FW_NO_PM,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x8086,
|
||||
device: 0x51F0,
|
||||
class_mask: 0xFF0000,
|
||||
class_match: 0x028000,
|
||||
flags: F_NEED_FW_NO_PM,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1022,
|
||||
device: 0x145C,
|
||||
flags: PciQuirkFlags::NO_MSIX,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1022,
|
||||
device: 0x1639,
|
||||
flags: PciQuirkFlags::NO_ASPM,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
PciQuirkEntry {
|
||||
vendor: 0x1022,
|
||||
device: 0x1483,
|
||||
flags: PciQuirkFlags::NO_RESOURCE_RELOC,
|
||||
..PciQuirkEntry::WILDCARD
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,558 @@
|
||||
use super::{
|
||||
dmi::{self, DmiInfo, DmiMatchRule, DmiPciQuirkRule},
|
||||
PciQuirkEntry, PciQuirkFlags, UsbQuirkEntry, UsbQuirkFlags, PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
const QUIRKS_DIR: &str = "/etc/quirks.d";
|
||||
|
||||
pub fn load_pci_quirks(info: &PciDeviceInfo) -> Result<PciQuirkFlags, ()> {
|
||||
let mut flags = PciQuirkFlags::empty();
|
||||
let entries = read_toml_pci_entries().map_err(|_| ())?;
|
||||
for entry in &entries {
|
||||
if entry.matches_toml(info) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
pub fn load_usb_quirks(vendor: u16, product: u16) -> Result<UsbQuirkFlags, ()> {
|
||||
let mut flags = UsbQuirkFlags::empty();
|
||||
let entries = read_toml_usb_entries().map_err(|_| ())?;
|
||||
for entry in &entries {
|
||||
if entry.matches(vendor, product) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
pub(crate) fn load_dmi_pci_quirks(
|
||||
info: &PciDeviceInfo,
|
||||
dmi_info: &DmiInfo,
|
||||
) -> Result<PciQuirkFlags, ()> {
|
||||
let entries = read_toml_dmi_entries().map_err(|_| ())?;
|
||||
Ok(dmi::apply_dmi_pci_quirk_rules(
|
||||
info,
|
||||
Some(dmi_info),
|
||||
&entries,
|
||||
))
|
||||
}
|
||||
|
||||
fn bounded_u16(val: &toml::Value, field: &str, path: &str) -> Option<u16> {
|
||||
match val.as_integer() {
|
||||
Some(v) => u16::try_from(v).ok().or_else(|| {
|
||||
log::warn!("quirks: {path}: {field}={v} out of u16 range, skipping entry");
|
||||
None
|
||||
}),
|
||||
None => {
|
||||
log::warn!("quirks: {path}: {field} is not an integer, skipping entry");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_u8(val: &toml::Value, field: &str, path: &str) -> Option<u8> {
|
||||
match val.as_integer() {
|
||||
Some(v) => u8::try_from(v).ok().or_else(|| {
|
||||
log::warn!("quirks: {path}: {field}={v} out of u8 range, skipping entry");
|
||||
None
|
||||
}),
|
||||
None => {
|
||||
log::warn!("quirks: {path}: {field} is not an integer, skipping entry");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounded_u32(val: &toml::Value, field: &str, path: &str) -> Option<u32> {
|
||||
match val.as_integer() {
|
||||
Some(v) => u32::try_from(v).ok().or_else(|| {
|
||||
log::warn!("quirks: {path}: {field}={v} out of u32 range, skipping entry");
|
||||
None
|
||||
}),
|
||||
None => {
|
||||
log::warn!("quirks: {path}: {field} is not an integer, skipping entry");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sorted_toml_files(dir: &str) -> std::io::Result<Vec<std::path::PathBuf>> {
|
||||
let mut paths: Vec<_> = std::fs::read_dir(dir)?
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().map_or(false, |e| e == "toml"))
|
||||
.collect();
|
||||
paths.sort();
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
const PCI_FLAG_NAMES: &[(&str, PciQuirkFlags)] = &[
|
||||
("no_msi", PciQuirkFlags::NO_MSI),
|
||||
("no_msix", PciQuirkFlags::NO_MSIX),
|
||||
("force_legacy_irq", PciQuirkFlags::FORCE_LEGACY_IRQ),
|
||||
("no_pm", PciQuirkFlags::NO_PM),
|
||||
("no_d3cold", PciQuirkFlags::NO_D3COLD),
|
||||
("no_aspm", PciQuirkFlags::NO_ASPM),
|
||||
("need_iommu", PciQuirkFlags::NEED_IOMMU),
|
||||
("no_iommu", PciQuirkFlags::NO_IOMMU),
|
||||
("dma_32bit_only", PciQuirkFlags::DMA_32BIT_ONLY),
|
||||
("resize_bar", PciQuirkFlags::RESIZE_BAR),
|
||||
("disable_bar_sizing", PciQuirkFlags::DISABLE_BAR_SIZING),
|
||||
("need_firmware", PciQuirkFlags::NEED_FIRMWARE),
|
||||
("disable_accel", PciQuirkFlags::DISABLE_ACCEL),
|
||||
("force_vram_only", PciQuirkFlags::FORCE_VRAM_ONLY),
|
||||
("no_usb3", PciQuirkFlags::NO_USB3),
|
||||
("reset_delay_ms", PciQuirkFlags::RESET_DELAY_MS),
|
||||
("no_string_fetch", PciQuirkFlags::NO_STRING_FETCH),
|
||||
("bad_eeprom", PciQuirkFlags::BAD_EEPROM),
|
||||
("bus_master_delay", PciQuirkFlags::BUS_MASTER_DELAY),
|
||||
("wrong_class", PciQuirkFlags::WRONG_CLASS),
|
||||
("broken_bridge", PciQuirkFlags::BROKEN_BRIDGE),
|
||||
("no_resource_reloc", PciQuirkFlags::NO_RESOURCE_RELOC),
|
||||
];
|
||||
|
||||
const USB_FLAG_NAMES: &[(&str, UsbQuirkFlags)] = &[
|
||||
("no_string_fetch", UsbQuirkFlags::NO_STRING_FETCH),
|
||||
("reset_delay", UsbQuirkFlags::RESET_DELAY),
|
||||
("no_usb3", UsbQuirkFlags::NO_USB3),
|
||||
("no_set_config", UsbQuirkFlags::NO_SET_CONFIG),
|
||||
("no_suspend", UsbQuirkFlags::NO_SUSPEND),
|
||||
("need_reset", UsbQuirkFlags::NEED_RESET),
|
||||
("bad_descriptor", UsbQuirkFlags::BAD_DESCRIPTOR),
|
||||
("no_lpm", UsbQuirkFlags::NO_LPM),
|
||||
("no_u1u2", UsbQuirkFlags::NO_U1U2),
|
||||
];
|
||||
|
||||
fn flag_from_name<F: Copy>(name: &str, mapping: &[(&str, F)]) -> Option<F> {
|
||||
mapping
|
||||
.iter()
|
||||
.find_map(|(candidate, flag)| (*candidate == name).then_some(*flag))
|
||||
}
|
||||
|
||||
fn parse_flags_from_names<F>(names: &[toml::Value], mapping: &[(&str, F)]) -> F
|
||||
where
|
||||
F: bitflags::Flags + Copy + std::ops::BitOrAssign,
|
||||
{
|
||||
let mut flags = F::empty();
|
||||
for name in names.iter().filter_map(toml::Value::as_str) {
|
||||
if let Some(flag) = flag_from_name(name, mapping) {
|
||||
flags |= flag;
|
||||
}
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
fn parse_flags<F>(table: &toml::Table, path: &str, kind: &str, mapping: &[(&str, F)]) -> F
|
||||
where
|
||||
F: bitflags::Flags + Copy + std::ops::BitOrAssign,
|
||||
{
|
||||
let Some(names) = table.get("flags").and_then(|v| v.as_array()) else {
|
||||
return F::empty();
|
||||
};
|
||||
|
||||
for flag in names {
|
||||
let Some(name) = flag.as_str() else {
|
||||
continue;
|
||||
};
|
||||
if flag_from_name(name, mapping).is_none() {
|
||||
log::warn!("quirks: {path}: unknown {kind} quirk flag '{name}'");
|
||||
}
|
||||
}
|
||||
|
||||
parse_flags_from_names(names, mapping)
|
||||
}
|
||||
|
||||
fn parse_string_field(
|
||||
table: &toml::Table,
|
||||
field: &str,
|
||||
path: &str,
|
||||
kind: &str,
|
||||
) -> Result<Option<Cow<'static, str>>, ()> {
|
||||
let Some(value) = table.get(field) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match value.as_str() {
|
||||
Some(value) => Ok(Some(Cow::Owned(value.to_string()))),
|
||||
None => {
|
||||
log::warn!("quirks: {path}: {kind}.{field} is not a string, skipping entry");
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dmi_match_rule(table: &toml::Table, path: &str) -> Option<DmiMatchRule> {
|
||||
let sys_vendor = match parse_string_field(table, "sys_vendor", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let board_vendor = match parse_string_field(table, "board_vendor", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let board_name = match parse_string_field(table, "board_name", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let board_version = match parse_string_field(table, "board_version", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let product_name = match parse_string_field(table, "product_name", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let product_version = match parse_string_field(table, "product_version", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
let bios_version = match parse_string_field(table, "bios_version", path, "match") {
|
||||
Ok(value) => value,
|
||||
Err(()) => return None,
|
||||
};
|
||||
|
||||
let rule = DmiMatchRule {
|
||||
sys_vendor,
|
||||
board_vendor,
|
||||
board_name,
|
||||
board_version,
|
||||
product_name,
|
||||
product_version,
|
||||
bios_version,
|
||||
};
|
||||
|
||||
if rule.is_empty() {
|
||||
log::warn!("quirks: {path}: dmi_system_quirk.match has no fields, skipping entry");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(rule)
|
||||
}
|
||||
|
||||
fn read_toml_pci_entries() -> std::io::Result<Vec<PciQuirkEntry>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
let path_str = path.display().to_string();
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to read {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let doc = match content.parse::<toml::Value>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to parse {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parse_pci_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_pci_toml(doc: &toml::Value, out: &mut Vec<PciQuirkEntry>, path: &str) {
|
||||
let Some(arr) = doc.get("pci_quirk").and_then(|v| v.as_array()) else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: pci_quirk entry is not a table, skipping");
|
||||
continue;
|
||||
};
|
||||
let vendor = table
|
||||
.get("vendor")
|
||||
.and_then(|v| bounded_u16(v, "vendor", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let device = table
|
||||
.get("device")
|
||||
.and_then(|v| bounded_u16(v, "device", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let subvendor = table
|
||||
.get("subvendor")
|
||||
.and_then(|v| bounded_u16(v, "subvendor", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let subdevice = table
|
||||
.get("subdevice")
|
||||
.and_then(|v| bounded_u16(v, "subdevice", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let class_match = table
|
||||
.get("class")
|
||||
.and_then(|v| bounded_u32(v, "class", path))
|
||||
.unwrap_or(0);
|
||||
let explicit_mask = table
|
||||
.get("class_mask")
|
||||
.and_then(|v| bounded_u32(v, "class_mask", path));
|
||||
let class_mask = explicit_mask.unwrap_or(if class_match != 0 { 0xFF0000 } else { 0 });
|
||||
let revision_lo = table
|
||||
.get("revision_lo")
|
||||
.and_then(|v| bounded_u8(v, "revision_lo", path))
|
||||
.unwrap_or(0x00);
|
||||
let revision_hi = table
|
||||
.get("revision_hi")
|
||||
.and_then(|v| bounded_u8(v, "revision_hi", path))
|
||||
.unwrap_or(0xFF);
|
||||
let flags = parse_flags(table, path, "PCI", PCI_FLAG_NAMES);
|
||||
out.push(PciQuirkEntry {
|
||||
vendor,
|
||||
device,
|
||||
subvendor,
|
||||
subdevice,
|
||||
class_mask,
|
||||
class_match,
|
||||
revision_lo,
|
||||
revision_hi,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn read_toml_usb_entries() -> std::io::Result<Vec<UsbQuirkEntry>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
let path_str = path.display().to_string();
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to read {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let doc = match content.parse::<toml::Value>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to parse {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parse_usb_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_usb_toml(doc: &toml::Value, out: &mut Vec<UsbQuirkEntry>, path: &str) {
|
||||
let Some(arr) = doc.get("usb_quirk").and_then(|v| v.as_array()) else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: usb_quirk entry is not a table, skipping");
|
||||
continue;
|
||||
};
|
||||
let vendor = table
|
||||
.get("vendor")
|
||||
.and_then(|v| bounded_u16(v, "vendor", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let product = table
|
||||
.get("product")
|
||||
.and_then(|v| bounded_u16(v, "product", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let flags = parse_flags(table, path, "USB", USB_FLAG_NAMES);
|
||||
out.push(UsbQuirkEntry {
|
||||
vendor,
|
||||
product,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn read_toml_dmi_entries() -> std::io::Result<Vec<DmiPciQuirkRule>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
let path_str = path.display().to_string();
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to read {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let doc = match content.parse::<toml::Value>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to parse {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parse_dmi_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_dmi_toml(doc: &toml::Value, out: &mut Vec<DmiPciQuirkRule>, path: &str) {
|
||||
let Some(arr) = doc.get("dmi_system_quirk").and_then(|v| v.as_array()) else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: dmi_system_quirk entry is not a table, skipping");
|
||||
continue;
|
||||
};
|
||||
let Some(match_table) = table.get("match").and_then(|v| v.as_table()) else {
|
||||
log::warn!("quirks: {path}: dmi_system_quirk entry is missing match table, skipping");
|
||||
continue;
|
||||
};
|
||||
let Some(dmi_match) = parse_dmi_match_rule(match_table, path) else {
|
||||
continue;
|
||||
};
|
||||
let vendor = match table.get("pci_vendor") {
|
||||
Some(value) => match bounded_u16(value, "pci_vendor", path) {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
},
|
||||
None => PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
let device = match table.get("pci_device") {
|
||||
Some(value) => match bounded_u16(value, "pci_device", path) {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
},
|
||||
None => PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
let flags = parse_flags(table, path, "PCI", PCI_FLAG_NAMES);
|
||||
|
||||
out.push(DmiPciQuirkRule {
|
||||
dmi_match,
|
||||
vendor,
|
||||
device,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::pci::{PciDeviceInfo, PciLocation};
|
||||
|
||||
fn make_info(vendor: u16, device: u16) -> PciDeviceInfo {
|
||||
PciDeviceInfo {
|
||||
location: PciLocation {
|
||||
segment: 0,
|
||||
bus: 0,
|
||||
device: 0,
|
||||
function: 0,
|
||||
},
|
||||
vendor_id: vendor,
|
||||
device_id: device,
|
||||
subsystem_vendor_id: 0,
|
||||
subsystem_device_id: 0,
|
||||
revision: 0,
|
||||
class_code: 0,
|
||||
subclass: 0,
|
||||
prog_if: 0,
|
||||
header_type: 0,
|
||||
irq: None,
|
||||
bars: Vec::new(),
|
||||
capabilities: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_toml_matches_sys_vendor_and_product_name() {
|
||||
let doc = r#"
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1002
|
||||
pci_device = 0x73BF
|
||||
flags = ["disable_accel"]
|
||||
match.sys_vendor = "Framework"
|
||||
match.product_name = "Laptop 16"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_dmi_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 1);
|
||||
|
||||
let dmi_info = DmiInfo {
|
||||
sys_vendor: Some("Framework".to_string()),
|
||||
product_name: Some("Laptop 16".to_string()),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
|
||||
let flags =
|
||||
dmi::apply_dmi_pci_quirk_rules(&make_info(0x1002, 0x73BF), Some(&dmi_info), &rules);
|
||||
assert!(flags.contains(PciQuirkFlags::DISABLE_ACCEL));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_toml_vendor_only_selector_matches_any_device_for_vendor() {
|
||||
let doc = r#"
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x8086
|
||||
flags = ["no_aspm"]
|
||||
match.sys_vendor = "LENOVO"
|
||||
match.product_name = "ThinkPad X1 Carbon"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_dmi_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 1);
|
||||
assert_eq!(rules[0].vendor, 0x8086);
|
||||
assert_eq!(rules[0].device, PCI_QUIRK_ANY_ID);
|
||||
|
||||
let dmi_info = DmiInfo {
|
||||
sys_vendor: Some("LENOVO".to_string()),
|
||||
product_name: Some("ThinkPad X1 Carbon".to_string()),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
|
||||
let flags =
|
||||
dmi::apply_dmi_pci_quirk_rules(&make_info(0x8086, 0x46A6), Some(&dmi_info), &rules);
|
||||
assert!(flags.contains(PciQuirkFlags::NO_ASPM));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_toml_rules_do_not_apply_without_dmi_info() {
|
||||
let doc = r#"
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1002
|
||||
flags = ["need_firmware"]
|
||||
match.sys_vendor = "Framework"
|
||||
match.product_name = "Laptop 16"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_dmi_toml(&doc, &mut rules, "test.toml");
|
||||
|
||||
let flags = dmi::apply_dmi_pci_quirk_rules(&make_info(0x1002, 0x73BF), None, &rules);
|
||||
assert!(flags.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dmi_toml_invalid_pci_selector_skips_entry() {
|
||||
let doc = r#"
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1_0000
|
||||
flags = ["disable_accel"]
|
||||
match.sys_vendor = "Framework"
|
||||
match.product_name = "Laptop 16"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_dmi_toml(&doc, &mut rules, "test.toml");
|
||||
|
||||
assert!(rules.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use super::{UsbQuirkEntry, UsbQuirkFlags, PCI_QUIRK_ANY_ID};
|
||||
|
||||
const F_NO_SUSP_RESET: UsbQuirkFlags = UsbQuirkFlags::from_bits_truncate(
|
||||
UsbQuirkFlags::NO_SUSPEND.bits() | UsbQuirkFlags::NEED_RESET.bits(),
|
||||
);
|
||||
const F_BAD_DESC_NO_CFG: UsbQuirkFlags = UsbQuirkFlags::from_bits_truncate(
|
||||
UsbQuirkFlags::BAD_DESCRIPTOR.bits() | UsbQuirkFlags::NO_SET_CONFIG.bits(),
|
||||
);
|
||||
|
||||
pub const USB_QUIRK_TABLE: &[UsbQuirkEntry] = &[
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x0BDA,
|
||||
product: 0x8153,
|
||||
flags: UsbQuirkFlags::NO_STRING_FETCH,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x0BDA,
|
||||
product: 0x8156,
|
||||
flags: UsbQuirkFlags::NO_STRING_FETCH,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x1A40,
|
||||
product: 0x0101,
|
||||
flags: UsbQuirkFlags::NO_LPM,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x2109,
|
||||
product: 0x2813,
|
||||
flags: UsbQuirkFlags::NO_LPM,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x2109,
|
||||
product: 0x0815,
|
||||
flags: UsbQuirkFlags::NO_U1U2,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x8087,
|
||||
product: 0x0025,
|
||||
flags: UsbQuirkFlags::NO_SUSPEND,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x8087,
|
||||
product: 0x0A2B,
|
||||
flags: F_NO_SUSP_RESET,
|
||||
},
|
||||
UsbQuirkEntry {
|
||||
vendor: 0x0A12,
|
||||
product: PCI_QUIRK_ANY_ID,
|
||||
flags: F_BAD_DESC_NO_CFG,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "custom"
|
||||
script = """
|
||||
mkdir -p "${COOKBOOK_STAGE}/etc/quirks.d"
|
||||
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/00-core.toml" "${COOKBOOK_STAGE}/etc/quirks.d/00-core.toml"
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/10-gpu.toml" "${COOKBOOK_STAGE}/etc/quirks.d/10-gpu.toml"
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/20-usb.toml" "${COOKBOOK_STAGE}/etc/quirks.d/20-usb.toml"
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/30-net.toml" "${COOKBOOK_STAGE}/etc/quirks.d/30-net.toml"
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/40-storage.toml" "${COOKBOOK_STAGE}/etc/quirks.d/40-storage.toml"
|
||||
cp "${COOKBOOK_SOURCE}/quirks.d/50-system.toml" "${COOKBOOK_STAGE}/etc/quirks.d/50-system.toml"
|
||||
"""
|
||||
@@ -0,0 +1,12 @@
|
||||
# Core chipset and bus controller quirks.
|
||||
# These apply to fundamental platform devices.
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x1483
|
||||
flags = ["no_resource_reloc"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
class = 0x060400
|
||||
flags = ["no_resource_reloc"]
|
||||
@@ -0,0 +1,31 @@
|
||||
# GPU display controller quirks.
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1002
|
||||
class = 0x030000
|
||||
flags = ["no_d3cold", "need_firmware"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
class = 0x030000
|
||||
flags = ["need_firmware"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x10DE
|
||||
class = 0x030000
|
||||
flags = ["no_d3cold", "need_firmware"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1002
|
||||
device = 0x744C
|
||||
flags = ["need_firmware", "need_iommu"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1002
|
||||
device = 0x73EF
|
||||
flags = ["need_firmware", "dma_32bit_only"]
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x0BDA
|
||||
product = 0x8153
|
||||
flags = ["no_string_fetch"]
|
||||
@@ -0,0 +1,40 @@
|
||||
# USB controller and device quirks.
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x1A40
|
||||
product = 0x0101
|
||||
flags = ["no_lpm"]
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x2109
|
||||
product = 0x2813
|
||||
flags = ["no_lpm"]
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x2109
|
||||
product = 0x0815
|
||||
flags = ["no_u1u2"]
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x8087
|
||||
product = 0x0A2B
|
||||
flags = ["no_suspend", "need_reset"]
|
||||
|
||||
[[usb_quirk]]
|
||||
vendor = 0x0A12
|
||||
flags = ["bad_descriptor", "no_set_config"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x145C
|
||||
flags = ["no_msix"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x1639
|
||||
flags = ["no_aspm"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x7AE0
|
||||
flags = ["reset_delay_ms"]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Network controller quirks.
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x10EC
|
||||
device = 0x8125
|
||||
flags = ["no_aspm"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x10EC
|
||||
device = 0x8168
|
||||
flags = ["no_aspm", "bus_master_delay"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x10EC
|
||||
device = 0x8139
|
||||
flags = ["no_pm", "dma_32bit_only"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x14E4
|
||||
device = 0x1657
|
||||
flags = ["no_aspm"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x14E4
|
||||
device = 0x16A1
|
||||
flags = ["no_aspm", "bad_eeprom"]
|
||||
@@ -0,0 +1,11 @@
|
||||
# Storage controller quirks.
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1B4B
|
||||
device = 0x9215
|
||||
flags = ["broken_bridge"]
|
||||
|
||||
[[pci_quirk]]
|
||||
vendor = 0x1B4B
|
||||
device = 0x9230
|
||||
flags = ["broken_bridge", "no_aspm"]
|
||||
@@ -0,0 +1,51 @@
|
||||
# DMI-based system-level quirk overrides.
|
||||
# These entries are evaluated at runtime when /scheme/acpi/dmi is available.
|
||||
# ACPI table skip rules are also supported via [[acpi_table_quirk]] for narrow,
|
||||
# DMI-conditioned suppression of known bad firmware tables.
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x8086
|
||||
flags = ["no_aspm"]
|
||||
match.sys_vendor = "LENOVO"
|
||||
match.product_name = "ThinkPad X1 Carbon"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
flags = ["no_msi"]
|
||||
match.sys_vendor = "Dell Inc."
|
||||
match.product_name = "OptiPlex 7090"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x14E4
|
||||
flags = ["no_msix", "no_aspm"]
|
||||
match.sys_vendor = "Dell Inc."
|
||||
match.product_name = "PowerEdge R740"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1002
|
||||
flags = ["need_iommu", "no_aspm"]
|
||||
match.sys_vendor = "Framework"
|
||||
match.product_name = "Laptop 16"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x8086
|
||||
flags = ["no_d3cold"]
|
||||
match.sys_vendor = "HP"
|
||||
match.product_name = "HP ProBook"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1002
|
||||
flags = ["no_aspm", "need_firmware"]
|
||||
match.sys_vendor = "ASUSTeK COMPUTER INC."
|
||||
match.board_name = "PRIME X570-PRO"
|
||||
|
||||
[[dmi_system_quirk]]
|
||||
pci_vendor = 0x1022
|
||||
flags = ["reset_delay_ms"]
|
||||
match.sys_vendor = "Gigabyte Technology Co., Ltd."
|
||||
match.board_name = "X570 AORUS MASTER"
|
||||
|
||||
# Example (uncomment when a concrete table-specific firmware bug is identified):
|
||||
# [[acpi_table_quirk]]
|
||||
# signature = "DMAR"
|
||||
# match.sys_vendor = "Example Vendor"
|
||||
# match.product_name = "Example Model"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../local/recipes/system/redbear-quirks
|
||||
Reference in New Issue
Block a user