261 lines
8.8 KiB
Rust
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('/', ".")
|
|
}
|