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 index 9b5bd93907..cd8a8d7009 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs @@ -1,4 +1,4 @@ -use super::{toml_loader, PciQuirkFlags, PCI_QUIRK_ANY_ID}; +use super::{toml_loader, PciQuirkFlags, QuirkPhase, PCI_QUIRK_ANY_ID}; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -112,6 +112,7 @@ pub struct DmiPciQuirkRule { pub vendor: u16, pub device: u16, pub flags: PciQuirkFlags, + pub phase: QuirkPhase, } /// Read DMI/SMBIOS data from the ACPI scheme. @@ -175,6 +176,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x8086, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::NO_ASPM, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -189,6 +191,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: PCI_QUIRK_ANY_ID, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::NO_MSI, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -203,6 +206,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x14E4, device: PCI_QUIRK_ANY_ID, flags: F_NO_MSIX_NO_ASPM, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -217,6 +221,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x8086, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::NO_D3COLD, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -231,6 +236,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x1002, device: PCI_QUIRK_ANY_ID, flags: F_NO_ASPM_NEED_FW, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -245,6 +251,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x1002, device: PCI_QUIRK_ANY_ID, flags: F_NEED_IOMMU_NO_ASPM, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -259,6 +266,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: PCI_QUIRK_ANY_ID, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::NO_USB3, + phase: QuirkPhase::Enable, }, DmiPciQuirkRule { dmi_match: DmiMatchRule { @@ -273,6 +281,7 @@ pub const DMI_PCI_QUIRK_RULES: &[DmiPciQuirkRule] = &[ vendor: 0x1022, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::RESET_DELAY_MS, + phase: QuirkPhase::Enable, }, ]; @@ -280,6 +289,7 @@ pub(crate) fn apply_dmi_pci_quirk_rules( info: &PciDeviceInfo, dmi_info: Option<&DmiInfo>, rules: &[DmiPciQuirkRule], + phase: QuirkPhase, ) -> PciQuirkFlags { let Some(dmi_info) = dmi_info else { return PciQuirkFlags::empty(); @@ -287,6 +297,9 @@ pub(crate) fn apply_dmi_pci_quirk_rules( let mut flags = PciQuirkFlags::empty(); for rule in rules { + if rule.phase != phase { + continue; + } if !rule.dmi_match.matches(dmi_info) { continue; } @@ -307,11 +320,18 @@ pub(crate) fn apply_dmi_pci_quirk_rules( /// 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 { + load_dmi_pci_quirks_for_phase(info, QuirkPhase::Enable) +} + +pub fn load_dmi_pci_quirks_for_phase( + info: &PciDeviceInfo, + phase: QuirkPhase, +) -> Result { let dmi_info = read_dmi_info()?; - let mut flags = apply_dmi_pci_quirk_rules(info, Some(&dmi_info), DMI_PCI_QUIRK_RULES); + let mut flags = apply_dmi_pci_quirk_rules(info, Some(&dmi_info), DMI_PCI_QUIRK_RULES, phase); - if let Ok(toml_flags) = toml_loader::load_dmi_pci_quirks(info, &dmi_info) { + if let Ok(toml_flags) = toml_loader::load_dmi_pci_quirks_for_phase(info, &dmi_info, phase) { flags |= toml_flags; } @@ -426,11 +446,72 @@ mod tests { vendor: 0x1002, device: PCI_QUIRK_ANY_ID, flags: PciQuirkFlags::DISABLE_ACCEL, + phase: QuirkPhase::Enable, }]; - let flags = apply_dmi_pci_quirk_rules(&info, None, &rules); + let flags = apply_dmi_pci_quirk_rules(&info, None, &rules, QuirkPhase::Enable); assert!(flags.is_empty()); } + + #[test] + fn apply_dmi_rules_filters_by_phase() { + 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 dmi_info = DmiInfo { + sys_vendor: Some("Framework".to_string()), + product_name: Some("Laptop 16".to_string()), + ..DmiInfo::default() + }; + let rules = [ + DmiPciQuirkRule { + dmi_match: DmiMatchRule { + sys_vendor: Some(Cow::Borrowed("Framework")), + product_name: Some(Cow::Borrowed("Laptop 16")), + ..Default::default() + }, + vendor: 0x1002, + device: PCI_QUIRK_ANY_ID, + flags: PciQuirkFlags::DISABLE_ACCEL, + phase: QuirkPhase::Early, + }, + DmiPciQuirkRule { + dmi_match: DmiMatchRule { + sys_vendor: Some(Cow::Borrowed("Framework")), + product_name: Some(Cow::Borrowed("Laptop 16")), + ..Default::default() + }, + vendor: 0x1002, + device: PCI_QUIRK_ANY_ID, + flags: PciQuirkFlags::NO_ASPM, + phase: QuirkPhase::Enable, + }, + ]; + + let early_flags = + apply_dmi_pci_quirk_rules(&info, Some(&dmi_info), &rules, QuirkPhase::Early); + assert!(early_flags.contains(PciQuirkFlags::DISABLE_ACCEL)); + assert!(!early_flags.contains(PciQuirkFlags::NO_ASPM)); + + let enable_flags = + apply_dmi_pci_quirk_rules(&info, Some(&dmi_info), &rules, QuirkPhase::Enable); + assert!(!enable_flags.contains(PciQuirkFlags::DISABLE_ACCEL)); + assert!(enable_flags.contains(PciQuirkFlags::NO_ASPM)); + } } pub fn load_platform_dmi_quirks() -> Vec { 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 index 0686323cdb..2e5075d152 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs @@ -197,6 +197,13 @@ pub enum DrmPanelOrientation { /// Wildcard value for PCI ID matching. pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub enum QuirkPhase { + Early, + #[default] + Enable, +} + /// Compiled-in PCI quirk entry. All matching entries' flags accumulate via OR. #[derive(Clone, Copy, Debug)] pub struct PciQuirkEntry { @@ -218,6 +225,7 @@ pub struct PciQuirkEntry { pub revision_hi: u8, /// Quirk flags to apply when this entry matches. pub flags: PciQuirkFlags, + pub phase: QuirkPhase, } impl PciQuirkEntry { @@ -234,6 +242,7 @@ impl PciQuirkEntry { revision_lo: 0x00, revision_hi: 0xFF, flags: PciQuirkFlags::empty(), + phase: QuirkPhase::Enable, }; fn matches_with_subsystem(&self, info: &PciDeviceInfo, match_subsystem: bool) -> bool { @@ -322,22 +331,26 @@ impl Default for UsbQuirkEntry { /// /// All matching entries' flags are ORed together. pub fn lookup_pci_quirks(info: &PciDeviceInfo) -> PciQuirkFlags { + lookup_pci_quirks_for_phase(info, QuirkPhase::Enable) +} + +pub fn lookup_pci_quirks_for_phase(info: &PciDeviceInfo, phase: QuirkPhase) -> PciQuirkFlags { let mut flags = PciQuirkFlags::empty(); // Layer 1: Compiled-in table for entry in pci_table::PCI_QUIRK_TABLE { - if entry.matches(info) { + if entry.phase == phase && 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) { + if let Ok(toml_flags) = toml_loader::load_pci_quirks_for_phase(info, phase) { flags |= toml_flags; } // Layer 3: DMI-based system quirks (best-effort) - if let Ok(dmi_flags) = dmi::load_dmi_pci_quirks(info) { + if let Ok(dmi_flags) = dmi::load_dmi_pci_quirks_for_phase(info, phase) { flags |= dmi_flags; } @@ -487,6 +500,26 @@ mod tests { let flags = lookup_usb_quirks(0x0000, 0x0000); assert!(!flags.contains(UsbQuirkFlags::NO_STRING_FETCH)); } + + // --- QuirkPhase regression tests --- + + #[test] + fn quirk_phase_default_is_enable() { + assert_eq!(QuirkPhase::default(), QuirkPhase::Enable); + } + + #[test] + fn wildcard_entry_uses_enable_phase() { + assert_eq!(PciQuirkEntry::WILDCARD.phase, QuirkPhase::Enable); + } + + #[test] + fn lookup_pci_quirks_equals_enable_phase_lookup() { + let info = make_info(0x1002, 0x7310, 0x03, 0x00, 0x00); + let via_shortcut = lookup_pci_quirks(&info); + let via_explicit = lookup_pci_quirks_for_phase(&info, QuirkPhase::Enable); + assert_eq!(via_shortcut, via_explicit); + } } /// Config-region access width used by [`QuirkAction`]. @@ -608,7 +641,8 @@ pub struct PciQuirkLookup { /// spawning the device driver. pub fn lookup_pci_quirks_full(info: &PciDeviceInfo) -> PciQuirkLookup { PciQuirkLookup { - flags: lookup_pci_quirks(info), + flags: lookup_pci_quirks_for_phase(info, QuirkPhase::Early) + | lookup_pci_quirks_for_phase(info, QuirkPhase::Enable), actions: Vec::new(), } } 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 index 7fd35521a2..52df9493f4 100644 --- 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 @@ -1,6 +1,6 @@ use super::{ dmi::{self, DmiInfo, DmiMatchRule, DmiPciQuirkRule}, - DmiSystemQuirkRule, PciQuirkEntry, PciQuirkFlags, SystemQuirkFlags, UsbQuirkEntry, + DmiSystemQuirkRule, PciQuirkEntry, PciQuirkFlags, QuirkPhase, SystemQuirkFlags, UsbQuirkEntry, UsbQuirkFlags, PCI_QUIRK_ANY_ID, }; use crate::pci::PciDeviceInfo; @@ -10,10 +10,17 @@ use std::convert::TryFrom; const QUIRKS_DIR: &str = "/etc/quirks.d"; pub fn load_pci_quirks(info: &PciDeviceInfo) -> Result { + load_pci_quirks_for_phase(info, QuirkPhase::Enable) +} + +pub fn load_pci_quirks_for_phase( + info: &PciDeviceInfo, + phase: QuirkPhase, +) -> Result { let mut flags = PciQuirkFlags::empty(); let entries = read_toml_pci_entries().map_err(|_| ())?; for entry in &entries { - if entry.matches_toml(info) { + if entry.phase == phase && entry.matches_toml(info) { flags |= entry.flags; } } @@ -31,18 +38,38 @@ pub fn load_usb_quirks(vendor: u16, product: u16) -> Result { Ok(flags) } -pub(crate) fn load_dmi_pci_quirks( +pub(crate) fn load_dmi_pci_quirks_for_phase( info: &PciDeviceInfo, dmi_info: &DmiInfo, + phase: QuirkPhase, ) -> Result { let (entries, _) = read_toml_dmi_entries().map_err(|_| ())?; Ok(dmi::apply_dmi_pci_quirk_rules( info, Some(dmi_info), &entries, + phase, )) } +fn parse_phase(table: &toml::Table, path: &str, kind: &str) -> Result { + let Some(value) = table.get("phase") else { + return Ok(QuirkPhase::Enable); + }; + let Some(phase) = value.as_str() else { + log::warn!("quirks: {path}: {kind}.phase is not a string, skipping entry"); + return Err(()); + }; + match phase { + "early" => Ok(QuirkPhase::Early), + "enable" => Ok(QuirkPhase::Enable), + other => { + log::warn!("quirks: {path}: unknown {kind} quirk phase '{other}', skipping entry"); + Err(()) + } + } +} + /// Accumulate machine-wide [`SystemQuirkFlags`] from all `[[dmi_system_quirk]]` /// TOML entries whose DMI rule matches this system. /// @@ -418,6 +445,10 @@ fn parse_pci_toml(doc: &toml::Value, out: &mut Vec, path: &str) { .get("revision_hi") .and_then(|v| bounded_u8(v, "revision_hi", path)) .unwrap_or(0xFF); + let phase = match parse_phase(table, path, "PCI") { + Ok(phase) => phase, + Err(()) => continue, + }; let flags = parse_flags(table, path, "PCI", PCI_FLAG_NAMES); out.push(PciQuirkEntry { vendor, @@ -429,6 +460,7 @@ fn parse_pci_toml(doc: &toml::Value, out: &mut Vec, path: &str) { revision_lo, revision_hi, flags, + phase, }); } } @@ -557,6 +589,10 @@ fn parse_dmi_toml( log::warn!("quirks: {path}: unknown quirk flag '{name}'"); } } + let phase = match parse_phase(table, path, "DMI PCI") { + Ok(phase) => phase, + Err(()) => continue, + }; let flags = parse_flags_from_names(names, PCI_FLAG_NAMES); let system_flags = parse_flags_from_names(names, SYSTEM_FLAG_NAMES); @@ -565,6 +601,7 @@ fn parse_dmi_toml( vendor, device, flags, + phase, }); if !system_flags.is_empty() { @@ -637,8 +674,12 @@ mod tests { bios_version: None, }; - let flags = - dmi::apply_dmi_pci_quirk_rules(&make_info(0x1002, 0x73BF), Some(&dmi_info), &rules); + let flags = dmi::apply_dmi_pci_quirk_rules( + &make_info(0x1002, 0x73BF), + Some(&dmi_info), + &rules, + QuirkPhase::Enable, + ); assert!(flags.contains(PciQuirkFlags::DISABLE_ACCEL)); } @@ -669,8 +710,12 @@ mod tests { bios_version: None, }; - let flags = - dmi::apply_dmi_pci_quirk_rules(&make_info(0x8086, 0x46A6), Some(&dmi_info), &rules); + let flags = dmi::apply_dmi_pci_quirk_rules( + &make_info(0x8086, 0x46A6), + Some(&dmi_info), + &rules, + QuirkPhase::Enable, + ); assert!(flags.contains(PciQuirkFlags::NO_ASPM)); } @@ -688,7 +733,12 @@ mod tests { let (rules, _) = parse_both(&doc); - let flags = dmi::apply_dmi_pci_quirk_rules(&make_info(0x1002, 0x73BF), None, &rules); + let flags = dmi::apply_dmi_pci_quirk_rules( + &make_info(0x1002, 0x73BF), + None, + &rules, + QuirkPhase::Enable, + ); assert!(flags.is_empty()); } @@ -769,6 +819,30 @@ mod tests { assert_eq!(system_rules[0].flags, SystemQuirkFlags::KBD_DEACTIVATE_FIXUP); } + #[test] + fn parse_phase_defaults_to_enable_when_absent() { + let table = "phase_absent = true".parse::().unwrap(); + let table = table.as_table().unwrap(); + let result = parse_phase(table, "test.toml", "test"); + assert_eq!(result, Ok(QuirkPhase::Enable)); + } + + #[test] + fn parse_phase_parses_early() { + let table = "phase = \"early\"".parse::().unwrap(); + let table = table.as_table().unwrap(); + let result = parse_phase(table, "test.toml", "test"); + assert_eq!(result, Ok(QuirkPhase::Early)); + } + + #[test] + fn parse_phase_skips_invalid_phase_string() { + let table = "phase = \"bogus\"".parse::().unwrap(); + let table = table.as_table().unwrap(); + let result = parse_phase(table, "test.toml", "test"); + assert!(result.is_err()); + } + #[test] fn domain_quirks_match_and_accumulate() { let dir = std::env::temp_dir().join(format!(