//! 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), Read(usize), } /// Validated, hardware-independent view of one transfer request. pub struct ValidatedRequest { pub address: u16, pub ten_bit: bool, pub segments: Vec, } /// 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 { 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>, DwError> { if !stop { return Err(DwError::Invalid("unterminated transfers (stop=false) are not supported")); } let mut reads: Vec> = 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>, ) -> 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 { 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/` 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::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, pub engine: DwI2c, pub supports_10bit_addr: bool, } enum Handle { SchemeRoot, Transfer { pending: Vec }, } struct Endpoint { handles: HandleMap, controllers: Vec, } /// Why a request could not be routed to exactly one controller. pub enum ResolveError { NoMatch, Ambiguous(Vec), } 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 = 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 { Ok(self.handles.insert(Handle::SchemeRoot)) } fn openat( &mut self, dirfd: usize, path: &str, _flags: usize, _fcntl_flags: u32, _ctx: &CallerCtx, ) -> syscall::Result { 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 { 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 { 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/` 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, on_ready: impl FnOnce() -> Result<()>, ) -> Result<()> { let socket = Socket::create().context("failed to create scheme socket")?; let mut endpoint = Endpoint { handles: HandleMap::new(), controllers, }; // type={scheme} handshake: deliver the capability fd to init over // INIT_NOTIFY so init registers /scheme/ and returns from // call_ro(FD). register_sync_scheme alone deadlocks init (ptyd-class). let cap_id = endpoint .scheme_root() .context("failed to compute scheme root")?; let cap_fd = socket .create_this_scheme_fd(0, cap_id, 0, 0) .context("failed to create scheme capability fd")?; notify_init_scheme_fd(cap_fd).context("failed to deliver scheme fd to init")?; 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(()) } fn notify_init_scheme_fd(cap_fd: usize) -> Result<()> { let pipe_fd: usize = std::env::var("INIT_NOTIFY") .context("INIT_NOTIFY is not set")? .parse() .context("INIT_NOTIFY is not a valid fd")?; syscall::call_wo(pipe_fd, &cap_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) .context("failed to write scheme fd to the INIT_NOTIFY pipe")?; Ok(()) } #[cfg(test)] mod tests { use super::*; fn resolve_via<'a>( controllers: &'a [(&'a str, Vec)], 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()); } } }