blob: 550060c2b1cb9ce51d424601684eb07069ba952c [file]
// Licensed under the Apache-2.0 license
//! I2C IPC Client
//!
//! This crate provides an I2cClient implementation that uses IPC to communicate
//! with the I2C server via Pigweed's `userspace::syscall::channel_transact`.
//!
//! ## Usage
//!
//! ```rust,ignore
//! use i2c_client::IpcI2cClient;
//! use i2c_api::{BusIndex, I2cAddress, I2cClient};
//!
//! // Create client bound to the I2C server channel (handle from app's handle module)
//! let mut client = IpcI2cClient::new(handle::I2C);
//!
//! let addr = I2cAddress::new(0x48)?;
//! let mut buf = [0u8; 2];
//! client.write_read(BusIndex::BUS_0, addr, &[], &mut buf)?;
//! ```
#![no_std]
#![warn(missing_docs)]
use i2c_api::{
wire::{
self, encode_configure_slave_request, encode_disable_slave_request,
encode_enable_slave_request,
encode_enable_slave_notification_request, encode_probe_request, encode_read_request,
encode_slave_receive_request, encode_slave_set_response_request,
encode_slave_wait_event_request, encode_write_read_request, encode_write_request,
I2cResponseHeader, WireError,
},
BusIndex, I2cAddress, I2cClient, I2cError, I2cErrorKind, I2cTargetClient, NoAcknowledgeSource,
Operation, ResponseCode, SlaveEventKind, TargetMessage, TARGET_MESSAGE_MAX_LEN,
};
use userspace::syscall;
use userspace::time::Instant;
// Re-export wire module for advanced users
pub use i2c_api::wire as protocol;
/// I2C client that communicates with the I2C server over Pigweed IPC
///
/// This client implements the `I2cClient` trait and uses the wire protocol
/// to encode/decode messages sent via `channel_transact`.
pub struct IpcI2cClient {
handle: u32,
request_buf: [u8; wire::MAX_REQUEST_SIZE],
response_buf: [u8; wire::MAX_RESPONSE_SIZE],
}
impl IpcI2cClient {
/// Create a new IPC I2C client bound to the given channel handle
///
/// # Arguments
/// * `handle` - Channel handle from the application's handle module (e.g., `handle::I2C`)
pub fn new(handle: u32) -> Self {
Self {
handle,
request_buf: [0u8; wire::MAX_REQUEST_SIZE],
response_buf: [0u8; wire::MAX_RESPONSE_SIZE],
}
}
/// Get the channel handle
pub fn handle(&self) -> u32 {
self.handle
}
/// Send request and receive response via IPC
fn send_recv(&mut self, req_len: usize) -> Result<usize, I2cError> {
syscall::channel_transact(
self.handle,
&self.request_buf[..req_len],
&mut self.response_buf,
Instant::MAX,
)
.map_err(|_| I2cError::from_code(ResponseCode::ServerError))
}
/// Decode a response and check for errors
fn decode_response(&self, len: usize) -> Result<&[u8], I2cError> {
if len < I2cResponseHeader::SIZE {
return Err(I2cError::from_code(ResponseCode::ServerError));
}
let header = wire::decode_response_header(&self.response_buf[..len])
.map_err(wire_to_i2c_error)?;
if !header.is_success() {
return Err(response_to_error(header.response_code()));
}
wire::get_response_data(&self.response_buf[..len], &header)
.map_err(wire_to_i2c_error)
}
}
/// Convert a WireError to an I2cError
fn wire_to_i2c_error(e: WireError) -> I2cError {
let code = match e {
WireError::BufferTooSmall => ResponseCode::BufferTooSmall,
WireError::PayloadTooLarge => ResponseCode::BufferTooLarge,
WireError::InvalidOpcode(_) => ResponseCode::ServerError,
WireError::Truncated => ResponseCode::ServerError,
};
I2cError::from_code(code)
}
/// Convert a ResponseCode to an I2cError
fn response_to_error(code: ResponseCode) -> I2cError {
let kind = match code {
ResponseCode::NoDevice => I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Address),
ResponseCode::NackData => I2cErrorKind::NoAcknowledge(NoAcknowledgeSource::Data),
ResponseCode::ArbitrationLost => I2cErrorKind::ArbitrationLoss,
ResponseCode::BusStuck => I2cErrorKind::Bus,
ResponseCode::Timeout => I2cErrorKind::Other,
_ => I2cErrorKind::Other,
};
I2cError::new(code, kind)
}
impl embedded_hal::i2c::ErrorType for IpcI2cClient {
type Error = I2cError;
}
impl I2cClient for IpcI2cClient {
fn write_read(
&mut self,
bus: BusIndex,
address: I2cAddress,
write: &[u8],
read: &mut [u8],
) -> Result<usize, Self::Error> {
// Handle different operation types
if write.is_empty() && read.is_empty() {
// Probe operation
let req_len = encode_probe_request(&mut self.request_buf, bus.value(), address.value())
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
return Ok(0);
}
if write.is_empty() {
// Read only
let req_len = encode_read_request(
&mut self.request_buf,
bus.value(),
address.value(),
read.len() as u16,
)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let data = self.decode_response(resp_len)?;
let copy_len = core::cmp::min(data.len(), read.len());
read[..copy_len].copy_from_slice(&data[..copy_len]);
return Ok(copy_len);
}
if read.is_empty() {
// Write only
let req_len =
encode_write_request(&mut self.request_buf, bus.value(), address.value(), write)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
return Ok(0);
}
// Write-read
let req_len = encode_write_read_request(
&mut self.request_buf,
bus.value(),
address.value(),
write,
read.len() as u16,
)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let data = self.decode_response(resp_len)?;
let copy_len = core::cmp::min(data.len(), read.len());
read[..copy_len].copy_from_slice(&data[..copy_len]);
Ok(copy_len)
}
fn transaction(
&mut self,
bus: BusIndex,
address: I2cAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
// For now, handle simple cases by converting to write_read
// A full implementation would encode all operations in a single transaction message
for op in operations.iter_mut() {
match op {
Operation::Write(data) => {
self.write_read(bus, address, data, &mut [])?;
}
Operation::Read(buffer) => {
self.write_read(bus, address, &[], buffer)?;
}
}
}
Ok(())
}
}
impl IpcI2cClient {
/// Pre-load data the slave will send when the master reads from us.
///
/// Must be called before [`slave_wait_event`] if a read response is needed.
pub fn slave_set_response(&mut self, bus: BusIndex, data: &[u8]) -> Result<(), I2cError> {
let req_len =
encode_slave_set_response_request(&mut self.request_buf, bus.value(), data)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
Ok(())
}
/// Block until the next slave event on `bus`.
///
/// Returns the event kind and, for `DataReceived`, the received bytes
/// written into `rx_buf`. The returned `usize` is the number of bytes
/// written.
pub fn slave_wait_event(
&mut self,
bus: BusIndex,
rx_buf: &mut [u8],
) -> Result<(SlaveEventKind, usize), I2cError> {
let max_rx = rx_buf.len().min(wire::MAX_PAYLOAD_SIZE - 1);
let req_len = encode_slave_wait_event_request(
&mut self.request_buf,
bus.value(),
max_rx as u16,
)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let data = self.decode_response(resp_len)?;
if data.is_empty() {
return Err(I2cError::from_code(ResponseCode::ServerError));
}
let kind = SlaveEventKind::from_u8(data[0])
.ok_or_else(|| I2cError::from_code(ResponseCode::ServerError))?;
let rx_len = if kind == SlaveEventKind::DataReceived {
let n = (data.len() - 1).min(rx_buf.len());
rx_buf[..n].copy_from_slice(&data[1..1 + n]);
n
} else {
0
};
Ok((kind, rx_len))
}
}
impl I2cTargetClient for IpcI2cClient {
fn configure_target_address(
&mut self,
bus: BusIndex,
address: I2cAddress,
) -> Result<(), Self::Error> {
let req_len =
encode_configure_slave_request(&mut self.request_buf, bus.value(), address.value())
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
Ok(())
}
fn enable_receive(&mut self, bus: BusIndex) -> Result<(), Self::Error> {
let req_len = encode_enable_slave_request(&mut self.request_buf, bus.value())
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
Ok(())
}
fn disable_receive(&mut self, bus: BusIndex) -> Result<(), Self::Error> {
let req_len = encode_disable_slave_request(&mut self.request_buf, bus.value())
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
Ok(())
}
fn wait_for_messages(
&mut self,
bus: BusIndex,
messages: &mut [TargetMessage],
_timeout: Option<core::time::Duration>,
) -> Result<usize, Self::Error> {
let mut count = 0;
for msg in messages.iter_mut() {
let req_len = encode_slave_receive_request(
&mut self.request_buf,
bus.value(),
TARGET_MESSAGE_MAX_LEN as u16,
)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let data = self.decode_response(resp_len)?;
if data.is_empty() {
// No data (Stop or timeout) — no more messages pending.
break;
}
let copy_len = core::cmp::min(data.len(), TARGET_MESSAGE_MAX_LEN);
msg.data_mut()[..copy_len].copy_from_slice(&data[..copy_len]);
msg.set_len(copy_len);
count += 1;
}
Ok(count)
}
fn register_notification(
&mut self,
bus: BusIndex,
_notification_mask: u32,
) -> Result<(), Self::Error> {
let req_len =
encode_enable_slave_notification_request(&mut self.request_buf, bus.value())
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let _ = self.decode_response(resp_len)?;
Ok(())
}
fn get_pending_messages(
&mut self,
bus: BusIndex,
messages: &mut [TargetMessage],
) -> Result<usize, Self::Error> {
// Non-blocking: in notification mode the server returns 0 bytes immediately
// when the buffer is empty, so we stop on the first empty response.
let mut count = 0;
for msg in messages.iter_mut() {
let req_len = encode_slave_receive_request(
&mut self.request_buf,
bus.value(),
TARGET_MESSAGE_MAX_LEN as u16,
)
.map_err(wire_to_i2c_error)?;
let resp_len = self.send_recv(req_len)?;
let data = self.decode_response(resp_len)?;
if data.is_empty() {
break;
}
let copy_len = data.len().min(TARGET_MESSAGE_MAX_LEN);
msg.data_mut()[..copy_len].copy_from_slice(&data[..copy_len]);
msg.set_len(copy_len);
count += 1;
}
Ok(count)
}
}