diff --git a/Cargo.lock b/Cargo.lock index d658e13d33..d30c2f3930 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,6 +678,7 @@ dependencies = [ "anyhow", "common", "daemon", + "dw-i2c", "i2c-interface", "libredox", "log", @@ -686,6 +687,20 @@ dependencies = [ "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]] name = "e1000d" version = "0.1.0" @@ -1250,11 +1265,14 @@ dependencies = [ "anyhow", "common", "daemon", + "dw-i2c", "i2c-interface", "libredox", "log", + "redox-scheme", "redox_syscall 0.9.0+rb0.3.1", "ron", + "scheme-utils", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 63ae099d07..9ab7a96765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ members = [ "drivers/i2c/i2c-interface", "drivers/i2c/i2cd", "drivers/i2c/amd-mp2-i2cd", + "drivers/i2c/designware", "drivers/i2c/dw-acpi-i2cd", "drivers/i2c/intel-lpss-i2cd", diff --git a/drivers/i2c/amd-mp2-i2cd/src/main.rs b/drivers/i2c/amd-mp2-i2cd/src/main.rs index 925b45e75f..e5cfde9af6 100644 --- a/drivers/i2c/amd-mp2-i2cd/src/main.rs +++ b/drivers/i2c/amd-mp2-i2cd/src/main.rs @@ -57,6 +57,8 @@ fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> R name: format!("amd-mp2:{}", pci_config.func.name()), max_transaction_size: 0, supports_10bit_addr: false, + provider_scheme: String::new(), + aliases: Vec::new(), }; let mut registration = register_adapter(&info) .context("failed to register AMD MP2 controller with i2cd")?; diff --git a/drivers/i2c/designware/Cargo.toml b/drivers/i2c/designware/Cargo.toml new file mode 100644 index 0000000000..d83c7fd132 --- /dev/null +++ b/drivers/i2c/designware/Cargo.toml @@ -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 diff --git a/drivers/i2c/designware/src/lib.rs b/drivers/i2c/designware/src/lib.rs new file mode 100644 index 0000000000..5bc22db706 --- /dev/null +++ b/drivers/i2c/designware/src/lib.rs @@ -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), + 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>, 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::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/` 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, + pub engine: DwI2c, + } + + enum Handle { + SchemeRoot, + Transfer { pending: Vec }, + } + + struct Endpoint { + handles: HandleMap, + controllers: Vec, + } + + 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 = 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 { + 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<()> { + 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")); + } + } +} diff --git a/drivers/i2c/dw-acpi-i2cd/Cargo.toml b/drivers/i2c/dw-acpi-i2cd/Cargo.toml index a90b48cce3..2c3bc134c9 100644 --- a/drivers/i2c/dw-acpi-i2cd/Cargo.toml +++ b/drivers/i2c/dw-acpi-i2cd/Cargo.toml @@ -15,6 +15,7 @@ ron.workspace = true acpi-resource = { path = "../../acpi-resource" } common = { path = "../../common" } daemon = { path = "../../../daemon" } +dw-i2c = { path = "../designware" } i2c-interface = { path = "../i2c-interface" } [lints] diff --git a/drivers/i2c/dw-acpi-i2cd/src/main.rs b/drivers/i2c/dw-acpi-i2cd/src/main.rs index b22a27739d..6c62673ad3 100644 --- a/drivers/i2c/dw-acpi-i2cd/src/main.rs +++ b/drivers/i2c/dw-acpi-i2cd/src/main.rs @@ -13,18 +13,22 @@ use common::{MemoryType, PhysBorrowed, Prot}; use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; use serde::Deserialize; +use dw_i2c::endpoint::EndpointController; +use dw_i2c::{BusTiming, DwI2c}; + const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"]; -const DW_IC_CON: usize = 0x00; -const DW_IC_TAR: usize = 0x04; -const DW_IC_SS_SCL_HCNT: usize = 0x14; -const DW_IC_SS_SCL_LCNT: usize = 0x18; -const DW_IC_DATA_CMD: usize = 0x10; -const DW_IC_INTR_MASK: usize = 0x30; -const DW_IC_CLR_INTR: usize = 0x40; -const DW_IC_ENABLE: usize = 0x6C; -const DW_IC_STATUS: usize = 0x70; -const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); +// Baytrail/Cherrytrail/AMD DesignWare blocks clock the controller at 100 MHz +// (Linux i2c-designware-pcidrv.c: ehl_get_clk_rate_khz and +// navi_amd_get_clk_rate_khz both return 100000). The fall-time constants +// reuse the Broxton values; the formula produces slower-than-spec timings +// on faster blocks, which is the safe direction. +const DW_100MHZ_TIMING: BusTiming = BusTiming { + ic_clk_hz: 100_000_000, + sda_hold_ns: 42, + sda_fall_ns: 171, + scl_fall_ns: 208, +}; #[derive(Debug, Deserialize)] struct AmlSymbol { @@ -53,11 +57,6 @@ struct ControllerDescriptor { resources: ControllerResources, } -struct RegisteredController { - _mmio: Option, - _registration: File, -} - fn main() { common::setup_logging( "bus", @@ -88,22 +87,26 @@ fn daemon_main(daemon: daemon::Daemon) -> Result<()> { 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 { - match register_controller("dw-acpi", controller) { - Ok(controller) => registered.push(controller), - Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"), + match bring_up_controller("dw-acpi", controller) { + Ok((endpoint, guard)) => { + endpoint_controllers.push(endpoint); + guards.push(guard); + } + Err(err) => log::warn!("dw-acpi-i2cd: controller bring-up skipped: {err:#}"), } } - daemon.ready(); - libredox::call::setrens(0, 0).context("failed to enter null namespace")?; - - log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len()); - - loop { - std::thread::park(); - } + // Registrations and MMIO mappings must outlive the scheme endpoint. + let _guards = guards; + dw_i2c::endpoint::serve("dw-acpi-i2c", endpoint_controllers, || { + daemon.ready(); + if let Err(err) = libredox::call::setrens(0, 0) { + log::error!("dw-acpi-i2cd: failed to enter null namespace: {err}"); + } + }) } fn discover_controllers(supported_ids: &[&str]) -> Result> { @@ -199,7 +202,7 @@ fn read_controller_resources(device: &str) -> Result { address_length, .. }) 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 { minimum, @@ -210,7 +213,7 @@ fn read_controller_resources(device: &str) -> Result { let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; mmio = Some(( *minimum as usize, - span.max((*address_length as usize).max(DW_MMIO_WINDOW)), + span.max((*address_length as usize).max(0x100)), )); } ResourceDescriptor::Address32(descriptor) @@ -219,7 +222,7 @@ fn read_controller_resources(device: &str) -> Result { { mmio = Some(( descriptor.minimum as usize, - (descriptor.address_length as usize).max(DW_MMIO_WINDOW), + (descriptor.address_length as usize).max(0x100), )); } ResourceDescriptor::Address64(descriptor) @@ -230,7 +233,7 @@ fn read_controller_resources(device: &str) -> Result { .context("64-bit MMIO base does not fit in usize")?; let len = usize::try_from(descriptor.address_length) .context("64-bit MMIO length does not fit in usize")?; - mmio = Some((base, len.max(DW_MMIO_WINDOW))); + mmio = Some((base, len.max(0x100))); } ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { irq = interrupts.first().copied().map(u32::from); @@ -256,29 +259,31 @@ fn read_controller_resources(device: &str) -> Result { }) } -fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { +fn bring_up_controller( + prefix: &str, + controller: ControllerDescriptor, +) -> Result<(EndpointController, (PhysBorrowed, File))> { let ControllerDescriptor { device, hid, resources, } = controller; - let mmio = match PhysBorrowed::map( + let mmio = PhysBorrowed::map( resources.mmio_base, resources.mmio_len, Prot::RW, MemoryType::Uncacheable, - ) { - Ok(mapping) => Some(mapping), - Err(err) => { - log::warn!( - "dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}", - resources.mmio_base, - resources.mmio_len, - ); - None - } - }; + ) + .with_context(|| { + format!( + "failed to map MMIO for {device} ({:#x}, len {:#x})", + resources.mmio_base, resources.mmio_len + ) + })?; + + let engine = unsafe { DwI2c::new(mmio.as_ptr() as *mut u8) }; + engine.init(&DW_100MHZ_TIMING); log::info!( "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.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 { id: 0, @@ -299,6 +301,8 @@ fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result .as_ref() .map(|bus| bus.access_mode_10bit) .unwrap_or(false), + provider_scheme: "dw-acpi-i2c".to_string(), + aliases: vec![device.clone()], }; let mut registration = register_adapter(&info) .with_context(|| format!("failed to register {device} with i2cd"))?; @@ -314,18 +318,36 @@ fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result } } - Ok(RegisteredController { - _mmio: mmio, - _registration: registration, - }) + let endpoint = EndpointController { + name: format!("{prefix}:{device}"), + aliases: vec![device], + engine, + }; + Ok((endpoint, (mmio, registration))) } fn register_adapter(info: &I2cAdapterInfo) -> Result { - let mut file = OpenOptions::new() - .read(true) - .write(true) - .open("/scheme/i2c/register") - .context("failed to open /scheme/i2c/register")?; + // 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()) diff --git a/drivers/i2c/i2c-interface/src/lib.rs b/drivers/i2c/i2c-interface/src/lib.rs index 9e5ab4448e..48721e3c2b 100644 --- a/drivers/i2c/i2c-interface/src/lib.rs +++ b/drivers/i2c/i2c-interface/src/lib.rs @@ -61,6 +61,17 @@ pub struct I2cAdapterInfo { pub name: String, pub max_transaction_size: usize, 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, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/drivers/i2c/i2cd/src/main.rs b/drivers/i2c/i2cd/src/main.rs index b7b7d89aab..8bc2cb2d42 100644 --- a/drivers/i2c/i2cd/src/main.rs +++ b/drivers/i2c/i2cd/src/main.rs @@ -23,7 +23,6 @@ enum Handle { struct AdapterEntry { info: I2cAdapterInfo, - provider_handle: usize, } struct I2cDaemon { @@ -85,44 +84,77 @@ impl I2cDaemon { Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters)) } - fn queue_transfer_stub( - handle: &mut Handle, - adapter: &I2cAdapterInfo, - request: &I2cTransferRequest, - ) -> syscall::Result<()> { - let write_bytes = request - .segments - .iter() - .filter_map(|segment| match &segment.op { - i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()), - i2c_interface::I2cTransferOp::Read(_) => None, + /// Resolve an adapter by request name. Consumers identify adapters by the + /// ACPI resource-source string from the device's `_CRS` (e.g. + /// "\_SB_.PC00.I2C0"), so matching is tolerant to normalization forms: + /// exact name/alias match first, then last-dot-component match. + fn resolve_adapter(&self, requested: &str) -> Option<&I2cAdapterInfo> { + fn last_component(value: &str) -> &str { + value.rsplit('.').next().unwrap_or(value) + } + + let normalized = requested + .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::(); - let read_segments = request - .segments - .iter() - .filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_))) - .count(); + } - log::info!( - "i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)", - adapter.id, - adapter.name, - request.adapter, - request.segments.len(), - write_bytes, - read_segments, - request.stop, - ); + /// Forward a transfer to the provider daemon that owns the adapter's + /// hardware: write the request to `/scheme//transfer` and read + /// back the response. The wire format is the bare `I2cTransferRequest` / + /// `I2cTransferResponse` pair that in-tree consumers (i2c-hidd) speak. + fn forward_transfer(request: &I2cTransferRequest, info: &I2cAdapterInfo) -> I2cTransferResponse { + let fail = |message: String| I2cTransferResponse { + ok: false, + read_data: Vec::new(), + error: Some(message), + }; - Self::set_pending_response( - handle, - I2cControlResponse::TransferResult(I2cTransferResponse { - ok: false, - read_data: Vec::new(), - error: Some(String::from("I2C controller transfer path is not implemented yet")), - }), - ) + if info.provider_scheme.is_empty() { + return fail(format!( + "adapter '{}' has no transfer provider", + info.name + )); + } + + 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::(&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 { @@ -250,6 +282,46 @@ impl SchemeSync for I2cDaemon { _fcntl_flags: u32, _ctx: &CallerCtx, ) -> syscall::Result { + // 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)?; match request { @@ -262,7 +334,6 @@ impl SchemeSync for I2cDaemon { adapter_id, AdapterEntry { info: info.clone(), - provider_handle: id, }, ); @@ -308,21 +379,12 @@ impl SchemeSync for I2cDaemon { request, } => { let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?; - log::debug!( - "i2cd: transfer requested for adapter {} via provider fd {}", - adapter_id, - entry.provider_handle, - ); + let info = entry.info.clone(); + let response = Self::forward_transfer(&request, &info); - let adapter_info = entry.info.clone(); let handle = self.handles.get_mut(id)?; - match handle { - Handle::Transfer { .. } => { - Self::queue_transfer_stub(handle, &adapter_info, &request)?; - Ok(buf.len()) - } - _ => Err(SysError::new(EINVAL)), - } + Self::set_pending_response(handle, I2cControlResponse::TransferResult(response))?; + Ok(buf.len()) } } } diff --git a/drivers/i2c/intel-lpss-i2cd/Cargo.toml b/drivers/i2c/intel-lpss-i2cd/Cargo.toml index 0e74cf94d6..8c6f67f5c2 100644 --- a/drivers/i2c/intel-lpss-i2cd/Cargo.toml +++ b/drivers/i2c/intel-lpss-i2cd/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" anyhow.workspace = true log.workspace = true redox_syscall = { workspace = true, features = ["std"] } +redox-scheme.workspace = true libredox.workspace = true serde.workspace = true ron.workspace = true @@ -15,7 +16,9 @@ ron.workspace = true acpi-resource = { path = "../../acpi-resource" } common = { path = "../../common" } daemon = { path = "../../../daemon" } +dw-i2c = { path = "../designware" } i2c-interface = { path = "../i2c-interface" } +scheme-utils = { path = "../../../scheme-utils" } [lints] workspace = true diff --git a/drivers/i2c/intel-lpss-i2cd/src/main.rs b/drivers/i2c/intel-lpss-i2cd/src/main.rs index ca3ead4374..5a3f6a492d 100644 --- a/drivers/i2c/intel-lpss-i2cd/src/main.rs +++ b/drivers/i2c/intel-lpss-i2cd/src/main.rs @@ -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::fs::{self, File, OpenOptions}; use std::io::{Read, Write}; @@ -13,18 +24,32 @@ use common::{MemoryType, PhysBorrowed, Prot}; use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse}; 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 DW_IC_TAR: usize = 0x04; -const DW_IC_SS_SCL_HCNT: usize = 0x14; -const DW_IC_SS_SCL_LCNT: usize = 0x18; -const DW_IC_DATA_CMD: usize = 0x10; -const DW_IC_INTR_MASK: usize = 0x30; -const DW_IC_CLR_INTR: usize = 0x40; -const DW_IC_ENABLE: usize = 0x6C; -const DW_IC_STATUS: usize = 0x70; -const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::(); +const SUPPORTED_ACPI_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"]; + +/// Intel LPSS I2C PCI IDs, ported from Linux 7.1 +/// `drivers/mfd/intel-lpss-pci.c` (entries carrying `*_i2c_info`). +/// All of these use the Broxton-derived timing (`bxt_i2c_info`, 133 MHz). +const LPSS_I2C_PCI_IDS: &[u16] = &[ + // ARL-H (0x7750/0x7751, 0x7778-0x777b) + 0x7750, 0x7751, 0x7778, 0x7779, 0x777a, 0x777b, + // MTL-P (0x7e50/0x7e51, 0x7e78-0x7e7b) + 0x7e50, 0x7e51, 0x7e78, 0x7e79, 0x7e7a, 0x7e7b, +]; + +const PCI_VENDOR_INTEL: u16 = 0x8086; +const PCI_CLASS_SERIAL_BUS: u8 = 0x0c; +const PCI_SUBCLASS_I2C: u8 = 0x80; + +struct Controller { + name: String, + aliases: Vec, + engine: DwI2c, + registration: File, + mmio: PhysBorrowed, +} #[derive(Debug, Deserialize)] struct AmlSymbol { @@ -47,17 +72,12 @@ struct ControllerResources { } #[derive(Debug)] -struct ControllerDescriptor { +struct DiscoveredController { device: String, hid: String, resources: ControllerResources, } -struct RegisteredController { - _mmio: Option, - _registration: File, -} - fn main() { common::setup_logging( "bus", @@ -82,31 +102,256 @@ fn daemon_runner(daemon: daemon::Daemon) -> ! { fn daemon_main(daemon: daemon::Daemon) -> Result<()> { common::init(); - let controllers = discover_controllers(SUPPORTED_IDS) - .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 controllers = Vec::new(); - let mut registered = Vec::new(); - for controller in controllers { - match register_controller("intel-lpss", controller) { - Ok(controller) => registered.push(controller), - Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"), + for discovered in discover_pci_controllers() { + match bring_up_controller(discovered) { + Ok(controller) => controllers.push(controller), + Err(err) => log::warn!("intel-lpss-i2cd: PCI controller bring-up skipped: {err:#}"), } } - daemon.ready(); - libredox::call::setrens(0, 0).context("failed to enter null namespace")?; - - log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len()); - - loop { - std::thread::park(); + for discovered in discover_acpi_controllers(SUPPORTED_ACPI_IDS) + .context("failed to discover Intel LPSS ACPI I2C controllers")? + { + match bring_up_controller(discovered) { + Ok(controller) => controllers.push(controller), + Err(err) => log::warn!("intel-lpss-i2cd: ACPI controller bring-up skipped: {err:#}"), + } } + + if controllers.is_empty() { + log::info!("intel-lpss-i2cd: no supported controllers found (PCI or ACPI)"); + } + + // i2cd registrations and MMIO mappings must outlive the scheme endpoint. + let mut guards = Vec::new(); + let endpoint_controllers: Vec = 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> { +// --------------------------------------------------------------------------- +// Controller bring-up: MMIO map, engine init, i2cd registration +// --------------------------------------------------------------------------- + +fn bring_up_controller(discovered: DiscoveredController) -> Result { + 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 { + // 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 { + 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 { + 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 { + 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> { let mut matched = BTreeMap::new(); let entries = match fs::read_dir("/scheme/acpi/symbols") { @@ -149,7 +394,7 @@ fn discover_controllers(supported_ids: &[&str]) -> Result Result { address_length, .. }) 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 { minimum, @@ -208,10 +453,7 @@ fn read_controller_resources(device: &str) -> Result { .. }) if mmio.is_none() && maximum >= minimum => { let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize; - mmio = Some(( - *minimum as usize, - span.max((*address_length as usize).max(DW_MMIO_WINDOW)), - )); + mmio = Some((*minimum as usize, span.max((*address_length as usize).max(0x100)))); } ResourceDescriptor::Address32(descriptor) if mmio.is_none() @@ -219,7 +461,7 @@ fn read_controller_resources(device: &str) -> Result { { mmio = Some(( descriptor.minimum as usize, - (descriptor.address_length as usize).max(DW_MMIO_WINDOW), + (descriptor.address_length as usize).max(0x100), )); } ResourceDescriptor::Address64(descriptor) @@ -230,7 +472,7 @@ fn read_controller_resources(device: &str) -> Result { .context("64-bit MMIO base does not fit in usize")?; let len = usize::try_from(descriptor.address_length) .context("64-bit MMIO length does not fit in usize")?; - mmio = Some((base, len.max(DW_MMIO_WINDOW))); + mmio = Some((base, len.max(0x100))); } ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => { irq = interrupts.first().copied().map(u32::from); @@ -256,91 +498,34 @@ fn read_controller_resources(device: &str) -> Result { }) } -fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result { - let ControllerDescriptor { - device, - hid, - resources, - } = controller; - - 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 - } +/// Find the ACPI device path whose `_CRS` FixedMemory32 window matches this +/// controller's MMIO base. That path ("PC00.I2C0") is what HID-over-I2C +/// devices name in their `I2cSerialBus` resource source, so it is the alias +/// consumers will use to address this adapter. +fn discover_acpi_aliases(mmio_base: usize) -> Vec { + let Ok(entries) = fs::read_dir("/scheme/acpi/resources") else { + return Vec::new(); }; - log::info!( - "intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}", - resources.mmio_base, - resources.mmio_len, - resources.irq, - ); - log::debug!( - "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 info = I2cAdapterInfo { - id: 0, - name: format!("{prefix}:{device}"), - max_transaction_size: 0, - supports_10bit_addr: resources - .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:?}"); + let mut aliases = Vec::new(); + for entry in entries.flatten() { + let Ok(contents) = fs::read_to_string(entry.path()) else { + continue; + }; + let Ok(resources) = ron::from_str::>(&contents) else { + continue; + }; + let matches_base = resources.iter().any(|resource| match resource { + ResourceDescriptor::FixedMemory32(descriptor) => descriptor.address as usize == mmio_base, + _ => false, + }); + if matches_base { + if let Some(name) = entry.file_name().to_str() { + aliases.push(name.to_owned()); + } } } - - Ok(RegisteredController { - _mmio: mmio, - _registration: registration, - }) -} - -fn register_adapter(info: &I2cAdapterInfo) -> Result { - 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 { - 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") + aliases } 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}" ) } + +#[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); + } + +}