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 8b0758ab96..e663e4af3f 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, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID}; +use super::{toml_loader, AcpiQuirkFlags, PciQuirkFlags, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID}; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -466,6 +466,64 @@ pub fn load_dmi_xhci_quirks( Ok(flags) } +/// One DMI-conditioned ACPI rule. Loaded from compiled-in tables +/// or runtime `quirks.d/*.toml` `[[dmi_acpi_quirk]]` entries. +/// +/// Phase R11 (2026-06-07) — Linux 7.1 source-of-truth: +/// `drivers/acpi/osi.c` (OSI disable), `drivers/acpi/sleep.c` +/// (old ordering, NVS save), `drivers/acpi/button.c` (lid init), +/// `drivers/acpi/battery.c` (BIX broken, AC broken), +/// `drivers/acpi/x86/blacklist.c` (ACPI _REV override). +#[derive(Clone, Debug)] +pub struct DmiAcpiQuirkRule { + pub dmi_match: DmiMatchRule, + pub flags: AcpiQuirkFlags, +} + +/// Compiled-in DMI-based ACPI rules. Mined from Linux 7.1 sources +/// referenced in the struct doc-comment above. Phase R11 (2026-06-07) +/// initial commit carries 0 entries; runtime TOML files are the +/// data surface for this phase. The constant exists so the lookup +/// shape is stable from day one. +pub const DMI_ACPI_QUIRK_RULES: &[DmiAcpiQuirkRule] = &[]; + +/// Look up DMI-based ACPI quirks for the host system. +/// +/// Reads live SMBIOS data via [`read_dmi_info`], then OR-accumulates +/// flags from the compiled-in [`DMI_ACPI_QUIRK_RULES`] and any +/// matching rules loaded from runtime TOML files. Returns an empty +/// set if SMBIOS data is not available. +pub fn load_dmi_acpi_quirks() -> AcpiQuirkFlags { + let dmi_info = match read_dmi_info() { + Ok(info) => info, + Err(()) => return AcpiQuirkFlags::empty(), + }; + + let mut flags = apply_dmi_acpi_quirk_rules(&dmi_info, DMI_ACPI_QUIRK_RULES); + + if let Ok(toml_flags) = toml_loader::load_dmi_acpi_quirks(&dmi_info) { + flags |= toml_flags; + } + + flags +} + +/// Walk a slice of `DmiAcpiQuirkRule` and OR-accumulate the flags +/// of every rule whose DMI match succeeds. Mirrors +/// `apply_dmi_xhci_quirk_rules` (R7-B) and `apply_dmi_pci_quirks`. +pub fn apply_dmi_acpi_quirk_rules( + dmi_info: &DmiInfo, + rules: &[DmiAcpiQuirkRule], +) -> AcpiQuirkFlags { + let mut flags = AcpiQuirkFlags::empty(); + for rule in rules { + if rule.dmi_match.matches(dmi_info) { + flags |= rule.flags; + } + } + flags +} + #[cfg(test)] mod tests { use super::*; @@ -720,4 +778,56 @@ mod tests { }; assert!(!rule.is_empty()); } + + /// Phase R11 — `apply_dmi_acpi_quirk_rules` returns empty when + /// no rules match. + #[test] + fn phase_r11_apply_dmi_acpi_quirk_rules_no_match_returns_empty() { + let info = DmiInfo { + sys_vendor: Some("Framework".to_string()), + product_name: Some("Laptop 16".to_string()), + ..DmiInfo::default() + }; + let rules = [DmiAcpiQuirkRule { + dmi_match: DmiMatchRule { + sys_vendor: Some(Cow::Borrowed("Apple Inc.")), + ..DmiMatchRule::default() + }, + flags: AcpiQuirkFlags::OSI_DISABLE_VISTA, + }]; + let flags = apply_dmi_acpi_quirk_rules(&info, &rules); + assert!(flags.is_empty()); + } + + /// Phase R11 — matching DMI rule fires its flag. + #[test] + fn phase_r11_apply_dmi_acpi_quirk_rules_match_or_accumulates() { + let info = DmiInfo { + sys_vendor: Some("Dell Inc.".to_string()), + product_name: Some("Latitude E7270".to_string()), + ..DmiInfo::default() + }; + let rules = [ + DmiAcpiQuirkRule { + dmi_match: DmiMatchRule { + sys_vendor: Some(Cow::Borrowed("Dell Inc.")), + product_name: Some(Cow::Borrowed("Latitude E7270")), + ..DmiMatchRule::default() + }, + flags: AcpiQuirkFlags::SLEEP_OLD_ORDERING, + }, + DmiAcpiQuirkRule { + dmi_match: DmiMatchRule { + sys_vendor: Some(Cow::Borrowed("Dell Inc.")), + ..DmiMatchRule::default() + }, + flags: AcpiQuirkFlags::LID_INIT_DISABLED, + }, + ]; + let flags = apply_dmi_acpi_quirk_rules(&info, &rules); + assert_eq!( + flags, + AcpiQuirkFlags::SLEEP_OLD_ORDERING | AcpiQuirkFlags::LID_INIT_DISABLED + ); + } } 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 a0e97a444f..6e2d9af72b 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 @@ -212,6 +212,48 @@ bitflags::bitflags! { } } +bitflags::bitflags! { + /// Flags for ACPI subsystem quirks, evaluated against the + /// system DMI information published by acpid at + /// `/scheme/acpi/dmi`. Mined from Linux 7.1 sources: + /// - `drivers/acpi/osi.c` (OSI disable on Vista / Win7 / Win8) + /// - `drivers/acpi/sleep.c` (old suspend ordering, NVS save) + /// - `drivers/acpi/button.c` (lid init state) + /// - `drivers/acpi/battery.c` (BIX broken, AC broken) + /// - `drivers/acpi/x86/blacklist.c` (ACPI _REV override) + /// + /// Phase R11 (2026-06-07). All bits are observability-only + /// at this revision — no consumer reads the flag word yet. + /// Future revisions can wire acpid / acpi-handlers to react + /// to specific bits (e.g. `OSI_DISABLE_VISTA` triggers + /// `acpi_osi_setup("!Windows 2006")`). + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct AcpiQuirkFlags: u64 { + // OSI override (osi.c) + const OSI_DISABLE_LINUX = 1 << 0; + const OSI_DISABLE_VISTA = 1 << 1; + const OSI_DISABLE_WIN7 = 1 << 2; + const OSI_DISABLE_WIN8 = 1 << 3; + + // Sleep (sleep.c) + const SLEEP_OLD_ORDERING = 1 << 4; + const SLEEP_NVS_NOSAVE = 1 << 5; + const SLEEP_DEFAULT_S3 = 1 << 6; + + // Button (button.c) + const LID_INIT_DISABLED = 1 << 7; + const LID_INIT_OPEN = 1 << 8; + + // Battery (battery.c) + const BATTERY_BIX_BROKEN_PACKAGE = 1 << 9; + const BATTERY_NOTIFICATION_DELAY = 1 << 10; + const BATTERY_AC_IS_BROKEN = 1 << 11; + + // ACPI rev override (x86/blacklist.c) + const REV_OVERRIDE = 1 << 12; + } +} + /// Wildcard value for PCI ID matching. pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF; 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 8e7e04aa41..b7fdda6f5b 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,8 +1,8 @@ use super::{ - dmi::{self, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule}, - HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, - PciQuirkPhase, QuirkAction, UsbQuirkEntry, UsbQuirkFlags, XhciControllerQuirk, - XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, + dmi::{self, DmiAcpiQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule}, + AcpiQuirkFlags, HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags, + PciQuirkLookup, PciQuirkPhase, QuirkAction, UsbQuirkEntry, UsbQuirkFlags, + XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, }; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -180,6 +180,82 @@ pub(crate) fn load_dmi_xhci_quirks( )) } +/// Phase R11 — `dmi_acpi_quirk` flag name table. Mined from +/// Linux 7.1 `drivers/acpi/osi.c`, `sleep.c`, `button.c`, +/// `battery.c`, and `x86/blacklist.c`. +pub const ACPI_FLAG_NAMES: &[(&str, AcpiQuirkFlags)] = &[ + ("osi_disable_linux", AcpiQuirkFlags::OSI_DISABLE_LINUX), + ("osi_disable_vista", AcpiQuirkFlags::OSI_DISABLE_VISTA), + ("osi_disable_win7", AcpiQuirkFlags::OSI_DISABLE_WIN7), + ("osi_disable_win8", AcpiQuirkFlags::OSI_DISABLE_WIN8), + ("sleep_old_ordering", AcpiQuirkFlags::SLEEP_OLD_ORDERING), + ("sleep_nvs_nosave", AcpiQuirkFlags::SLEEP_NVS_NOSAVE), + ("sleep_default_s3", AcpiQuirkFlags::SLEEP_DEFAULT_S3), + ("lid_init_disabled", AcpiQuirkFlags::LID_INIT_DISABLED), + ("lid_init_open", AcpiQuirkFlags::LID_INIT_OPEN), + ("battery_bix_broken_package", AcpiQuirkFlags::BATTERY_BIX_BROKEN_PACKAGE), + ("battery_notification_delay", AcpiQuirkFlags::BATTERY_NOTIFICATION_DELAY), + ("battery_ac_is_broken", AcpiQuirkFlags::BATTERY_AC_IS_BROKEN), + ("rev_override", AcpiQuirkFlags::REV_OVERRIDE), +]; + +pub(crate) fn read_toml_dmi_acpi_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_acpi_toml(&doc, &mut entries, &path_str); + } + Ok(entries) +} + +fn parse_dmi_acpi_toml( + doc: &toml::Value, + out: &mut Vec, + path: &str, +) { + let Some(arr) = doc.get("dmi_acpi_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_acpi_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_acpi_quirk entry is missing match table, skipping"); + continue; + }; + let Some(dmi_match) = parse_dmi_match_rule(match_table, path) else { + continue; + }; + let flags = parse_flags(table, path, "ACPI", ACPI_FLAG_NAMES); + out.push(DmiAcpiQuirkRule { dmi_match, flags }); + } +} + +/// Look up DMI-based ACPI flags contributed by runtime TOML quirk +/// files. Returns the OR-accumulated flags across every matching +/// `[[dmi_acpi_quirk]]` entry, or an empty set if no files are +/// present. +pub(crate) fn load_dmi_acpi_quirks(dmi_info: &DmiInfo) -> Result { + let entries = read_toml_dmi_acpi_entries().map_err(|_| ())?; + Ok(dmi::apply_dmi_acpi_quirk_rules(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(|| {