From 0ca545d35ab1092352057dde18596e80eddb7b4c Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Fri, 24 Jul 2026 13:29:31 +0900 Subject: [PATCH] =?UTF-8?q?acpid:=20WMI=20(PNP0C14)=20subsystem=20?= =?UTF-8?q?=E2=80=94=20=5FWDG=20parsing=20+=20event=20GUID=20dispatch=20+?= =?UTF-8?q?=20=5FWED=20(Phase=209.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bounded WMI subsystem ported from Linux 7.1 drivers/platform/wmi/core.c and drivers/platform/x86/lg-laptop.c: - wmi.rs: guid_block (20-byte _WDG entry) parser, PNP0C14 device discovery (candidate-path _HID probe mirroring EC discovery), _WDG buffer read via aml_eval (AmlSerdeValue::Buffer), event-GUID enumeration (flags & ACPI_WMI_EVENT), _WED evaluation for eventcode decode, LG keymap (0x70 control-panel / 0x74 touchpad-toggle / 0xf020000 read-mode / 0x10000000 kbd-backlight). 4 unit tests cover guid_block parsing, event flag detection, short-input rejection, and keymap coverage. - power_events.rs: WMI notify handling in the AML notification drain — when a Notify fires on the WMI device, _WED is evaluated to decode the eventcode, mapped to a key name, and emitted as PowerEvent::Wmi. - main.rs: WMI discovery at init (after wake registry). PowerEvent::Wmi logged in the main loop. - acpi.rs: wmi_registry field on AcpiContext. Works on any x86 WMI laptop (LG, Dell, HP, Lenovo, ASUS, etc.). On QEMU (no PNP0C14) it is a no-op. --- drivers/acpid/src/acpi.rs | 2 + drivers/acpid/src/main.rs | 17 +++ drivers/acpid/src/power_events.rs | 24 ++++ drivers/acpid/src/wmi.rs | 210 ++++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 drivers/acpid/src/wmi.rs diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index f6e24bb3b2..38ab8af6fe 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -442,6 +442,7 @@ pub struct AcpiContext { pub fan_devices: RwLock>, pub wake_registry: RwLock>, + pub wmi_registry: RwLock>, } #[derive(Clone, Debug, Default)] @@ -576,6 +577,7 @@ impl AcpiContext { sleep_button_events: RwLock::new(0), fan_devices: RwLock::new(Vec::new()), wake_registry: RwLock::new(None), + wmi_registry: RwLock::new(None), sdt_order: RwLock::new(Vec::new()), dmi: None, diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs index f374e14172..0e97d13649 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs @@ -19,6 +19,7 @@ mod gpe; mod notifications; mod power_events; mod wake; +mod wmi; mod scheme; @@ -118,6 +119,12 @@ fn daemon(daemon: daemon::Daemon) -> ! { } *acpi_context.wake_registry.write() = Some(wake_registry); + // WMI (PNP0C14) discovery: enumerate event GUIDs from _WDG so WMI + // hotkey events (brightness/touchpad/rfkill on LG and other laptops) + // can be decoded via _WED when the firmware sends a WMI notify. + let wmi_registry = wmi::WmiRegistry::discover(&acpi_context); + *acpi_context.wmi_registry.write() = Some(wmi_registry); + // TODO: I/O permission bitmap? #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3"); @@ -292,6 +299,16 @@ fn daemon(daemon: daemon::Daemon) -> ! { power_events::PowerEvent::Notify { device, value } => { log::debug!("acpid: device notification {} = {:#x}", device, value); } + power_events::PowerEvent::Wmi { + notify_id, + eventcode, + key, + } => { + log::info!( + "acpid: WMI hotkey event notify_id={:#x} eventcode={:#x} key={}", + notify_id, eventcode, key + ); + } } } } else { diff --git a/drivers/acpid/src/power_events.rs b/drivers/acpid/src/power_events.rs index 6054f9ea0f..0bb98a363b 100644 --- a/drivers/acpid/src/power_events.rs +++ b/drivers/acpid/src/power_events.rs @@ -20,6 +20,11 @@ pub enum PowerEvent { EcQuery(u8), LidChanged(bool), Notify { device: String, value: u64 }, + Wmi { + notify_id: u8, + eventcode: u32, + key: &'static str, + }, } /// ECDT (Embedded Controller Boot Resources Table) payload after the @@ -272,8 +277,27 @@ pub fn handle_sci(context: &AcpiContext) -> Vec { // Drain AML notifications (from _Qxx methods and any other Notify). let lid_path = context.lid_device.read().clone(); + let wmi_path = context + .wmi_registry + .read() + .as_ref() + .and_then(|r| r.device_path()); for (device, value) in context.notifications().drain() { log::info!("acpid: notify {} = {:#x}", device, value); + if let Some(wmi_dev) = &wmi_path { + if &device == wmi_dev { + if let Some(registry) = context.wmi_registry.read().as_ref() { + if let Some(wmi_event) = registry.handle_notify(context, value as u8) { + events.push(PowerEvent::Wmi { + notify_id: wmi_event.notify_id, + eventcode: wmi_event.eventcode, + key: wmi_event.key, + }); + } + } + continue; + } + } if let Some(lid) = &lid_path { if &device == lid { let state = read_lid_state(context); diff --git a/drivers/acpid/src/wmi.rs b/drivers/acpid/src/wmi.rs new file mode 100644 index 0000000000..4dab7d3d40 --- /dev/null +++ b/drivers/acpid/src/wmi.rs @@ -0,0 +1,210 @@ +use log::{debug, info, warn}; + +use acpi::aml::namespace::AmlName; +use amlserde::AmlSerdeValue; +use std::str::FromStr; +use std::sync::RwLock; + +use crate::acpi::AcpiContext; + +const ACPI_WMI_EVENT: u8 = 1 << 3; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct GuidBlock { + pub guid: [u8; 16], + pub notify_id: u8, + pub instance_count: u8, + pub flags: u8, +} + +impl GuidBlock { + pub const SIZE: usize = 20; + + pub fn parse(data: &[u8]) -> Option { + if data.len() < Self::SIZE { + return None; + } + let mut guid = [0u8; 16]; + guid.copy_from_slice(&data[0..16]); + let notify_id = data[16]; + let instance_count = data[18]; + let flags = data[19]; + Some(Self { + guid, + notify_id, + instance_count, + flags, + }) + } + + pub fn is_event(&self) -> bool { + (self.flags & ACPI_WMI_EVENT) != 0 + } +} + +#[derive(Clone, Debug)] +pub struct WmiEvent { + pub notify_id: u8, + pub eventcode: u32, + pub key: &'static str, +} + +pub struct WmiRegistry { + device_path: RwLock>, + event_notify_ids: RwLock>, +} + +fn lg_key_for_eventcode(eventcode: u32) -> &'static str { + match eventcode { + 0x70 => "lg-control-panel", + 0x74 => "touchpad-toggle", + 0xf020000 => "read-mode", + 0x10000000 => "kbd-backlight", + _ => "unknown", + } +} + +impl WmiRegistry { + pub fn new() -> Self { + Self { + device_path: RwLock::new(None), + event_notify_ids: RwLock::new(Vec::new()), + } + } + + pub fn device_path(&self) -> Option { + self.device_path.read().unwrap().clone() + } + + pub fn has_notify_id(&self, notify_id: u8) -> bool { + self.event_notify_ids.read().unwrap().contains(¬ify_id) + } + + pub fn discover(context: &AcpiContext) -> Self { + let registry = Self::new(); + const CANDIDATES: &[&str] = &[ + "\\_SB.PC00.WMID", + "\\_SB.PCI0.WMID", + "\\_SB.WMID", + "\\_SB.PC00.LPCB.WMID", + "\\_SB.PCI0.LPCB.WMID", + ]; + for path in CANDIDATES { + let Ok(hid) = context.evaluate_acpi_method(path, "_HID", &[]) else { + continue; + }; + if hid.first().copied() != Some(0x0C14) { + continue; + } + info!("acpid: WMI device at {}", path); + *registry.device_path.write().unwrap() = Some(path.to_string()); + registry.enumerate_event_guids(context, path); + break; + } + if registry.device_path().is_none() { + debug!("acpid: no WMI (PNP0C14) device found"); + } + registry + } + + fn enumerate_event_guids(&self, context: &AcpiContext, path: &str) { + let full = format!("{path}._WDG"); + let Ok(name) = AmlName::from_str(&full) else { + warn!("acpid: invalid _WDG path {}", full); + return; + }; + let value = match context.aml_eval(name, Vec::new()) { + Ok(v) => v, + Err(err) => { + warn!("acpid: _WDG evaluation failed at {}: {:?}", path, err); + return; + } + }; + let buffer = match value { + AmlSerdeValue::Buffer(b) => b, + _ => { + warn!("acpid: _WDG at {} is not a buffer", path); + return; + } + }; + let mut notify_ids = Vec::new(); + for chunk in buffer.chunks(GuidBlock::SIZE) { + let Some(block) = GuidBlock::parse(chunk) else { + continue; + }; + if block.is_event() { + notify_ids.push(block.notify_id); + } + } + let count = notify_ids.len(); + *self.event_notify_ids.write().unwrap() = notify_ids; + info!( + "acpid: WMI enumerated {} event GUID(s) from _WDG ({} total bytes)", + count, + buffer.len() + ); + } + + pub fn handle_notify(&self, context: &AcpiContext, notify_id: u8) -> Option { + if !self.has_notify_id(notify_id) { + return None; + } + let path = self.device_path()?; + let full = format!("{path}._WED"); + let Ok(name) = AmlName::from_str(&full) else { + return None; + }; + let value = context + .aml_eval(name, vec![AmlSerdeValue::Integer(notify_id as u64)]) + .ok()?; + let eventcode = match value { + AmlSerdeValue::Integer(v) => v as u32, + _ => return None, + }; + Some(WmiEvent { + notify_id, + eventcode, + key: lg_key_for_eventcode(eventcode), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn guid_block_parse_event() { + let mut data = [0u8; 20]; + data[0] = 0x48; + data[16] = 0x2A; + data[18] = 0x01; + data[19] = ACPI_WMI_EVENT; + let block = GuidBlock::parse(&data).unwrap(); + assert_eq!(block.guid[0], 0x48); + assert_eq!(block.notify_id, 0x2A); + assert_eq!(block.instance_count, 1); + assert!(block.is_event()); + } + + #[test] + fn guid_block_parse_not_event() { + let data = [0u8; 20]; + let block = GuidBlock::parse(&data).unwrap(); + assert!(!block.is_event()); + } + + #[test] + fn guid_block_too_short() { + assert!(GuidBlock::parse(&[0u8; 19]).is_none()); + } + + #[test] + fn keymap_covers_lg_codes() { + assert_eq!(lg_key_for_eventcode(0x10000000), "kbd-backlight"); + assert_eq!(lg_key_for_eventcode(0x74), "touchpad-toggle"); + assert_eq!(lg_key_for_eventcode(0x70), "lg-control-panel"); + assert_eq!(lg_key_for_eventcode(0xf020000), "read-mode"); + assert_eq!(lg_key_for_eventcode(0xdead), "unknown"); + } +}