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:
2026-04-17 00:03:17 +01:00
parent e2d2fd138c
commit 6b887e9166
16 changed files with 1722 additions and 2 deletions
@@ -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"
+1
View File
@@ -0,0 +1 @@
../../local/recipes/system/redbear-quirks