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).
116 lines
3.5 KiB
Rust
116 lines
3.5 KiB
Rust
use std::process;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
mod acpi;
|
|
mod hid;
|
|
mod input;
|
|
mod quirks;
|
|
mod report_desc;
|
|
|
|
use acpi::{
|
|
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
|
scan_acpi_i2c_hid_devices,
|
|
};
|
|
use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
|
|
use input::InputForwarder;
|
|
use quirks::match_probe_failure_quirk;
|
|
|
|
fn main() {
|
|
daemon::Daemon::new(daemon);
|
|
}
|
|
|
|
fn daemon(daemon: daemon::Daemon) -> ! {
|
|
common::setup_logging(
|
|
"input",
|
|
"i2c-hid",
|
|
"i2c-hidd",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
if let Err(err) = run(daemon) {
|
|
log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
|
|
process::exit(1);
|
|
}
|
|
|
|
process::exit(0);
|
|
}
|
|
|
|
fn run(daemon: daemon::Daemon) -> Result<()> {
|
|
log::info!("RB_I2C_HIDD_SCHEMA version=1");
|
|
|
|
let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
|
|
if devices.is_empty() {
|
|
log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
|
|
}
|
|
|
|
let mut workers = Vec::new();
|
|
for device in devices {
|
|
log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
|
|
workers.push(thread::spawn(move || {
|
|
if let Err(err) = bind_device(&device) {
|
|
log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
|
|
}
|
|
}));
|
|
}
|
|
|
|
daemon.ready();
|
|
|
|
if workers.is_empty() {
|
|
loop {
|
|
thread::sleep(Duration::from_secs(5));
|
|
}
|
|
}
|
|
|
|
for worker in workers {
|
|
let _ = worker.join();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn bind_device(device_path: &str) -> Result<()> {
|
|
prepare_acpi_device(device_path)
|
|
.with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
|
|
|
|
let resources = read_decoded_resources(device_path)
|
|
.with_context(|| format!("failed to decode _CRS for {device_path}"))?;
|
|
log::info!(
|
|
"RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
|
|
device_path,
|
|
resources.i2c.adapter,
|
|
resources.i2c.address,
|
|
resources.irq,
|
|
resources.gpio_int.len(),
|
|
resources.gpio_io.len()
|
|
);
|
|
|
|
let hid_desc_addr = hid_descriptor_address(device_path)
|
|
.with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
|
|
let adapter = I2cAdapterClient::new(resources.i2c.clone());
|
|
let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
|
|
.with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
|
|
let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
|
|
.with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
|
|
let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
|
|
|
|
match stream_input_reports(
|
|
&adapter,
|
|
resources.i2c.address,
|
|
&hid_desc,
|
|
&report_desc,
|
|
&mut forwarder,
|
|
) {
|
|
Ok(()) => Ok(()),
|
|
Err(err) => {
|
|
let quirk =
|
|
match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
|
|
recover_acpi_device(device_path, &resources, quirk.as_ref())
|
|
.with_context(|| format!("failed ACPI recovery for {device_path}"))?;
|
|
Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
|
|
}
|
|
}
|
|
}
|