Files
RedBear-OS/drivers/input/i2c-hidd/src/input.rs
T
Red Bear OS f315a9be3b acpid + i2c-hidd: Phase 5 (GPE/EC/lid/buttons/fan) + Phase 4.4 (HID descriptor parser)
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).
2026-07-21 21:36:07 +09:00

249 lines
7.4 KiB
Rust

use std::collections::BTreeSet;
use anyhow::Result;
use inputd::ProducerHandle;
use orbclient::{
ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP,
K_BRACE_CLOSE, K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL,
K_LEFT_SHIFT, K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT,
K_RIGHT_SUPER, K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
};
use crate::hid::ReportDescriptorSummary;
use crate::report_desc::{decode_report, DecodedReport, ReportLayout};
pub struct InputForwarder {
producer: ProducerHandle,
keyboard_state: BTreeSet<u8>,
last_buttons: u8,
}
impl InputForwarder {
pub fn new() -> Result<Self> {
Ok(Self {
producer: ProducerHandle::new()?,
keyboard_state: BTreeSet::new(),
last_buttons: 0,
})
}
pub fn forward_report(
&mut self,
summary: &ReportDescriptorSummary,
report: &[u8],
) -> Result<()> {
if report.is_empty() {
return Ok(());
}
if summary.has_keyboard_page && report.len() >= 8 {
self.forward_boot_keyboard(report)?;
return Ok(());
}
if summary.has_pointer_page && report.len() >= 3 {
self.forward_boot_pointer(report)?;
return Ok(());
}
Ok(())
}
/// Descriptor-driven report path (multitouch digitizers, absolute
/// touchpads, button/wheel mice). Boot-protocol fields keep working
/// through `forward_report` for devices whose reports are boot-shaped.
pub fn forward_layout_report(&mut self, layouts: &[ReportLayout], report: &[u8]) -> Result<()> {
let Some(decoded) = decode_report(layouts, report) else {
return Ok(());
};
if decoded.keyboard_page && report.len() >= 8 {
self.forward_boot_keyboard(report)?;
return Ok(());
}
self.forward_decoded(decoded)
}
fn forward_decoded(&mut self, decoded: DecodedReport) -> Result<()> {
// Contacts drive absolute pointer position: the first touching
// contact is the pointer; a second contact converts to two-finger
// scroll (the common touchpad gesture).
let touching: Vec<&(u32, bool, i32, i32)> =
decoded.contacts.iter().filter(|contact| contact.1).collect();
match touching.as_slice() {
[] => {}
[first] => {
self.producer.write_event(MouseEvent {
x: first.2,
y: first.3,
}
.to_event())?;
}
[first, second, ..] => {
let dy = (second.3 - first.3) / 8;
if dy != 0 {
self.producer
.write_event(ScrollEvent { x: 0, y: dy }.to_event())?;
}
self.producer.write_event(MouseEvent {
x: first.2,
y: first.3,
}
.to_event())?;
}
}
if let (Some(x), Some(y)) = (decoded.x, decoded.y) {
self.producer
.write_event(MouseEvent { x, y }.to_event())?;
}
if let Some(wheel) = decoded.wheel {
if wheel != 0 {
self.producer
.write_event(ScrollEvent { x: 0, y: wheel }.to_event())?;
}
}
let buttons = (decoded.buttons & 0x07) as u8;
if buttons != self.last_buttons {
self.producer.write_event(
ButtonEvent {
left: buttons & 0x01 != 0,
middle: buttons & 0x04 != 0,
right: buttons & 0x02 != 0,
}
.to_event(),
)?;
}
self.last_buttons = buttons;
Ok(())
}
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
let modifiers = report[0];
for (bit, scancode) in [
(0_u8, K_LEFT_CTRL),
(1, K_LEFT_SHIFT),
(2, K_ALT),
(3, K_LEFT_SUPER),
(4, K_RIGHT_CTRL),
(5, K_RIGHT_SHIFT),
(6, K_ALT_GR),
(7, K_RIGHT_SUPER),
] {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: modifiers & (1 << bit) != 0,
}
.to_event(),
)?;
}
let current = report[2..8]
.iter()
.copied()
.filter(|code| *code != 0)
.collect::<BTreeSet<_>>();
for code in current.difference(&self.keyboard_state) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: true,
}
.to_event(),
)?;
}
}
for code in self.keyboard_state.difference(&current) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: false,
}
.to_event(),
)?;
}
}
self.keyboard_state = current;
Ok(())
}
fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> {
let dx = i8::from_ne_bytes([report[1]]) as i32;
let dy = i8::from_ne_bytes([report[2]]) as i32;
if dx != 0 || dy != 0 {
self.producer
.write_event(MouseRelativeEvent { dx, dy }.to_event())?;
}
if let Some(scroll) = report.get(3).copied() {
let scroll = i8::from_ne_bytes([scroll]) as i32;
if scroll != 0 {
self.producer
.write_event(ScrollEvent { x: 0, y: scroll }.to_event())?;
}
}
let buttons = report[0] & 0x07;
for index in 0..3 {
let mask = 1 << index;
if (buttons & mask) != (self.last_buttons & mask) {
self.producer.write_event(
ButtonEvent {
left: buttons & 0x01 != 0,
middle: buttons & 0x04 != 0,
right: buttons & 0x02 != 0,
}
.to_event(),
)?;
break;
}
}
self.last_buttons = buttons;
Ok(())
}
}
fn map_boot_keyboard_usage(usage: u8) -> Option<u8> {
Some(match usage {
0x04..=0x1D => b'a' + (usage - 0x04),
0x1E => b'1',
0x1F => b'2',
0x20 => b'3',
0x21 => b'4',
0x22 => b'5',
0x23 => b'6',
0x24 => b'7',
0x25 => b'8',
0x26 => b'9',
0x27 => b'0',
0x28 => K_ENTER,
0x29 => K_ESC,
0x2A => K_BKSP,
0x2B => K_TAB,
0x2C => K_SPACE,
0x2D => K_MINUS,
0x2E => K_EQUALS,
0x2F => K_BRACE_OPEN,
0x30 => K_BRACE_CLOSE,
0x33 => K_SEMICOLON,
0x34 => K_QUOTE,
0x35 => K_TICK,
0x36 => K_COMMA,
0x37 => K_PERIOD,
0x38 => K_SLASH,
0x39 => K_CAPS,
_ => return None,
})
}