0ca545d35a
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.
211 lines
5.8 KiB
Rust
211 lines
5.8 KiB
Rust
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");
|
|
}
|
|
}
|