From 5d06b0fa03dcc9681b76784972c63c0df46048fa Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sun, 7 Jun 2026 21:25:01 +0300 Subject: [PATCH] quirks: ACPI DMI infrastructure (R11 part 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase R11 (2026-06-07) — ACPI DMI rules first commit. Lands the infrastructure pieces (flag type, rule struct, TOML parser, lookup function) needed for runtime ACPI quirk matching against system DMI data. The data side arrives in the follow-up commit (four quirks.d/*.toml files). Changes: 1. AcpiQuirkFlags bitflags (mod.rs:225) with 13 bits sourced from Linux 7.1: OSI_DISABLE_{LINUX,VISTA,WIN7,WIN8} — drivers/acpi/osi.c SLEEP_{OLD_ORDERING,NVS_NOSAVE,DEFAULT_S3} — drivers/acpi/sleep.c LID_INIT_{DISABLED,OPEN} — drivers/acpi/button.c BATTERY_{BIX_BROKEN_PACKAGE, NOTIFICATION_DELAY, AC_IS_BROKEN} — drivers/acpi/battery.c REV_OVERRIDE — drivers/acpi/x86/blacklist.c 2. DmiAcpiQuirkRule (dmi.rs:476) — DMI match + flag word, mirrors the existing DmiXhciQuirkRule shape. 3. DMI_ACPI_QUIRK_RULES — empty compiled-in table for now; runtime TOML is the data surface (R11 part 2). The constant exists so the three-layer lookup shape is stable from day one. 4. load_dmi_acpi_quirks() — reads live SMBIOS, applies compiled-in + TOML rules, returns AcpiQuirkFlags. Pattern mirrors load_dmi_xhci_quirks (R7-B). 5. apply_dmi_acpi_quirk_rules() — pure function, OR- accumulates matching rules. Mirrors apply_dmi_xhci_quirk_rules. 6. ACPI_FLAG_NAMES + parse_dmi_acpi_toml + load_dmi_acpi_quirks in toml_loader.rs. New TOML table type [[dmi_acpi_quirk]] with sub-table + array of strings. 7. Two unit tests in dmi.rs: empty result for no match, OR-accumulation for partial match (one rule fires one flag, the other fires another — both must land). cargo test: 122/122 (was 120, +2 for the new tests). cargo check: clean. cargo clippy: no new warnings in this code. The data side (46-acpi-sleep.toml, 47-acpi-button.toml, 48-acpi-battery.toml) lands in the follow-up commit. --- .../redox-driver-sys/source/src/quirks/dmi.rs | 112 +++++++++++++++++- .../redox-driver-sys/source/src/quirks/mod.rs | 42 +++++++ .../source/src/quirks/toml_loader.rs | 84 ++++++++++++- 3 files changed, 233 insertions(+), 5 deletions(-) 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(|| {