acpid: WMI (PNP0C14) subsystem — _WDG parsing + event GUID dispatch + _WED (Phase 9.2)
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.
This commit is contained in:
@@ -442,6 +442,7 @@ pub struct AcpiContext {
|
|||||||
pub fan_devices: RwLock<Vec<String>>,
|
pub fan_devices: RwLock<Vec<String>>,
|
||||||
|
|
||||||
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
|
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
|
||||||
|
pub wmi_registry: RwLock<Option<crate::wmi::WmiRegistry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -576,6 +577,7 @@ impl AcpiContext {
|
|||||||
sleep_button_events: RwLock::new(0),
|
sleep_button_events: RwLock::new(0),
|
||||||
fan_devices: RwLock::new(Vec::new()),
|
fan_devices: RwLock::new(Vec::new()),
|
||||||
wake_registry: RwLock::new(None),
|
wake_registry: RwLock::new(None),
|
||||||
|
wmi_registry: RwLock::new(None),
|
||||||
|
|
||||||
sdt_order: RwLock::new(Vec::new()),
|
sdt_order: RwLock::new(Vec::new()),
|
||||||
dmi: None,
|
dmi: None,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ mod gpe;
|
|||||||
mod notifications;
|
mod notifications;
|
||||||
mod power_events;
|
mod power_events;
|
||||||
mod wake;
|
mod wake;
|
||||||
|
mod wmi;
|
||||||
|
|
||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
@@ -118,6 +119,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
}
|
}
|
||||||
*acpi_context.wake_registry.write() = Some(wake_registry);
|
*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?
|
// TODO: I/O permission bitmap?
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[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");
|
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 } => {
|
power_events::PowerEvent::Notify { device, value } => {
|
||||||
log::debug!("acpid: device notification {} = {:#x}", 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 {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ pub enum PowerEvent {
|
|||||||
EcQuery(u8),
|
EcQuery(u8),
|
||||||
LidChanged(bool),
|
LidChanged(bool),
|
||||||
Notify { device: String, value: u64 },
|
Notify { device: String, value: u64 },
|
||||||
|
Wmi {
|
||||||
|
notify_id: u8,
|
||||||
|
eventcode: u32,
|
||||||
|
key: &'static str,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECDT (Embedded Controller Boot Resources Table) payload after the
|
/// ECDT (Embedded Controller Boot Resources Table) payload after the
|
||||||
@@ -272,8 +277,27 @@ pub fn handle_sci(context: &AcpiContext) -> Vec<PowerEvent> {
|
|||||||
|
|
||||||
// Drain AML notifications (from _Qxx methods and any other Notify).
|
// Drain AML notifications (from _Qxx methods and any other Notify).
|
||||||
let lid_path = context.lid_device.read().clone();
|
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() {
|
for (device, value) in context.notifications().drain() {
|
||||||
log::info!("acpid: notify {} = {:#x}", device, value);
|
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 let Some(lid) = &lid_path {
|
||||||
if &device == lid {
|
if &device == lid {
|
||||||
let state = read_lid_state(context);
|
let state = read_lid_state(context);
|
||||||
|
|||||||
@@ -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<Self> {
|
||||||
|
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<Option<String>>,
|
||||||
|
event_notify_ids: RwLock<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> {
|
||||||
|
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<WmiEvent> {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user