Files
RedBear-OS/drivers/i2c/designware/src/lib.rs
T
Red Bear OS 6db0f48170 i2c: review-fix round — hardware-correct DesignWare engine + LPSS bring-up
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
2026-07-21 12:03:00 +09:00

937 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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());
}
}
}