| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! IPC dispatch loop. **One IPC channel per bus** — a client process is wired |
| //! by configuration to exactly one bus's channel and physically cannot |
| //! address another. The server owns one driver per bus (master *and* slave) |
| //! and routes each wake-up to its bus by channel handle. |
| //! |
| //! Topology-agnostic, like the usart server's loop: the runtime takes a list |
| //! of bus bindings + the i2c IRQ, registers them with the WaitGroup, and |
| //! routes wake-ups via `user_data`. |
| //! |
| //! ## Slave-receive notification (thin slice) |
| //! |
| //! The WaitGroup multiplexes the per-bus client channels (`READABLE`) **and** |
| //! the i2c hardware IRQ. On the IRQ: for every notification-armed bus, drain |
| //! the slave RX into that bus's latch, `interrupt_ack`, then raise |
| //! `Signals::USER` on that bus's client channel. The client then issues |
| //! `SlaveReceive`, which returns the latched bytes (status `NoData` if empty). |
| //! Arm/disarm is `EnableSlaveNotification` / `DisableSlaveNotification`. |
| //! |
| //! The **only** kernel-tagged server crate; it wraps the host-buildable |
| //! `i2c_server::{dispatch, slave::dispatch_slave}` in the Pigweed loop. |
| |
| #![no_std] |
| |
| use i2c_api::seam::{ |
| I2c, I2cBusRecovery, I2cIsrEvent, I2cSlaveBuffer, I2cSlaveEvent, SevenBitAddress, |
| }; |
| use i2c_api::{I2cError, I2cOp, I2cRequestHeader, I2cResponseHeader, SlaveEvent, MAX_PAYLOAD_SIZE}; |
| use i2c_server::slave::dispatch_slave; |
| use i2c_server::{dispatch, MAX_BUF_SIZE}; |
| use userspace::syscall::{self, Signals}; |
| use userspace::time::Instant; |
| |
| /// One bus the server owns: its dedicated IPC channel, IRQ handle, the driver instance |
| /// (master + slave), and the per-bus slave-RX notification latch. |
| pub struct Bus<B> { |
| /// IPC channel handle (`channel_handler`) dedicated to this bus. |
| pub channel: u32, |
| /// IRQ handle for this bus's controller. |
| pub irq: u32, |
| /// The bus driver — implements both the master and slave seams. |
| pub driver: B, |
| notif_enabled: bool, |
| rx: [u8; MAX_PAYLOAD_SIZE], |
| rx_len: usize, |
| /// Source address (7-bit) of the master that wrote to us; only valid |
| /// when rx_len > 0. Captured on IRQ drain; ignored if not available. |
| rx_source: u8, |
| /// Event kind that triggered the latch (DataReceived, ReadRequest, Stop). |
| /// Only meaningful when rx_len > 0 or notification was armed. |
| rx_event_kind: SlaveEvent, |
| } |
| |
| impl<B> Bus<B> { |
| pub const fn new(channel: u32, irq: u32, driver: B) -> Self { |
| Self { |
| channel, |
| irq, |
| driver, |
| notif_enabled: false, |
| rx: [0u8; MAX_PAYLOAD_SIZE], |
| rx_len: 0, |
| rx_source: 0, |
| rx_event_kind: SlaveEvent::DataReceived, |
| } |
| } |
| } |
| |
| fn encode_error(resp: &mut [u8], e: I2cError) -> usize { |
| let h = I2cResponseHeader::error(e); |
| resp[..I2cResponseHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&h)); |
| I2cResponseHeader::SIZE |
| } |
| |
| fn encode_ok(resp: &mut [u8], payload_len: usize) -> usize { |
| let h = I2cResponseHeader::success(payload_len as u16); |
| resp[..I2cResponseHeader::SIZE].copy_from_slice(zerocopy::IntoBytes::as_bytes(&h)); |
| I2cResponseHeader::SIZE + payload_len |
| } |
| |
| /// Parse just the request header (op + the SlaveReceive max-len in op_count). |
| fn header(req: &[u8]) -> Option<(I2cOp, usize)> { |
| if req.len() < I2cRequestHeader::SIZE { |
| return None; |
| } |
| let h = |
| zerocopy::Ref::<_, I2cRequestHeader>::from_bytes(&req[..I2cRequestHeader::SIZE]).ok()?; |
| Some((h.operation().ok()?, h.op_count_value())) |
| } |
| |
| /// Run the i2c server forever. |
| /// |
| /// Registers every bus channel (`READABLE`) and each bus's IRQ (`irq_signals`) |
| /// with `wg`, then loops. `buses` must be non-empty with distinct channels. |
| pub fn run<B>(wg: u32, irq_signals: Signals, buses: &mut [Bus<B>]) -> ! |
| where |
| B: I2c<SevenBitAddress> + I2cSlaveBuffer<SevenBitAddress> + I2cSlaveEvent + I2cBusRecovery, |
| { |
| for bus in buses.iter() { |
| if let Err(_) = |
| syscall::wait_group_add(wg, bus.channel, Signals::READABLE, bus.channel as usize) |
| { |
| pw_log::error!("wait_group_add bus channel failed"); |
| } |
| if let Err(_) = syscall::wait_group_add(wg, bus.irq, irq_signals, bus.irq as usize) { |
| pw_log::error!("wait_group_add irq failed"); |
| } |
| } |
| |
| let mut request_buf = [0u8; MAX_BUF_SIZE]; |
| let mut response_buf = [0u8; MAX_BUF_SIZE]; |
| let wait_mask = Signals::READABLE | irq_signals; |
| |
| loop { |
| let Ok(w) = syscall::object_wait(wg, wait_mask, Instant::MAX) else { |
| continue; |
| }; |
| |
| // ---- hardware slave IRQ: drain the armed bus, ack, wake client ---- |
| // Each bus registered its IRQ with user_data = bus.irq, so check if this |
| // signal came from an IRQ (vs. a client channel READABLE event). |
| if w.pending_signals.contains(irq_signals) { |
| let irq = w.user_data as u32; |
| let acked = w.pending_signals & irq_signals; |
| if let Some(bus) = buses.iter_mut().find(|b| b.irq == irq) { |
| if bus.notif_enabled { |
| match bus.driver.try_next_slave_event() { |
| Ok(Some((kind, _))) => match kind { |
| I2cIsrEvent::SlaveWrRecvd => { |
| // Master finished writing — drain buffer into latch. |
| bus.rx_event_kind = SlaveEvent::DataReceived; |
| match bus.driver.read_slave_buffer(&mut bus.rx) { |
| Ok(n) => { |
| if n > 0 { |
| bus.rx_len = n; |
| // Source address extraction from MCTP-I2C header. |
| // With AST_I2CC_SLAVE_PKT_SAVE_ADDR set, the hardware |
| // prepends dest address at byte[0]; the MCTP-I2C header |
| // carries source at byte[3]: src_addr << 1 | 1. |
| // Extract it (bits 7:1 = 7-bit address). |
| if n > 3 { |
| bus.rx_source = (bus.rx[3] >> 1) & 0x7F; |
| } else { |
| bus.rx_source = 0xFF; // Invalid: message too short |
| } |
| } |
| } |
| Err(_) => { |
| pw_log::error!("read_slave_buffer failed"); |
| } |
| } |
| } |
| I2cIsrEvent::SlaveRdReq => { |
| // Master wants to read from us — no RX data, update kind only. |
| bus.rx_event_kind = SlaveEvent::ReadRequest; |
| } |
| I2cIsrEvent::SlaveStop => { |
| // Transaction ended — update kind so client can detect boundaries. |
| bus.rx_event_kind = SlaveEvent::Stop; |
| } |
| I2cIsrEvent::SlaveWrReq | _ => { |
| // SlaveWrReq: write is starting, data not yet received. |
| // SlaveRdProc: our TX bytes were clocked out, informational. |
| // Unknown future events: ignore. |
| // Do not update rx_event_kind or wake the client. |
| pw_log::debug!( |
| "slave IRQ: intermediate or unknown event, no latch update" |
| ); |
| } |
| }, |
| Ok(None) => { |
| pw_log::debug!( |
| "slave IRQ fired but no data ready — spurious or non-data event" |
| ); |
| } |
| Err(_) => { |
| pw_log::error!("try_next_slave_event failed"); |
| } |
| } |
| } |
| if let Err(_) = syscall::interrupt_ack(irq, acked) { |
| pw_log::error!("interrupt_ack failed"); |
| } |
| // Wake client on data events (DataReceived with bytes) or transaction |
| // boundaries (Stop). ReadRequest without data is deferred (post-demo). |
| let should_wake = |
| bus.notif_enabled && (bus.rx_len > 0 || bus.rx_event_kind == SlaveEvent::Stop); |
| if should_wake { |
| // ORs USER onto the bus channel without disturbing READABLE. |
| if let Err(_) = syscall::object_set_peer_user_signal(bus.channel, true) { |
| pw_log::error!("object_set_peer_user_signal failed"); |
| } |
| } |
| } |
| continue; |
| } |
| |
| if !w.pending_signals.contains(Signals::READABLE) { |
| continue; |
| } |
| let channel = w.user_data as u32; |
| let Some(bus) = buses.iter_mut().find(|b| b.channel == channel) else { |
| continue; |
| }; |
| let Ok(req_len) = syscall::channel_read(channel, 0, &mut request_buf) else { |
| continue; |
| }; |
| let req = &request_buf[..req_len]; |
| |
| let resp_len = match header(req) { |
| Some((I2cOp::Transaction, _)) => { |
| let n = dispatch(&mut bus.driver, req, &mut response_buf); |
| // After a bus-level error, attempt hardware recovery so the |
| // next transaction starts clean. The client already has the |
| // encoded error; recovery is best-effort (ignore its result). |
| if n >= I2cResponseHeader::SIZE { |
| if let Some(rhdr) = zerocopy::Ref::<_, I2cResponseHeader>::from_bytes( |
| &response_buf[..I2cResponseHeader::SIZE], |
| ) |
| .ok() |
| { |
| use i2c_api::I2cError; |
| if let Some(code) = rhdr.error_code() { |
| match code { |
| I2cError::Bus | I2cError::ArbitrationLoss | I2cError::Timeout => { |
| let _ = bus.driver.recover_bus(); |
| } |
| _ => {} |
| } |
| } |
| } |
| } |
| n |
| } |
| Some((I2cOp::ConfigureSlave | I2cOp::EnableSlave | I2cOp::DisableSlave, _)) => { |
| dispatch_slave(&mut bus.driver, req, &mut response_buf) |
| } |
| Some((I2cOp::EnableSlaveNotification, _)) => { |
| bus.notif_enabled = true; |
| encode_ok(&mut response_buf, 0) |
| } |
| Some((I2cOp::DisableSlaveNotification, _)) => { |
| bus.notif_enabled = false; |
| bus.rx_len = 0; |
| bus.rx_source = 0; |
| bus.rx_event_kind = SlaveEvent::DataReceived; |
| encode_ok(&mut response_buf, 0) |
| } |
| Some((I2cOp::SlaveReceive, max_len)) => { |
| let _ = syscall::object_set_peer_user_signal(bus.channel, false); |
| if bus.rx_len == 0 { |
| encode_error(&mut response_buf, I2cError::NoData) |
| } else { |
| // Response payload: [kind (1), source_addr (1), data (0..max_len)] |
| let cap = response_buf.len() - I2cResponseHeader::SIZE; |
| let metadata_size = 2; // kind + source |
| if cap < metadata_size { |
| encode_error(&mut response_buf, I2cError::BufferTooSmall) |
| } else { |
| let data_cap = cap - metadata_size; |
| let n = bus.rx_len.min(max_len).min(data_cap); |
| let payload_offset = I2cResponseHeader::SIZE; |
| response_buf[payload_offset] = bus.rx_event_kind as u8; |
| response_buf[payload_offset + 1] = bus.rx_source; |
| response_buf[payload_offset + 2..payload_offset + 2 + n] |
| .copy_from_slice(&bus.rx[..n]); |
| bus.rx_len = 0; |
| encode_ok(&mut response_buf, metadata_size + n) |
| } |
| } |
| } |
| // NOTE: not needed for MCTP (master-write only). For testing slave-TX patterns. |
| Some((I2cOp::SlaveSetResponse, _)) => { |
| let hdr_end = I2cRequestHeader::SIZE; |
| if req.len() < hdr_end { |
| encode_error(&mut response_buf, I2cError::InvalidOperation) |
| } else { |
| let payload = &req[hdr_end..]; |
| match bus.driver.write_slave_response(payload) { |
| Ok(()) => encode_ok(&mut response_buf, 0), |
| Err(_) => encode_error(&mut response_buf, I2cError::InternalError), |
| } |
| } |
| } |
| Some((_, _)) | None => encode_error(&mut response_buf, I2cError::InvalidOperation), |
| }; |
| if let Err(_) = syscall::channel_respond(channel, &response_buf[..resp_len]) { |
| pw_log::error!("channel_respond failed"); |
| } |
| } |
| } |