6db0f48170
Post-implementation review returned FAIL with two verified-CRITICAL findings; all legitimate findings fixed in this round: CRITICAL — engine wrote IC_CON/IC_TAR while enabled (DesignWare databook forbids; Linux i2c_dw_xfer_init disables first). Engine restructured: wait-idle -> disable -> program -> enable with IC_ENABLE_STATUS polling on every transfer. CRITICAL — Intel LPSS PCI bring-up skipped parent-device init. intel-lpss-i2cd now claims functions via pcid_interface (connect_by_path + enable_device), validates the real BAR size, and performs intel_lpss_init_dev's sequence (reset-deassert + 64-bit remap address at BAR0+0x200), ported from Linux drivers/mfd/intel-lpss.c. MAJOR fixes: - wait_for checks TX_ABRT before success predicates — a NACKed write no longer reports success via post-abort idle state - SCL timing corrected to Linux's formulas: HCNT uses sda_fall_ns, round-to-nearest division (FS 100/200, SS 552/652 for bxt 133 MHz) - stop=false rejected honestly instead of hanging on the idle wait - recover(): ABORT-bit cycle + disable + state flush after any failed transfer - validate_request: segment/byte/address/10-bit limits before any MMIO - i2cd + endpoint: exact-first adapter resolution; last-component matching accepted only when unambiguous - i2cd registration validates provider_scheme as a single safe scheme-name component - I2cTransferResponse gains typed status (I2cTransferStatus, serde-defaulted, wire-compatible) - endpoint::serve takes a Result-returning on_ready callback; setrens failure is fatal (fail-closed namespace reduction) - unexpected i2cd registration responses are fatal for that controller Tests: dw-i2c 5 (timing vectors, validation, resolution), intel-lpss-i2cd 1 (PCI ID table coverage), full workspace cargo check clean, base cooks for x86_64-unknown-redox. Refs: local/docs/LG-GRAM-16Z90TP-COMPATIBILITY-PLAN.md review-fix round
636 lines
21 KiB
Rust
636 lines
21 KiB
Rust
//! Intel LPSS I2C controller daemon.
|
|
//!
|
|
//! Discovery follows Linux 7.1 `drivers/mfd/intel-lpss-pci.c`: modern
|
|
//! platforms (Meteor Lake, Arrow Lake) bind the LPSS I2C blocks by PCI ID,
|
|
//! while older platforms (Haswell through some CNL boards) expose them as
|
|
//! ACPI devices with LPSS HIDs. Both paths converge on the same DesignWare
|
|
//! engine (`dw.rs`, ported from Linux's i2c-designware master driver).
|
|
//!
|
|
//! The daemon registers each controller with i2cd and serves
|
|
//! `/scheme/i2c-lpss/transfer`, executing real I2C transactions on hardware.
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::fs::{self, File, OpenOptions};
|
|
use std::io::{Read, Write};
|
|
use std::path::Path;
|
|
use std::process;
|
|
|
|
use acpi_resource::{
|
|
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
|
|
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
|
|
};
|
|
use anyhow::{Context, Result};
|
|
use common::{MemoryType, PhysBorrowed, Prot};
|
|
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
|
|
use serde::Deserialize;
|
|
|
|
use dw_i2c::endpoint::EndpointController;
|
|
use dw_i2c::{DwI2c, BXT_I2C_TIMING};
|
|
use pcid_interface::PciFunctionHandle;
|
|
|
|
// Intel LPSS private registers at BAR0 + 0x200 (Linux drivers/mfd/intel-lpss.c
|
|
// intel_lpss_init_dev): reset cycle + 64-bit remap address programming.
|
|
const LPSS_PRIV_OFFSET: usize = 0x200;
|
|
const LPSS_PRIV_RESETS: usize = 0x04;
|
|
const LPSS_PRIV_RESETS_FUNC_IDMA: u32 = 0x3 | (1 << 2);
|
|
const LPSS_PRIV_REMAP_ADDR_LO: usize = 0x40;
|
|
const LPSS_PRIV_REMAP_ADDR_HI: usize = 0x44;
|
|
|
|
const SUPPORTED_ACPI_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"];
|
|
|
|
/// Intel LPSS I2C PCI IDs, ported from Linux 7.1
|
|
/// `drivers/mfd/intel-lpss-pci.c` (entries carrying `*_i2c_info`).
|
|
/// All of these use the Broxton-derived timing (`bxt_i2c_info`, 133 MHz).
|
|
const LPSS_I2C_PCI_IDS: &[u16] = &[
|
|
// ARL-H (0x7750/0x7751, 0x7778-0x777b)
|
|
0x7750, 0x7751, 0x7778, 0x7779, 0x777a, 0x777b,
|
|
// MTL-P (0x7e50/0x7e51, 0x7e78-0x7e7b)
|
|
0x7e50, 0x7e51, 0x7e78, 0x7e79, 0x7e7a, 0x7e7b,
|
|
];
|
|
|
|
const PCI_VENDOR_INTEL: u16 = 0x8086;
|
|
const PCI_CLASS_SERIAL_BUS: u8 = 0x0c;
|
|
const PCI_SUBCLASS_I2C: u8 = 0x80;
|
|
|
|
/// Keeps the hardware claim and MMIO mapping alive. The PCI variant owns
|
|
/// the pcid claim (released on drop); the ACPI variant owns the raw mapping.
|
|
/// The fields are intentionally never read — ownership is the mechanism.
|
|
#[allow(dead_code)]
|
|
enum MmioGuard {
|
|
Pci(PciFunctionHandle),
|
|
Acpi(PhysBorrowed),
|
|
}
|
|
|
|
struct Controller {
|
|
name: String,
|
|
aliases: Vec<String>,
|
|
engine: DwI2c,
|
|
supports_10bit_addr: bool,
|
|
registration: File,
|
|
guard: MmioGuard,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct AmlSymbol {
|
|
name: String,
|
|
value: AmlValue,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
enum AmlValue {
|
|
Integer(u64),
|
|
String(String),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct ControllerResources {
|
|
mmio_base: usize,
|
|
mmio_len: usize,
|
|
irq: Option<u32>,
|
|
serial_bus: Option<I2cSerialBusDescriptor>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct DiscoveredController {
|
|
device: String,
|
|
hid: String,
|
|
resources: ControllerResources,
|
|
}
|
|
|
|
fn main() {
|
|
common::setup_logging(
|
|
"bus",
|
|
"i2c",
|
|
"intel-lpss-i2cd",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
daemon::Daemon::new(daemon_runner);
|
|
}
|
|
|
|
fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
|
if let Err(err) = daemon_main(daemon) {
|
|
log::error!("intel-lpss-i2cd: {err:#}");
|
|
process::exit(1);
|
|
}
|
|
|
|
process::exit(0);
|
|
}
|
|
|
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
|
common::init();
|
|
|
|
let mut controllers = Vec::new();
|
|
|
|
for candidate in discover_pci_controllers() {
|
|
match bring_up_pci_controller(candidate) {
|
|
Ok(controller) => controllers.push(controller),
|
|
Err(err) => log::warn!("intel-lpss-i2cd: PCI controller bring-up skipped: {err:#}"),
|
|
}
|
|
}
|
|
|
|
for discovered in discover_acpi_controllers(SUPPORTED_ACPI_IDS)
|
|
.context("failed to discover Intel LPSS ACPI I2C controllers")?
|
|
{
|
|
match bring_up_controller(discovered) {
|
|
Ok(controller) => controllers.push(controller),
|
|
Err(err) => log::warn!("intel-lpss-i2cd: ACPI controller bring-up skipped: {err:#}"),
|
|
}
|
|
}
|
|
|
|
if controllers.is_empty() {
|
|
log::info!("intel-lpss-i2cd: no supported controllers found (PCI or ACPI)");
|
|
}
|
|
|
|
// i2cd registrations and MMIO mappings must outlive the scheme endpoint.
|
|
let mut guards = Vec::new();
|
|
let endpoint_controllers: Vec<EndpointController> = controllers
|
|
.into_iter()
|
|
.map(|controller| {
|
|
let Controller {
|
|
name,
|
|
aliases,
|
|
engine,
|
|
supports_10bit_addr,
|
|
registration,
|
|
guard,
|
|
} = controller;
|
|
guards.push((registration, guard));
|
|
EndpointController {
|
|
name,
|
|
aliases,
|
|
engine,
|
|
supports_10bit_addr,
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
// Register the scheme first, then notify init (xhcid ordering) so a
|
|
// `type = { scheme = "i2c-lpss" }` service only reports ready once the
|
|
// scheme path actually exists. Namespace reduction is fail-closed: if it
|
|
// fails the daemon exits instead of running unsandboxed.
|
|
dw_i2c::endpoint::serve("i2c-lpss", endpoint_controllers, || {
|
|
daemon.ready();
|
|
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Controller bring-up: MMIO map, engine init, i2cd registration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn bring_up_controller(discovered: DiscoveredController) -> Result<Controller> {
|
|
let mmio = PhysBorrowed::map(
|
|
discovered.resources.mmio_base,
|
|
discovered.resources.mmio_len,
|
|
Prot::RW,
|
|
MemoryType::Uncacheable,
|
|
)
|
|
.with_context(|| {
|
|
format!(
|
|
"failed to map MMIO for {} ({:#x}, len {:#x})",
|
|
discovered.device, discovered.resources.mmio_base, discovered.resources.mmio_len
|
|
)
|
|
})?;
|
|
|
|
let engine = unsafe { DwI2c::new(mmio.as_ptr() as *mut u8) };
|
|
engine.init(&BXT_I2C_TIMING);
|
|
|
|
let name = format!("intel-lpss:{}", discovered.device);
|
|
let aliases = discover_acpi_aliases(discovered.resources.mmio_base);
|
|
|
|
log::info!(
|
|
"intel-lpss-i2cd: controller {} hid={} mmio={:#x}+{:#x} irq={:?} aliases={:?}",
|
|
name,
|
|
discovered.hid,
|
|
discovered.resources.mmio_base,
|
|
discovered.resources.mmio_len,
|
|
discovered.resources.irq,
|
|
aliases,
|
|
);
|
|
|
|
let supports_10bit_addr = discovered
|
|
.resources
|
|
.serial_bus
|
|
.as_ref()
|
|
.map(|bus| bus.access_mode_10bit)
|
|
.unwrap_or(true);
|
|
|
|
let registration = register_adapter(&I2cAdapterInfo {
|
|
id: 0,
|
|
name: name.clone(),
|
|
max_transaction_size: 0,
|
|
supports_10bit_addr,
|
|
provider_scheme: "i2c-lpss".to_string(),
|
|
aliases: aliases.clone(),
|
|
})
|
|
.and_then(|mut file| read_registration_response(&mut file).map(|response| (file, response)))
|
|
.with_context(|| format!("failed to register {name} with i2cd"))?;
|
|
|
|
match registration.1 {
|
|
I2cControlResponse::AdapterRegistered { id } => {
|
|
log::info!("intel-lpss-i2cd: adapter {name} registered with i2cd as {id}");
|
|
}
|
|
other => anyhow::bail!("unexpected i2cd registration response for {name}: {other:?}"),
|
|
}
|
|
|
|
Ok(Controller {
|
|
name,
|
|
aliases,
|
|
engine,
|
|
supports_10bit_addr,
|
|
registration: registration.0,
|
|
guard: MmioGuard::Acpi(mmio),
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PCI bring-up (Linux intel-lpss-pci.c model)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct PciCandidate {
|
|
path: std::path::PathBuf,
|
|
location: String,
|
|
device_id: u16,
|
|
}
|
|
|
|
fn bring_up_pci_controller(candidate: PciCandidate) -> Result<Controller> {
|
|
let mut handle = PciFunctionHandle::connect_by_path(&candidate.path)
|
|
.with_context(|| format!("failed to claim {}", candidate.location))?;
|
|
|
|
handle.enable_device();
|
|
|
|
let config = handle.config();
|
|
let (bar_phys, bar_len) = config.func.bars[0].expect_mem();
|
|
let bar_phys = usize::try_from(bar_phys).context("BAR0 address does not fit in usize")?;
|
|
anyhow::ensure!(
|
|
bar_len >= LPSS_PRIV_OFFSET + LPSS_PRIV_REMAP_ADDR_HI + 4,
|
|
"{}: BAR0 too small for LPSS private registers ({bar_len:#x})",
|
|
candidate.location,
|
|
);
|
|
|
|
let engine = unsafe {
|
|
let mapped = handle.map_bar(0);
|
|
DwI2c::new(mapped.ptr.as_ptr())
|
|
};
|
|
|
|
// intel_lpss_init_dev: reset cycle, then program the 64-bit remap address.
|
|
unsafe {
|
|
let priv_base = engine.mmio_base().add(LPSS_PRIV_OFFSET);
|
|
(priv_base.add(LPSS_PRIV_RESETS) as *mut u32).write_volatile(0);
|
|
(priv_base.add(LPSS_PRIV_RESETS) as *mut u32)
|
|
.write_volatile(LPSS_PRIV_RESETS_FUNC_IDMA);
|
|
(priv_base.add(LPSS_PRIV_REMAP_ADDR_LO) as *mut u32)
|
|
.write_volatile(bar_phys as u32);
|
|
(priv_base.add(LPSS_PRIV_REMAP_ADDR_HI) as *mut u32)
|
|
.write_volatile((bar_phys >> 32) as u32);
|
|
}
|
|
|
|
engine.init(&BXT_I2C_TIMING);
|
|
|
|
let name = format!("intel-lpss:{}", candidate.location);
|
|
let aliases = discover_acpi_aliases(bar_phys);
|
|
|
|
log::info!(
|
|
"intel-lpss-i2cd: controller {} pci=8086:{:04x} bar={:#x}+{:#x} aliases={:?}",
|
|
name,
|
|
candidate.device_id,
|
|
bar_phys,
|
|
bar_len,
|
|
aliases,
|
|
);
|
|
|
|
let registration = register_adapter(&I2cAdapterInfo {
|
|
id: 0,
|
|
name: name.clone(),
|
|
max_transaction_size: 0,
|
|
supports_10bit_addr: true,
|
|
provider_scheme: "i2c-lpss".to_string(),
|
|
aliases: aliases.clone(),
|
|
})
|
|
.and_then(|mut file| read_registration_response(&mut file).map(|response| (file, response)))
|
|
.with_context(|| format!("failed to register {name} with i2cd"))?;
|
|
|
|
match registration.1 {
|
|
I2cControlResponse::AdapterRegistered { id } => {
|
|
log::info!("intel-lpss-i2cd: adapter {name} registered with i2cd as {id}");
|
|
}
|
|
other => anyhow::bail!("unexpected i2cd registration response for {name}: {other:?}"),
|
|
}
|
|
|
|
Ok(Controller {
|
|
name,
|
|
aliases,
|
|
engine,
|
|
supports_10bit_addr: true,
|
|
registration: registration.0,
|
|
guard: MmioGuard::Pci(handle),
|
|
})
|
|
}
|
|
|
|
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
|
|
// i2cd is ordered before us via init requires_weak, but scheme
|
|
// registration is asynchronous — tolerate a short startup skew instead
|
|
// of permanently losing the controller.
|
|
let mut file = None;
|
|
for attempt in 0..100 {
|
|
match OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.open("/scheme/i2c/register")
|
|
{
|
|
Ok(opened) => {
|
|
file = Some(opened);
|
|
break;
|
|
}
|
|
Err(err) if attempt == 99 => {
|
|
return Err(err).context("failed to open /scheme/i2c/register");
|
|
}
|
|
Err(_) => std::thread::sleep(std::time::Duration::from_millis(50)),
|
|
}
|
|
}
|
|
let mut file = file.context("failed to open /scheme/i2c/register")?;
|
|
|
|
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
|
|
.context("failed to encode I2C adapter registration")?;
|
|
file.write_all(payload.as_bytes())
|
|
.context("failed to send I2C adapter registration")?;
|
|
Ok(file)
|
|
}
|
|
|
|
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
|
|
let mut buffer = vec![0_u8; 4096];
|
|
let count = file
|
|
.read(&mut buffer)
|
|
.context("failed to read I2C registration response")?;
|
|
buffer.truncate(count);
|
|
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
|
|
ron::from_str(text).context("failed to decode I2C registration response")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PCI discovery (Linux intel-lpss-pci.c model)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn discover_pci_controllers() -> Vec<PciCandidate> {
|
|
let entries = match fs::read_dir("/scheme/pci") {
|
|
Ok(entries) => entries,
|
|
Err(err) => {
|
|
log::warn!("intel-lpss-i2cd: cannot read /scheme/pci: {err}");
|
|
return Vec::new();
|
|
}
|
|
};
|
|
|
|
let mut out = Vec::new();
|
|
for entry in entries.flatten() {
|
|
let config_path = entry.path().join("config");
|
|
let Ok(config) = fs::read(&config_path) else {
|
|
continue;
|
|
};
|
|
if config.len() < 0x40 {
|
|
continue;
|
|
}
|
|
let vendor = u16::from_le_bytes([config[0x00], config[0x01]]);
|
|
let device = u16::from_le_bytes([config[0x02], config[0x03]]);
|
|
let class_code = config[0x0b];
|
|
let subclass = config[0x0a];
|
|
if vendor != PCI_VENDOR_INTEL
|
|
|| class_code != PCI_CLASS_SERIAL_BUS
|
|
|| subclass != PCI_SUBCLASS_I2C
|
|
|| !LPSS_I2C_PCI_IDS.contains(&device)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let location = entry
|
|
.file_name()
|
|
.to_str()
|
|
.map(str::to_owned)
|
|
.unwrap_or_else(|| format!("pci-{device:04x}"));
|
|
|
|
out.push(PciCandidate {
|
|
path: entry.path(),
|
|
location,
|
|
device_id: device,
|
|
});
|
|
}
|
|
|
|
out
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ACPI discovery (legacy LPSS HID model)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
fn discover_acpi_controllers(supported_ids: &[&str]) -> Result<Vec<DiscoveredController>> {
|
|
let mut matched = BTreeMap::new();
|
|
|
|
let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
|
Ok(entries) => entries,
|
|
Err(err)
|
|
if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) =>
|
|
{
|
|
log::debug!("intel-lpss-i2cd: ACPI symbols are not ready yet");
|
|
return Ok(Vec::new());
|
|
}
|
|
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
|
};
|
|
|
|
for entry in entries {
|
|
let entry = entry.context("failed to read ACPI symbol directory 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 Some(id) = read_symbol_id(&entry.path())? else {
|
|
continue;
|
|
};
|
|
if !supported_ids.iter().any(|candidate| *candidate == id) {
|
|
continue;
|
|
}
|
|
|
|
let device = file_name
|
|
.strip_suffix("_HID")
|
|
.or_else(|| file_name.strip_suffix("_CID"))
|
|
.map(str::to_owned);
|
|
if let Some(device) = device {
|
|
matched.entry(device).or_insert(id);
|
|
}
|
|
}
|
|
|
|
let mut controllers = Vec::new();
|
|
for (device, hid) in matched {
|
|
let resources = read_controller_resources(&device)
|
|
.with_context(|| format!("failed to read resources for {device}"))?;
|
|
controllers.push(DiscoveredController {
|
|
device,
|
|
hid,
|
|
resources,
|
|
});
|
|
}
|
|
|
|
Ok(controllers)
|
|
}
|
|
|
|
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
|
|
let contents = fs::read_to_string(path)
|
|
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
|
|
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
|
|
Ok(symbol) => symbol,
|
|
Err(err) => {
|
|
log::debug!(
|
|
"intel-lpss-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}",
|
|
path.display(),
|
|
);
|
|
return Ok(None);
|
|
}
|
|
};
|
|
|
|
let id = match symbol.value {
|
|
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
|
|
AmlValue::String(string) => string,
|
|
};
|
|
|
|
log::debug!("intel-lpss-i2cd: {} -> {id}", symbol.name);
|
|
Ok(Some(id))
|
|
}
|
|
|
|
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
|
|
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
|
|
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
|
|
.with_context(|| format!("failed to decode RON resources for {device}"))?;
|
|
|
|
let mut mmio = None;
|
|
let mut irq = None;
|
|
let mut serial_bus = None;
|
|
|
|
for resource in &resources {
|
|
match resource {
|
|
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
|
|
address,
|
|
address_length,
|
|
..
|
|
}) if mmio.is_none() => {
|
|
mmio = Some((*address as usize, (*address_length as usize).max(0x100)));
|
|
}
|
|
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
|
|
minimum,
|
|
maximum,
|
|
address_length,
|
|
..
|
|
}) if mmio.is_none() && maximum >= minimum => {
|
|
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
|
|
mmio = Some((*minimum as usize, span.max((*address_length as usize).max(0x100))));
|
|
}
|
|
ResourceDescriptor::Address32(descriptor)
|
|
if mmio.is_none()
|
|
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
|
|
{
|
|
mmio = Some((
|
|
descriptor.minimum as usize,
|
|
(descriptor.address_length as usize).max(0x100),
|
|
));
|
|
}
|
|
ResourceDescriptor::Address64(descriptor)
|
|
if mmio.is_none()
|
|
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
|
|
{
|
|
let base = usize::try_from(descriptor.minimum)
|
|
.context("64-bit MMIO base does not fit in usize")?;
|
|
let len = usize::try_from(descriptor.address_length)
|
|
.context("64-bit MMIO length does not fit in usize")?;
|
|
mmio = Some((base, len.max(0x100)));
|
|
}
|
|
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
|
|
irq = interrupts.first().copied().map(u32::from);
|
|
}
|
|
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
|
|
if irq.is_none() =>
|
|
{
|
|
irq = interrupts.first().copied();
|
|
}
|
|
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
|
|
serial_bus = Some(descriptor.clone());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
|
|
Ok(ControllerResources {
|
|
mmio_base,
|
|
mmio_len,
|
|
irq,
|
|
serial_bus,
|
|
})
|
|
}
|
|
|
|
/// Find the ACPI device path whose `_CRS` FixedMemory32 window matches this
|
|
/// controller's MMIO base. That path ("PC00.I2C0") is what HID-over-I2C
|
|
/// devices name in their `I2cSerialBus` resource source, so it is the alias
|
|
/// consumers will use to address this adapter.
|
|
fn discover_acpi_aliases(mmio_base: usize) -> Vec<String> {
|
|
let Ok(entries) = fs::read_dir("/scheme/acpi/resources") else {
|
|
return Vec::new();
|
|
};
|
|
|
|
let mut aliases = Vec::new();
|
|
for entry in entries.flatten() {
|
|
let Ok(contents) = fs::read_to_string(entry.path()) else {
|
|
continue;
|
|
};
|
|
let Ok(resources) = ron::from_str::<Vec<ResourceDescriptor>>(&contents) else {
|
|
continue;
|
|
};
|
|
let matches_base = resources.iter().any(|resource| match resource {
|
|
ResourceDescriptor::FixedMemory32(descriptor) => descriptor.address as usize == mmio_base,
|
|
_ => false,
|
|
});
|
|
if matches_base {
|
|
if let Some(name) = entry.file_name().to_str() {
|
|
aliases.push(name.to_owned());
|
|
}
|
|
}
|
|
}
|
|
aliases
|
|
}
|
|
|
|
fn eisa_id_from_integer(integer: u64) -> String {
|
|
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;
|
|
|
|
format!(
|
|
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn lpss_pci_id_table_covers_arl_h_and_mtl_p() {
|
|
// Host-critical controllers (LG Gram 16Z90TP: 8086:7778, 8086:7779).
|
|
for id in [0x7778u16, 0x7779, 0x777a, 0x777b, 0x7750, 0x7751] {
|
|
assert!(LPSS_I2C_PCI_IDS.contains(&id), "ARL-H id {id:#06x} missing");
|
|
}
|
|
for id in [0x7e50u16, 0x7e51, 0x7e78, 0x7e79, 0x7e7a, 0x7e7b] {
|
|
assert!(LPSS_I2C_PCI_IDS.contains(&id), "MTL-P id {id:#06x} missing");
|
|
}
|
|
}
|
|
}
|