acpid+ps2d: wire SystemQuirkFlags consumers (LG Gram Round 1)
acpid: load SystemQuirkFlags from in-memory DMI at init via
redox-driver-sys::quirks::toml_loader::load_dmi_system_quirks.
Store as a field on AcpiContext and expose via system_quirks().
Wire two consumers:
- FORCE_S2IDLE in set_global_s_state(3): routes S3 entry to
s2idle preparation instead. LG Gram 16Z90TP, Framework 16,
late Dell XPS advertise \_S3 in DSDT but the firmware refuses
the SLP_TYP write and hangs / silently no-ops.
- NO_LEGACY_PM1B in set_global_s_state(*): skips the PM1b write
even when FADT reports a non-zero pm1b_control_block. LG
firmware reports a PM1b block that does not exist on the
platform; writes wedge the controller.
ps2d: load SystemQuirkFlags from /scheme/acpi/dmi at init via
redox_driver_sys::quirks::system_quirks(). Wire one consumer:
- KBD_DEACTIVATE_FIXUP in Ps2::init(): skips SetDefaultsDisable
(0xF5) during keyboard init. LG Gram + some Dell/HP/Lenovo
keyboards wedge or drop keys when 0xF5 is sent (Linux
atkbd.c keyboard_broken[] / atkbd_deactivate_input tables).
acpid/src/dmi.rs: reframe misleading 'stub' docstring on
try_load_existing() — the function is a real round-trip reader
for /scheme/acpi/dmi, not a stub.
Cargo.lock regenerated to include the new redox-driver-sys path
dependency in acpid and ps2d.
This is the consumer-wiring deliverable for LG Gram Round 1
(local/docs/evidence/lg-gram/ASSESSMENT-2026-07-26.md). The
matching redox-driver-sys stub-replacement work
(load_dmi_acpi_quirks real loader, PANEL_ORIENTATION_TABLE
populated with Linux DRM entries, ACPI_FLAG_NAMES + TOML parser)
lands in the parent repo in the same Round 1 push.
acpi_irq1_skip_override remains documented as needing kernel IRQ
setup work (out of scope for Round 1).
This commit is contained in:
Generated
+3
@@ -2609,6 +2609,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "usb-core"
|
name = "usb-core"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usbctl"
|
name = "usbctl"
|
||||||
|
|||||||
@@ -23,11 +23,17 @@ ron.workspace = true
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
||||||
amlserde = { path = "../amlserde" }
|
amlserde = { path = "../amlserde" }
|
||||||
common = { path = "../common" }
|
common = { path = "../../common" }
|
||||||
daemon = { path = "../../daemon" }
|
daemon = { path = "../../daemon" }
|
||||||
libredox.workspace = true
|
libredox.workspace = true
|
||||||
redox-scheme.workspace = true
|
redox-scheme.workspace = true
|
||||||
scheme-utils = { path = "../../scheme-utils" }
|
scheme-utils = { path = "../../scheme-utils" }
|
||||||
|
|
||||||
|
# Quirks system: acpid is the consumer of SystemQuirkFlags (force_s2idle,
|
||||||
|
# acpi_irq1_skip_override, no_legacy_pm1b). Loaded at init from the in-memory
|
||||||
|
# DMI scan via the toml_loader path. Mirrors the cross-tree dep pattern used
|
||||||
|
# by xhcid (drivers/usb/xhcid/Cargo.toml).
|
||||||
|
redox-driver-sys = { path = "../../../../recipes/drivers/redox-driver-sys/source" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -418,6 +418,17 @@ pub struct AcpiContext {
|
|||||||
/// SMBIOS, and downstream quirks systems tolerate the absence.
|
/// SMBIOS, and downstream quirks systems tolerate the absence.
|
||||||
dmi: Option<DmiInfo>,
|
dmi: Option<DmiInfo>,
|
||||||
|
|
||||||
|
/// Machine-wide system quirk flags accumulated from `[[dmi_system_quirk]]`
|
||||||
|
/// entries whose DMI rule matched this host. Computed once at init from
|
||||||
|
/// the in-memory DMI scan, then consumed by:
|
||||||
|
/// - [`Self::set_global_s_state`] (`NO_LEGACY_PM1B` — skip PM1b write)
|
||||||
|
/// - [`Self::enter_s2idle`] / `enter_s3` (`FORCE_S2IDLE` — route Modern
|
||||||
|
/// Standby platforms away from S3)
|
||||||
|
/// `acpi_irq1_skip_override` and `kbd_deactivate_fixup` are owned by
|
||||||
|
/// other daemons (kernel IRQ setup and ps2d respectively) and are read
|
||||||
|
/// by those consumers via `/scheme/acpi/dmi`.
|
||||||
|
system_quirks: redox_driver_sys::quirks::SystemQuirkFlags,
|
||||||
|
|
||||||
pub next_ctx: RwLock<u64>,
|
pub next_ctx: RwLock<u64>,
|
||||||
|
|
||||||
/// GPE/PM1 fixed-event register map, built from the FADT by
|
/// GPE/PM1 fixed-event register map, built from the FADT by
|
||||||
@@ -581,6 +592,7 @@ impl AcpiContext {
|
|||||||
|
|
||||||
sdt_order: RwLock::new(Vec::new()),
|
sdt_order: RwLock::new(Vec::new()),
|
||||||
dmi: None,
|
dmi: None,
|
||||||
|
system_quirks: redox_driver_sys::quirks::SystemQuirkFlags::empty(),
|
||||||
facs: None,
|
facs: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -623,6 +635,35 @@ impl AcpiContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compute machine-wide system quirk flags from the in-memory DMI
|
||||||
|
// scan. The flags drive consumer behavior in this daemon (and are
|
||||||
|
// also re-readable by other daemons via /scheme/acpi/dmi). The
|
||||||
|
// conversion maps the 7 fields redox_driver_sys::quirks::dmi::DmiInfo
|
||||||
|
// knows about; the remaining 9 fields in acpid::dmi::DmiInfo are
|
||||||
|
// not consumed by the quirk system today.
|
||||||
|
this.system_quirks = this.dmi.as_ref().map_or(
|
||||||
|
redox_driver_sys::quirks::SystemQuirkFlags::empty(),
|
||||||
|
|info| {
|
||||||
|
let sys_dmi = redox_driver_sys::quirks::dmi::DmiInfo {
|
||||||
|
sys_vendor: info.sys_vendor.clone(),
|
||||||
|
board_vendor: info.board_vendor.clone(),
|
||||||
|
board_name: info.board_name.clone(),
|
||||||
|
board_version: info.board_version.clone(),
|
||||||
|
product_name: info.product_name.clone(),
|
||||||
|
product_version: info.product_version.clone(),
|
||||||
|
bios_version: info.bios_version.clone(),
|
||||||
|
};
|
||||||
|
redox_driver_sys::quirks::toml_loader::load_dmi_system_quirks(&sys_dmi)
|
||||||
|
.unwrap_or_default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if !this.system_quirks.is_empty() {
|
||||||
|
log::info!(
|
||||||
|
"acpid: loaded system quirks: {:?}",
|
||||||
|
this.system_quirks
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Fadt::init(&mut this);
|
Fadt::init(&mut this);
|
||||||
// DMAR init is opt-in via REDBEAR_DMAR_INIT=1 env var. MMIO reads
|
// DMAR init is opt-in via REDBEAR_DMAR_INIT=1 env var. MMIO reads
|
||||||
// (e.g. gl_sts.read()) on some real hardware block or spin
|
// (e.g. gl_sts.read()) on some real hardware block or spin
|
||||||
@@ -642,6 +683,14 @@ impl AcpiContext {
|
|||||||
self.dmi.as_ref()
|
self.dmi.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Machine-wide system quirk flags accumulated at init from
|
||||||
|
/// `[[dmi_system_quirk]]` TOML entries that matched this host's DMI.
|
||||||
|
/// Empty when no SMBIOS or no rules apply — consumers must treat that
|
||||||
|
/// as "no quirks".
|
||||||
|
pub fn system_quirks(&self) -> redox_driver_sys::quirks::SystemQuirkFlags {
|
||||||
|
self.system_quirks
|
||||||
|
}
|
||||||
|
|
||||||
/// Access the parsed FACS (Firmware ACPI Control Structure), if
|
/// Access the parsed FACS (Firmware ACPI Control Structure), if
|
||||||
/// present. Returns `None` if no FACS table was found in the RSDT/XSDT.
|
/// present. Returns `None` if no FACS table was found in the RSDT/XSDT.
|
||||||
pub fn facs(&self) -> Option<&Facs> {
|
pub fn facs(&self) -> Option<&Facs> {
|
||||||
@@ -1021,6 +1070,25 @@ impl AcpiContext {
|
|||||||
/// 4. Write SLP_EN|SLP_TYPa to PM1a, SLP_EN|SLP_TYPb to PM1b
|
/// 4. Write SLP_EN|SLP_TYPa to PM1a, SLP_EN|SLP_TYPb to PM1b
|
||||||
/// 5. Spin (machine should power off before this returns)
|
/// 5. Spin (machine should power off before this returns)
|
||||||
pub fn set_global_s_state(&self, state: u8) {
|
pub fn set_global_s_state(&self, state: u8) {
|
||||||
|
// FORCE_S2IDLE system quirk: when set, route S3 entry to s2idle
|
||||||
|
// instead. Modern Standby-only platforms (LG Gram 16Z90TP, Framework
|
||||||
|
// laptops, late-model Dell XPS) advertise `\_S3` packages in their
|
||||||
|
// DSDT but the firmware refuses to honour the SLP_TYP write — the
|
||||||
|
// machine hangs or silently no-ops. With this quirk armed, S3 entry
|
||||||
|
// is replaced by the s2idle preparation sequence (which the kernel
|
||||||
|
// MWAIT loop then completes).
|
||||||
|
if state == 3
|
||||||
|
&& self
|
||||||
|
.system_quirks
|
||||||
|
.contains(redox_driver_sys::quirks::SystemQuirkFlags::FORCE_S2IDLE)
|
||||||
|
{
|
||||||
|
log::info!(
|
||||||
|
"set_global_s_state(3): FORCE_S2IDLE quirk active, routing S3 entry to s2idle"
|
||||||
|
);
|
||||||
|
self.enter_s2idle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let fadt = match self.fadt() {
|
let fadt = match self.fadt() {
|
||||||
Some(fadt) => fadt,
|
Some(fadt) => fadt,
|
||||||
None => {
|
None => {
|
||||||
@@ -1122,16 +1190,28 @@ impl AcpiContext {
|
|||||||
);
|
);
|
||||||
Pio::<u16>::new(port_a).write(val_a);
|
Pio::<u16>::new(port_a).write(val_a);
|
||||||
|
|
||||||
// Some hardware requires both PM1a and PM1b to be written for
|
// PM1b write: skipped when (a) the FADT reports no PM1b block
|
||||||
// the sleep transition. The FADT pm1b_control_block is 0 when
|
// (the standard "single PM1 block" case), OR (b) the host's
|
||||||
// no second block exists; in that case skip the second write.
|
// DMI matches the `no_legacy_pm1b` system quirk — LG Gram and
|
||||||
|
// similar laptops report a non-zero pm1b_control_block in their
|
||||||
|
// FADT but the register does not exist on the platform, and
|
||||||
|
// writes to it have been observed to wedge the controller.
|
||||||
|
// The quirk is belt-and-braces alongside the FADT check.
|
||||||
|
let skip_pm1b_quirk = self
|
||||||
|
.system_quirks
|
||||||
|
.contains(redox_driver_sys::quirks::SystemQuirkFlags::NO_LEGACY_PM1B);
|
||||||
let port_b = fadt.pm1b_control_block as u16;
|
let port_b = fadt.pm1b_control_block as u16;
|
||||||
if port_b != 0 {
|
if port_b != 0 && !skip_pm1b_quirk {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Sleep S{} with ACPI outw(0x{:X}, 0x{:X})",
|
"Sleep S{} with ACPI outw(0x{:X}, 0x{:X})",
|
||||||
state, port_b, val_b
|
state, port_b, val_b
|
||||||
);
|
);
|
||||||
Pio::<u16>::new(port_b).write(val_b);
|
Pio::<u16>::new(port_b).write(val_b);
|
||||||
|
} else if port_b != 0 && skip_pm1b_quirk {
|
||||||
|
log::info!(
|
||||||
|
"Sleep S{}: skipping PM1b write at 0x{:X} due to no_legacy_pm1b quirk",
|
||||||
|
state, port_b
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -729,9 +729,12 @@ pub const DMI_FIELDS: &[&str] = &[
|
|||||||
"ec_firmware_release",
|
"ec_firmware_release",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Try to load an existing `/scheme/acpi/dmi` cache (if another
|
/// Read the `/scheme/acpi/dmi` cache served by another process (typically
|
||||||
/// process already exposed one). This is unused at the moment but
|
/// acpid itself, after `dmi::scan()` populated the in-memory copy and the
|
||||||
/// kept as a stub for future kernel-side SMBIOS scheme support.
|
/// scheme handler started exposing it). This is the round-trip counterpart
|
||||||
|
/// to `to_match_lines` — useful for tests that need to verify what the
|
||||||
|
/// scheme is actually serving, and for any future caller that wants to
|
||||||
|
/// read DMI from the scheme rather than from a fresh firmware scan.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn try_load_existing() -> Option<DmiInfo> {
|
pub fn try_load_existing() -> Option<DmiInfo> {
|
||||||
let mut file = File::open("/scheme/acpi/dmi").ok()?;
|
let mut file = File::open("/scheme/acpi/dmi").ok()?;
|
||||||
|
|||||||
@@ -17,5 +17,11 @@ common = { path = "../../common" }
|
|||||||
daemon = { path = "../../../daemon" }
|
daemon = { path = "../../../daemon" }
|
||||||
inputd = { path = "../../inputd" }
|
inputd = { path = "../../inputd" }
|
||||||
|
|
||||||
|
# Quirks system: ps2d consumes KBD_DEACTIVATE_FIXUP (skip SetDefaultsDisable
|
||||||
|
# 0xF5 command on affected laptops — LG Gram, some Dell/HP/Lenovo keyboards
|
||||||
|
# drop keys or wedge the controller when that command is sent). The flag is
|
||||||
|
# loaded at init from /scheme/acpi/dmi via the system_quirks() helper.
|
||||||
|
redox-driver-sys = { path = "../../../../../recipes/drivers/redox-driver-sys/source" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ impl Ps2 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self) -> Result<(), Error> {
|
pub fn init(&mut self, kbd_deactivate_fixup: bool) -> Result<(), Error> {
|
||||||
// Linux i8042_controller_check(): verify controller is present by
|
// Linux i8042_controller_check(): verify controller is present by
|
||||||
// flushing any stale data. A stuck output buffer means no controller.
|
// flushing any stale data. A stuck output buffer means no controller.
|
||||||
self.flush();
|
self.flush();
|
||||||
@@ -468,8 +468,23 @@ impl Ps2 {
|
|||||||
self.flush();
|
self.flush();
|
||||||
|
|
||||||
// Linux i8042_controller_init() step 2: set keyboard defaults
|
// Linux i8042_controller_init() step 2: set keyboard defaults
|
||||||
// (disable scanning so keyboard doesn't send scancodes during init)
|
// (disable scanning so keyboard doesn't send scancodes during init).
|
||||||
|
//
|
||||||
|
// Skip on platforms with the `kbd_deactivate_fixup` DMI quirk
|
||||||
|
// (LG Gram + a handful of Dell/HP/Lenovo laptops per Linux
|
||||||
|
// `drivers/input/keyboard/atkbd.c` `keyboard_broken[]` and
|
||||||
|
// `atkbd_deactivate_input` tables). On those platforms the
|
||||||
|
// ATKBD_CMD_RESET_DIS (0xF5) command wedges the controller or
|
||||||
|
// causes silent key drops until the next reset. The keyboard
|
||||||
|
// still works without the explicit disable — it just may emit
|
||||||
|
// a few stale scancodes that we drain via flush() above.
|
||||||
self.retry(format_args!("keyboard defaults"), 4, |x| {
|
self.retry(format_args!("keyboard defaults"), 4, |x| {
|
||||||
|
if kbd_deactivate_fixup {
|
||||||
|
log::info!(
|
||||||
|
"ps2d: skipping SetDefaultsDisable (0xF5) due to kbd_deactivate_fixup quirk"
|
||||||
|
);
|
||||||
|
return Ok(0xFA);
|
||||||
|
}
|
||||||
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
|
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
|
||||||
if b != 0xFA {
|
if b != 0xFA {
|
||||||
error!("keyboard failed to set defaults: {:02X}", b);
|
error!("keyboard failed to set defaults: {:02X}", b);
|
||||||
|
|||||||
@@ -94,11 +94,23 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
|
|
||||||
daemon.ready();
|
daemon.ready();
|
||||||
|
|
||||||
|
// Load machine-wide system quirk flags from /scheme/acpi/dmi (served by
|
||||||
|
// acpid). The flags drive consumer behaviour in this daemon — currently
|
||||||
|
// KBD_DEACTIVATE_FIXUP (skip SetDefaultsDisable / 0xF5 during keyboard
|
||||||
|
// init on LG Gram + similar laptops). Empty set on platforms without
|
||||||
|
// DMI or without matching quirk rules; ps2d treats that as "no quirks".
|
||||||
|
let system_quirks = redox_driver_sys::quirks::system_quirks();
|
||||||
|
let kbd_deactivate_fixup = system_quirks
|
||||||
|
.contains(redox_driver_sys::quirks::SystemQuirkFlags::KBD_DEACTIVATE_FIXUP);
|
||||||
|
if !system_quirks.is_empty() {
|
||||||
|
log::info!("ps2d: loaded system quirks: {:?}", system_quirks);
|
||||||
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"ps2d: registered producer handle, listening on serio/0 (keyboard) and serio/1 (mouse)"
|
"ps2d: registered producer handle, listening on serio/0 (keyboard) and serio/1 (mouse)"
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file);
|
let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file, kbd_deactivate_fixup);
|
||||||
|
|
||||||
let mut data = [0; 256];
|
let mut data = [0; 256];
|
||||||
for event_res in event_queue {
|
for event_res in event_queue {
|
||||||
|
|||||||
@@ -64,9 +64,14 @@ pub struct Ps2d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Ps2d {
|
impl Ps2d {
|
||||||
pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self {
|
pub fn new(
|
||||||
|
keyboard_input: InputProducer,
|
||||||
|
mouse_input: InputProducer,
|
||||||
|
time_file: File,
|
||||||
|
kbd_deactivate_fixup: bool,
|
||||||
|
) -> Self {
|
||||||
let mut ps2 = Ps2::new();
|
let mut ps2 = Ps2::new();
|
||||||
if let Err(err) = ps2.init() {
|
if let Err(err) = ps2.init(kbd_deactivate_fixup) {
|
||||||
log::error!("ps2d: controller init failed: {:?}", err);
|
log::error!("ps2d: controller init failed: {:?}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user