quirks: Platform DMI dispatch infrastructure + 31-entry data file (R13)
Phase R13 (2026-06-07) — Laptop/Embedded DMI quirks. The
data side lands now; consumer wiring in inputd,
thermald, and redbear-upower is a follow-up.
Changes:
1. PlatformDmiQuirkFlags (mod.rs:286) with 7 bits:
TOUCHSCREEN, HOTKEY, ACCELEROMETER, ALS,
TABLET_MODE, BATTERY, PROXIMITY.
2. PlatformSubsystem enum (mod.rs:316) — dispatch label
for TOML and consumers. from_name() for parsing,
as_str() for logging, flag_bit() for converting to
the bitflags.
3. PlatformDmiQuirkRule (dmi.rs:566) — DMI match +
subsystem. Each entry fires one subsystem.
4. load_platform_dmi_quirks() (dmi.rs:583) — reads live
SMBIOS, returns Vec<PlatformDmiQuirkRule> of all
rules that fire. Falls back to empty vector if DMI
data is unavailable.
5. read_toml_platform_dmi_entries + parse_platform_dmi_toml
(toml_loader.rs) — new [[platform_dmi_quirk]] TOML
table with sub-table + string.
Unknown subsystem names log a warning and skip.
6. 1 new unit test: phase_r13_platform_subsystem_from_name_round_trip
exercises all 7 subsystems. 124/124 tests pass.
7. quirks.d/80-platform-x86.toml (201 lines) — 31 DMI
entries covering:
touchscreen (3): Chuwi Hi8 / Hi8 Pro / Hi10 Plus
tablet_mode (8): Acer, Asus, Lenovo convertibles
hotkey (12): Dynabook, GPD, AYA NEO, AYN, OneXPlayer,
Valve Steam Deck family
accelerometer (5): GPD WIN series, AYA NEO 2, Valve
battery (2): Samsung Galaxy Book, Chuwi Hi10 Plus
Targeted at Red Bear's 2026 hardware scope. The full
Linux 7.1 platform/x86 DMI surface is ~1153 entries;
this is a focused subset that maps to actual Red Bear
targets.
cargo test: 124/124 (was 123, +1 for the new test).
cargo check: clean.
Consumer wiring: load_platform_dmi_quirks() is callable
today from inputd, thermald, redbear-upower. Each
consumer can filter on rule.subsystem to dispatch the
appropriate behavior. This is a follow-up commit.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
toml_loader, AcpiQuirkFlags, DrmPanelOrientation, PciQuirkFlags,
|
||||
toml_loader, AcpiQuirkFlags, DrmPanelOrientation, PciQuirkFlags, PlatformSubsystem,
|
||||
XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
@@ -543,6 +543,36 @@ pub fn load_drm_panel_orientation() -> DrmPanelOrientation {
|
||||
.unwrap_or(DrmPanelOrientation::Normal)
|
||||
}
|
||||
|
||||
/// One DMI-conditioned platform / subsystem dispatch rule.
|
||||
///
|
||||
/// Phase R13 (2026-06-07). Sourced from Linux 7.1
|
||||
/// `drivers/platform/x86/*` DMI tables (touchscreen_dmi.c,
|
||||
/// wireless-hotkey.c, etc.).
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PlatformDmiQuirkRule {
|
||||
pub dmi_match: DmiMatchRule,
|
||||
pub subsystem: PlatformSubsystem,
|
||||
}
|
||||
|
||||
/// Look up the platform DMI rules that fire for the host system,
|
||||
/// grouped by subsystem. Returns an empty vector if DMI data is
|
||||
/// unavailable or no rule matches.
|
||||
pub fn load_platform_dmi_quirks() -> Vec<PlatformDmiQuirkRule> {
|
||||
let dmi_info = match read_dmi_info() {
|
||||
Ok(info) => info,
|
||||
Err(()) => return Vec::new(),
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
for rule in toml_loader::read_toml_platform_dmi_entries()
|
||||
.unwrap_or_default()
|
||||
{
|
||||
if rule.dmi_match.matches(&dmi_info) {
|
||||
out.push(rule);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
@@ -888,4 +918,25 @@ mod tests {
|
||||
);
|
||||
assert!(DrmPanelOrientation::from_name("Bogus").is_none());
|
||||
}
|
||||
|
||||
/// Phase R13 — `PlatformSubsystem::from_name` round-trips all
|
||||
/// seven subsystem identifiers.
|
||||
#[test]
|
||||
fn phase_r13_platform_subsystem_from_name_round_trip() {
|
||||
for name in [
|
||||
"touchscreen",
|
||||
"hotkey",
|
||||
"accelerometer",
|
||||
"als",
|
||||
"tablet_mode",
|
||||
"battery",
|
||||
"proximity",
|
||||
] {
|
||||
assert_eq!(
|
||||
PlatformSubsystem::from_name(name).unwrap().as_str(),
|
||||
name
|
||||
);
|
||||
}
|
||||
assert!(PlatformSubsystem::from_name("not_a_subsystem").is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,82 @@ impl DrmPanelOrientation {
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Per-subsystem platform DMI quirk flags. Each bit names a
|
||||
/// consumer subsystem that the DMI rule fires for; consumers
|
||||
/// (inputd, acpid, thermald, etc.) read the bit and react
|
||||
/// accordingly.
|
||||
///
|
||||
/// Phase R13 (2026-06-07) — initial set covers the
|
||||
/// sub-subsystems Red Bear actively consumes from
|
||||
/// `drivers/platform/x86/`. New bits can be added as
|
||||
/// consumer wiring lands.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct PlatformDmiQuirkFlags: u64 {
|
||||
const TOUCHSCREEN = 1 << 0; // drivers/platform/x86/touchscreen_dmi.c
|
||||
const HOTKEY = 1 << 1; // drivers/platform/x86/wireless-hotkey.c
|
||||
const ACCELEROMETER = 1 << 2; // drivers/platform/x86/dual_accel_detect.h
|
||||
const ALS = 1 << 3; // drivers/platform/x86/x86-android-tablets/ — ALS
|
||||
const TABLET_MODE = 1 << 4; // convertible / 360° hinge detection
|
||||
const BATTERY = 1 << 5; // battery reporting quirks (separate from ACPI)
|
||||
const PROXIMITY = 1 << 6; // proximity sensor quirks
|
||||
}
|
||||
}
|
||||
|
||||
/// Sub-system dispatch label used in `[[platform_dmi_quirk]]` TOML
|
||||
/// entries and by consumers to filter the flag set. Stable
|
||||
/// string identifiers (lowercase snake_case) so the TOML side
|
||||
/// is robust against enum reorderings.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PlatformSubsystem {
|
||||
Touchscreen,
|
||||
Hotkey,
|
||||
Accelerometer,
|
||||
Als,
|
||||
TabletMode,
|
||||
Battery,
|
||||
Proximity,
|
||||
}
|
||||
|
||||
impl PlatformSubsystem {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
"touchscreen" => Some(Self::Touchscreen),
|
||||
"hotkey" => Some(Self::Hotkey),
|
||||
"accelerometer" => Some(Self::Accelerometer),
|
||||
"als" => Some(Self::Als),
|
||||
"tablet_mode" => Some(Self::TabletMode),
|
||||
"battery" => Some(Self::Battery),
|
||||
"proximity" => Some(Self::Proximity),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Touchscreen => "touchscreen",
|
||||
Self::Hotkey => "hotkey",
|
||||
Self::Accelerometer => "accelerometer",
|
||||
Self::Als => "als",
|
||||
Self::TabletMode => "tablet_mode",
|
||||
Self::Battery => "battery",
|
||||
Self::Proximity => "proximity",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flag_bit(&self) -> PlatformDmiQuirkFlags {
|
||||
match self {
|
||||
Self::Touchscreen => PlatformDmiQuirkFlags::TOUCHSCREEN,
|
||||
Self::Hotkey => PlatformDmiQuirkFlags::HOTKEY,
|
||||
Self::Accelerometer => PlatformDmiQuirkFlags::ACCELEROMETER,
|
||||
Self::Als => PlatformDmiQuirkFlags::ALS,
|
||||
Self::TabletMode => PlatformDmiQuirkFlags::TABLET_MODE,
|
||||
Self::Battery => PlatformDmiQuirkFlags::BATTERY,
|
||||
Self::Proximity => PlatformDmiQuirkFlags::PROXIMITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wildcard value for PCI ID matching.
|
||||
pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::{
|
||||
dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule},
|
||||
dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule, PlatformDmiQuirkRule},
|
||||
AcpiQuirkFlags, DrmPanelOrientation, HidQuirkEntry, HidQuirkFlags, MaskWidth,
|
||||
PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase, QuirkAction, UsbQuirkEntry,
|
||||
UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID,
|
||||
PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase, PlatformSubsystem,
|
||||
QuirkAction, UsbQuirkEntry, UsbQuirkFlags, XhciControllerQuirk,
|
||||
XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
use std::borrow::Cow;
|
||||
@@ -331,6 +332,70 @@ pub(crate) fn load_drm_panel_orientation(
|
||||
Ok(DrmPanelOrientation::Normal)
|
||||
}
|
||||
|
||||
pub(crate) fn read_toml_platform_dmi_entries() -> std::io::Result<Vec<PlatformDmiQuirkRule>> {
|
||||
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_platform_dmi_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_platform_dmi_toml(
|
||||
doc: &toml::Value,
|
||||
out: &mut Vec<PlatformDmiQuirkRule>,
|
||||
path: &str,
|
||||
) {
|
||||
let Some(arr) = doc.get("platform_dmi_quirk").and_then(|v| v.as_array()) else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: platform_dmi_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}: platform_dmi_quirk entry is missing match table, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(dmi_match) = parse_dmi_match_rule(match_table, path) else {
|
||||
continue;
|
||||
};
|
||||
let Some(subsystem_str) = table.get("subsystem").and_then(|v| v.as_str()) else {
|
||||
log::warn!(
|
||||
"quirks: {path}: platform_dmi_quirk entry is missing subsystem string, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let Some(subsystem) = PlatformSubsystem::from_name(subsystem_str) else {
|
||||
log::warn!(
|
||||
"quirks: {path}: unknown platform_dmi_quirk subsystem {subsystem_str:?}, skipping"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
out.push(PlatformDmiQuirkRule {
|
||||
dmi_match,
|
||||
subsystem,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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(|| {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# Platform DMI dispatch rules — DMI-based, subsystem-tagged.
|
||||
# Mined from Linux 7.1 `drivers/platform/x86/*` DMI tables.
|
||||
# Each `[[platform_dmi_quirk]]` entry maps a DMI fingerprint to
|
||||
# a single `subsystem` (touchscreen / hotkey / accelerometer /
|
||||
# als / tablet_mode / battery / proximity). Consumers
|
||||
# (inputd, acpid, thermald, redbear-upower, etc.) read the
|
||||
# firing rules and react accordingly.
|
||||
#
|
||||
# Phase R13 (2026-06-07) initial commit. The data file is
|
||||
# sparse by design — the audit estimated ~1153 Linux 7.1
|
||||
# entries, but Red Bear's hardware scope is much narrower.
|
||||
# Focus on hardware that ships in 2026 and that Red Bear
|
||||
# actually targets: Framework, GPD, AYANEO, AYN, Dell,
|
||||
# Lenovo, Asus, Valve, Chuwi, Acer.
|
||||
#
|
||||
# Consumers currently in the tree that could read this:
|
||||
# - inputd: touchscreen, hotkey, accelerometer, tablet_mode
|
||||
# - acpid: button, lid (related, not a direct match)
|
||||
# - thermald: als (ambient light → thermal profile)
|
||||
# - redbear-upower: battery
|
||||
|
||||
# Framework Laptop 13 / 16 — well-behaved; no quirks needed.
|
||||
# Listed here as documentation of the absence of quirks.
|
||||
|
||||
# Chuwi Hi8 (CWI506 / ilife S806) — touchscreen needs I2C-HID fallback
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "touchscreen"
|
||||
match.sys_vendor = "ilife"
|
||||
match.product_name = "S806"
|
||||
|
||||
# Chuwi Hi8 Pro (CWI513 / Hampoo X1D3_C806N) — same family
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "touchscreen"
|
||||
match.sys_vendor = "Hampoo"
|
||||
match.product_name = "X1D3_C806N"
|
||||
|
||||
# Chuwi Hi10 Plus (CWI527) — touchscreen quirks
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "touchscreen"
|
||||
match.board_vendor = "Hampoo"
|
||||
match.product_name = "Hi10 Pro tablet"
|
||||
|
||||
# Acer Switch V 10 (SW5-017) — convertible / tablet_mode
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "Acer"
|
||||
match.product_name = "SW5-017"
|
||||
|
||||
# Acer One 10 (S1003) — convertible / tablet_mode
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "Acer"
|
||||
match.product_name = "One S1003"
|
||||
|
||||
# Asus T100HAN — convertible / tablet_mode
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "ASUSTeK COMPUTER INC."
|
||||
match.product_name = "T100HAN"
|
||||
|
||||
# Asus T101HA / T103HAF — convertible / tablet_mode
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "ASUSTeK COMPUTER INC."
|
||||
match.product_name = "T101HA"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "ASUSTeK COMPUTER INC."
|
||||
match.product_name = "T103HAF"
|
||||
|
||||
# Lenovo Ideapad Miix 320 (80SG) — convertible
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "LENOVO"
|
||||
match.product_name = "80SG"
|
||||
|
||||
# Lenovo Ideapad D330 (80XF) — convertible
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "LENOVO"
|
||||
match.product_name = "80XF"
|
||||
|
||||
# Lenovo Yoga Book (YB1-X91) — convertible + Wacom touchscreen
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "tablet_mode"
|
||||
match.sys_vendor = "Intel Corporation"
|
||||
match.product_name = "CHERRYVIEW D1 PLATFORM"
|
||||
|
||||
# Dynabook K50 — wireless hotkey quirks
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "Dynabook Inc."
|
||||
match.product_name = "dynabook K50/FR"
|
||||
|
||||
# GPD MicroPC — wireless hotkey (airplane-mode)
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "MicroPC"
|
||||
|
||||
# GPD Pocket 2/3 — wireless hotkey
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "Pocket 2"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "Pocket 3"
|
||||
|
||||
# AYA NEO 2 / AIR / SLIDE — wireless hotkey (airplane-mode)
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "AYANEO"
|
||||
match.product_name = "AYANEO 2"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "AYANEO"
|
||||
match.product_name = "AIR"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "AYANEO"
|
||||
match.product_name = "SLIDE"
|
||||
|
||||
# AYN Loki Max / Zero — wireless hotkey
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "ayn"
|
||||
match.product_name = "Loki Max"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "ayn"
|
||||
match.product_name = "Loki Zero"
|
||||
|
||||
# OneXPlayer — wireless hotkey
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "ONE-NETBOOK TECHNOLOGY CO., LTD."
|
||||
match.product_name = "ONE XPLAYER"
|
||||
|
||||
# Valve Steam Deck (Jupiter) — wireless hotkey
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "Valve"
|
||||
match.product_name = "Jupiter"
|
||||
|
||||
# Valve Steam Deck OLED (Galileo)
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "hotkey"
|
||||
match.sys_vendor = "Valve"
|
||||
match.product_name = "Galileo"
|
||||
|
||||
# GPD WIN2/3/4 — accelerometer (auto-rotation in handheld mode)
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "WIN2"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "G1618-03"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "GPD"
|
||||
match.product_name = "G1618-04"
|
||||
|
||||
# AYA NEO 2 — accelerometer
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "AYANEO"
|
||||
match.product_name = "AYANEO 2"
|
||||
|
||||
# Valve Steam Deck — accelerometer
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "Valve"
|
||||
match.product_name = "Jupiter"
|
||||
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "accelerometer"
|
||||
match.sys_vendor = "Valve"
|
||||
match.product_name = "Galileo"
|
||||
|
||||
# Samsung Galaxy Book 10.6 — battery reporting quirks
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "battery"
|
||||
match.sys_vendor = "SAMSUNG ELECTRONICS CO., LTD."
|
||||
match.product_name = "Galaxy Book 10.6"
|
||||
|
||||
# Chuwi Hi10 Plus — battery quirk (BIX broken)
|
||||
[[platform_dmi_quirk]]
|
||||
subsystem = "battery"
|
||||
match.board_vendor = "Hampoo"
|
||||
match.product_name = "Hi10 Pro tablet"
|
||||
Reference in New Issue
Block a user