Files
RedBear-OS/drivers/input/i2c-hidd/src/acpi.rs
T
Red Bear OS bd595851e2 base: apply Red Bear patches on latest upstream/main
251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils,
inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps,
version +rb0.3.1, author attribution
2026-07-11 11:39:24 +03:00

308 lines
10 KiB
Rust

use acpi_resource::{GpioDescriptor, ResourceDescriptor};
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{anyhow, bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::{ErrorKind, Read};
use crate::quirks::ProbeFailureQuirk;
const I2C_HID_DSM_GUID: [u8; 16] = [
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76,
];
#[derive(Clone, Debug)]
pub struct I2cBinding {
pub adapter: String,
pub address: u16,
}
#[derive(Clone, Debug)]
pub struct AcpiDeviceResources {
pub i2c: I2cBinding,
pub irq: Option<u32>,
pub gpio_int: Vec<GpioDescriptor>,
pub gpio_io: Vec<GpioDescriptor>,
}
pub fn scan_acpi_i2c_hid_devices() -> Result<Vec<String>> {
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
continue;
}
let symbol = read_aml_symbol(&file_name)
.with_context(|| format!("failed to read ACPI symbol {file_name}"))?;
let Some(id) = decode_hardware_id(&symbol.value) else {
continue;
};
if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") {
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".");
if !device.is_empty() {
devices.insert(device);
}
}
}
Ok(devices.into_iter().collect())
}
pub fn read_decoded_resources(path: &str) -> Result<AcpiDeviceResources> {
let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path));
let serialized = fs::read_to_string(&resource_path)
.with_context(|| format!("failed to read {resource_path}"))?;
let descriptors: Vec<ResourceDescriptor> = ron::from_str(&serialized)
.with_context(|| format!("invalid ACPI resources in {resource_path}"))?;
let mut i2c = None;
let mut irq = None;
let mut gpio_int = Vec::new();
let mut gpio_io = Vec::new();
for descriptor in descriptors {
match descriptor {
ResourceDescriptor::I2cSerialBus(bus) => {
if i2c.is_none() {
let adapter = bus
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| "ACPI-I2C".to_string());
i2c = Some(I2cBinding {
adapter,
address: bus.slave_address,
});
}
}
ResourceDescriptor::Irq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied().map(u32::from);
}
}
ResourceDescriptor::ExtendedIrq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied();
}
}
ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor),
ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor),
_ => {}
}
}
let mut resources = AcpiDeviceResources {
i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?,
irq,
gpio_int,
gpio_io,
};
if let Some(override_address) = companion_icrs_override(path)? {
log::info!(
"{}: applying THC companion ICRS override {:04x} -> {:04x}",
path,
resources.i2c.address,
override_address
);
resources.i2c.address = override_address;
}
Ok(resources)
}
pub fn prepare_acpi_device(path: &str) -> Result<()> {
let sta = evaluate_integer_method(path, "_STA").ok();
if let Some(sta) = sta {
if sta & 0x01 == 0 {
bail!("ACPI device is not present according to _STA={sta:#x}");
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn recover_acpi_device(
path: &str,
resources: &AcpiDeviceResources,
quirk: Option<&ProbeFailureQuirk>,
) -> Result<()> {
let _ = evaluate_method(path, "_PS3", &[]);
if let Some(quirk) = quirk {
if !resources.gpio_io.is_empty() {
log::warn!(
"{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources",
path,
quirk.name,
quirk.system_vendor,
quirk.product_name,
quirk.board_name,
resources.gpio_io.len()
);
} else {
log::warn!(
"{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed",
path,
quirk.name
,
quirk.system_vendor,
quirk.product_name,
quirk.board_name
);
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn hid_descriptor_address(path: &str) -> Result<u16> {
let args = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &args) {
Ok(AmlSerdeValue::Integer(value)) => {
return u16::try_from(value).context("_DSM descriptor address out of range")
}
Ok(other) => log::warn!(
"{}._DSM returned unexpected value {:?}; retrying fallback index",
path,
other
),
Err(err) => log::warn!(
"{}._DSM function 1 failed: {err}; retrying function 0",
path
),
}
let fallback = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(0),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &fallback)? {
AmlSerdeValue::Integer(value) => {
u16::try_from(value).context("fallback _DSM descriptor address out of range")
}
other => bail!("fallback _DSM returned unexpected value {other:?}"),
}
}
fn companion_icrs_override(path: &str) -> Result<Option<u16>> {
let value = match evaluate_integer_method(path, "ICRS") {
Ok(value) => value,
Err(_) => return Ok(None),
};
Ok(Some(
u16::try_from(value).context("ICRS override out of range")?,
))
}
pub fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
match evaluate_method(path, method, &[])? {
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!(
"{}.{} returned non-integer AML value {other:?}",
path,
method
),
}
}
pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result<AmlSerdeValue> {
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
.with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?;
let serialized = ron::to_string(args)
.with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?;
let mut payload = serialized.into_bytes();
payload.resize(payload.len() + 4096, 0);
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
let response = std::str::from_utf8(&payload[..used])
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
ron::from_str(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))
}
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
let path = format!("/scheme/acpi/symbols/{file_name}");
let mut file = OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("failed to open {path}"))?;
let mut ron_text = String::new();
file.read_to_string(&mut ron_text)
.with_context(|| format!("failed to read {path}"))?;
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
}
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
match value {
AmlSerdeValue::String(value) => Some(value.clone()),
AmlSerdeValue::Integer(integer) => {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
Some(format!(
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
))
}
_ => None,
}
}
pub fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}