From 6b887e9166f9c9d10f65d6b230e8c92beda64597 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Fri, 17 Apr 2026 00:03:17 +0100 Subject: [PATCH] Update redox-driver-sys PCI and quirk support Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .../redox-driver-sys/source/Cargo.toml | 1 + .../redox-driver-sys/source/src/lib.rs | 1 + .../redox-driver-sys/source/src/pci.rs | 40 +- .../redox-driver-sys/source/src/quirks/dmi.rs | 432 ++++++++++++++ .../redox-driver-sys/source/src/quirks/mod.rs | 362 ++++++++++++ .../source/src/quirks/pci_table.rs | 92 +++ .../source/src/quirks/toml_loader.rs | 558 ++++++++++++++++++ .../source/src/quirks/usb_table.rs | 51 ++ .../recipes/system/redbear-quirks/recipe.toml | 15 + .../source/quirks.d/00-core.toml | 12 + .../source/quirks.d/10-gpu.toml | 31 + .../source/quirks.d/20-usb.toml | 40 ++ .../source/quirks.d/30-net.toml | 26 + .../source/quirks.d/40-storage.toml | 11 + .../source/quirks.d/50-system.toml | 51 ++ recipes/system/redbear-quirks | 1 + 16 files changed, 1722 insertions(+), 2 deletions(-) create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/pci_table.rs create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs create mode 100644 local/recipes/drivers/redox-driver-sys/source/src/quirks/usb_table.rs create mode 100644 local/recipes/system/redbear-quirks/recipe.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/00-core.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/10-gpu.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/20-usb.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/30-net.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/40-storage.toml create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/50-system.toml create mode 120000 recipes/system/redbear-quirks diff --git a/local/recipes/drivers/redox-driver-sys/source/Cargo.toml b/local/recipes/drivers/redox-driver-sys/source/Cargo.toml index b1c1c104..06adc7e1 100644 --- a/local/recipes/drivers/redox-driver-sys/source/Cargo.toml +++ b/local/recipes/drivers/redox-driver-sys/source/Cargo.toml @@ -12,6 +12,7 @@ thiserror = "2" bitflags = "2" serde = { version = "1", features = ["derive"] } bincode = "1" +toml = "0.8" [features] default = [] diff --git a/local/recipes/drivers/redox-driver-sys/source/src/lib.rs b/local/recipes/drivers/redox-driver-sys/source/src/lib.rs index 859cc0e4..eaebeef4 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/lib.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/lib.rs @@ -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; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs index 0105fe4a..8dd65edd 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/pci.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/pci.rs @@ -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) -> Result> { 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, diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs new file mode 100644 index 00000000..6245c9f2 --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs @@ -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>, + pub board_vendor: Option>, + pub board_name: Option>, + pub board_version: Option>, + pub product_name: Option>, + pub product_version: Option>, + pub bios_version: Option>, +} + +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, + pub board_vendor: Option, + pub board_name: Option, + pub board_version: Option, + pub product_name: Option, + pub product_version: Option, + pub bios_version: Option, +} + +/// 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 { + 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 { + 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 { + 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()); + } +} diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs new file mode 100644 index 00000000..689b1254 --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs @@ -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)); + } +} diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/pci_table.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/pci_table.rs new file mode 100644 index 00000000..88fa2bfc --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/pci_table.rs @@ -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 + }, +]; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs new file mode 100644 index 00000000..430e8d32 --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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> { + 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(name: &str, mapping: &[(&str, F)]) -> Option { + mapping + .iter() + .find_map(|(candidate, flag)| (*candidate == name).then_some(*flag)) +} + +fn parse_flags_from_names(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(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>, ()> { + 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 { + 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> { + 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::() { + 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, 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> { + 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::() { + 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, 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> { + 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::() { + 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, 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::() + .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::() + .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::() + .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::() + .unwrap(); + + let mut rules = Vec::new(); + parse_dmi_toml(&doc, &mut rules, "test.toml"); + + assert!(rules.is_empty()); + } +} diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/usb_table.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/usb_table.rs new file mode 100644 index 00000000..82fa933e --- /dev/null +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/usb_table.rs @@ -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, + }, +]; diff --git a/local/recipes/system/redbear-quirks/recipe.toml b/local/recipes/system/redbear-quirks/recipe.toml new file mode 100644 index 00000000..59be7845 --- /dev/null +++ b/local/recipes/system/redbear-quirks/recipe.toml @@ -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" +""" diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/00-core.toml b/local/recipes/system/redbear-quirks/source/quirks.d/00-core.toml new file mode 100644 index 00000000..bf0d2181 --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/00-core.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"] diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/10-gpu.toml b/local/recipes/system/redbear-quirks/source/quirks.d/10-gpu.toml new file mode 100644 index 00000000..c410716d --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/10-gpu.toml @@ -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"] diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/20-usb.toml b/local/recipes/system/redbear-quirks/source/quirks.d/20-usb.toml new file mode 100644 index 00000000..735b25a8 --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/20-usb.toml @@ -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"] diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/30-net.toml b/local/recipes/system/redbear-quirks/source/quirks.d/30-net.toml new file mode 100644 index 00000000..95ab408d --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/30-net.toml @@ -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"] diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/40-storage.toml b/local/recipes/system/redbear-quirks/source/quirks.d/40-storage.toml new file mode 100644 index 00000000..d236dbbb --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/40-storage.toml @@ -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"] diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/50-system.toml b/local/recipes/system/redbear-quirks/source/quirks.d/50-system.toml new file mode 100644 index 00000000..71894984 --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/50-system.toml @@ -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" diff --git a/recipes/system/redbear-quirks b/recipes/system/redbear-quirks new file mode 120000 index 00000000..1e8a0d6a --- /dev/null +++ b/recipes/system/redbear-quirks @@ -0,0 +1 @@ +../../local/recipes/system/redbear-quirks \ No newline at end of file