Files
RedBear-OS/drivers/input/intel-thc-hidd/src/main.rs
T

261 lines
8.8 KiB
Rust

use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::Read;
use std::process;
use std::thread;
use std::time::Duration;
use acpi_resource::ResourceDescriptor;
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use pcid_interface::PciFunctionHandle;
mod quicki2c;
mod thc;
use quicki2c::QuickI2cTransport;
use thc::{ThcController, SUPPORTED_PCI_IDS};
fn main() {
pcid_interface::pci_daemon(daemon);
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
common::setup_logging(
"input",
"intel-thc",
"intel-thc-hidd",
common::output_level(),
common::file_level(),
);
if let Err(err) = run(daemon, &mut pcid_handle) {
log::error!("RB_THC_HIDD_FATAL error={err:#}");
process::exit(1);
}
process::exit(0);
}
fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
log::info!("RB_THC_HIDD_SCHEMA version=1");
let pci_config = pcid_handle.config();
let id = (
pci_config.func.full_device_id.vendor_id,
pci_config.func.full_device_id.device_id,
);
if !SUPPORTED_PCI_IDS.contains(&id) {
bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
}
pcid_handle.enable_device();
let bar = unsafe { pcid_handle.map_bar(0) };
let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
.context("failed to create THC controller")?;
let companion = resolve_acpi_companion(&pci_config.func.addr)
.context("failed to resolve ACPI companion for THC device")?;
let override_address = companion
.as_deref()
.map(companion_slave_address_override)
.transpose()
.context("failed to evaluate THC slave-address override")?
.flatten();
let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
.context("failed to scan PNP0C50 devices for THC controller")?;
let effective_address = override_address.unwrap_or(0x0015);
let transport = QuickI2cTransport::new(controller, effective_address);
transport.prime_controller();
transport.emulate_transfer(&[]);
log::debug!("RB_THC_HIDD status={:#x}", transport.status());
match transport.register_with_i2cd(companion.as_deref(), override_address) {
Ok(()) => {}
Err(err) => {
log::warn!("RB_THC_HIDD registration error={err:#}");
}
}
log::info!(
"RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
pci_config.func.name(),
companion,
override_address,
hid_devices.len()
);
daemon.ready();
loop {
thread::sleep(Duration::from_secs(5));
}
}
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
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("._ADR") {
continue;
}
let symbol = read_aml_symbol(&file_name)?;
if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
continue;
}
let device = symbol
.name
.strip_suffix("._ADR")
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
return Ok(Some(device));
}
Ok(None)
}
fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
let icrs = evaluate_integer_method(path, "ICRS").ok();
let isub = evaluate_integer_method(path, "ISUB").ok();
Ok(icrs
.or(isub)
.map(|value| u16::try_from(value))
.transpose()
.context("THC ACPI override out of range")?)
}
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI HID 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)?;
let is_hid = matches!(
decode_hardware_id(&symbol.value).as_deref(),
Some("PNP0C50" | "ACPI0C50")
);
if !is_hid {
continue;
}
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
if let Some(companion) = companion {
if !is_bound_to_companion(&device, companion)? {
continue;
}
}
devices.insert(device);
}
Ok(devices.into_iter().collect())
}
fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
let resource_path = format!("/scheme/acpi/resources/{device}");
let serialized = match fs::read_to_string(&resource_path) {
Ok(serialized) => serialized,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
};
let resources: Vec<ResourceDescriptor> =
ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
Ok(resources.into_iter().any(|resource| match resource {
ResourceDescriptor::I2cSerialBus(bus) => bus
.resource_source
.as_ref()
.map(|source| source.source == companion)
.unwrap_or(false),
_ => false,
}))
}
fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
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}"))?;
let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
.context("failed to serialize ACPI call arguments")?
.into_bytes();
payload.resize(payload.len() + 2048, 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}"))?;
match ron::from_str::<AmlSerdeValue>(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
{
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!("{}.{} returned non-integer value {other:?}", path, method),
}
}
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,
}
}
fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}