blob: c6996927019dca0c0f4e56d5a25526aa7c09dd89 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Wire protocol for the i2c userspace driver.
//!
//! The unit marshalled across the IPC boundary is **one whole
//! `embedded_hal::i2c::I2c::transaction`** — an address plus an ordered list
//! of `Read`/`Write` operations the server replays atomically on the real bus
//! before replying. There is no per-operation round-trip and no bus
//! lock/unlock exposed across the boundary; that is what preserves the
//! exclusive-atomic contract of `I2c` between processes.
//!
//! ```text
//! request : [I2cRequestHeader] [I2cOpDesc; op_count] [write payloads...]
//! response: [I2cResponseHeader] [read payloads...]
//! ```
//!
//! `Write` ops carry their bytes inline (in operation order) after the
//! descriptor array. `Read` ops contribute nothing to the request; their
//! bytes come back, concatenated in operation order, after the response
//! header and are scattered into the caller's slices by the client.
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
/// Max total payload (sum of read or write bytes) in one transaction.
pub const MAX_PAYLOAD_SIZE: usize = 256;
/// Max number of `Operation`s in one transaction.
pub const MAX_OPS: usize = 16;
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cOp {
/// One whole `I2c::transaction(address, &mut [Operation])`. Master mode.
Transaction = 0x01,
// ---- Target/slave mode (thin notification slice) ----
// One IPC channel per bus, so none of these carry a bus field: the
// server-runtime knows the bus from the channel the request arrived on
// (same invariant as master). All use `I2cRequestHeader`; field meanings
// are noted per op below.
/// Set this bus's slave address. `address` = 7-bit slave address.
ConfigureSlave = 0x02,
/// Enter slave mode on this bus. No args.
EnableSlave = 0x03,
/// Leave slave mode on this bus. No args.
DisableSlave = 0x04,
/// Arm interrupt-driven slave-RX notification for this bus. No args.
/// After this, the server raises `Signals::USER` on the bus channel when
/// slave data has been latched.
EnableSlaveNotification = 0x05,
/// Disarm slave-RX notification for this bus. No args.
DisableSlaveNotification = 0x06,
/// Fetch the latched slave-RX buffer (non-blocking). `op_count` = caller's
/// max byte count. Status `NoData` if nothing is latched.
///
/// **Response payload layout** (on success):
/// ```text
/// [ kind (1) | source_addr (1) | data (0..max_len) ]
/// ```
/// - `kind`: [`SlaveEvent`] discriminant — what triggered the latch
/// (DataReceived, ReadRequest, Stop).
/// - `source_addr`: 7-bit address of the sending master (`0x00..=0x7F`),
/// or `0xFF` if unavailable (message too short to extract it).
/// - `data`: the received bytes, up to `op_count` (caller's max).
SlaveReceive = 0x07,
/// Pre-load the slave TX buffer for the next master read.
/// `write_len` bytes from the request payload are written into the
/// hardware TX buffer. The data is sent when the master issues a read
/// to our slave address.
///
/// NOTE: not required for MCTP-over-I2C (master-write only). Provided
/// for completeness and testing of slave-TX / register-echo patterns.
SlaveSetResponse = 0x08,
}
impl TryFrom<u8> for I2cOp {
type Error = I2cError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(Self::Transaction),
0x02 => Ok(Self::ConfigureSlave),
0x03 => Ok(Self::EnableSlave),
0x04 => Ok(Self::DisableSlave),
0x05 => Ok(Self::EnableSlaveNotification),
0x06 => Ok(Self::DisableSlaveNotification),
0x07 => Ok(Self::SlaveReceive),
0x08 => Ok(Self::SlaveSetResponse),
_ => Err(I2cError::InvalidOperation),
}
}
}
/// Kind of event returned by `SlaveWaitEvent`.
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlaveEvent {
/// Master wrote data to our slave address.
DataReceived = 0x00,
/// Master issued a read from our slave address.
ReadRequest = 0x01,
/// Master stopped the transaction (stop condition).
Stop = 0x02,
}
impl TryFrom<u8> for SlaveEvent {
type Error = I2cError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::DataReceived),
0x01 => Ok(Self::ReadRequest),
0x02 => Ok(Self::Stop),
_ => Err(I2cError::InvalidOperation),
}
}
}
/// Kind of a single bus operation within a transaction.
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cOpKind {
Write = 0x00,
Read = 0x01,
}
impl TryFrom<u8> for I2cOpKind {
type Error = I2cError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x00 => Ok(Self::Write),
0x01 => Ok(Self::Read),
_ => Err(I2cError::InvalidOperation),
}
}
}
/// Status / error code carried in `I2cResponseHeader`.
///
/// The bus-failure variants map 1-to-1 onto `embedded_hal::i2c::ErrorKind`
/// (see [`crate::seam::error_kind`]).
#[non_exhaustive]
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cError {
InvalidOperation = 0x01,
BufferTooSmall = 0x02,
TooManyOperations = 0x03,
/// NACK on the address phase.
AddressNack = 0x04,
/// NACK on a data byte.
DataNack = 0x05,
/// NACK, phase unknown.
Nack = 0x06,
ArbitrationLoss = 0x07,
Bus = 0x08,
Overrun = 0x09,
Timeout = 0x0A,
/// `SlaveReceive` found nothing latched (no slave data pending).
NoData = 0x0B,
InternalError = 0xFF,
}
impl From<u8> for I2cError {
fn from(value: u8) -> Self {
match value {
0x01 => Self::InvalidOperation,
0x02 => Self::BufferTooSmall,
0x03 => Self::TooManyOperations,
0x04 => Self::AddressNack,
0x05 => Self::DataNack,
0x06 => Self::Nack,
0x07 => Self::ArbitrationLoss,
0x08 => Self::Bus,
0x09 => Self::Overrun,
0x0A => Self::Timeout,
0x0B => Self::NoData,
_ => Self::InternalError,
}
}
}
impl core::fmt::Display for I2cError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidOperation => f.write_str("invalid i2c operation"),
Self::BufferTooSmall => f.write_str("buffer too small"),
Self::TooManyOperations => f.write_str("too many operations"),
Self::AddressNack => f.write_str("nack on address phase"),
Self::DataNack => f.write_str("nack on data byte"),
Self::Nack => f.write_str("nack (phase unknown)"),
Self::ArbitrationLoss => f.write_str("arbitration loss"),
Self::Bus => f.write_str("bus error"),
Self::Overrun => f.write_str("overrun"),
Self::Timeout => f.write_str("timeout"),
Self::NoData => f.write_str("no slave data pending"),
Self::InternalError => f.write_str("internal i2c server error"),
}
}
}
impl core::error::Error for I2cError {}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct I2cRequestHeader {
pub(crate) op_code: u8,
pub(crate) flags: u8,
/// Target address. 7-bit address in the low 7 bits; 10-bit reserved.
pub(crate) address: u16,
/// Number of `I2cOpDesc` records that follow this header.
pub(crate) op_count: u16,
/// Total bytes after the header (op descriptors + inline write data).
pub(crate) payload_len: u16,
}
impl I2cRequestHeader {
pub const SIZE: usize = core::mem::size_of::<Self>();
pub fn new(op: I2cOp, address: u16, op_count: u16, payload_len: u16) -> Self {
Self {
op_code: op as u8,
flags: 0,
address: address.to_le(),
op_count: op_count.to_le(),
payload_len: payload_len.to_le(),
}
}
pub fn operation(&self) -> Result<I2cOp, I2cError> {
I2cOp::try_from(self.op_code)
}
pub fn address_value(&self) -> u16 {
u16::from_le(self.address)
}
pub fn op_count_value(&self) -> usize {
u16::from_le(self.op_count) as usize
}
pub fn payload_length(&self) -> usize {
u16::from_le(self.payload_len) as usize
}
}
/// One operation descriptor in the request body. For `Write`, `len` bytes of
/// inline payload follow (concatenated across ops, in order); for `Read`,
/// `len` is the number of bytes expected back in the response payload.
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct I2cOpDesc {
pub(crate) kind: u8,
pub(crate) reserved: u8,
pub(crate) len: u16,
}
impl I2cOpDesc {
pub const SIZE: usize = core::mem::size_of::<Self>();
pub fn new(kind: I2cOpKind, len: u16) -> Self {
Self {
kind: kind as u8,
reserved: 0,
len: len.to_le(),
}
}
pub fn op_kind(&self) -> Result<I2cOpKind, I2cError> {
I2cOpKind::try_from(self.kind)
}
pub fn length(&self) -> usize {
u16::from_le(self.len) as usize
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct I2cResponseHeader {
pub(crate) status: u8,
pub(crate) reserved: u8,
/// Total read-payload bytes following this header.
pub(crate) payload_len: u16,
}
impl I2cResponseHeader {
pub const SIZE: usize = core::mem::size_of::<Self>();
pub fn success(payload_len: u16) -> Self {
Self {
status: 0,
reserved: 0,
payload_len: payload_len.to_le(),
}
}
pub fn error(error: I2cError) -> Self {
Self {
status: error as u8,
reserved: 0,
payload_len: 0,
}
}
pub fn is_success(&self) -> bool {
self.status == 0
}
pub fn error_code(&self) -> Option<I2cError> {
if self.is_success() {
None
} else {
Some(I2cError::from(self.status))
}
}
pub fn payload_length(&self) -> usize {
u16::from_le(self.payload_len) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
use zerocopy::{FromBytes, IntoBytes};
#[test]
fn request_header_roundtrips_through_bytes() {
let h = I2cRequestHeader::new(I2cOp::Transaction, 0x48, 3, 40);
let bytes = h.as_bytes();
assert_eq!(bytes.len(), I2cRequestHeader::SIZE);
let decoded = I2cRequestHeader::ref_from_bytes(bytes).unwrap();
assert_eq!(decoded.operation(), Ok(I2cOp::Transaction));
assert_eq!(decoded.address_value(), 0x48);
assert_eq!(decoded.op_count_value(), 3);
assert_eq!(decoded.payload_length(), 40);
}
#[test]
fn op_desc_roundtrips_and_decodes_kind() {
for (kind, raw) in [(I2cOpKind::Write, 0u8), (I2cOpKind::Read, 1u8)] {
let d = I2cOpDesc::new(kind, 17);
let bytes = d.as_bytes();
assert_eq!(bytes.len(), I2cOpDesc::SIZE);
let decoded = I2cOpDesc::ref_from_bytes(bytes).unwrap();
assert_eq!(decoded.length(), 17);
assert_eq!(decoded.kind, raw);
assert_eq!(decoded.op_kind(), Ok(kind));
}
}
#[test]
fn response_header_success_and_error() {
let ok = I2cResponseHeader::success(12);
let ok = I2cResponseHeader::ref_from_bytes(ok.as_bytes()).unwrap();
assert!(ok.is_success());
assert_eq!(ok.payload_length(), 12);
let err = I2cResponseHeader::error(I2cError::AddressNack);
let err = I2cResponseHeader::ref_from_bytes(err.as_bytes()).unwrap();
assert!(!err.is_success());
assert_eq!(err.error_code(), Some(I2cError::AddressNack));
assert_eq!(err.payload_length(), 0);
}
#[test]
fn error_and_op_byte_mapping_is_stable() {
for raw in 0x01u8..=0x0B {
assert_eq!(I2cError::from(raw) as u8, raw);
}
// 0x00 is the success sentinel on the wire — it is not an I2cError variant.
assert_eq!(I2cError::from(0x00), I2cError::InternalError);
assert_eq!(I2cError::from(0x0B), I2cError::NoData);
assert_eq!(I2cError::from(0xFF), I2cError::InternalError);
assert_eq!(I2cError::from(0x42), I2cError::InternalError);
assert_eq!(I2cOp::try_from(0x01), Ok(I2cOp::Transaction));
assert_eq!(I2cOp::try_from(0x99), Err(I2cError::InvalidOperation));
}
#[test]
fn slave_opcodes_roundtrip() {
for (raw, op) in [
(0x02u8, I2cOp::ConfigureSlave),
(0x03, I2cOp::EnableSlave),
(0x04, I2cOp::DisableSlave),
(0x05, I2cOp::EnableSlaveNotification),
(0x06, I2cOp::DisableSlaveNotification),
(0x07, I2cOp::SlaveReceive),
(0x08, I2cOp::SlaveSetResponse),
] {
assert_eq!(I2cOp::try_from(raw), Ok(op));
assert_eq!(op as u8, raw);
}
}
#[test]
fn slave_event_kinds_roundtrip() {
for (raw, kind) in [
(0x00u8, SlaveEvent::DataReceived),
(0x01, SlaveEvent::ReadRequest),
(0x02, SlaveEvent::Stop),
] {
assert_eq!(SlaveEvent::try_from(raw), Ok(kind));
assert_eq!(kind as u8, raw);
}
assert_eq!(SlaveEvent::try_from(0xFF), Err(I2cError::InvalidOperation));
}
#[test]
fn configure_slave_header_carries_address() {
// ConfigureSlave: address field = 7-bit slave address, no payload.
let h = I2cRequestHeader::new(I2cOp::ConfigureSlave, 0x39, 0, 0);
let h = I2cRequestHeader::ref_from_bytes(h.as_bytes()).unwrap();
assert_eq!(h.operation(), Ok(I2cOp::ConfigureSlave));
assert_eq!(h.address_value(), 0x39);
assert_eq!(h.payload_length(), 0);
}
#[test]
fn slave_receive_header_carries_max_len_in_op_count() {
// SlaveReceive: op_count field = caller's max RX length.
let h = I2cRequestHeader::new(I2cOp::SlaveReceive, 0, 64, 0);
let h = I2cRequestHeader::ref_from_bytes(h.as_bytes()).unwrap();
assert_eq!(h.operation(), Ok(I2cOp::SlaveReceive));
assert_eq!(h.op_count_value(), 64);
// NoData status round-trips for the empty-latch case.
let nd = I2cResponseHeader::error(I2cError::NoData);
let nd = I2cResponseHeader::ref_from_bytes(nd.as_bytes()).unwrap();
assert!(!nd.is_success());
assert_eq!(nd.error_code(), Some(I2cError::NoData));
}
}