Files
RedBear-OS/drivers/acpid/src/wmi.rs
T
Red Bear OS 0ca545d35a 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.
2026-07-24 13:29:31 +09:00

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(&notify_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");
}
}