Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hal/blocking/src/i2c_hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ pub mod slave {
SlaveRdProc,
/// Slave has received write data from master
SlaveWrRecvd,
/// Slave received write data and the same hardware status included STOP
SlaveWrRecvdStop,
/// Stop condition received
SlaveStop,
}
Expand Down
3 changes: 2 additions & 1 deletion platform/impls/baremetal/mock/src/i2c_hardware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,8 @@ impl MockI2cHardware {

// Simulate event handling based on event type
match event {
openprot_hal_blocking::i2c_hardware::slave::I2cIsrEvent::SlaveWrRecvd => {
openprot_hal_blocking::i2c_hardware::slave::I2cIsrEvent::SlaveWrRecvd
| openprot_hal_blocking::i2c_hardware::slave::I2cIsrEvent::SlaveWrRecvdStop => {
// Simulate receiving data using safe injection
self.inject_slave_data(&[0xAA, 0xBB, 0xCC]);
}
Expand Down
3 changes: 0 additions & 3 deletions services/i2c/api/src/seam.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ pub trait I2cSlaveEvent: I2cSlaveBuffer {
}
}

/// Blanket impl so all I2cSlaveBuffer types get the default behavior.
impl<T: I2cSlaveBuffer> I2cSlaveEvent for T {}

/// Map a wire status code onto the `embedded_hal::i2c::ErrorKind` taxonomy so
/// the client can satisfy `embedded_hal::i2c::Error`.
pub fn error_kind(err: I2cError) -> ErrorKind {
Expand Down
163 changes: 107 additions & 56 deletions services/i2c/server-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
//!
//! 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
//! the slave RX into that bus's event queue, `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).
//! `SlaveReceive`, which returns the oldest queued event (status `NoData` if empty).
//! Arm/disarm is `EnableSlaveNotification` / `DisableSlaveNotification`.
//!
//! The **only** kernel-tagged server crate; it wraps the host-buildable
Expand All @@ -33,8 +33,10 @@ use i2c_server::{dispatch, MAX_BUF_SIZE};
use userspace::syscall::{self, Signals};
use userspace::time::Instant;

const SLAVE_EVENT_QUEUE_DEPTH: usize = 4;

/// One bus the server owns: its dedicated IPC channel, IRQ handle, the driver instance
/// (master + slave), and the per-bus slave-RX notification latch.
/// (master + slave), and the per-bus slave event notification queue.
pub struct Bus<B> {
/// IPC channel handle (`channel_handler`) dedicated to this bus.
pub channel: u32,
Expand All @@ -43,14 +45,13 @@ pub struct Bus<B> {
/// 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,
event_data: [[u8; MAX_PAYLOAD_SIZE]; SLAVE_EVENT_QUEUE_DEPTH],
event_len: [usize; SLAVE_EVENT_QUEUE_DEPTH],
event_source: [u8; SLAVE_EVENT_QUEUE_DEPTH],
event_kind: [SlaveEvent; SLAVE_EVENT_QUEUE_DEPTH],
event_head: usize,
event_tail: usize,
event_count: usize,
}

impl<B> Bus<B> {
Expand All @@ -60,11 +61,59 @@ impl<B> Bus<B> {
irq,
driver,
notif_enabled: false,
rx: [0u8; MAX_PAYLOAD_SIZE],
rx_len: 0,
rx_source: 0,
rx_event_kind: SlaveEvent::DataReceived,
event_data: [[0u8; MAX_PAYLOAD_SIZE]; SLAVE_EVENT_QUEUE_DEPTH],
event_len: [0; SLAVE_EVENT_QUEUE_DEPTH],
event_source: [0; SLAVE_EVENT_QUEUE_DEPTH],
event_kind: [SlaveEvent::DataReceived; SLAVE_EVENT_QUEUE_DEPTH],
event_head: 0,
event_tail: 0,
event_count: 0,
}
}

fn clear_slave_events(&mut self) {
self.event_head = 0;
self.event_tail = 0;
self.event_count = 0;
self.event_len = [0; SLAVE_EVENT_QUEUE_DEPTH];
}

fn has_slave_event(&self) -> bool {
self.event_count > 0
}

fn push_slave_event(&mut self, kind: SlaveEvent, source: u8, data: &[u8]) -> bool {
if self.event_count == SLAVE_EVENT_QUEUE_DEPTH {
return false;
}

let slot = self.event_tail;
let n = data.len().min(MAX_PAYLOAD_SIZE);
if n > 0 {
self.event_data[slot][..n].copy_from_slice(&data[..n]);
}
self.event_len[slot] = n;
self.event_source[slot] = source;
self.event_kind[slot] = kind;
self.event_tail = (self.event_tail + 1) % SLAVE_EVENT_QUEUE_DEPTH;
self.event_count += 1;
true
}

/// Push an event, logging if the queue is full (the common call shape).
fn push_or_warn(&mut self, kind: SlaveEvent, source: u8, data: &[u8]) {
if !self.push_slave_event(kind, source, data) {
pw_log::error!("slave event queue full");
}
}

fn pop_slave_event(&mut self) {
if self.event_count == 0 {
return;
}
self.event_len[self.event_head] = 0;
self.event_head = (self.event_head + 1) % SLAVE_EVENT_QUEUE_DEPTH;
self.event_count -= 1;
}
}

Expand Down Expand Up @@ -111,6 +160,7 @@ where

let mut request_buf = [0u8; MAX_BUF_SIZE];
let mut response_buf = [0u8; MAX_BUF_SIZE];
let mut slave_event_buf = [0u8; MAX_PAYLOAD_SIZE];
let wait_mask = Signals::READABLE | irq_signals;

loop {
Expand All @@ -128,35 +178,36 @@ where
if bus.notif_enabled {
match bus.driver.try_next_slave_event() {
Ok(Some((kind, _))) => {
// Store the actual hardware event kind
bus.rx_event_kind = match kind {
I2cIsrEvent::SlaveWrRecvd => SlaveEvent::DataReceived,
I2cIsrEvent::SlaveRdReq => SlaveEvent::ReadRequest,
I2cIsrEvent::SlaveStop => SlaveEvent::Stop,
_ => SlaveEvent::DataReceived,
};
// For DataReceived, read the buffer; other events have no data
if kind == I2cIsrEvent::SlaveWrRecvd {
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
}
}
match kind {
I2cIsrEvent::SlaveWrRecvd
| I2cIsrEvent::SlaveWrRecvdStop => {
// Plain offset-addressed mailbox: RX is
// [offset, data...] with no MCTP source header
// (AST_I2CC_SLAVE_PKT_SAVE_ADDR is cleared). No
// source to extract; the client defaults to port 0.
let stop_after_data =
kind == I2cIsrEvent::SlaveWrRecvdStop;
slave_event_buf.fill(0);
match bus.driver.read_slave_buffer(&mut slave_event_buf) {
Ok(n) if n > 0 => bus.push_or_warn(
SlaveEvent::DataReceived,
0xFF,
&slave_event_buf[..n],
),
Ok(_) => {}
Err(_) => pw_log::error!("read_slave_buffer failed"),
}
Err(_) => {
pw_log::error!("read_slave_buffer failed");
if stop_after_data {
bus.push_or_warn(SlaveEvent::Stop, 0xFF, &[]);
}
}
I2cIsrEvent::SlaveRdReq | I2cIsrEvent::SlaveRdProc => {
bus.push_or_warn(SlaveEvent::ReadRequest, 0xFF, &[]);
}
I2cIsrEvent::SlaveStop => {
bus.push_or_warn(SlaveEvent::Stop, 0xFF, &[]);
}
I2cIsrEvent::SlaveWrReq => {}
}
}
Ok(None) => {
Expand All @@ -172,11 +223,7 @@ where
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 {
if bus.notif_enabled && bus.has_slave_event() {
// 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");
Expand Down Expand Up @@ -230,13 +277,11 @@ where
}
Some((I2cOp::DisableSlaveNotification, _)) => {
bus.notif_enabled = false;
bus.rx_len = 0;
bus.rx_source = 0;
bus.rx_event_kind = SlaveEvent::DataReceived;
bus.clear_slave_events();
encode_ok(&mut response_buf, 0)
}
Some((I2cOp::SlaveReceive, max_len)) => {
if bus.rx_len == 0 {
if !bus.has_slave_event() {
encode_error(&mut response_buf, I2cError::NoData)
} else {
// Response payload: [kind (1), source_addr (1), data (0..max_len)]
Expand All @@ -245,14 +290,20 @@ where
if cap < metadata_size {
encode_error(&mut response_buf, I2cError::BufferTooSmall)
} else {
let slot = bus.event_head;
let data_cap = cap - metadata_size;
let n = bus.rx_len.min(max_len).min(data_cap);
let n = bus.event_len[slot].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;
response_buf[payload_offset] = bus.event_kind[slot] as u8;
response_buf[payload_offset + 1] = bus.event_source[slot];
if n > 0 {
response_buf[payload_offset + 2..payload_offset + 2 + n]
.copy_from_slice(&bus.event_data[slot][..n]);
}
bus.pop_slave_event();
if bus.has_slave_event() {
let _ = syscall::object_set_peer_user_signal(bus.channel, true);
}
encode_ok(&mut response_buf, metadata_size + n)
}
}
Expand Down
39 changes: 39 additions & 0 deletions target/ast10x0/backend/i2c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@

use ast1060_pac::{i2c::RegisterBlock, i2cbuff::RegisterBlock as BuffRegisterBlock};
use embedded_hal::i2c::{ErrorType, I2c, Operation, SevenBitAddress};
use i2c_api::seam::{I2cIsrEvent, I2cSlaveEvent};
use openprot_hal_blocking::i2c_hardware::slave::{I2cSlaveBuffer, I2cSlaveCore};
use openprot_hal_blocking::i2c_hardware::I2cBusRecovery;

pub use ast10x0_peripherals::i2c::{
Ast1060I2c, Ast1060I2cRegisters, ClockConfig, I2cConfig, I2cError, I2cSpeed, I2cXferMode,
SlaveEvent,
};

/// The yield closure type stored in every bus driver.
Expand Down Expand Up @@ -192,6 +194,43 @@ impl I2cSlaveBuffer<SevenBitAddress> for Ast1060I2cBackend {
}
}

impl Ast1060I2cBackend {
/// True when the controller is clock-stretching a master read, waiting for
/// the firmware to load the TX byte. Lets the server serve a read inline.
pub fn slave_waiting_for_tx(&mut self) -> bool {
self.make_driver().slave_waiting_for_tx()
}

/// Debug: (i2cs20 irq-enable mask, i2cc00 control, i2cs24 status).
pub fn dbg_slave_regs(&mut self) -> (u32, u32, u32) {
self.make_driver().dbg_slave_regs()
}
}

impl I2cSlaveEvent for Ast1060I2cBackend {
fn try_next_slave_event(&mut self) -> Result<Option<(I2cIsrEvent, usize)>, Self::Error> {
let event = {
let mut driver = self.make_driver();
driver.handle_slave_interrupt()
};

Ok(match event {
Some(SlaveEvent::ReadRequest) => Some((I2cIsrEvent::SlaveRdReq, 0)),
Some(SlaveEvent::WriteRequest) => Some((I2cIsrEvent::SlaveWrReq, 0)),
Some(SlaveEvent::DataReceived { len }) => Some((I2cIsrEvent::SlaveWrRecvd, len)),
Some(SlaveEvent::DataReceivedStop { len }) => {
Some((I2cIsrEvent::SlaveWrRecvdStop, len))
}
Some(SlaveEvent::DataReceivedAndSent { rx_len, .. }) => {
Some((I2cIsrEvent::SlaveWrRecvd, rx_len))
}
Some(SlaveEvent::DataSent { len }) => Some((I2cIsrEvent::SlaveRdProc, len)),
Some(SlaveEvent::Stop) => Some((I2cIsrEvent::SlaveStop, 0)),
None => None,
})
}
}

impl I2cBusRecovery for Ast1060I2cBackend {
fn recover_bus(&mut self) -> Result<(), Self::Error> {
self.make_driver().recover_bus()
Expand Down
12 changes: 12 additions & 0 deletions target/ast10x0/peripherals/i2c/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ pub const AST_I2CS_PKT_ERROR: u32 = 1 << 17;
pub const AST_I2CS_WAIT_TX_DMA: u32 = 1 << 25;
/// Waiting for RX DMA
pub const AST_I2CS_WAIT_RX_DMA: u32 = 1 << 24;
/// Slave transaction is pending
pub const AST_I2CS_SLAVE_PENDING: u32 = 1 << 29;
/// Encoded matched-address indicator bits
pub const AST_I2CS_ADDR_INDICATE_MASK: u32 = 3 << 30;
/// Address 3 NAK status
pub const AST_I2CS_ADDR3_NAK: u32 = 1 << 22;
/// Address 2 NAK status
pub const AST_I2CS_ADDR2_NAK: u32 = 1 << 21;
/// Address 1 NAK status
pub const AST_I2CS_ADDR1_NAK: u32 = 1 << 20;
/// Address status field
pub const AST_I2CS_ADDR_MASK: u32 = 3 << 18;

/// Helper to build packet mode address field
#[inline]
Expand Down
3 changes: 2 additions & 1 deletion target/ast10x0/peripherals/i2c/hal_slave_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn to_hal_event(ev: SlaveEvent) -> I2cIsrEvent {
SlaveEvent::DataReceived { .. } | SlaveEvent::DataReceivedAndSent { .. } => {
I2cIsrEvent::SlaveWrRecvd
}
SlaveEvent::DataReceivedStop { .. } => I2cIsrEvent::SlaveWrRecvdStop,
SlaveEvent::DataSent { .. } => I2cIsrEvent::SlaveRdProc,
SlaveEvent::Stop => I2cIsrEvent::SlaveStop,
}
Expand All @@ -40,7 +41,7 @@ fn to_hal_event(ev: SlaveEvent) -> I2cIsrEvent {
/// Bytes received in the event, if any (the only thing the drain path needs).
fn rx_len(ev: SlaveEvent) -> Option<usize> {
match ev {
SlaveEvent::DataReceived { len } => Some(len),
SlaveEvent::DataReceived { len } | SlaveEvent::DataReceivedStop { len } => Some(len),
SlaveEvent::DataReceivedAndSent { rx_len, .. } => Some(rx_len),
_ => None,
}
Expand Down
Loading