f315a9be3b
Phase 5 — Laptop ACPI events (LG Gram 16Z90TP compatibility plan):
- Vendor acpi-rs crate (rev 90cbe88) into base fork with a real
Opcode::Notify executor; upstream panicked on every Notify opcode,
which is the backbone of ACPI event flow on real laptop firmware.
Handler::handle_notify hook added; acpid + amlserde pointed at the
path dep so every consumer sees the same fork.
- gpe.rs: FADT-driven GPE block register map (status half + enable
half), write-1-to-clear status bits, read-modify-write enable
preserving every other GPE's firmware state. PM1 fixed-event bits
(PWRBTN/SLPBTN/RTC) per ACPI 6.4 §4.8.3.1.
- power_events.rs: init builds the GPE map, discovers the EC via ECDT
(ACPI 6.4 §5.2.16) with a _HID=PNP0C09 probe fallback over conven-
tional paths, enables the EC GPE + PM1 fixed events, discovers lid
and ACPI 4.0 fan devices. handle_sci dispatches PM1 → EC query loop
(bounded at 32) → AML notification drain, following Linux 7.1
evgpe.c / ec.c / button.c.
- notifications.rs: shared AmlNotifications queue (parking_lot::Mutex)
populated by the vendored acpi-rs handle_notify hook, drained by the
SCI handler and redbear-upower.
- ec.rs: sci_evt_set() and query() exposed on Ec for the SCI handler.
- scheme.rs: new handle kinds Lid, LidState, ButtonDir, Button,
Notifications, Fan, FanState, FanSpeed with proper dir/file
separation. Fan _FST on read, _FSL on write (percent clamped 0-100).
- main.rs: subscribes /scheme/irq/{sci_irq} (default 9) on the event
queue, dispatches SCI events through handle_sci. PowerButton incre-
ments edge counter and triggers the shutdown path; SleepButton incre-
ments counter and calls enter_s2idle.
- acpi.rs: AcpiContext gained gpe, ec_device, lid_device, lid_state,
power_button_events, sleep_button_events, fan_devices fields (all
RwLock-guarded). evaluate_acpi_method and enter_s2idle changed from
&mut self to &self (AML mutation goes through RwLock inner state).
- aml_physmem.rs: handle_notify impl pushes onto the shared queue.
Phase 4.4 — HID report descriptor parser (i2c-hidd):
- report_desc.rs: HID 1.11 §6.2.2 descriptor parser + decoder. Global-
state stack (Push/Pop), usage-min/max expansion, sign-extended
fields, contact reconstruction (id, tip, x, y). 3 unit tests.
- input.rs: forward_layout_report dispatches descriptor-driven reports
through forward_decoded: first touching contact → absolute
MouseEvent; two simultaneous contacts → vertical ScrollEvent (two-
finger scroll); buttons → ButtonEvent; keyboard-page reports fall
back to the existing boot-protocol path.
- hid.rs: stream_input_reports parses the descriptor once and uses
forward_layout_report when layouts are non-empty, otherwise keeps the
boot-protocol summary path.
Reference: Linux 7.1 drivers/acpi/{evgpeblk.c,evgpe.c,ec.c,button.c}
and drivers/hid/hid-core.c.
All affected crates compile for x86_64-unknown-redox; i2c-hidd 3/3
unit tests pass on host; acpid host-side tests still can't link
(pre-existing libredox limitation — must run via redoxer).
289 lines
8.5 KiB
Rust
289 lines
8.5 KiB
Rust
use std::time::Duration;
|
|
|
|
use acpi::aml::{
|
|
op_region::{OpRegion, RegionHandler, RegionSpace},
|
|
AmlError,
|
|
};
|
|
use common::{
|
|
io::{Io, Pio},
|
|
timeout::Timeout,
|
|
};
|
|
use log::*;
|
|
|
|
const EC_DATA: u16 = 0x62;
|
|
const EC_SC: u16 = 0x66;
|
|
|
|
const OBF: u8 = 1 << 0; // output full / data ready for host <> empty
|
|
const IBF: u8 = 1 << 1; // input full / data ready for ec <> empty
|
|
const CMD: u8 = 1 << 3; // byte in data reg is command <> data
|
|
const BURST: u8 = 1 << 4; // burst mode <> normal mode
|
|
const SCI_EVT: u8 = 1 << 5; // sci event pending <> not
|
|
const SMI_EVT: u8 = 1 << 6; // smi event pending <> not
|
|
|
|
const RD_EC: u8 = 0x80;
|
|
const WR_EC: u8 = 0x81;
|
|
const BE_EC: u8 = 0x82;
|
|
const BD_EC: u8 = 0x83;
|
|
const QR_EC: u8 = 0x84;
|
|
|
|
const BURST_ACK: u8 = 0x90;
|
|
|
|
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
|
|
|
|
#[repr(transparent)]
|
|
pub struct ScBits(u8);
|
|
#[allow(dead_code)]
|
|
impl ScBits {
|
|
const fn obf(&self) -> bool {
|
|
(self.0 & OBF) != 0
|
|
}
|
|
const fn ibf(&self) -> bool {
|
|
(self.0 & IBF) != 0
|
|
}
|
|
const fn cmd(&self) -> bool {
|
|
(self.0 & CMD) != 0
|
|
}
|
|
const fn burst(&self) -> bool {
|
|
(self.0 & BURST) != 0
|
|
}
|
|
const fn sci_evt(&self) -> bool {
|
|
(self.0 & SCI_EVT) != 0
|
|
}
|
|
const fn smi_evt(&self) -> bool {
|
|
(self.0 & SMI_EVT) != 0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Ec {
|
|
sc: u16,
|
|
data: u16,
|
|
|
|
timeout: Duration,
|
|
}
|
|
impl Ec {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
sc: EC_SC,
|
|
data: EC_DATA,
|
|
timeout: DEFAULT_EC_TIMEOUT,
|
|
}
|
|
}
|
|
#[allow(dead_code)]
|
|
pub fn with_address(sc: u16, data: u16, timeout: Duration) -> Self {
|
|
Self { sc, data, timeout }
|
|
}
|
|
#[inline]
|
|
fn read_reg_sc(&self) -> ScBits {
|
|
ScBits(Pio::<u8>::new(self.sc).read())
|
|
}
|
|
#[inline]
|
|
fn read_reg_data(&self) -> u8 {
|
|
Pio::<u8>::new(self.data).read()
|
|
}
|
|
#[inline]
|
|
fn write_reg_sc(&self, value: u8) {
|
|
Pio::<u8>::new(self.sc).write(value);
|
|
}
|
|
#[inline]
|
|
fn write_reg_data(&self, value: u8) {
|
|
Pio::<u8>::new(self.data).write(value);
|
|
}
|
|
#[inline]
|
|
fn wait_for_write_ready(&self) -> Option<()> {
|
|
let timeout = Timeout::new(self.timeout);
|
|
loop {
|
|
if !self.read_reg_sc().ibf() {
|
|
return Some(());
|
|
}
|
|
timeout.run().ok()?;
|
|
}
|
|
}
|
|
#[inline]
|
|
fn wait_for_read_ready(&self) -> Option<()> {
|
|
let timeout = Timeout::new(self.timeout);
|
|
loop {
|
|
if self.read_reg_sc().obf() {
|
|
return Some(());
|
|
}
|
|
timeout.run().ok()?;
|
|
}
|
|
}
|
|
|
|
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
|
|
pub fn read(&self, address: u8) -> Option<u8> {
|
|
trace!("ec read addr: {:x}", address);
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_sc(RD_EC);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(address);
|
|
|
|
self.wait_for_read_ready()?;
|
|
|
|
let val = self.read_reg_data();
|
|
trace!("got: {:x}", val);
|
|
Some(val)
|
|
}
|
|
pub fn write(&self, address: u8, value: u8) -> Option<()> {
|
|
trace!("ec write addr: {:x}, with: {:x}", address, value);
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_sc(WR_EC);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(address);
|
|
|
|
self.wait_for_write_ready()?;
|
|
|
|
self.write_reg_data(value);
|
|
trace!("done");
|
|
Some(())
|
|
}
|
|
// disabled if not met
|
|
// First Access - 400 microseconds
|
|
// Subsequent Accesses - 50 microseconds each
|
|
// Total Burst Time - 1 millisecond
|
|
//Accesses should be responded to within 50 microseconds.
|
|
#[allow(dead_code)]
|
|
fn enable_burst(&self) -> bool {
|
|
trace!("ec burst enable");
|
|
self.wait_for_write_ready();
|
|
|
|
self.write_reg_sc(BE_EC);
|
|
|
|
self.wait_for_read_ready();
|
|
|
|
let res = self.read_reg_data() == BURST_ACK;
|
|
trace!("success: {}", res);
|
|
res
|
|
}
|
|
#[allow(dead_code)]
|
|
fn disable_burst(&self) {
|
|
trace!("ec burst disable");
|
|
self.wait_for_write_ready();
|
|
self.write_reg_sc(BD_EC);
|
|
trace!("done");
|
|
}
|
|
/// Whether the EC has a pending System Control Interrupt event
|
|
/// (ACPI 12.4 `SCI_EVT` flag in EC_SC).
|
|
pub fn sci_evt_set(&self) -> bool {
|
|
self.read_reg_sc().sci_evt()
|
|
}
|
|
|
|
/// `QR_EC` command (ACPI 12.4): returns the next pending query value,
|
|
/// or 0 when the queue is empty. Called while `sci_evt_set()` holds.
|
|
pub fn query(&mut self) -> u8 {
|
|
self.wait_for_write_ready();
|
|
|
|
self.write_reg_sc(QR_EC);
|
|
|
|
self.wait_for_read_ready();
|
|
|
|
self.read_reg_data()
|
|
}
|
|
|
|
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
|
|
#[allow(dead_code)]
|
|
fn queue_query(&mut self) -> u8 {
|
|
trace!("ec query");
|
|
self.wait_for_write_ready();
|
|
|
|
self.write_reg_sc(QR_EC);
|
|
|
|
self.wait_for_read_ready();
|
|
|
|
let val = self.read_reg_data();
|
|
trace!("got: {}", val);
|
|
val
|
|
}
|
|
}
|
|
impl RegionHandler for Ec {
|
|
fn read_u8(
|
|
&self,
|
|
region: &acpi::aml::op_region::OpRegion,
|
|
offset: usize,
|
|
) -> Result<u8, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
|
}
|
|
fn write_u8(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u8,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
self.write(offset as u8, value)
|
|
.ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
|
|
}
|
|
fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
// EC is 8-bit; compose 16-bit AML reads as little-endian 8-bit EC reads.
|
|
// Cross-referenced with Linux drivers/acpi/ec.c: acpi_ec_read() and
|
|
// AML acpi_extract_value() which handles the same byte-decomposition.
|
|
let lo = self.read_u8(region, offset)? as u16;
|
|
let hi = self.read_u8(region, offset + 1)? as u16;
|
|
Ok(lo | (hi << 8))
|
|
}
|
|
fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let part = self.read_u16(region, offset)? as u32;
|
|
let part2 = self.read_u16(region, offset + 2)? as u32;
|
|
Ok(part | (part2 << 16))
|
|
}
|
|
fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let part = self.read_u32(region, offset)? as u64;
|
|
let part2 = self.read_u32(region, offset + 4)? as u64;
|
|
Ok(part | (part2 << 32))
|
|
}
|
|
fn write_u16(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u16,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
Ok(())
|
|
}
|
|
fn write_u32(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u32,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
self.write_u8(region, offset + 2, bytes[2])?;
|
|
self.write_u8(region, offset + 3, bytes[3])?;
|
|
Ok(())
|
|
}
|
|
fn write_u64(
|
|
&self,
|
|
region: &OpRegion,
|
|
offset: usize,
|
|
value: u64,
|
|
) -> Result<(), acpi::aml::AmlError> {
|
|
assert_eq!(region.space, RegionSpace::EmbeddedControl);
|
|
let bytes = value.to_le_bytes();
|
|
self.write_u8(region, offset, bytes[0])?;
|
|
self.write_u8(region, offset + 1, bytes[1])?;
|
|
self.write_u8(region, offset + 2, bytes[2])?;
|
|
self.write_u8(region, offset + 3, bytes[3])?;
|
|
self.write_u8(region, offset + 4, bytes[4])?;
|
|
self.write_u8(region, offset + 5, bytes[5])?;
|
|
self.write_u8(region, offset + 6, bytes[6])?;
|
|
self.write_u8(region, offset + 7, bytes[7])?;
|
|
Ok(())
|
|
}
|
|
}
|