bd595851e2
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
308 lines
10 KiB
Rust
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('/', ".")
|
|
}
|