i2c: real DesignWare transfer chain — engine, PCI discovery, provider routing
The I2C transfer chain was stubbed end-to-end: - i2cd's transfer path returned 'not implemented yet' for every transfer, and its wire format didn't match the only in-tree consumer (i2c-hidd sends a bare I2cTransferRequest, i2cd expected I2cControlRequest::Transfer). - intel-lpss-i2cd / dw-acpi-i2cd / amd-mp2-i2cd registered an adapter name and parked forever without initializing the controller or executing a transfer. - intel-lpss-i2cd matched only legacy ACPI HIDs, which do not exist on Meteor Lake / Arrow Lake platforms (LPSS I2C binds by PCI ID there, per Linux drivers/mfd/intel-lpss-pci.c). Replace with real implementations: - New shared crate drivers/i2c/designware (dw-i2c): DesignWare I2C master engine ported from Linux 7.1 i2c-designware-master.c and i2c-designware-common.c — IC_CON + SCL timing computed from ic_clk with Linux's exact hcnt/lcnt formulas (bxt_i2c_info 133 MHz for LPSS, 100 MHz for ACPI-designated blocks), SDA hold with RX-hold workaround, polling transfer engine with RESTART/STOP sequencing, TX_ABRT decode (Nack/arbitration-lost/abort/timeout), 7/10-bit addressing, bounded timeouts. Also provides the shared /scheme/<name> transfer endpoint with register-then-ready ordering. - intel-lpss-i2cd: PCI discovery for ARL-H (0x7750/0x7751, 0x7778-0x777b) and MTL-P (0x7e50/0x7e51, 0x7e78-0x7e7b) with 32/64-bit BAR0 decode, ACPI alias resolution via FixedMemory32 == BAR0 across /scheme/acpi/resources, legacy ACPI-HID path kept, i2cd registration with bounded retry, serves /scheme/i2c-lpss. - dw-acpi-i2cd: converted from register-and-park to the shared engine, serves /scheme/dw-acpi-i2c. - i2cd: real transfer routing — adapter resolution by name/alias (exact, normalized, last-component) and forwarding to the provider daemon's /scheme/<provider>/transfer endpoint. - i2c-interface: I2cAdapterInfo gains provider_scheme + aliases (serde-default, wire-compatible). Tests: dw-i2c 3, intel-lpss-i2cd 3; full workspace cargo check clean; base cooks for x86_64-unknown-redox. Refs: local/docs/LG-GRAM-16Z90TP-COMPATIBILITY-PLAN.md Phase 4
This commit is contained in:
Generated
+18
@@ -678,6 +678,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
"daemon",
|
"daemon",
|
||||||
|
"dw-i2c",
|
||||||
"i2c-interface",
|
"i2c-interface",
|
||||||
"libredox",
|
"libredox",
|
||||||
"log",
|
"log",
|
||||||
@@ -686,6 +687,20 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dw-i2c"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"i2c-interface",
|
||||||
|
"log",
|
||||||
|
"redox-scheme",
|
||||||
|
"redox_syscall 0.9.0+rb0.3.1",
|
||||||
|
"ron",
|
||||||
|
"scheme-utils",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "e1000d"
|
name = "e1000d"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1250,11 +1265,14 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
"daemon",
|
"daemon",
|
||||||
|
"dw-i2c",
|
||||||
"i2c-interface",
|
"i2c-interface",
|
||||||
"libredox",
|
"libredox",
|
||||||
"log",
|
"log",
|
||||||
|
"redox-scheme",
|
||||||
"redox_syscall 0.9.0+rb0.3.1",
|
"redox_syscall 0.9.0+rb0.3.1",
|
||||||
"ron",
|
"ron",
|
||||||
|
"scheme-utils",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ members = [
|
|||||||
"drivers/i2c/i2c-interface",
|
"drivers/i2c/i2c-interface",
|
||||||
"drivers/i2c/i2cd",
|
"drivers/i2c/i2cd",
|
||||||
"drivers/i2c/amd-mp2-i2cd",
|
"drivers/i2c/amd-mp2-i2cd",
|
||||||
|
"drivers/i2c/designware",
|
||||||
"drivers/i2c/dw-acpi-i2cd",
|
"drivers/i2c/dw-acpi-i2cd",
|
||||||
"drivers/i2c/intel-lpss-i2cd",
|
"drivers/i2c/intel-lpss-i2cd",
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> R
|
|||||||
name: format!("amd-mp2:{}", pci_config.func.name()),
|
name: format!("amd-mp2:{}", pci_config.func.name()),
|
||||||
max_transaction_size: 0,
|
max_transaction_size: 0,
|
||||||
supports_10bit_addr: false,
|
supports_10bit_addr: false,
|
||||||
|
provider_scheme: String::new(),
|
||||||
|
aliases: Vec::new(),
|
||||||
};
|
};
|
||||||
let mut registration = register_adapter(&info)
|
let mut registration = register_adapter(&info)
|
||||||
.context("failed to register AMD MP2 controller with i2cd")?;
|
.context("failed to register AMD MP2 controller with i2cd")?;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "dw-i2c"
|
||||||
|
description = "Synopsys DesignWare I2C master controller engine (Linux i2c-designware port)"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log.workspace = true
|
||||||
|
redox_syscall = { workspace = true, features = ["std"] }
|
||||||
|
redox-scheme.workspace = true
|
||||||
|
ron.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
|
||||||
|
i2c-interface = { path = "../i2c-interface" }
|
||||||
|
scheme-utils = { path = "../../../scheme-utils" }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
@@ -0,0 +1,671 @@
|
|||||||
|
//! Synopsys DesignWare I2C master controller engine.
|
||||||
|
//!
|
||||||
|
//! Ported from Linux 7.1 `drivers/i2c/busses/i2c-designware-master.c` and
|
||||||
|
//! `i2c-designware-common.c` (register model in `i2c-designware-core.h`).
|
||||||
|
//! Polling variant: transfers complete by polling IC_STATUS with bounded
|
||||||
|
//! timeouts instead of interrupt-driven completion, the same approach used
|
||||||
|
//! by the U-Boot DesignWare driver. Transfer semantics (RESTART/STOP
|
||||||
|
//! sequencing, TX_ABRT error decode, 7/10-bit addressing) follow the Linux
|
||||||
|
//! master driver.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
// Register offsets (i2c-designware-core.h).
|
||||||
|
const DW_IC_CON: usize = 0x00;
|
||||||
|
const DW_IC_TAR: usize = 0x04;
|
||||||
|
const DW_IC_DATA_CMD: usize = 0x10;
|
||||||
|
const DW_IC_SS_SCL_HCNT: usize = 0x14;
|
||||||
|
const DW_IC_SS_SCL_LCNT: usize = 0x18;
|
||||||
|
const DW_IC_FS_SCL_HCNT: usize = 0x1c;
|
||||||
|
const DW_IC_FS_SCL_LCNT: usize = 0x20;
|
||||||
|
const DW_IC_INTR_MASK: usize = 0x30;
|
||||||
|
const DW_IC_RAW_INTR_STAT: usize = 0x34;
|
||||||
|
const DW_IC_CLR_INTR: usize = 0x40;
|
||||||
|
const DW_IC_CLR_TX_ABRT: usize = 0x54;
|
||||||
|
const DW_IC_ENABLE: usize = 0x6c;
|
||||||
|
const DW_IC_STATUS: usize = 0x70;
|
||||||
|
const DW_IC_SDA_HOLD: usize = 0x7c;
|
||||||
|
const DW_IC_TX_ABRT_SOURCE: usize = 0x80;
|
||||||
|
const DW_IC_COMP_VERSION: usize = 0xf8;
|
||||||
|
|
||||||
|
// IC_CON bits (i2c-designware-core.h).
|
||||||
|
const IC_CON_MASTER: u32 = 1 << 0;
|
||||||
|
const IC_CON_SPEED_FAST: u32 = 2 << 1;
|
||||||
|
const IC_CON_10BITADDR_MASTER: u32 = 1 << 4;
|
||||||
|
const IC_CON_RESTART_EN: u32 = 1 << 5;
|
||||||
|
const IC_CON_SLAVE_DISABLE: u32 = 1 << 6;
|
||||||
|
|
||||||
|
// IC_DATA_CMD bits: [7:0] data, [8] read, [9] stop, [10] restart.
|
||||||
|
const IC_DATA_CMD_READ: u32 = 1 << 8;
|
||||||
|
const IC_DATA_CMD_STOP: u32 = 1 << 9;
|
||||||
|
const IC_DATA_CMD_RESTART: u32 = 1 << 10;
|
||||||
|
|
||||||
|
// IC_STATUS bits (i2c-designware-core.h).
|
||||||
|
const IC_STATUS_ACTIVITY: u32 = 1 << 0;
|
||||||
|
const IC_STATUS_TFNF: u32 = 1 << 1;
|
||||||
|
const IC_STATUS_TFE: u32 = 1 << 2;
|
||||||
|
const IC_STATUS_RFNE: u32 = 1 << 3;
|
||||||
|
const IC_STATUS_MASTER_ACTIVITY: u32 = 1 << 5;
|
||||||
|
|
||||||
|
// IC_RAW_INTR_STAT bit for transfer abort.
|
||||||
|
const IC_RAW_TX_ABRT: u32 = 1 << 6;
|
||||||
|
|
||||||
|
// IC_TX_ABRT_SOURCE codes (i2c-designware-core.h).
|
||||||
|
const ABRT_7B_ADDR_NOACK: u32 = 1 << 0;
|
||||||
|
const ABRT_10ADDR1_NOACK: u32 = 1 << 1;
|
||||||
|
const ABRT_10ADDR2_NOACK: u32 = 1 << 2;
|
||||||
|
const ABRT_TXDATA_NOACK: u32 = 1 << 3;
|
||||||
|
const ABRT_GCALL_NOACK: u32 = 1 << 4;
|
||||||
|
const ABRT_MASTER_DIS: u32 = 1 << 11;
|
||||||
|
const ABRT_ARB_LOST: u32 = 1 << 12;
|
||||||
|
const ABRT_NOACK_MASK: u32 = ABRT_7B_ADDR_NOACK
|
||||||
|
| ABRT_10ADDR1_NOACK
|
||||||
|
| ABRT_10ADDR2_NOACK
|
||||||
|
| ABRT_TXDATA_NOACK
|
||||||
|
| ABRT_GCALL_NOACK;
|
||||||
|
|
||||||
|
const IC_SDA_HOLD_MIN_VERS: u32 = 0x3131_312a; // "111*"
|
||||||
|
const IC_SDA_HOLD_RX_SHIFT: u32 = 16;
|
||||||
|
|
||||||
|
const DW_IC_TAR_10BITADDR_MASTER: u32 = 1 << 12;
|
||||||
|
|
||||||
|
/// Polling budget for a single byte-level wait. HID-over-I2C workloads are
|
||||||
|
/// small; 50 ms is generous even for a heavily loaded 100 kHz bus.
|
||||||
|
const BYTE_WAIT_TIMEOUT: Duration = Duration::from_millis(50);
|
||||||
|
/// Polling budget for whole-transfer completion (FIFO drain + bus idle).
|
||||||
|
const COMPLETION_TIMEOUT: Duration = Duration::from_millis(250);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum DwError {
|
||||||
|
/// Slave addressed NACKed (address or data phase).
|
||||||
|
Nack,
|
||||||
|
/// Arbitration lost (multi-master contention).
|
||||||
|
ArbitrationLost,
|
||||||
|
/// Hardware abort that is not a simple NACK.
|
||||||
|
Abort(u32),
|
||||||
|
/// Polling timeout waiting for FIFO space, data, or bus idle.
|
||||||
|
Timeout,
|
||||||
|
/// Controller reported MASTER_DIS abort (master mode disabled).
|
||||||
|
MasterDisabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DwError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DwError::Nack => write!(f, "slave NACK"),
|
||||||
|
DwError::ArbitrationLost => write!(f, "arbitration lost"),
|
||||||
|
DwError::Abort(src) => write!(f, "transfer abort (IC_TX_ABRT_SOURCE={src:#x})"),
|
||||||
|
DwError::Timeout => write!(f, "transfer timeout"),
|
||||||
|
DwError::MasterDisabled => write!(f, "master mode disabled"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for DwError {}
|
||||||
|
|
||||||
|
/// SCL timing parameters for one bus speed, derived from the controller
|
||||||
|
/// clock exactly as Linux's `i2c_dw_scl_hcnt`/`i2c_dw_scl_lcnt` do.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SclTiming {
|
||||||
|
pub hcnt: u32,
|
||||||
|
pub lcnt: u32,
|
||||||
|
pub sda_hold_tx: u32,
|
||||||
|
pub sda_fall_ns: u32,
|
||||||
|
pub scl_fall_ns: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bus timing inputs in the same shape Linux's intel-lpss-pci.c carries them
|
||||||
|
/// (`bxt_i2c_info`: 133 MHz clock, sda-hold 42 ns, sda-fall 171 ns,
|
||||||
|
/// scl-fall 208 ns).
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BusTiming {
|
||||||
|
pub ic_clk_hz: u32,
|
||||||
|
pub sda_hold_ns: u32,
|
||||||
|
pub sda_fall_ns: u32,
|
||||||
|
pub scl_fall_ns: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Broxton-derived LPSS timing used by every modern Intel LPSS I2C block
|
||||||
|
/// (ARL-H 0x7778/0x7779 and MTL-P 0x7e50/0x7e78 families per
|
||||||
|
/// `intel-lpss-pci.c`: `.clk_rate = 133000000`, `bxt_i2c_properties`).
|
||||||
|
pub const BXT_I2C_TIMING: BusTiming = BusTiming {
|
||||||
|
ic_clk_hz: 133_000_000,
|
||||||
|
sda_hold_ns: 42,
|
||||||
|
sda_fall_ns: 171,
|
||||||
|
scl_fall_ns: 208,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl BusTiming {
|
||||||
|
/// Fast-mode (400 kHz) SCL counts. Formulas from
|
||||||
|
/// `i2c-designware-common.c:i2c_dw_scl_hcnt/lcnt`:
|
||||||
|
/// hcnt = IC_CLK * (tHD;STA + tf) - 3
|
||||||
|
/// lcnt = IC_CLK * (tLOW + tf) - 1
|
||||||
|
/// with tHD;STA = 0.6 us and tLOW = 1.3 us (I2C fast-mode spec).
|
||||||
|
pub fn fast_mode(&self) -> SclTiming {
|
||||||
|
let clk_khz = self.ic_clk_hz / 1000;
|
||||||
|
let hcnt = (clk_khz as u64 * (600 + self.scl_fall_ns as u64) / 1_000_000)
|
||||||
|
.saturating_sub(3) as u32;
|
||||||
|
let lcnt = (clk_khz as u64 * (1300 + self.scl_fall_ns as u64) / 1_000_000)
|
||||||
|
.saturating_sub(1) as u32;
|
||||||
|
// IC_SDA_HOLD TX value is in IC_CLK cycles (datasheet §5.11).
|
||||||
|
let sda_hold_tx = (clk_khz as u64 * self.sda_hold_ns as u64).div_ceil(1_000_000) as u32;
|
||||||
|
SclTiming {
|
||||||
|
hcnt,
|
||||||
|
lcnt,
|
||||||
|
sda_hold_tx,
|
||||||
|
sda_fall_ns: self.sda_fall_ns,
|
||||||
|
scl_fall_ns: self.scl_fall_ns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard-mode (100 kHz) SCL counts: tHD;STA = 4.0 us, tLOW = 4.7 us.
|
||||||
|
pub fn standard_mode(&self) -> SclTiming {
|
||||||
|
let clk_khz = self.ic_clk_hz / 1000;
|
||||||
|
let hcnt = (clk_khz as u64 * (4000 + self.scl_fall_ns as u64) / 1_000_000)
|
||||||
|
.saturating_sub(3) as u32;
|
||||||
|
let lcnt = (clk_khz as u64 * (4700 + self.scl_fall_ns as u64) / 1_000_000)
|
||||||
|
.saturating_sub(1) as u32;
|
||||||
|
let sda_hold_tx = (clk_khz as u64 * self.sda_hold_ns as u64).div_ceil(1_000_000) as u32;
|
||||||
|
SclTiming {
|
||||||
|
hcnt,
|
||||||
|
lcnt,
|
||||||
|
sda_hold_tx,
|
||||||
|
sda_fall_ns: self.sda_fall_ns,
|
||||||
|
scl_fall_ns: self.scl_fall_ns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One transfer segment: either a write of `data` or a read of `len` bytes.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Segment {
|
||||||
|
Write(Vec<u8>),
|
||||||
|
Read(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DesignWare master engine over a mapped MMIO register block.
|
||||||
|
pub struct DwI2c {
|
||||||
|
base: *mut u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The MMIO block is process-local and the driver serves transfers from a
|
||||||
|
// single scheme handler thread.
|
||||||
|
unsafe impl Send for DwI2c {}
|
||||||
|
|
||||||
|
impl DwI2c {
|
||||||
|
/// # Safety
|
||||||
|
/// `base` must point at the start of a mapped DesignWare register block
|
||||||
|
/// of at least 0x100 bytes, exclusively owned by the caller.
|
||||||
|
pub unsafe fn new(base: *mut u8) -> Self {
|
||||||
|
Self { base }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read32(&self, reg: usize) -> u32 {
|
||||||
|
unsafe { (self.base.add(reg) as *const u32).read_volatile() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write32(&self, reg: usize, value: u32) {
|
||||||
|
unsafe { (self.base.add(reg) as *mut u32).write_volatile(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enabled(&self) -> bool {
|
||||||
|
self.read32(DW_IC_ENABLE) & 1 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_enabled(&self, enable: bool) {
|
||||||
|
self.write32(DW_IC_ENABLE, enable as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the controller for master-mode operation.
|
||||||
|
///
|
||||||
|
/// Mirrors `i2c_dw_init_master` (i2c-designware-master.c): disable,
|
||||||
|
/// program IC_CON and SCL counts for both speed grades, apply SDA hold
|
||||||
|
/// times with the RX-hold workaround, mask all interrupts (polling
|
||||||
|
/// driver), flush stale state, then enable.
|
||||||
|
pub fn init(&self, timing: &BusTiming) {
|
||||||
|
let fast = timing.fast_mode();
|
||||||
|
let standard = timing.standard_mode();
|
||||||
|
|
||||||
|
self.set_enabled(false);
|
||||||
|
|
||||||
|
let con = IC_CON_MASTER
|
||||||
|
| IC_CON_SPEED_FAST
|
||||||
|
| IC_CON_RESTART_EN
|
||||||
|
| IC_CON_SLAVE_DISABLE;
|
||||||
|
self.write32(DW_IC_CON, con);
|
||||||
|
|
||||||
|
self.write32(DW_IC_SS_SCL_HCNT, standard.hcnt);
|
||||||
|
self.write32(DW_IC_SS_SCL_LCNT, standard.lcnt);
|
||||||
|
self.write32(DW_IC_FS_SCL_HCNT, fast.hcnt);
|
||||||
|
self.write32(DW_IC_FS_SCL_LCNT, fast.lcnt);
|
||||||
|
|
||||||
|
// i2c_dw_set_sda_hold: only controllers at version >= 1.11 have a
|
||||||
|
// writable IC_SDA_HOLD; apply the TX hold and force a 1-cycle RX
|
||||||
|
// hold to avoid TX arbitration loss against fast slaves.
|
||||||
|
if self.read32(DW_IC_COMP_VERSION) >= IC_SDA_HOLD_MIN_VERS {
|
||||||
|
let mut hold = fast.sda_hold_tx;
|
||||||
|
if self.read32(DW_IC_SDA_HOLD) >> IC_SDA_HOLD_RX_SHIFT == 0 {
|
||||||
|
hold |= 1 << IC_SDA_HOLD_RX_SHIFT;
|
||||||
|
}
|
||||||
|
self.write32(DW_IC_SDA_HOLD, hold);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polling driver: no interrupt sources, clear any pending state.
|
||||||
|
self.write32(DW_IC_INTR_MASK, 0);
|
||||||
|
let _ = self.read32(DW_IC_CLR_INTR);
|
||||||
|
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||||||
|
|
||||||
|
self.set_enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for(&self, mut condition: impl FnMut(u32) -> bool, timeout: Duration) -> Result<(), DwError> {
|
||||||
|
let start = Instant::now();
|
||||||
|
loop {
|
||||||
|
let status = self.read32(DW_IC_STATUS);
|
||||||
|
if condition(status) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.read32(DW_IC_RAW_INTR_STAT) & IC_RAW_TX_ABRT != 0 {
|
||||||
|
return Err(self.decode_abort());
|
||||||
|
}
|
||||||
|
if start.elapsed() > timeout {
|
||||||
|
return Err(DwError::Timeout);
|
||||||
|
}
|
||||||
|
std::thread::yield_now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_abort(&self) -> DwError {
|
||||||
|
let source = self.read32(DW_IC_TX_ABRT_SOURCE);
|
||||||
|
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||||||
|
if source & ABRT_NOACK_MASK != 0 {
|
||||||
|
DwError::Nack
|
||||||
|
} else if source & ABRT_ARB_LOST != 0 {
|
||||||
|
DwError::ArbitrationLost
|
||||||
|
} else if source & ABRT_MASTER_DIS != 0 {
|
||||||
|
DwError::MasterDisabled
|
||||||
|
} else {
|
||||||
|
DwError::Abort(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_target_address(&self, address: u16, ten_bit: bool) {
|
||||||
|
let mut con = self.read32(DW_IC_CON);
|
||||||
|
if ten_bit {
|
||||||
|
con |= IC_CON_10BITADDR_MASTER;
|
||||||
|
} else {
|
||||||
|
con &= !IC_CON_10BITADDR_MASTER;
|
||||||
|
}
|
||||||
|
self.write32(DW_IC_CON, con);
|
||||||
|
|
||||||
|
let mut tar = u32::from(address & 0x3ff);
|
||||||
|
if ten_bit {
|
||||||
|
tar |= DW_IC_TAR_10BITADDR_MASTER;
|
||||||
|
}
|
||||||
|
self.write32(DW_IC_TAR, tar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a sequence of write/read segments against one slave address,
|
||||||
|
/// issuing a repeated START between segments and a STOP after the last
|
||||||
|
/// one (or leaving the bus held when `stop` is false).
|
||||||
|
///
|
||||||
|
/// Mirrors `i2c_dw_xfer` segment handling (i2c-designware-master.c):
|
||||||
|
/// writes feed IC_DATA_CMD with STOP on the final byte of the final
|
||||||
|
/// segment, reads issue IC_DATA_CMD_READ commands (RESTART on the first
|
||||||
|
/// read command after a direction change) and drain RX FIFO as data
|
||||||
|
/// arrives.
|
||||||
|
pub fn xfer(
|
||||||
|
&self,
|
||||||
|
address: u16,
|
||||||
|
ten_bit: bool,
|
||||||
|
segments: &[Segment],
|
||||||
|
stop: bool,
|
||||||
|
) -> Result<Vec<Vec<u8>>, DwError> {
|
||||||
|
if segments.is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
if !self.enabled() {
|
||||||
|
return Err(DwError::MasterDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_target_address(address, ten_bit);
|
||||||
|
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||||||
|
|
||||||
|
let mut reads: Vec<Vec<u8>> = Vec::new();
|
||||||
|
let last = segments.len() - 1;
|
||||||
|
|
||||||
|
for (index, segment) in segments.iter().enumerate() {
|
||||||
|
let is_last_segment = index == last;
|
||||||
|
match segment {
|
||||||
|
Segment::Write(data) => {
|
||||||
|
for (byte_index, byte) in data.iter().enumerate() {
|
||||||
|
let mut cmd = u32::from(*byte);
|
||||||
|
if is_last_segment && byte_index == data.len() - 1 && stop {
|
||||||
|
cmd |= IC_DATA_CMD_STOP;
|
||||||
|
}
|
||||||
|
self.wait_for(
|
||||||
|
|status| status & IC_STATUS_TFNF != 0,
|
||||||
|
BYTE_WAIT_TIMEOUT,
|
||||||
|
)?;
|
||||||
|
self.write32(DW_IC_DATA_CMD, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Segment::Read(len) => {
|
||||||
|
let mut buf = Vec::with_capacity(*len);
|
||||||
|
for byte_index in 0..*len {
|
||||||
|
let mut cmd = IC_DATA_CMD_READ;
|
||||||
|
if byte_index == 0 {
|
||||||
|
cmd |= IC_DATA_CMD_RESTART;
|
||||||
|
}
|
||||||
|
if is_last_segment && byte_index == len - 1 && stop {
|
||||||
|
cmd |= IC_DATA_CMD_STOP;
|
||||||
|
}
|
||||||
|
self.wait_for(
|
||||||
|
|status| status & IC_STATUS_TFNF != 0,
|
||||||
|
BYTE_WAIT_TIMEOUT,
|
||||||
|
)?;
|
||||||
|
self.write32(DW_IC_DATA_CMD, cmd);
|
||||||
|
|
||||||
|
self.wait_for(
|
||||||
|
|status| status & IC_STATUS_RFNE != 0,
|
||||||
|
BYTE_WAIT_TIMEOUT,
|
||||||
|
)?;
|
||||||
|
buf.push((self.read32(DW_IC_DATA_CMD) & 0xff) as u8);
|
||||||
|
}
|
||||||
|
reads.push(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the transfer to drain and the bus to go idle
|
||||||
|
// (i2c_dw_wait_bus_not_busy).
|
||||||
|
self.wait_for(
|
||||||
|
|status| {
|
||||||
|
status & IC_STATUS_TFE != 0
|
||||||
|
&& status & (IC_STATUS_ACTIVITY | IC_STATUS_MASTER_ACTIVITY) == 0
|
||||||
|
},
|
||||||
|
COMPLETION_TIMEOUT,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bxt_fast_mode_matches_linux_formula() {
|
||||||
|
// 133 MHz, tHD;STA=0.6us, tLOW=1.3us, scl_fall=208ns:
|
||||||
|
// hcnt = 133000*808/1e6 - 3 = 104, lcnt = 133000*1508/1e6 - 1 = 199.
|
||||||
|
let timing = BXT_I2C_TIMING.fast_mode();
|
||||||
|
assert_eq!(timing.hcnt, 104);
|
||||||
|
assert_eq!(timing.lcnt, 199);
|
||||||
|
// sda_hold_tx = ceil(133000 * 42 / 1e6) = ceil(5.586) = 6.
|
||||||
|
assert_eq!(timing.sda_hold_tx, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bxt_standard_mode_matches_linux_formula() {
|
||||||
|
// hcnt = 133000*4208/1e6 - 3 = 556, lcnt = 133000*4908/1e6 - 1 = 651.
|
||||||
|
let timing = BXT_I2C_TIMING.standard_mode();
|
||||||
|
assert_eq!(timing.hcnt, 556);
|
||||||
|
assert_eq!(timing.lcnt, 651);
|
||||||
|
assert_eq!(timing.sda_hold_tx, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod endpoint {
|
||||||
|
//! Shared `/scheme/<name>` transfer endpoint for DesignWare-based
|
||||||
|
//! controller daemons. Accepts bare `I2cTransferRequest` payloads and
|
||||||
|
//! executes them on the matching controller's engine.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use i2c_interface::{I2cTransferRequest, I2cTransferResponse};
|
||||||
|
use redox_scheme::scheme::{register_sync_scheme, SchemeSync};
|
||||||
|
use redox_scheme::{CallerCtx, OpenResult, Socket};
|
||||||
|
use scheme_utils::{Blocking, HandleMap};
|
||||||
|
use syscall::schemev2::NewFdFlags;
|
||||||
|
use syscall::{Error as SysError, EBADF, EINVAL, ENOENT};
|
||||||
|
|
||||||
|
use super::{DwI2c, Segment};
|
||||||
|
|
||||||
|
/// A brought-up controller: initialized engine plus the names consumers
|
||||||
|
/// use to address it (driver name and ACPI aliases).
|
||||||
|
pub struct EndpointController {
|
||||||
|
pub name: String,
|
||||||
|
pub aliases: Vec<String>,
|
||||||
|
pub engine: DwI2c,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Handle {
|
||||||
|
SchemeRoot,
|
||||||
|
Transfer { pending: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Endpoint {
|
||||||
|
handles: HandleMap<Handle>,
|
||||||
|
controllers: Vec<EndpointController>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint {
|
||||||
|
fn execute(&self, request: &I2cTransferRequest) -> I2cTransferResponse {
|
||||||
|
if request.segments.is_empty() {
|
||||||
|
return I2cTransferResponse {
|
||||||
|
ok: true,
|
||||||
|
read_data: Vec::new(),
|
||||||
|
error: None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(controller) = self
|
||||||
|
.controllers
|
||||||
|
.iter()
|
||||||
|
.find(|controller| adapter_matches(&controller.name, &controller.aliases, &request.adapter))
|
||||||
|
else {
|
||||||
|
return I2cTransferResponse {
|
||||||
|
ok: false,
|
||||||
|
read_data: Vec::new(),
|
||||||
|
error: Some(format!(
|
||||||
|
"no controller matches adapter '{}'",
|
||||||
|
request.adapter
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = &request.segments[0];
|
||||||
|
let (address, ten_bit) = (first.address, first.ten_bit_address);
|
||||||
|
if request
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.any(|segment| segment.address != address || segment.ten_bit_address != ten_bit)
|
||||||
|
{
|
||||||
|
return I2cTransferResponse {
|
||||||
|
ok: false,
|
||||||
|
read_data: Vec::new(),
|
||||||
|
error: Some("mixed-address segment sequences are not supported".to_string()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let ops: Vec<Segment> = request
|
||||||
|
.segments
|
||||||
|
.iter()
|
||||||
|
.map(|segment| match &segment.op {
|
||||||
|
i2c_interface::I2cTransferOp::Write(data) => Segment::Write(data.clone()),
|
||||||
|
i2c_interface::I2cTransferOp::Read(len) => Segment::Read(*len),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match controller.engine.xfer(address, ten_bit, &ops, request.stop) {
|
||||||
|
Ok(reads) => I2cTransferResponse {
|
||||||
|
ok: true,
|
||||||
|
read_data: reads,
|
||||||
|
error: None,
|
||||||
|
},
|
||||||
|
Err(err) => I2cTransferResponse {
|
||||||
|
ok: false,
|
||||||
|
read_data: Vec::new(),
|
||||||
|
error: Some(format!("{}: {err}", controller.name)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adapter name matching tolerant to ACPI path normalization forms:
|
||||||
|
/// exact match, then last-dot-component match
|
||||||
|
/// ("I2C0" ≡ "PC00.I2C0" ≡ "\_SB_.PC00.I2C0").
|
||||||
|
fn adapter_matches(name: &str, aliases: &[String], requested: &str) -> bool {
|
||||||
|
fn last_component(value: &str) -> &str {
|
||||||
|
value.rsplit('.').next().unwrap_or(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalized = requested
|
||||||
|
.trim_start_matches("\\_SB_.")
|
||||||
|
.trim_start_matches('_');
|
||||||
|
if name == requested || aliases.iter().any(|alias| alias == requested || alias == &normalized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if last_component(name) == last_component(normalized) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
aliases
|
||||||
|
.iter()
|
||||||
|
.any(|alias| last_component(alias) == last_component(normalized))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemeSync for Endpoint {
|
||||||
|
fn scheme_root(&mut self) -> syscall::Result<usize> {
|
||||||
|
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openat(
|
||||||
|
&mut self,
|
||||||
|
dirfd: usize,
|
||||||
|
path: &str,
|
||||||
|
_flags: usize,
|
||||||
|
_fcntl_flags: u32,
|
||||||
|
_ctx: &CallerCtx,
|
||||||
|
) -> syscall::Result<OpenResult> {
|
||||||
|
let handle = self.handles.get(dirfd)?;
|
||||||
|
let segments = path.trim_matches('/');
|
||||||
|
|
||||||
|
let new_handle = match handle {
|
||||||
|
Handle::SchemeRoot => match segments {
|
||||||
|
"transfer" => Handle::Transfer {
|
||||||
|
pending: Vec::new(),
|
||||||
|
},
|
||||||
|
_ => return Err(SysError::new(ENOENT)),
|
||||||
|
},
|
||||||
|
_ => return Err(SysError::new(EBADF)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let fd = self.handles.insert(new_handle);
|
||||||
|
Ok(OpenResult::ThisScheme {
|
||||||
|
number: fd,
|
||||||
|
flags: NewFdFlags::empty(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(
|
||||||
|
&mut self,
|
||||||
|
id: usize,
|
||||||
|
buf: &mut [u8],
|
||||||
|
offset: u64,
|
||||||
|
_fcntl_flags: u32,
|
||||||
|
_ctx: &CallerCtx,
|
||||||
|
) -> syscall::Result<usize> {
|
||||||
|
let handle = self.handles.get_mut(id)?;
|
||||||
|
let pending = match handle {
|
||||||
|
Handle::Transfer { pending } => pending,
|
||||||
|
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
if offset >= pending.len() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let copy_len = buf.len().min(pending.len() - offset);
|
||||||
|
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
|
||||||
|
Ok(copy_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(
|
||||||
|
&mut self,
|
||||||
|
id: usize,
|
||||||
|
buf: &[u8],
|
||||||
|
_offset: u64,
|
||||||
|
_fcntl_flags: u32,
|
||||||
|
_ctx: &CallerCtx,
|
||||||
|
) -> syscall::Result<usize> {
|
||||||
|
if !matches!(self.handles.get(id)?, Handle::Transfer { .. }) {
|
||||||
|
return Err(SysError::new(EBADF));
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = std::str::from_utf8(buf).map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
let request: I2cTransferRequest =
|
||||||
|
ron::from_str(text).map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
|
||||||
|
let response = self.execute(&request);
|
||||||
|
let pending = ron::ser::to_string(&response)
|
||||||
|
.map(|text| text.into_bytes())
|
||||||
|
.map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
|
||||||
|
let handle = self.handles.get_mut(id)?;
|
||||||
|
let Handle::Transfer { pending: slot } = handle else {
|
||||||
|
return Err(SysError::new(EBADF));
|
||||||
|
};
|
||||||
|
*slot = pending;
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register `/scheme/<scheme_name>` and serve transfer requests forever.
|
||||||
|
///
|
||||||
|
/// `on_ready` runs after the scheme is registered but before requests
|
||||||
|
/// are processed — daemons use it for their init-system readiness
|
||||||
|
/// notification (matching the xhcid ordering: register, then ready).
|
||||||
|
pub fn serve(
|
||||||
|
scheme_name: &str,
|
||||||
|
controllers: Vec<EndpointController>,
|
||||||
|
on_ready: impl FnOnce(),
|
||||||
|
) -> Result<()> {
|
||||||
|
let socket = Socket::create().context("failed to create scheme socket")?;
|
||||||
|
let mut endpoint = Endpoint {
|
||||||
|
handles: HandleMap::new(),
|
||||||
|
controllers,
|
||||||
|
};
|
||||||
|
register_sync_scheme(&socket, scheme_name, &mut endpoint)
|
||||||
|
.with_context(|| format!("failed to register {scheme_name} scheme"))?;
|
||||||
|
|
||||||
|
log::info!(
|
||||||
|
"dw-i2c: serving /scheme/{scheme_name} with {} controller(s)",
|
||||||
|
endpoint.controllers.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
on_ready();
|
||||||
|
|
||||||
|
let blocking = Blocking::new(&socket, 16);
|
||||||
|
blocking
|
||||||
|
.process_requests_blocking(endpoint)
|
||||||
|
.context("scheme handler exited")?;
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn adapter_name_matching_forms() {
|
||||||
|
let aliases = vec!["PC00.I2C0".to_string()];
|
||||||
|
assert!(adapter_matches("intel-lpss:00--15.0", &aliases, "PC00.I2C0"));
|
||||||
|
assert!(adapter_matches("intel-lpss:00--15.0", &aliases, "\\_SB_.PC00.I2C0"));
|
||||||
|
assert!(adapter_matches("intel-lpss:00--15.0", &aliases, "I2C0"));
|
||||||
|
assert!(!adapter_matches("intel-lpss:00--15.0", &aliases, "I2C3"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ ron.workspace = true
|
|||||||
acpi-resource = { path = "../../acpi-resource" }
|
acpi-resource = { path = "../../acpi-resource" }
|
||||||
common = { path = "../../common" }
|
common = { path = "../../common" }
|
||||||
daemon = { path = "../../../daemon" }
|
daemon = { path = "../../../daemon" }
|
||||||
|
dw-i2c = { path = "../designware" }
|
||||||
i2c-interface = { path = "../i2c-interface" }
|
i2c-interface = { path = "../i2c-interface" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -13,18 +13,22 @@ use common::{MemoryType, PhysBorrowed, Prot};
|
|||||||
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
|
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use dw_i2c::endpoint::EndpointController;
|
||||||
|
use dw_i2c::{BusTiming, DwI2c};
|
||||||
|
|
||||||
const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"];
|
const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"];
|
||||||
|
|
||||||
const DW_IC_CON: usize = 0x00;
|
// Baytrail/Cherrytrail/AMD DesignWare blocks clock the controller at 100 MHz
|
||||||
const DW_IC_TAR: usize = 0x04;
|
// (Linux i2c-designware-pcidrv.c: ehl_get_clk_rate_khz and
|
||||||
const DW_IC_SS_SCL_HCNT: usize = 0x14;
|
// navi_amd_get_clk_rate_khz both return 100000). The fall-time constants
|
||||||
const DW_IC_SS_SCL_LCNT: usize = 0x18;
|
// reuse the Broxton values; the formula produces slower-than-spec timings
|
||||||
const DW_IC_DATA_CMD: usize = 0x10;
|
// on faster blocks, which is the safe direction.
|
||||||
const DW_IC_INTR_MASK: usize = 0x30;
|
const DW_100MHZ_TIMING: BusTiming = BusTiming {
|
||||||
const DW_IC_CLR_INTR: usize = 0x40;
|
ic_clk_hz: 100_000_000,
|
||||||
const DW_IC_ENABLE: usize = 0x6C;
|
sda_hold_ns: 42,
|
||||||
const DW_IC_STATUS: usize = 0x70;
|
sda_fall_ns: 171,
|
||||||
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
|
scl_fall_ns: 208,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AmlSymbol {
|
struct AmlSymbol {
|
||||||
@@ -53,11 +57,6 @@ struct ControllerDescriptor {
|
|||||||
resources: ControllerResources,
|
resources: ControllerResources,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RegisteredController {
|
|
||||||
_mmio: Option<PhysBorrowed>,
|
|
||||||
_registration: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
common::setup_logging(
|
common::setup_logging(
|
||||||
"bus",
|
"bus",
|
||||||
@@ -88,22 +87,26 @@ fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
|||||||
log::info!("dw-acpi-i2cd: no supported ACPI controllers found");
|
log::info!("dw-acpi-i2cd: no supported ACPI controllers found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut registered = Vec::new();
|
let mut guards = Vec::new();
|
||||||
|
let mut endpoint_controllers = Vec::new();
|
||||||
for controller in controllers {
|
for controller in controllers {
|
||||||
match register_controller("dw-acpi", controller) {
|
match bring_up_controller("dw-acpi", controller) {
|
||||||
Ok(controller) => registered.push(controller),
|
Ok((endpoint, guard)) => {
|
||||||
Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"),
|
endpoint_controllers.push(endpoint);
|
||||||
|
guards.push(guard);
|
||||||
|
}
|
||||||
|
Err(err) => log::warn!("dw-acpi-i2cd: controller bring-up skipped: {err:#}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.ready();
|
// Registrations and MMIO mappings must outlive the scheme endpoint.
|
||||||
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
let _guards = guards;
|
||||||
|
dw_i2c::endpoint::serve("dw-acpi-i2c", endpoint_controllers, || {
|
||||||
log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len());
|
daemon.ready();
|
||||||
|
if let Err(err) = libredox::call::setrens(0, 0) {
|
||||||
loop {
|
log::error!("dw-acpi-i2cd: failed to enter null namespace: {err}");
|
||||||
std::thread::park();
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
|
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
|
||||||
@@ -199,7 +202,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
address_length,
|
address_length,
|
||||||
..
|
..
|
||||||
}) if mmio.is_none() => {
|
}) if mmio.is_none() => {
|
||||||
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
|
mmio = Some((*address as usize, (*address_length as usize).max(0x100)));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
|
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
|
||||||
minimum,
|
minimum,
|
||||||
@@ -210,7 +213,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
|
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
|
||||||
mmio = Some((
|
mmio = Some((
|
||||||
*minimum as usize,
|
*minimum as usize,
|
||||||
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
|
span.max((*address_length as usize).max(0x100)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Address32(descriptor)
|
ResourceDescriptor::Address32(descriptor)
|
||||||
@@ -219,7 +222,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
{
|
{
|
||||||
mmio = Some((
|
mmio = Some((
|
||||||
descriptor.minimum as usize,
|
descriptor.minimum as usize,
|
||||||
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
|
(descriptor.address_length as usize).max(0x100),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Address64(descriptor)
|
ResourceDescriptor::Address64(descriptor)
|
||||||
@@ -230,7 +233,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
.context("64-bit MMIO base does not fit in usize")?;
|
.context("64-bit MMIO base does not fit in usize")?;
|
||||||
let len = usize::try_from(descriptor.address_length)
|
let len = usize::try_from(descriptor.address_length)
|
||||||
.context("64-bit MMIO length does not fit in usize")?;
|
.context("64-bit MMIO length does not fit in usize")?;
|
||||||
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
|
mmio = Some((base, len.max(0x100)));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
|
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
|
||||||
irq = interrupts.first().copied().map(u32::from);
|
irq = interrupts.first().copied().map(u32::from);
|
||||||
@@ -256,29 +259,31 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
|
fn bring_up_controller(
|
||||||
|
prefix: &str,
|
||||||
|
controller: ControllerDescriptor,
|
||||||
|
) -> Result<(EndpointController, (PhysBorrowed, File))> {
|
||||||
let ControllerDescriptor {
|
let ControllerDescriptor {
|
||||||
device,
|
device,
|
||||||
hid,
|
hid,
|
||||||
resources,
|
resources,
|
||||||
} = controller;
|
} = controller;
|
||||||
|
|
||||||
let mmio = match PhysBorrowed::map(
|
let mmio = PhysBorrowed::map(
|
||||||
resources.mmio_base,
|
resources.mmio_base,
|
||||||
resources.mmio_len,
|
resources.mmio_len,
|
||||||
Prot::RW,
|
Prot::RW,
|
||||||
MemoryType::Uncacheable,
|
MemoryType::Uncacheable,
|
||||||
) {
|
)
|
||||||
Ok(mapping) => Some(mapping),
|
.with_context(|| {
|
||||||
Err(err) => {
|
format!(
|
||||||
log::warn!(
|
"failed to map MMIO for {device} ({:#x}, len {:#x})",
|
||||||
"dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
|
resources.mmio_base, resources.mmio_len
|
||||||
resources.mmio_base,
|
)
|
||||||
resources.mmio_len,
|
})?;
|
||||||
);
|
|
||||||
None
|
let engine = unsafe { DwI2c::new(mmio.as_ptr() as *mut u8) };
|
||||||
}
|
engine.init(&DW_100MHZ_TIMING);
|
||||||
};
|
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
|
"dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
|
||||||
@@ -286,9 +291,6 @@ fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result
|
|||||||
resources.mmio_len,
|
resources.mmio_len,
|
||||||
resources.irq,
|
resources.irq,
|
||||||
);
|
);
|
||||||
log::debug!(
|
|
||||||
"dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
|
|
||||||
);
|
|
||||||
|
|
||||||
let info = I2cAdapterInfo {
|
let info = I2cAdapterInfo {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -299,6 +301,8 @@ fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|bus| bus.access_mode_10bit)
|
.map(|bus| bus.access_mode_10bit)
|
||||||
.unwrap_or(false),
|
.unwrap_or(false),
|
||||||
|
provider_scheme: "dw-acpi-i2c".to_string(),
|
||||||
|
aliases: vec![device.clone()],
|
||||||
};
|
};
|
||||||
let mut registration = register_adapter(&info)
|
let mut registration = register_adapter(&info)
|
||||||
.with_context(|| format!("failed to register {device} with i2cd"))?;
|
.with_context(|| format!("failed to register {device} with i2cd"))?;
|
||||||
@@ -314,18 +318,36 @@ fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RegisteredController {
|
let endpoint = EndpointController {
|
||||||
_mmio: mmio,
|
name: format!("{prefix}:{device}"),
|
||||||
_registration: registration,
|
aliases: vec![device],
|
||||||
})
|
engine,
|
||||||
|
};
|
||||||
|
Ok((endpoint, (mmio, registration)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
|
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
|
||||||
let mut file = OpenOptions::new()
|
// i2cd is ordered before us via init requires_weak, but scheme
|
||||||
.read(true)
|
// registration is asynchronous — tolerate a short startup skew instead
|
||||||
.write(true)
|
// of permanently losing the controller.
|
||||||
.open("/scheme/i2c/register")
|
let mut file = None;
|
||||||
.context("failed to open /scheme/i2c/register")?;
|
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() })
|
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
|
||||||
.context("failed to encode I2C adapter registration")?;
|
.context("failed to encode I2C adapter registration")?;
|
||||||
file.write_all(payload.as_bytes())
|
file.write_all(payload.as_bytes())
|
||||||
|
|||||||
@@ -61,6 +61,17 @@ pub struct I2cAdapterInfo {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub max_transaction_size: usize,
|
pub max_transaction_size: usize,
|
||||||
pub supports_10bit_addr: bool,
|
pub supports_10bit_addr: bool,
|
||||||
|
/// Scheme name of the provider daemon that executes transfers for this
|
||||||
|
/// adapter (e.g. "i2c-lpss" → `/scheme/i2c-lpss/transfer`). Empty when the
|
||||||
|
/// adapter is registered by a daemon that cannot execute transfers.
|
||||||
|
#[serde(default)]
|
||||||
|
pub provider_scheme: String,
|
||||||
|
/// Alternative names that identify this adapter in transfer requests —
|
||||||
|
/// typically the ACPI path of the controller (e.g. "PC00.I2C0"), which is
|
||||||
|
/// what HID-over-I2C consumers obtain from the device's `_CRS`
|
||||||
|
/// `I2cSerialBus` resource source.
|
||||||
|
#[serde(default)]
|
||||||
|
pub aliases: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|||||||
+112
-50
@@ -23,7 +23,6 @@ enum Handle {
|
|||||||
|
|
||||||
struct AdapterEntry {
|
struct AdapterEntry {
|
||||||
info: I2cAdapterInfo,
|
info: I2cAdapterInfo,
|
||||||
provider_handle: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct I2cDaemon {
|
struct I2cDaemon {
|
||||||
@@ -85,44 +84,77 @@ impl I2cDaemon {
|
|||||||
Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters))
|
Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_transfer_stub(
|
/// Resolve an adapter by request name. Consumers identify adapters by the
|
||||||
handle: &mut Handle,
|
/// ACPI resource-source string from the device's `_CRS` (e.g.
|
||||||
adapter: &I2cAdapterInfo,
|
/// "\_SB_.PC00.I2C0"), so matching is tolerant to normalization forms:
|
||||||
request: &I2cTransferRequest,
|
/// exact name/alias match first, then last-dot-component match.
|
||||||
) -> syscall::Result<()> {
|
fn resolve_adapter(&self, requested: &str) -> Option<&I2cAdapterInfo> {
|
||||||
let write_bytes = request
|
fn last_component(value: &str) -> &str {
|
||||||
.segments
|
value.rsplit('.').next().unwrap_or(value)
|
||||||
.iter()
|
}
|
||||||
.filter_map(|segment| match &segment.op {
|
|
||||||
i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()),
|
let normalized = requested
|
||||||
i2c_interface::I2cTransferOp::Read(_) => None,
|
.trim_start_matches("\\_SB_.")
|
||||||
|
.trim_start_matches('_');
|
||||||
|
|
||||||
|
self.adapters
|
||||||
|
.values()
|
||||||
|
.map(|entry| &entry.info)
|
||||||
|
.find(|info| {
|
||||||
|
info.name == requested
|
||||||
|
|| info
|
||||||
|
.aliases
|
||||||
|
.iter()
|
||||||
|
.any(|alias| alias == requested || alias == &normalized)
|
||||||
|
|| last_component(&info.name) == last_component(normalized)
|
||||||
|
|| info
|
||||||
|
.aliases
|
||||||
|
.iter()
|
||||||
|
.any(|alias| last_component(alias) == last_component(normalized))
|
||||||
})
|
})
|
||||||
.sum::<usize>();
|
}
|
||||||
let read_segments = request
|
|
||||||
.segments
|
|
||||||
.iter()
|
|
||||||
.filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_)))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
log::info!(
|
/// Forward a transfer to the provider daemon that owns the adapter's
|
||||||
"i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)",
|
/// hardware: write the request to `/scheme/<provider>/transfer` and read
|
||||||
adapter.id,
|
/// back the response. The wire format is the bare `I2cTransferRequest` /
|
||||||
adapter.name,
|
/// `I2cTransferResponse` pair that in-tree consumers (i2c-hidd) speak.
|
||||||
request.adapter,
|
fn forward_transfer(request: &I2cTransferRequest, info: &I2cAdapterInfo) -> I2cTransferResponse {
|
||||||
request.segments.len(),
|
let fail = |message: String| I2cTransferResponse {
|
||||||
write_bytes,
|
ok: false,
|
||||||
read_segments,
|
read_data: Vec::new(),
|
||||||
request.stop,
|
error: Some(message),
|
||||||
);
|
};
|
||||||
|
|
||||||
Self::set_pending_response(
|
if info.provider_scheme.is_empty() {
|
||||||
handle,
|
return fail(format!(
|
||||||
I2cControlResponse::TransferResult(I2cTransferResponse {
|
"adapter '{}' has no transfer provider",
|
||||||
ok: false,
|
info.name
|
||||||
read_data: Vec::new(),
|
));
|
||||||
error: Some(String::from("I2C controller transfer path is not implemented yet")),
|
}
|
||||||
}),
|
|
||||||
)
|
let path = format!("/scheme/{}/transfer", info.provider_scheme);
|
||||||
|
let mut file = match std::fs::OpenOptions::new().read(true).write(true).open(&path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(err) => return fail(format!("cannot open {path}: {err}")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = match ron::ser::to_string(request) {
|
||||||
|
Ok(payload) => payload,
|
||||||
|
Err(err) => return fail(format!("cannot encode transfer request: {err}")),
|
||||||
|
};
|
||||||
|
if let Err(err) = std::io::Write::write_all(&mut file, payload.as_bytes()) {
|
||||||
|
return fail(format!("cannot write transfer to {path}: {err}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut text = String::new();
|
||||||
|
if let Err(err) = std::io::Read::read_to_string(&mut file, &mut text) {
|
||||||
|
return fail(format!("cannot read transfer response from {path}: {err}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ron::from_str::<I2cTransferResponse>(&text) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(err) => fail(format!("invalid transfer response from {path}: {err}")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
|
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
|
||||||
@@ -250,6 +282,46 @@ impl SchemeSync for I2cDaemon {
|
|||||||
_fcntl_flags: u32,
|
_fcntl_flags: u32,
|
||||||
_ctx: &CallerCtx,
|
_ctx: &CallerCtx,
|
||||||
) -> syscall::Result<usize> {
|
) -> syscall::Result<usize> {
|
||||||
|
// The transfer endpoint speaks the bare I2cTransferRequest /
|
||||||
|
// I2cTransferResponse wire format (what i2c-hidd and other in-tree
|
||||||
|
// consumers use); every other endpoint speaks I2cControlRequest.
|
||||||
|
if matches!(self.handles.get(id)?, Handle::Transfer { .. }) {
|
||||||
|
let text = std::str::from_utf8(buf).map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
let transfer: I2cTransferRequest =
|
||||||
|
ron::from_str(text).map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
|
||||||
|
let response = match self.resolve_adapter(&transfer.adapter) {
|
||||||
|
Some(info) => {
|
||||||
|
let info = info.clone();
|
||||||
|
log::debug!(
|
||||||
|
"i2cd: routing transfer for '{}' to provider '{}' ({})",
|
||||||
|
transfer.adapter,
|
||||||
|
info.provider_scheme,
|
||||||
|
info.name,
|
||||||
|
);
|
||||||
|
Self::forward_transfer(&transfer, &info)
|
||||||
|
}
|
||||||
|
None => I2cTransferResponse {
|
||||||
|
ok: false,
|
||||||
|
read_data: Vec::new(),
|
||||||
|
error: Some(format!(
|
||||||
|
"no registered adapter matches '{}'",
|
||||||
|
transfer.adapter
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let pending = ron::ser::to_string(&response)
|
||||||
|
.map(|text| text.into_bytes())
|
||||||
|
.map_err(|_| SysError::new(EINVAL))?;
|
||||||
|
let handle = self.handles.get_mut(id)?;
|
||||||
|
let Handle::Transfer { pending: slot } = handle else {
|
||||||
|
return Err(SysError::new(EBADF));
|
||||||
|
};
|
||||||
|
*slot = pending;
|
||||||
|
return Ok(buf.len());
|
||||||
|
}
|
||||||
|
|
||||||
let request = Self::deserialize_request(buf)?;
|
let request = Self::deserialize_request(buf)?;
|
||||||
|
|
||||||
match request {
|
match request {
|
||||||
@@ -262,7 +334,6 @@ impl SchemeSync for I2cDaemon {
|
|||||||
adapter_id,
|
adapter_id,
|
||||||
AdapterEntry {
|
AdapterEntry {
|
||||||
info: info.clone(),
|
info: info.clone(),
|
||||||
provider_handle: id,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -308,21 +379,12 @@ impl SchemeSync for I2cDaemon {
|
|||||||
request,
|
request,
|
||||||
} => {
|
} => {
|
||||||
let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?;
|
let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?;
|
||||||
log::debug!(
|
let info = entry.info.clone();
|
||||||
"i2cd: transfer requested for adapter {} via provider fd {}",
|
let response = Self::forward_transfer(&request, &info);
|
||||||
adapter_id,
|
|
||||||
entry.provider_handle,
|
|
||||||
);
|
|
||||||
|
|
||||||
let adapter_info = entry.info.clone();
|
|
||||||
let handle = self.handles.get_mut(id)?;
|
let handle = self.handles.get_mut(id)?;
|
||||||
match handle {
|
Self::set_pending_response(handle, I2cControlResponse::TransferResult(response))?;
|
||||||
Handle::Transfer { .. } => {
|
Ok(buf.len())
|
||||||
Self::queue_transfer_stub(handle, &adapter_info, &request)?;
|
|
||||||
Ok(buf.len())
|
|
||||||
}
|
|
||||||
_ => Err(SysError::new(EINVAL)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ edition = "2021"
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
redox_syscall = { workspace = true, features = ["std"] }
|
redox_syscall = { workspace = true, features = ["std"] }
|
||||||
|
redox-scheme.workspace = true
|
||||||
libredox.workspace = true
|
libredox.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
ron.workspace = true
|
ron.workspace = true
|
||||||
@@ -15,7 +16,9 @@ ron.workspace = true
|
|||||||
acpi-resource = { path = "../../acpi-resource" }
|
acpi-resource = { path = "../../acpi-resource" }
|
||||||
common = { path = "../../common" }
|
common = { path = "../../common" }
|
||||||
daemon = { path = "../../../daemon" }
|
daemon = { path = "../../../daemon" }
|
||||||
|
dw-i2c = { path = "../designware" }
|
||||||
i2c-interface = { path = "../i2c-interface" }
|
i2c-interface = { path = "../i2c-interface" }
|
||||||
|
scheme-utils = { path = "../../../scheme-utils" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
//! 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::collections::BTreeMap;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File, OpenOptions};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
@@ -13,18 +24,32 @@ use common::{MemoryType, PhysBorrowed, Prot};
|
|||||||
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
|
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
const SUPPORTED_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"];
|
use dw_i2c::endpoint::EndpointController;
|
||||||
|
use dw_i2c::{DwI2c, BXT_I2C_TIMING};
|
||||||
|
|
||||||
const DW_IC_CON: usize = 0x00;
|
const SUPPORTED_ACPI_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"];
|
||||||
const DW_IC_TAR: usize = 0x04;
|
|
||||||
const DW_IC_SS_SCL_HCNT: usize = 0x14;
|
/// Intel LPSS I2C PCI IDs, ported from Linux 7.1
|
||||||
const DW_IC_SS_SCL_LCNT: usize = 0x18;
|
/// `drivers/mfd/intel-lpss-pci.c` (entries carrying `*_i2c_info`).
|
||||||
const DW_IC_DATA_CMD: usize = 0x10;
|
/// All of these use the Broxton-derived timing (`bxt_i2c_info`, 133 MHz).
|
||||||
const DW_IC_INTR_MASK: usize = 0x30;
|
const LPSS_I2C_PCI_IDS: &[u16] = &[
|
||||||
const DW_IC_CLR_INTR: usize = 0x40;
|
// ARL-H (0x7750/0x7751, 0x7778-0x777b)
|
||||||
const DW_IC_ENABLE: usize = 0x6C;
|
0x7750, 0x7751, 0x7778, 0x7779, 0x777a, 0x777b,
|
||||||
const DW_IC_STATUS: usize = 0x70;
|
// MTL-P (0x7e50/0x7e51, 0x7e78-0x7e7b)
|
||||||
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
|
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;
|
||||||
|
|
||||||
|
struct Controller {
|
||||||
|
name: String,
|
||||||
|
aliases: Vec<String>,
|
||||||
|
engine: DwI2c,
|
||||||
|
registration: File,
|
||||||
|
mmio: PhysBorrowed,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct AmlSymbol {
|
struct AmlSymbol {
|
||||||
@@ -47,17 +72,12 @@ struct ControllerResources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ControllerDescriptor {
|
struct DiscoveredController {
|
||||||
device: String,
|
device: String,
|
||||||
hid: String,
|
hid: String,
|
||||||
resources: ControllerResources,
|
resources: ControllerResources,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RegisteredController {
|
|
||||||
_mmio: Option<PhysBorrowed>,
|
|
||||||
_registration: File,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
common::setup_logging(
|
common::setup_logging(
|
||||||
"bus",
|
"bus",
|
||||||
@@ -82,31 +102,256 @@ fn daemon_runner(daemon: daemon::Daemon) -> ! {
|
|||||||
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
|
||||||
common::init();
|
common::init();
|
||||||
|
|
||||||
let controllers = discover_controllers(SUPPORTED_IDS)
|
let mut controllers = Vec::new();
|
||||||
.context("failed to discover Intel LPSS ACPI I2C controllers")?;
|
|
||||||
if controllers.is_empty() {
|
|
||||||
log::info!("intel-lpss-i2cd: no supported ACPI controllers found");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut registered = Vec::new();
|
for discovered in discover_pci_controllers() {
|
||||||
for controller in controllers {
|
match bring_up_controller(discovered) {
|
||||||
match register_controller("intel-lpss", controller) {
|
Ok(controller) => controllers.push(controller),
|
||||||
Ok(controller) => registered.push(controller),
|
Err(err) => log::warn!("intel-lpss-i2cd: PCI controller bring-up skipped: {err:#}"),
|
||||||
Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.ready();
|
for discovered in discover_acpi_controllers(SUPPORTED_ACPI_IDS)
|
||||||
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
.context("failed to discover Intel LPSS ACPI I2C controllers")?
|
||||||
|
{
|
||||||
log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len());
|
match bring_up_controller(discovered) {
|
||||||
|
Ok(controller) => controllers.push(controller),
|
||||||
loop {
|
Err(err) => log::warn!("intel-lpss-i2cd: ACPI controller bring-up skipped: {err:#}"),
|
||||||
std::thread::park();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
registration,
|
||||||
|
mmio,
|
||||||
|
} = controller;
|
||||||
|
guards.push((registration, mmio));
|
||||||
|
EndpointController {
|
||||||
|
name,
|
||||||
|
aliases,
|
||||||
|
engine,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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.
|
||||||
|
dw_i2c::endpoint::serve("i2c-lpss", endpoint_controllers, || {
|
||||||
|
daemon.ready();
|
||||||
|
if let Err(err) = libredox::call::setrens(0, 0) {
|
||||||
|
log::error!("intel-lpss-i2cd: failed to enter null namespace: {err}");
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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 info = I2cAdapterInfo {
|
||||||
|
id: 0,
|
||||||
|
name: name.clone(),
|
||||||
|
max_transaction_size: 0,
|
||||||
|
supports_10bit_addr: discovered
|
||||||
|
.resources
|
||||||
|
.serial_bus
|
||||||
|
.as_ref()
|
||||||
|
.map(|bus| bus.access_mode_10bit)
|
||||||
|
.unwrap_or(true),
|
||||||
|
provider_scheme: "i2c-lpss".to_string(),
|
||||||
|
aliases: aliases.clone(),
|
||||||
|
};
|
||||||
|
let mut registration = register_adapter(&info)
|
||||||
|
.with_context(|| format!("failed to register {name} with i2cd"))?;
|
||||||
|
let response = read_registration_response(&mut registration)
|
||||||
|
.with_context(|| format!("failed to read i2cd registration response for {name}"))?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
I2cControlResponse::AdapterRegistered { id } => {
|
||||||
|
log::info!("intel-lpss-i2cd: adapter {name} registered with i2cd as {id}");
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
log::warn!("intel-lpss-i2cd: unexpected i2cd registration response for {name}: {other:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Controller {
|
||||||
|
name,
|
||||||
|
aliases,
|
||||||
|
engine,
|
||||||
|
registration,
|
||||||
|
mmio,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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<DiscoveredController> {
|
||||||
|
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 Some(mmio_base) = pci_bar0_address(&config) else {
|
||||||
|
log::warn!("intel-lpss-i2cd: {device:04x} has no usable BAR0, skipped");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let location = entry
|
||||||
|
.file_name()
|
||||||
|
.to_str()
|
||||||
|
.map(str::to_owned)
|
||||||
|
.unwrap_or_else(|| format!("pci-{device:04x}"));
|
||||||
|
let irq = config[0x3c];
|
||||||
|
let irq = (irq != 0 && irq != 0xff).then_some(u32::from(irq));
|
||||||
|
|
||||||
|
out.push(DiscoveredController {
|
||||||
|
device: location,
|
||||||
|
hid: format!("pci:8086:{device:04x}"),
|
||||||
|
resources: ControllerResources {
|
||||||
|
mmio_base,
|
||||||
|
mmio_len: 0x1000,
|
||||||
|
irq,
|
||||||
|
serial_bus: None,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a usable memory BAR0 from raw PCI config space. Handles both
|
||||||
|
/// 32-bit and 64-bit memory BARs; returns the CPU-physical base address.
|
||||||
|
fn pci_bar0_address(config: &[u8]) -> Option<usize> {
|
||||||
|
let bar_lo = u32::from_le_bytes([config[0x10], config[0x11], config[0x12], config[0x13]]);
|
||||||
|
if bar_lo == 0 || bar_lo & 1 != 0 {
|
||||||
|
// Not present or an I/O-space BAR; LPSS I2C is always memory-mapped.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let is_64bit = (bar_lo >> 1) & 0x3 == 0x2;
|
||||||
|
let base_lo = u64::from(bar_lo & !0xf);
|
||||||
|
let base = if is_64bit {
|
||||||
|
let bar_hi = u32::from_le_bytes([config[0x14], config[0x15], config[0x16], config[0x17]]);
|
||||||
|
base_lo | (u64::from(bar_hi) << 32)
|
||||||
|
} else {
|
||||||
|
base_lo
|
||||||
|
};
|
||||||
|
(base != 0).then_some(base as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ACPI discovery (legacy LPSS HID model)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fn discover_acpi_controllers(supported_ids: &[&str]) -> Result<Vec<DiscoveredController>> {
|
||||||
let mut matched = BTreeMap::new();
|
let mut matched = BTreeMap::new();
|
||||||
|
|
||||||
let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||||
@@ -149,7 +394,7 @@ fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescript
|
|||||||
for (device, hid) in matched {
|
for (device, hid) in matched {
|
||||||
let resources = read_controller_resources(&device)
|
let resources = read_controller_resources(&device)
|
||||||
.with_context(|| format!("failed to read resources for {device}"))?;
|
.with_context(|| format!("failed to read resources for {device}"))?;
|
||||||
controllers.push(ControllerDescriptor {
|
controllers.push(DiscoveredController {
|
||||||
device,
|
device,
|
||||||
hid,
|
hid,
|
||||||
resources,
|
resources,
|
||||||
@@ -199,7 +444,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
address_length,
|
address_length,
|
||||||
..
|
..
|
||||||
}) if mmio.is_none() => {
|
}) if mmio.is_none() => {
|
||||||
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
|
mmio = Some((*address as usize, (*address_length as usize).max(0x100)));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
|
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
|
||||||
minimum,
|
minimum,
|
||||||
@@ -208,10 +453,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
..
|
..
|
||||||
}) if mmio.is_none() && maximum >= minimum => {
|
}) if mmio.is_none() && maximum >= minimum => {
|
||||||
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
|
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
|
||||||
mmio = Some((
|
mmio = Some((*minimum as usize, span.max((*address_length as usize).max(0x100))));
|
||||||
*minimum as usize,
|
|
||||||
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Address32(descriptor)
|
ResourceDescriptor::Address32(descriptor)
|
||||||
if mmio.is_none()
|
if mmio.is_none()
|
||||||
@@ -219,7 +461,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
{
|
{
|
||||||
mmio = Some((
|
mmio = Some((
|
||||||
descriptor.minimum as usize,
|
descriptor.minimum as usize,
|
||||||
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
|
(descriptor.address_length as usize).max(0x100),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Address64(descriptor)
|
ResourceDescriptor::Address64(descriptor)
|
||||||
@@ -230,7 +472,7 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
.context("64-bit MMIO base does not fit in usize")?;
|
.context("64-bit MMIO base does not fit in usize")?;
|
||||||
let len = usize::try_from(descriptor.address_length)
|
let len = usize::try_from(descriptor.address_length)
|
||||||
.context("64-bit MMIO length does not fit in usize")?;
|
.context("64-bit MMIO length does not fit in usize")?;
|
||||||
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
|
mmio = Some((base, len.max(0x100)));
|
||||||
}
|
}
|
||||||
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
|
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
|
||||||
irq = interrupts.first().copied().map(u32::from);
|
irq = interrupts.first().copied().map(u32::from);
|
||||||
@@ -256,91 +498,34 @@ fn read_controller_resources(device: &str) -> Result<ControllerResources> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
|
/// Find the ACPI device path whose `_CRS` FixedMemory32 window matches this
|
||||||
let ControllerDescriptor {
|
/// controller's MMIO base. That path ("PC00.I2C0") is what HID-over-I2C
|
||||||
device,
|
/// devices name in their `I2cSerialBus` resource source, so it is the alias
|
||||||
hid,
|
/// consumers will use to address this adapter.
|
||||||
resources,
|
fn discover_acpi_aliases(mmio_base: usize) -> Vec<String> {
|
||||||
} = controller;
|
let Ok(entries) = fs::read_dir("/scheme/acpi/resources") else {
|
||||||
|
return Vec::new();
|
||||||
let mmio = match PhysBorrowed::map(
|
|
||||||
resources.mmio_base,
|
|
||||||
resources.mmio_len,
|
|
||||||
Prot::RW,
|
|
||||||
MemoryType::Uncacheable,
|
|
||||||
) {
|
|
||||||
Ok(mapping) => Some(mapping),
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!(
|
|
||||||
"intel-lpss-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
|
|
||||||
resources.mmio_base,
|
|
||||||
resources.mmio_len,
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
let mut aliases = Vec::new();
|
||||||
"intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
|
for entry in entries.flatten() {
|
||||||
resources.mmio_base,
|
let Ok(contents) = fs::read_to_string(entry.path()) else {
|
||||||
resources.mmio_len,
|
continue;
|
||||||
resources.irq,
|
};
|
||||||
);
|
let Ok(resources) = ron::from_str::<Vec<ResourceDescriptor>>(&contents) else {
|
||||||
log::debug!(
|
continue;
|
||||||
"intel-lpss-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
|
};
|
||||||
);
|
let matches_base = resources.iter().any(|resource| match resource {
|
||||||
|
ResourceDescriptor::FixedMemory32(descriptor) => descriptor.address as usize == mmio_base,
|
||||||
let info = I2cAdapterInfo {
|
_ => false,
|
||||||
id: 0,
|
});
|
||||||
name: format!("{prefix}:{device}"),
|
if matches_base {
|
||||||
max_transaction_size: 0,
|
if let Some(name) = entry.file_name().to_str() {
|
||||||
supports_10bit_addr: resources
|
aliases.push(name.to_owned());
|
||||||
.serial_bus
|
}
|
||||||
.as_ref()
|
|
||||||
.map(|bus| bus.access_mode_10bit)
|
|
||||||
.unwrap_or(false),
|
|
||||||
};
|
|
||||||
let mut registration = register_adapter(&info)
|
|
||||||
.with_context(|| format!("failed to register {device} with i2cd"))?;
|
|
||||||
let response = read_registration_response(&mut registration)
|
|
||||||
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
|
|
||||||
|
|
||||||
match response {
|
|
||||||
I2cControlResponse::AdapterRegistered { id } => {
|
|
||||||
log::info!("intel-lpss-i2cd: adapter {device} registered with i2cd as {id}");
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
aliases
|
||||||
Ok(RegisteredController {
|
|
||||||
_mmio: mmio,
|
|
||||||
_registration: registration,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
|
|
||||||
let mut file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.open("/scheme/i2c/register")
|
|
||||||
.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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eisa_id_from_integer(integer: u64) -> String {
|
fn eisa_id_from_integer(integer: u64) -> String {
|
||||||
@@ -359,3 +544,32 @@ fn eisa_id_from_integer(integer: u64) -> String {
|
|||||||
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
|
"{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 bar0_32bit_memory() {
|
||||||
|
let mut config = [0u8; 0x40];
|
||||||
|
config[0x10..0x14].copy_from_slice(&0x5019_2de0u32.to_le_bytes());
|
||||||
|
assert_eq!(pci_bar0_address(&config), Some(0x5019_2de0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bar0_64bit_memory() {
|
||||||
|
let mut config = [0u8; 0x40];
|
||||||
|
// 64-bit BAR: flags bits [2:1] == 0b10, base 0x5019_92de_0000.
|
||||||
|
config[0x10..0x14].copy_from_slice(&0x92de_0004u32.to_le_bytes());
|
||||||
|
config[0x14..0x18].copy_from_slice(&0x5019u32.to_le_bytes());
|
||||||
|
assert_eq!(pci_bar0_address(&config), Some(0x5019_92de_0000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bar0_io_space_rejected() {
|
||||||
|
let mut config = [0u8; 0x40];
|
||||||
|
config[0x10..0x14].copy_from_slice(&0xefa1u32.to_le_bytes());
|
||||||
|
assert_eq!(pci_bar0_address(&config), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user