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
937 lines
33 KiB
Rust
937 lines
33 KiB
Rust
//! 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
|
||
//! deadlines instead of interrupt-driven completion, the same approach
|
||
//! used by the U-Boot DesignWare driver. Every transaction follows the
|
||
//! Linux `i2c_dw_xfer_init` ordering: wait for bus idle, disable the
|
||
//! adapter, program IC_CON/IC_TAR, re-enable, then transfer — the
|
||
//! DesignWare databook forbids IC_CON/IC_TAR writes while enabled.
|
||
|
||
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_ENABLE_STATUS: usize = 0x9c;
|
||
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 / IC_ENABLE bits.
|
||
const IC_RAW_TX_ABRT: u32 = 1 << 6;
|
||
const IC_ENABLE_ENABLE: u32 = 1 << 0;
|
||
const IC_ENABLE_ABORT: u32 = 1 << 1;
|
||
|
||
// 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);
|
||
/// Enable/disable acknowledgement budget. Linux uses up to 100 rounds of a
|
||
/// 25 µs signaling-period wait (~2.5 ms at 400 kHz); 10 ms is safer here.
|
||
const ENABLE_STATUS_TIMEOUT: Duration = Duration::from_millis(10);
|
||
/// ABORT bit self-clearing budget (Linux: 10 × 1 ms).
|
||
const ABORT_TIMEOUT: Duration = Duration::from_millis(100);
|
||
|
||
/// Hard limits for incoming requests — the bus is slow and small by design,
|
||
/// and unbounded requests would OOM or monopolize the daemon.
|
||
pub const MAX_SEGMENTS: usize = 16;
|
||
pub const MAX_SEGMENT_BYTES: usize = 4096;
|
||
pub const MAX_TOTAL_BYTES: usize = 16384;
|
||
|
||
#[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 deadline expired waiting for FIFO space, data, bus idle,
|
||
/// or an enable/abort acknowledgement.
|
||
Timeout,
|
||
/// Controller reported MASTER_DIS abort (master mode disabled).
|
||
MasterDisabled,
|
||
/// Request violated engine limits or used an unsupported mode.
|
||
Invalid(&'static str),
|
||
}
|
||
|
||
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"),
|
||
DwError::Invalid(reason) => write!(f, "invalid request: {reason}"),
|
||
}
|
||
}
|
||
}
|
||
|
||
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,
|
||
}
|
||
|
||
/// 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 {
|
||
fn scl_counts(&self, t_symbol_ns: u64, t_low_ns: u64) -> SclTiming {
|
||
let ic_clk_khz = self.ic_clk_hz as u64 / 1000;
|
||
// i2c_dw_scl_hcnt: IC_CLK * (tHD;STA + sda_fall) rounded, minus 3.
|
||
// i2c_dw_scl_lcnt: IC_CLK * (tLOW + scl_fall) rounded, minus 1.
|
||
let hcnt = (ic_clk_khz * (t_symbol_ns + self.sda_fall_ns as u64) + 500_000)
|
||
/ 1_000_000
|
||
- 3;
|
||
let lcnt = (ic_clk_khz * (t_low_ns + self.scl_fall_ns as u64) + 500_000)
|
||
/ 1_000_000
|
||
- 1;
|
||
// IC_SDA_HOLD TX value is in IC_CLK cycles (datasheet §5.11).
|
||
let sda_hold_tx = (ic_clk_khz * self.sda_hold_ns as u64).div_ceil(1_000_000) as u32;
|
||
SclTiming {
|
||
hcnt: hcnt as u32,
|
||
lcnt: lcnt as u32,
|
||
sda_hold_tx,
|
||
}
|
||
}
|
||
|
||
/// Fast-mode (400 kHz) counts: tHD;STA = 0.6 µs, tLOW = 1.3 µs.
|
||
pub fn fast_mode(&self) -> SclTiming {
|
||
self.scl_counts(600, 1300)
|
||
}
|
||
|
||
/// Standard-mode (100 kHz) counts: tHD;STA = 4.0 µs, tLOW = 4.7 µs.
|
||
pub fn standard_mode(&self) -> SclTiming {
|
||
self.scl_counts(4000, 4700)
|
||
}
|
||
}
|
||
|
||
/// 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),
|
||
}
|
||
|
||
/// Validated, hardware-independent view of one transfer request.
|
||
pub struct ValidatedRequest {
|
||
pub address: u16,
|
||
pub ten_bit: bool,
|
||
pub segments: Vec<Segment>,
|
||
}
|
||
|
||
/// Validate request shape and engine limits before any MMIO happens.
|
||
pub fn validate_request(
|
||
address: u16,
|
||
ten_bit: bool,
|
||
segments: &[Segment],
|
||
supports_10bit: bool,
|
||
) -> Result<ValidatedRequest, DwError> {
|
||
if segments.is_empty() {
|
||
return Err(DwError::Invalid("no segments"));
|
||
}
|
||
if segments.len() > MAX_SEGMENTS {
|
||
return Err(DwError::Invalid("too many segments"));
|
||
}
|
||
if ten_bit {
|
||
if !supports_10bit {
|
||
return Err(DwError::Invalid("10-bit addressing not supported by adapter"));
|
||
}
|
||
if address > 0x3ff {
|
||
return Err(DwError::Invalid("10-bit address out of range"));
|
||
}
|
||
} else if address > 0x7f {
|
||
return Err(DwError::Invalid("7-bit address out of range"));
|
||
}
|
||
|
||
let mut total = 0usize;
|
||
for segment in segments {
|
||
let len = match segment {
|
||
Segment::Write(data) => data.len(),
|
||
Segment::Read(len) => *len,
|
||
};
|
||
if len == 0 {
|
||
return Err(DwError::Invalid("zero-length segment"));
|
||
}
|
||
if len > MAX_SEGMENT_BYTES {
|
||
return Err(DwError::Invalid("segment too large"));
|
||
}
|
||
total += len;
|
||
if total > MAX_TOTAL_BYTES {
|
||
return Err(DwError::Invalid("transfer too large"));
|
||
}
|
||
}
|
||
|
||
Ok(ValidatedRequest {
|
||
address,
|
||
ten_bit,
|
||
segments: segments.to_vec(),
|
||
})
|
||
}
|
||
|
||
/// 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 }
|
||
}
|
||
|
||
/// Base of the mapped register block. Callers use it to reach
|
||
/// vendor-specific registers outside the DesignWare window (e.g. the
|
||
/// Intel LPSS private block at +0x200) after validating the real
|
||
/// mapping length themselves.
|
||
pub fn mmio_base(&self) -> *mut u8 {
|
||
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 sleep_poll() {
|
||
// Linux polls enable/abort status with 25 µs signaling-period waits;
|
||
// short sleeps keep the daemon from burning a timeslice per byte.
|
||
std::thread::sleep(Duration::from_micros(25));
|
||
}
|
||
|
||
/// Write ENABLE=0 and wait for IC_ENABLE_STATUS to deassert
|
||
/// (`__i2c_dw_disable` minus the abort branch, which `recover()` owns).
|
||
fn disable(&self) -> Result<(), DwError> {
|
||
let start = Instant::now();
|
||
loop {
|
||
self.write32(DW_IC_ENABLE, 0);
|
||
if self.read32(DW_IC_ENABLE_STATUS) & 1 == 0 {
|
||
return Ok(());
|
||
}
|
||
if start.elapsed() > ENABLE_STATUS_TIMEOUT {
|
||
return Err(DwError::Timeout);
|
||
}
|
||
Self::sleep_poll();
|
||
}
|
||
}
|
||
|
||
fn enable(&self) -> Result<(), DwError> {
|
||
let start = Instant::now();
|
||
self.write32(DW_IC_ENABLE, IC_ENABLE_ENABLE);
|
||
loop {
|
||
if self.read32(DW_IC_ENABLE_STATUS) & 1 != 0 {
|
||
return Ok(());
|
||
}
|
||
if start.elapsed() > ENABLE_STATUS_TIMEOUT {
|
||
return Err(DwError::Timeout);
|
||
}
|
||
Self::sleep_poll();
|
||
}
|
||
}
|
||
|
||
/// Recover the controller after an abort or timeout: set the ABORT bit
|
||
/// and wait for it to self-clear (Linux `__i2c_dw_disable` abort branch),
|
||
/// then disable and flush stale state. Best-effort by design — the next
|
||
/// transfer re-initializes target state anyway.
|
||
fn recover(&self) {
|
||
let enabled = self.read32(DW_IC_ENABLE) & IC_ENABLE_ENABLE != 0;
|
||
if !enabled {
|
||
self.write32(DW_IC_ENABLE, IC_ENABLE_ENABLE);
|
||
std::thread::sleep(Duration::from_micros(25));
|
||
}
|
||
self.write32(DW_IC_ENABLE, IC_ENABLE_ENABLE | IC_ENABLE_ABORT);
|
||
|
||
let start = Instant::now();
|
||
while self.read32(DW_IC_ENABLE) & IC_ENABLE_ABORT != 0 {
|
||
if start.elapsed() > ABORT_TIMEOUT {
|
||
break;
|
||
}
|
||
Self::sleep_poll();
|
||
}
|
||
|
||
let _ = self.disable();
|
||
let _ = self.read32(DW_IC_CLR_INTR);
|
||
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||
}
|
||
|
||
/// 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();
|
||
|
||
let _ = self.disable();
|
||
|
||
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);
|
||
}
|
||
|
||
self.write32(DW_IC_INTR_MASK, 0);
|
||
let _ = self.read32(DW_IC_CLR_INTR);
|
||
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||
|
||
let _ = self.enable();
|
||
}
|
||
|
||
/// Poll until `condition(IC_STATUS)` holds. An abort is checked FIRST on
|
||
/// every iteration — a NACKed write drains the FIFO and drops activity,
|
||
/// which would otherwise satisfy completion predicates and report
|
||
/// success for a failed transaction.
|
||
fn wait_for(
|
||
&self,
|
||
mut condition: impl FnMut(u32) -> bool,
|
||
timeout: Duration,
|
||
) -> Result<(), DwError> {
|
||
let start = Instant::now();
|
||
loop {
|
||
if self.read32(DW_IC_RAW_INTR_STAT) & IC_RAW_TX_ABRT != 0 {
|
||
return Err(self.decode_abort());
|
||
}
|
||
let status = self.read32(DW_IC_STATUS);
|
||
if condition(status) {
|
||
return Ok(());
|
||
}
|
||
if start.elapsed() > timeout {
|
||
return Err(DwError::Timeout);
|
||
}
|
||
Self::sleep_poll();
|
||
}
|
||
}
|
||
|
||
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)
|
||
}
|
||
}
|
||
|
||
/// Linux `i2c_dw_xfer_init`: disable the adapter, program addressing
|
||
/// mode and target address, re-enable, then flush stale interrupt state.
|
||
fn xfer_init(&self, address: u16, ten_bit: bool) -> Result<(), DwError> {
|
||
// i2c_dw_wait_bus_not_busy before reprogramming.
|
||
self.wait_for(
|
||
|status| status & (IC_STATUS_ACTIVITY | IC_STATUS_MASTER_ACTIVITY) == 0,
|
||
COMPLETION_TIMEOUT,
|
||
)?;
|
||
|
||
self.disable()?;
|
||
|
||
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);
|
||
|
||
self.write32(DW_IC_INTR_MASK, 0);
|
||
self.enable()?;
|
||
let _ = self.read32(DW_IC_ENABLE_STATUS);
|
||
let _ = self.read32(DW_IC_CLR_INTR);
|
||
let _ = self.read32(DW_IC_CLR_TX_ABRT);
|
||
Ok(())
|
||
}
|
||
|
||
/// Execute a validated sequence of write/read segments against one slave
|
||
/// address, issuing a repeated START between segments and a STOP after
|
||
/// the last byte.
|
||
///
|
||
/// `stop == false` (bus held after the last segment, combined-format
|
||
/// continuation across calls) is rejected honestly: the completion wait
|
||
/// requires bus idle, so an unterminated transaction can only time out.
|
||
pub fn xfer(&self, request: &ValidatedRequest, stop: bool) -> Result<Vec<Vec<u8>>, DwError> {
|
||
if !stop {
|
||
return Err(DwError::Invalid("unterminated transfers (stop=false) are not supported"));
|
||
}
|
||
|
||
let mut reads: Vec<Vec<u8>> = Vec::new();
|
||
let result = self.xfer_inner(request, &mut reads);
|
||
if result.is_err() {
|
||
self.recover();
|
||
}
|
||
result.map(|_| reads)
|
||
}
|
||
|
||
fn xfer_inner(
|
||
&self,
|
||
request: &ValidatedRequest,
|
||
reads: &mut Vec<Vec<u8>>,
|
||
) -> Result<(), DwError> {
|
||
self.xfer_init(request.address, request.ten_bit)?;
|
||
|
||
let last = request.segments.len() - 1;
|
||
for (index, segment) in request.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 index > 0 && byte_index == 0 {
|
||
cmd |= IC_DATA_CMD_RESTART;
|
||
}
|
||
if is_last_segment && byte_index == data.len() - 1 {
|
||
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 index > 0 && byte_index == 0 {
|
||
cmd |= IC_DATA_CMD_RESTART;
|
||
}
|
||
if is_last_segment && byte_index == len - 1 {
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
// i2c_dw_wait_bus_not_busy: FIFO drained and master logic idle.
|
||
self.wait_for(
|
||
|status| {
|
||
status & IC_STATUS_TFE != 0
|
||
&& status & (IC_STATUS_ACTIVITY | IC_STATUS_MASTER_ACTIVITY) == 0
|
||
},
|
||
COMPLETION_TIMEOUT,
|
||
)
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn bxt_fast_mode_matches_linux_formula() {
|
||
// i2c-designware-master.c i2c_dw_init_master: hcnt uses sda_fall,
|
||
// lcnt uses scl_fall, division rounds to nearest:
|
||
// hcnt = round(133000*(600+171)/1e6) - 3 = 103 - 3 = 100
|
||
// lcnt = round(133000*(1300+208)/1e6) - 1 = 201 - 1 = 200
|
||
let timing = BXT_I2C_TIMING.fast_mode();
|
||
assert_eq!(timing.hcnt, 100);
|
||
assert_eq!(timing.lcnt, 200);
|
||
assert_eq!(timing.sda_hold_tx, 6);
|
||
}
|
||
|
||
#[test]
|
||
fn bxt_standard_mode_matches_linux_formula() {
|
||
// hcnt = round(133000*(4000+171)/1e6) - 3 = 555 - 3 = 552
|
||
// lcnt = round(133000*(4700+208)/1e6) - 1 = 653 - 1 = 652
|
||
let timing = BXT_I2C_TIMING.standard_mode();
|
||
assert_eq!(timing.hcnt, 552);
|
||
assert_eq!(timing.lcnt, 652);
|
||
assert_eq!(timing.sda_hold_tx, 6);
|
||
}
|
||
|
||
fn valid_segments() -> Vec<Segment> {
|
||
vec![Segment::Write(vec![0x00, 0x01]), Segment::Read(4)]
|
||
}
|
||
|
||
#[test]
|
||
fn validation_accepts_legitimate_request() {
|
||
let request = validate_request(0x2c, false, &valid_segments(), false).unwrap();
|
||
assert_eq!(request.address, 0x2c);
|
||
assert!(!request.ten_bit);
|
||
}
|
||
|
||
#[test]
|
||
fn validation_rejects_bad_requests() {
|
||
assert!(validate_request(0x2c, false, &[], false).is_err());
|
||
assert!(validate_request(0x80, false, &valid_segments(), false).is_err());
|
||
assert!(validate_request(0x2c, false, &valid_segments(), true).is_ok());
|
||
assert!(validate_request(0x400, true, &valid_segments(), true).is_err());
|
||
assert!(validate_request(0x2c, false, &[Segment::Read(0)], false).is_err());
|
||
assert!(validate_request(0x2c, false, &[Segment::Read(MAX_SEGMENT_BYTES + 1)], false).is_err());
|
||
let oversized = vec![Segment::Read(MAX_TOTAL_BYTES / 2 + 1); 3];
|
||
assert!(validate_request(0x2c, false, &oversized, false).is_err());
|
||
}
|
||
}
|
||
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, I2cTransferStatus};
|
||
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::{validate_request, DwError, 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,
|
||
pub supports_10bit_addr: bool,
|
||
}
|
||
|
||
enum Handle {
|
||
SchemeRoot,
|
||
Transfer { pending: Vec<u8> },
|
||
}
|
||
|
||
struct Endpoint {
|
||
handles: HandleMap<Handle>,
|
||
controllers: Vec<EndpointController>,
|
||
}
|
||
|
||
/// Why a request could not be routed to exactly one controller.
|
||
pub enum ResolveError {
|
||
NoMatch,
|
||
Ambiguous(Vec<String>),
|
||
}
|
||
|
||
impl Endpoint {
|
||
fn failure(status: I2cTransferStatus, message: String) -> I2cTransferResponse {
|
||
I2cTransferResponse {
|
||
ok: false,
|
||
read_data: Vec::new(),
|
||
error: Some(message),
|
||
status,
|
||
}
|
||
}
|
||
|
||
/// Resolve the requested adapter to exactly one controller. Exact
|
||
/// name/alias matches win globally; a short last-component form
|
||
/// (e.g. "I2C0") is accepted only when it identifies one controller
|
||
/// unambiguously.
|
||
fn resolve(&self, requested: &str) -> Result<&EndpointController, ResolveError> {
|
||
fn last_component(value: &str) -> &str {
|
||
value.rsplit('.').next().unwrap_or(value)
|
||
}
|
||
|
||
let normalized = requested
|
||
.trim_start_matches("\\_SB_.")
|
||
.trim_start_matches('_');
|
||
|
||
if let Some(exact) = self.controllers.iter().find(|controller| {
|
||
controller.name == requested
|
||
|| controller
|
||
.aliases
|
||
.iter()
|
||
.any(|alias| alias == requested || alias == &normalized)
|
||
}) {
|
||
return Ok(exact);
|
||
}
|
||
|
||
let fuzzy: Vec<&EndpointController> = self
|
||
.controllers
|
||
.iter()
|
||
.filter(|controller| {
|
||
last_component(&controller.name) == last_component(normalized)
|
||
|| controller
|
||
.aliases
|
||
.iter()
|
||
.any(|alias| last_component(alias) == last_component(normalized))
|
||
})
|
||
.collect();
|
||
|
||
match fuzzy.as_slice() {
|
||
[only] => Ok(*only),
|
||
[] => Err(ResolveError::NoMatch),
|
||
many => Err(ResolveError::Ambiguous(
|
||
many.iter().map(|controller| controller.name.clone()).collect(),
|
||
)),
|
||
}
|
||
}
|
||
|
||
fn execute(&self, request: &I2cTransferRequest) -> I2cTransferResponse {
|
||
if request.segments.is_empty() {
|
||
return I2cTransferResponse {
|
||
ok: true,
|
||
read_data: Vec::new(),
|
||
error: None,
|
||
status: I2cTransferStatus::Ok,
|
||
};
|
||
}
|
||
|
||
let controller = match self.resolve(&request.adapter) {
|
||
Ok(controller) => controller,
|
||
Err(ResolveError::NoMatch) => {
|
||
return Self::failure(
|
||
I2cTransferStatus::Error,
|
||
format!("no controller matches adapter '{}'", request.adapter),
|
||
);
|
||
}
|
||
Err(ResolveError::Ambiguous(names)) => {
|
||
return Self::failure(
|
||
I2cTransferStatus::Error,
|
||
format!(
|
||
"adapter '{}' is ambiguous between controllers: {}",
|
||
request.adapter,
|
||
names.join(", ")
|
||
),
|
||
);
|
||
}
|
||
};
|
||
|
||
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 Self::failure(
|
||
I2cTransferStatus::Error,
|
||
"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();
|
||
|
||
let validated = match validate_request(address, ten_bit, &ops, controller.supports_10bit_addr) {
|
||
Ok(validated) => validated,
|
||
Err(err) => {
|
||
return Self::failure(I2cTransferStatus::Error, format!("{err}"));
|
||
}
|
||
};
|
||
|
||
match controller.engine.xfer(&validated, request.stop) {
|
||
Ok(reads) => I2cTransferResponse {
|
||
ok: true,
|
||
read_data: reads,
|
||
error: None,
|
||
status: I2cTransferStatus::Ok,
|
||
},
|
||
Err(err) => {
|
||
let status = match err {
|
||
DwError::Nack => I2cTransferStatus::Nack,
|
||
DwError::Timeout => I2cTransferStatus::Timeout,
|
||
_ => I2cTransferStatus::Error,
|
||
};
|
||
Self::failure(status, format!("{}: {err}", controller.name))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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<()>,
|
||
) -> 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()
|
||
);
|
||
|
||
// Readiness is reported only if the daemon's own setup (namespace
|
||
// reduction included) fully succeeds.
|
||
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::*;
|
||
|
||
fn resolve_via<'a>(
|
||
controllers: &'a [(&'a str, Vec<String>)],
|
||
requested: &str,
|
||
) -> Result<&'a str, ()> {
|
||
fn last_component(value: &str) -> &str {
|
||
value.rsplit('.').next().unwrap_or(value)
|
||
}
|
||
let normalized = requested
|
||
.trim_start_matches("\\_SB_.")
|
||
.trim_start_matches('_');
|
||
if let Some(exact) = controllers.iter().find(|(name, aliases)| {
|
||
*name == requested
|
||
|| aliases
|
||
.iter()
|
||
.any(|alias| alias == requested || alias == &normalized)
|
||
}) {
|
||
return Ok(exact.0);
|
||
}
|
||
let fuzzy: Vec<&&str> = controllers
|
||
.iter()
|
||
.map(|(name, aliases)| (name, aliases))
|
||
.filter(|(name, aliases)| {
|
||
last_component(name) == last_component(normalized)
|
||
|| aliases
|
||
.iter()
|
||
.any(|alias| last_component(alias) == last_component(normalized))
|
||
})
|
||
.map(|(name, _)| name)
|
||
.collect();
|
||
match fuzzy.as_slice() {
|
||
[only] => Ok(**only),
|
||
_ => Err(()),
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn resolve_prefers_exact_over_fuzzy() {
|
||
let controllers = vec![
|
||
("intel-lpss:00--15.0", vec!["PC00.I2C0".to_string()]),
|
||
("intel-lpss:00--15.1", vec!["PC00.I2C1".to_string()]),
|
||
];
|
||
assert_eq!(resolve_via(&controllers, "PC00.I2C0"), Ok("intel-lpss:00--15.0"));
|
||
assert_eq!(resolve_via(&controllers, "\\_SB_.PC00.I2C1"), Ok("intel-lpss:00--15.1"));
|
||
assert_eq!(resolve_via(&controllers, "I2C0"), Ok("intel-lpss:00--15.0"));
|
||
assert!(resolve_via(&controllers, "I2C3").is_err());
|
||
}
|
||
}
|
||
}
|