diff --git a/Cargo.lock b/Cargo.lock index 933349e437..f0eee42f04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2609,6 +2609,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "usb-core" version = "0.3.1" +dependencies = [ + "log", +] [[package]] name = "usbctl" diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml index f422b88950..70b0b51ee1 100644 --- a/drivers/acpid/Cargo.toml +++ b/drivers/acpid/Cargo.toml @@ -23,11 +23,17 @@ ron.workspace = true serde.workspace = true amlserde = { path = "../amlserde" } -common = { path = "../common" } +common = { path = "../../common" } daemon = { path = "../../daemon" } libredox.workspace = true redox-scheme.workspace = true 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] workspace = true diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index 38ab8af6fe..0631e6016a 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -418,6 +418,17 @@ pub struct AcpiContext { /// SMBIOS, and downstream quirks systems tolerate the absence. dmi: Option, + /// 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, /// GPE/PM1 fixed-event register map, built from the FADT by @@ -581,6 +592,7 @@ impl AcpiContext { sdt_order: RwLock::new(Vec::new()), dmi: None, + system_quirks: redox_driver_sys::quirks::SystemQuirkFlags::empty(), 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); // 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 @@ -642,6 +683,14 @@ impl AcpiContext { 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 /// present. Returns `None` if no FACS table was found in the RSDT/XSDT. 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 /// 5. Spin (machine should power off before this returns) 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() { Some(fadt) => fadt, None => { @@ -1122,16 +1190,28 @@ impl AcpiContext { ); Pio::::new(port_a).write(val_a); - // Some hardware requires both PM1a and PM1b to be written for - // the sleep transition. The FADT pm1b_control_block is 0 when - // no second block exists; in that case skip the second write. + // PM1b write: skipped when (a) the FADT reports no PM1b block + // (the standard "single PM1 block" case), OR (b) the host's + // 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; - if port_b != 0 { + if port_b != 0 && !skip_pm1b_quirk { log::warn!( "Sleep S{} with ACPI outw(0x{:X}, 0x{:X})", state, port_b, val_b ); Pio::::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 + ); } } diff --git a/drivers/acpid/src/dmi.rs b/drivers/acpid/src/dmi.rs index 89e5991bde..bd89e8cb0a 100644 --- a/drivers/acpid/src/dmi.rs +++ b/drivers/acpid/src/dmi.rs @@ -729,9 +729,12 @@ pub const DMI_FIELDS: &[&str] = &[ "ec_firmware_release", ]; -/// Try to load an existing `/scheme/acpi/dmi` cache (if another -/// process already exposed one). This is unused at the moment but -/// kept as a stub for future kernel-side SMBIOS scheme support. +/// Read the `/scheme/acpi/dmi` cache served by another process (typically +/// acpid itself, after `dmi::scan()` populated the in-memory copy and the +/// 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)] pub fn try_load_existing() -> Option { let mut file = File::open("/scheme/acpi/dmi").ok()?; diff --git a/drivers/input/ps2d/Cargo.toml b/drivers/input/ps2d/Cargo.toml index fdba06b634..fec3112ae1 100644 --- a/drivers/input/ps2d/Cargo.toml +++ b/drivers/input/ps2d/Cargo.toml @@ -17,5 +17,11 @@ common = { path = "../../common" } daemon = { path = "../../../daemon" } 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] workspace = true diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs index f64784cb00..ff70147474 100644 --- a/drivers/input/ps2d/src/controller.rs +++ b/drivers/input/ps2d/src/controller.rs @@ -385,7 +385,7 @@ impl Ps2 { 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 // flushing any stale data. A stuck output buffer means no controller. self.flush(); @@ -468,8 +468,23 @@ impl Ps2 { self.flush(); // 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| { + 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)?; if b != 0xFA { error!("keyboard failed to set defaults: {:02X}", b); diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs index 2b19562b9d..f0f6ab8543 100644 --- a/drivers/input/ps2d/src/main.rs +++ b/drivers/input/ps2d/src/main.rs @@ -94,11 +94,23 @@ fn daemon(daemon: daemon::Daemon) -> ! { 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!( "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]; for event_res in event_queue { diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs index 355060b19a..4e4eaa6b39 100644 --- a/drivers/input/ps2d/src/state.rs +++ b/drivers/input/ps2d/src/state.rs @@ -64,9 +64,14 @@ pub struct 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(); - if let Err(err) = ps2.init() { + if let Err(err) = ps2.init(kbd_deactivate_fixup) { log::error!("ps2d: controller init failed: {:?}", err); }