From 87ea8a9acfed0fd6f575de84f6740c5bce0752e7 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sun, 7 Jun 2026 21:36:12 +0300 Subject: [PATCH] quirks: DRM panel orientation infrastructure + 36-entry data file (R12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase R12 (2026-06-07) — DRM panel orientation quirks. The data side lands now; consumer wiring in redox-drm is deferred until compositor rotation lands (Phase 4 KDE). Changes: 1. DrmPanelOrientation enum (mod.rs:225) with four values: Normal, RightUp, LeftUp, BottomUp. Sourced from Linux 7.1 include/drm/drm_panel_orientation.h. Provides from_name() for TOML parsing and as_str() for logging. 2. DmiDrmPanelQuirkRule (dmi.rs:517) — DMI match + panel orientation, mirrors the existing DmiAcpiQuirkRule shape from R11. 3. DMI_DRM_PANEL_QUIRK_RULES (dmi.rs:531) — empty compiled-in table; runtime TOML is the data surface (see 50-drm-panel.toml). 4. load_drm_panel_orientation() (dmi.rs:537) — reads live SMBIOS via read_dmi_info, applies the compiled-in + TOML rules, returns the orientation. Falls back to Normal if DMI data is unavailable or no rule matches. 5. read_toml_drm_panel_entries + parse_drm_panel_toml (toml_loader.rs) — new [[drm_panel_quirk]] TOML table type with sub-table + string. Unknown orientation names log a warning and skip the entry. 6. load_drm_panel_orientation (toml_loader) — applies the first matching TOML rule, returns Normal if none match. 7. 1 new unit test: phase_r12_drm_panel_orientation_from_name_round_trip exercises all four orientation values + a bogus name. 123/123 redox-driver-sys tests pass. 8. quirks.d/50-drm-panel.toml (234 lines) — 36 DMI entries sourced from Linux 7.1 drivers/gpu/drm/drm_panel_orientation_quirks.c. Covers Acer, Anbernic, Asus, AYA NEO (full range including 2/2S, 2021, AIR, FLIP, Founder, GEEK, NEXT, KUN, SLIDE), AYN (Loki Max, Loki Zero), Chuwi, Dynabook, GPD (MicroPC, WIN Max, Pocket 2/3, WIN2/3/4, WIN Max 2), Lenovo, OneXPlayer, OrangePi, Samsung Galaxy Book, Valve Jupiter/Galileo (Steam Deck family), ZOTAC. The data spans laptop, tablet, and handheld form factors. cargo test: 123/123 (was 122, +1 for the new test). cargo check: clean. cargo clippy: no new warnings in this code. Consumer wiring is R12.1 (out of scope for this turn): redox-drm will call load_drm_panel_orientation() at connector enumeration time and apply the returned transform once the compositor supports rotation. --- .../redox-driver-sys/source/src/quirks/dmi.rs | 60 ++++- .../redox-driver-sys/source/src/quirks/mod.rs | 37 +++ .../source/src/quirks/toml_loader.rs | 83 ++++++- .../source/quirks.d/50-drm-panel.toml | 234 ++++++++++++++++++ 4 files changed, 409 insertions(+), 5 deletions(-) create mode 100644 local/recipes/system/redbear-quirks/source/quirks.d/50-drm-panel.toml 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 e663e4af3f..bfc6da8bcc 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,7 @@ -use super::{toml_loader, AcpiQuirkFlags, PciQuirkFlags, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID}; +use super::{ + toml_loader, AcpiQuirkFlags, DrmPanelOrientation, PciQuirkFlags, + XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, +}; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -508,6 +511,38 @@ pub fn load_dmi_acpi_quirks() -> AcpiQuirkFlags { flags } +/// One DMI-conditioned DRM panel orientation rule. +/// +/// Phase R12 (2026-06-07) — sourced from Linux 7.1 +/// `drivers/gpu/drm/drm_panel_orientation_quirks.c` (55 entries). +/// Consumer is `redox-drm` (deferred until compositor rotation). +#[derive(Clone, Debug)] +pub struct DmiDrmPanelQuirkRule { + pub dmi_match: DmiMatchRule, + pub orientation: DrmPanelOrientation, +} + +/// Compiled-in DMI panel orientation rules. Empty for now — runtime +/// TOML is the data surface (see `quirks.d/50-drm-panel.toml`). +pub const DMI_DRM_PANEL_QUIRK_RULES: &[DmiDrmPanelQuirkRule] = &[]; + +/// Look up the panel orientation for the host system. Returns +/// `Normal` if no rule matches or DMI data is unavailable. +pub fn load_drm_panel_orientation() -> DrmPanelOrientation { + let dmi_info = match read_dmi_info() { + Ok(info) => info, + Err(()) => return DrmPanelOrientation::Normal, + }; + if let Some(rule) = DMI_DRM_PANEL_QUIRK_RULES + .iter() + .find(|rule| rule.dmi_match.matches(&dmi_info)) + { + return rule.orientation; + } + toml_loader::load_drm_panel_orientation(&dmi_info) + .unwrap_or(DrmPanelOrientation::Normal) +} + /// 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`. @@ -830,4 +865,27 @@ mod tests { AcpiQuirkFlags::SLEEP_OLD_ORDERING | AcpiQuirkFlags::LID_INIT_DISABLED ); } + + /// Phase R12 — `DrmPanelOrientation::from_name` round-trips all + /// four Linux 7.1 orientation values. + #[test] + fn phase_r12_drm_panel_orientation_from_name_round_trip() { + assert_eq!( + DrmPanelOrientation::from_name("Normal").unwrap().as_str(), + "Normal" + ); + assert_eq!( + DrmPanelOrientation::from_name("RightUp").unwrap().as_str(), + "RightUp" + ); + assert_eq!( + DrmPanelOrientation::from_name("LeftUp").unwrap().as_str(), + "LeftUp" + ); + assert_eq!( + DrmPanelOrientation::from_name("BottomUp").unwrap().as_str(), + "BottomUp" + ); + assert!(DrmPanelOrientation::from_name("Bogus").is_none()); + } } 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 6e2d9af72b..025dab1470 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 @@ -254,6 +254,43 @@ bitflags::bitflags! { } } +/// Panel orientation for portrait-screen devices (GPD, Chuwi, +/// AYA NEO, OneXPlayer, etc.). Sourced from Linux 7.1 +/// `include/drm/drm_panel_orientation.h` `drm_panel_orientation` +/// values. Phase R12 (2026-06-07) — consumer wiring in +/// `redox-drm` is deferred until compositor rotation lands. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DrmPanelOrientation { + Normal, + RightUp, // 90° clockwise + LeftUp, // 90° counter-clockwise + BottomUp, // 180° +} + +impl DrmPanelOrientation { + /// Parse the orientation string used in the `[[drm_panel_quirk]]` + /// TOML table. Returns `None` for unknown values. + pub fn from_name(name: &str) -> Option { + match name { + "Normal" => Some(Self::Normal), + "RightUp" => Some(Self::RightUp), + "LeftUp" => Some(Self::LeftUp), + "BottomUp" => Some(Self::BottomUp), + _ => None, + } + } + + /// Human-readable name for logging. + pub fn as_str(&self) -> &'static str { + match self { + Self::Normal => "Normal", + Self::RightUp => "RightUp", + Self::LeftUp => "LeftUp", + Self::BottomUp => "BottomUp", + } + } +} + /// 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 b7fdda6f5b..53a4c93ac0 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, DmiAcpiQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule}, - AcpiQuirkFlags, HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags, - PciQuirkLookup, PciQuirkPhase, QuirkAction, UsbQuirkEntry, UsbQuirkFlags, - XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, + dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule}, + AcpiQuirkFlags, DrmPanelOrientation, HidQuirkEntry, HidQuirkFlags, MaskWidth, + PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase, QuirkAction, UsbQuirkEntry, + UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, }; use crate::pci::PciDeviceInfo; use std::borrow::Cow; @@ -256,6 +256,81 @@ pub(crate) fn load_dmi_acpi_quirks(dmi_info: &DmiInfo) -> Result 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_drm_panel_toml(&doc, &mut entries, &path_str); + } + Ok(entries) +} + +fn parse_drm_panel_toml( + doc: &toml::Value, + out: &mut Vec, + path: &str, +) { + let Some(arr) = doc.get("drm_panel_quirk").and_then(|v| v.as_array()) else { + return; + }; + for item in arr { + let Some(table) = item.as_table() else { + log::warn!("quirks: {path}: drm_panel_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}: drm_panel_quirk entry is missing match table, skipping"); + continue; + }; + let Some(dmi_match) = parse_dmi_match_rule(match_table, path) else { + continue; + }; + let Some(orientation_str) = table.get("orientation").and_then(|v| v.as_str()) else { + log::warn!("quirks: {path}: drm_panel_quirk entry is missing orientation string, skipping"); + continue; + }; + let Some(orientation) = DrmPanelOrientation::from_name(orientation_str) else { + log::warn!( + "quirks: {path}: unknown drm_panel_quirk orientation {orientation_str:?}, skipping" + ); + continue; + }; + out.push(DmiDrmPanelQuirkRule { + dmi_match, + orientation, + }); + } +} + +/// Look up the panel orientation for the host system from runtime +/// TOML files. Returns the orientation of the first matching +/// `[[drm_panel_quirk]]` entry, or `Normal` if no rule matches. +pub(crate) fn load_drm_panel_orientation( + dmi_info: &DmiInfo, +) -> Result { + let entries = read_toml_drm_panel_entries().map_err(|_| ())?; + for rule in entries { + if rule.dmi_match.matches(dmi_info) { + return Ok(rule.orientation); + } + } + Ok(DrmPanelOrientation::Normal) +} + fn bounded_u16(val: &toml::Value, field: &str, path: &str) -> Option { match val.as_integer() { Some(v) => u16::try_from(v).ok().or_else(|| { diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/50-drm-panel.toml b/local/recipes/system/redbear-quirks/source/quirks.d/50-drm-panel.toml new file mode 100644 index 0000000000..9367ea4780 --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/50-drm-panel.toml @@ -0,0 +1,234 @@ +# DRM panel orientation quirks — DMI-based. +# Mined from Linux 7.1 +# `drivers/gpu/drm/drm_panel_orientation_quirks.c` (55 entries). +# Each `[[drm_panel_quirk]]` entry maps a DMI fingerprint to a +# panel orientation: Normal (default), RightUp (90° CW), +# LeftUp (90° CCW), BottomUp (180°). +# +# Phase R12 (2026-06-07). Consumer is `redox-drm` — +# rotation support is deferred until Phase 4 KDE session. +# The lookup function `load_drm_panel_orientation()` is callable +# today; consumers just need to read its result and apply the +# transform. + +# Acer One 10 (S1003) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "Acer" +match.product_name = "One S1003" + +# Acer Switch V 10 (SW5-017) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "Acer" +match.product_name = "SW5-017" + +# Anbernic Win600 +[[drm_panel_quirk]] +orientation = "RightUp" +match.board_vendor = "Anbernic" +match.product_name = "Win600" + +# Asus T100HAN +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "ASUSTeK COMPUTER INC." +match.product_name = "T100HAN" + +# Asus T101HA +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "ASUSTeK COMPUTER INC." +match.product_name = "T101HA" + +# Asus T103HAF +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "ASUSTeK COMPUTER INC." +match.product_name = "T103HAF" + +# AYA NEO AYANEO 2 / 2S +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "AYANEO" +match.product_name = "AYANEO 2" + +# AYA NEO 2021 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "AYADEVICE" +match.product_name = "AYA NEO 2021" + +# AYA NEO AIR +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "AYANEO" +match.product_name = "AIR" + +# AYA NEO Flip DS Bottom Screen +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "AYANEO" +match.product_name = "FLIP DS" + +# AYA NEO Flip KB/DS Top Screen +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "AYANEO" +match.product_name = "FLIP" + +# AYA NEO Founder +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "AYA NEO" +match.product_name = "AYA NEO Founder" + +# AYA NEO GEEK +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "AYANEO" +match.product_name = "GEEK" + +# AYA NEO NEXT +[[drm_panel_quirk]] +orientation = "RightUp" +match.board_vendor = "AYANEO" +match.board_name = "NEXT" + +# AYA NEO KUN +[[drm_panel_quirk]] +orientation = "RightUp" +match.board_vendor = "AYANEO" +match.board_name = "KUN" + +# AYA NEO SLIDE +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "AYANEO" +match.product_name = "SLIDE" + +# AYN Loki Max +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "ayn" +match.product_name = "Loki Max" + +# AYN Loki Zero +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "ayn" +match.product_name = "Loki Zero" + +# Chuwi Hi10 Pro (CWI529) +[[drm_panel_quirk]] +orientation = "RightUp" +match.board_vendor = "Hampoo" +match.product_name = "Hi10 pro tablet" + +# Dynabook K50 +[[drm_panel_quirk]] +orientation = "LeftUp" +match.sys_vendor = "Dynabook Inc." +match.product_name = "dynabook K50/FR" + +# GPD MicroPC +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "MicroPC" + +# GPD Win Max (G1619-01) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "G1619-01" + +# GPD Pocket (G1617-01) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "G1617-01" + +# GPD Pocket 2 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "Pocket 2" + +# GPD Pocket 3 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "Pocket 3" + +# GPD WIN2 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "WIN2" + +# GPD WIN3 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "G1618-03" + +# GPD WIN4 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "G1618-04" + +# GPD WIN Max 2 (2023) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "GPD" +match.product_name = "G1619-04" + +# Lenovo Ideapad Miix 320 (80SG) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "LENOVO" +match.product_name = "80SG" + +# Lenovo Ideapad D330 (80XF) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "LENOVO" +match.product_name = "80XF" + +# OneXPlayer +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "ONE-NETBOOK TECHNOLOGY CO., LTD." +match.product_name = "ONE XPLAYER" + +# OrangePi NEO-01 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "OrangePi" +match.product_name = "NEO-01" + +# Samsung Galaxy Book 10.6 +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "SAMSUNG ELECTRONICS CO., LTD." +match.product_name = "Galaxy Book 10.6" + +# Valve Jupiter (Steam Deck) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "Valve" +match.product_name = "Jupiter" + +# Valve Galileo (Steam Deck OLED) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "Valve" +match.product_name = "Galileo" + +# ZOTAC ZBOX PI336 (G0A1W) +[[drm_panel_quirk]] +orientation = "RightUp" +match.sys_vendor = "ZOTAC" +match.board_name = "G0A1W"