Skip to content

Commit

Permalink
redesign i2c slave async
Browse files Browse the repository at this point in the history
- listen(): wait for address and determine read/write

- respond_to_read(): transmit to master. We are requiring the user of
  this API to pass in enough data tocomplete the transaction

- respond_to_write(): receive from master

Found HW race conditions:

- We would get the DMA complete interrupt where the slave status is in
  addressed (0x0) instead of transmit (0x2). Then we are just stuck
  because the master is still expecting more data and we didn't exit the
  poll_fn to rearm the DMA to provide more data. That is why we have the
  restriction for respond_to_read()

- respond_to_read() would exit the poll_fn with 0x4C01 with DMA
  completed and slave state being transmit. However, shortly it would be
  become i2c status of 0x4801, where slave state is addressed and not
  deselected. Then 0x8101 where the slave state is addressed and
  deselected.

- Auto-ack and auto-match, after an ack is sent by auto-ack, auto-ack is
  not rearmed. So if a second transaction comes in quickly while we are
  still handling the first transaction, then we have to manually ack the
  address phase of the second transaction.
  • Loading branch information
jerrysxie authored and Matteo Tullo committed Nov 8, 2024
1 parent 84c348a commit 9f8c6a4
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 91 deletions.
59 changes: 37 additions & 22 deletions examples/rt685s-evk/src/bin/i2c-slave-async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,55 @@

extern crate embassy_imxrt_examples;

use defmt::{error, info};
use defmt::info;
use embassy_executor::Spawner;
use embassy_imxrt::i2c::{
slave::{Address, I2cSlave},
slave::{Address, Command, I2cSlave, Response},
Async,
};
use embassy_imxrt::pac;
use embassy_imxrt::peripherals::{DMA0_CH4, FLEXCOMM2};

const SLAVE_ADDR: Option<Address> = Address::new(0x20);
const BUFLEN: usize = 8;

#[embassy_executor::task]
async fn slave_service(mut i2c: I2cSlave<'static, FLEXCOMM2, Async, DMA0_CH4>) {
loop {
let magic_code = [0xF0, 0x05, 0xBA, 0x11];

let mut cmd_length: [u8; 1] = [0xAA; 1];
info!("i2cs example - wait for cmd - read cmd length first");
i2c.listen(&mut cmd_length, false).await.unwrap();
info!("cmd length = {:02X}", cmd_length);

let mut cmd: [u8; 4] = [0xAA; 4];
info!("i2cs example - wait for cmd - read the actual cmd");
i2c.listen(&mut cmd, true).await.unwrap();
info!("cmd length = {:02X}", cmd_length);

if cmd == [0xDE, 0xAD, 0xBE, 0xEF] {
info!("i2cs example - receive init cmd");
} else if cmd == [0xDE, 0xCA, 0xFB, 0xAD] {
info!("i2cs example - receive magic cmd, writing back magic code to host");
i2c.respond(&magic_code).await.unwrap();
} else {
error!("unexpected cmd = {:02X}", cmd);
panic!("i2cs example - unexpected cmd");
let mut buf: [u8; BUFLEN] = [0xAA; BUFLEN];

for (i, e) in buf.iter_mut().enumerate() {
*e = i as u8;
}

match i2c.listen().await.unwrap() {
Command::Probe => {
info!("Probe, nothing to do");
}
Command::Read => {
info!("Read");
loop {
match i2c.respond_to_read(&buf).await.unwrap() {
Response::Complete(_) => {
info!("Response complete read with {} bytes", n);
break;
}
Response::Pending(n) => info!("Response to read got {} bytes, more bytes to fill", n),
}
}
}
Command::Write => {
info!("Write");
loop {
match i2c.respond_to_write(&mut buf).await.unwrap() {
Response::Complete(_) => {
info!("Response complete write with {} bytes", n);
break;
}
Response::Pending(n) => info!("Response to write got {} bytes, more bytes pending", n),
}
}
}
}
}
}
Expand Down
228 changes: 159 additions & 69 deletions src/i2c/slave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ impl From<Address> for u8 {
}
}

/// Command from master
pub enum Command {
/// I2C probe with no data
Probe,

/// I2C Read
Read,

/// I2C Write
Write,
}

/// Result of response functions
pub enum Response {
/// I2C transaction complete with this amount of bytes
Complete(usize),

/// I2C transaction pending wutg this amount of bytes completed so far
Pending(usize),
}

/// use `FCn` as I2C Slave controller
pub struct I2cSlave<'a, FC: Instance, M: Mode, D: dma::Instance> {
bus: crate::flexcomm::I2cBus<'a, FC>,
Expand Down Expand Up @@ -152,39 +173,6 @@ impl<'a, FC: Instance, D: dma::Instance> I2cSlave<'a, FC, Async, D> {
let ch = dma::Dma::reserve_channel(dma_ch);
Self::new_inner(bus, scl, sda, address, Some(ch))
}

async fn block_until_addressed(&self) -> Result<()> {
let i2c = self.bus.i2c();

i2c.intenset()
.write(|w| w.slvpendingen().set_bit().slvdeselen().set_bit());

poll_fn(|cx: &mut core::task::Context<'_>| {
self.bus.waker().register(cx.waker());
if i2c.stat().read().slvpending().bit_is_set() {
return Poll::Ready(());
}

if i2c.stat().read().slvdesel().bit_is_set() {
i2c.stat().write(|w| w.slvdesel().deselected());
return Poll::Ready(());
}

Poll::Pending
})
.await;

i2c.intenclr()
.write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit());

if !i2c.stat().read().slvstate().is_slave_address() {
return Err(TransferError::AddressNack.into());
}

i2c.slvctl().modify(|_, w| w.slvcontinue().continue_());

Ok(())
}
}

impl<FC: Instance, D: dma::Instance> I2cSlave<'_, FC, Blocking, D> {
Expand Down Expand Up @@ -238,16 +226,54 @@ impl<FC: Instance, D: dma::Instance> I2cSlave<'_, FC, Blocking, D> {

impl<FC: Instance, D: dma::Instance> I2cSlave<'_, FC, Async, D> {
/// Listen for commands from the I2C Master asynchronously
pub async fn listen(&mut self, request: &mut [u8], expect_stop: bool) -> Result<()> {
pub async fn listen(&mut self) -> Result<Command> {
let i2c = self.bus.i2c();

// Skip address phase if we are already in receive mode
if !i2c.stat().read().slvstate().is_slave_receive() {
self.block_until_addressed().await?;
// Disable DMA
i2c.slvctl().write(|w| w.slvdma().disabled());

// Check whether we already have a matched address and just waiting
// for software ack/nack
if !i2c.stat().read().slvpending().is_pending() {
self.poll_sw_action().await;
}

// Verify that we are ready to receive after addressed
if !i2c.stat().read().slvstate().is_slave_receive() {
if i2c.stat().read().slvstate().is_slave_address() {
i2c.slvctl().write(|w| w.slvcontinue().continue_());
} else {
// If we are not addressed here, then we have issues.
return Err(TransferError::OtherBusError.into());
}

// Poll for HW to transitioning from addressed to receive/transmit
self.poll_sw_action().await;

// We are deselected, so it must be an 0 byte write transaction
if i2c.stat().read().slvdesel().is_deselected() {
// Clear the deselected bit
i2c.stat().write(|w| w.slvdesel().deselected());
return Ok(Command::Probe);
}

let state = i2c.stat().read().slvstate().variant();
match state {
Some(crate::pac::i2c0::stat::Slvstate::SlaveReceive) => Ok(Command::Write),
Some(crate::pac::i2c0::stat::Slvstate::SlaveTransmit) => Ok(Command::Read),
_ => Err(TransferError::OtherBusError.into()),
}
}

/// Respond to write command from master
pub async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result<Response> {
let i2c = self.bus.i2c();

// Verify that we are ready for write
let stat = i2c.stat().read();
if !stat.slvstate().is_slave_receive() {
// 0 byte write
if stat.slvdesel().is_deselected() {
return Ok(Response::Complete(0));
}
return Err(TransferError::ReadFail.into());
}

Expand All @@ -256,90 +282,154 @@ impl<FC: Instance, D: dma::Instance> I2cSlave<'_, FC, Async, D> {

// Enable interrupt
i2c.intenset()
.write(|w| w.slvpendingen().set_bit().slvdeselen().set_bit());
.write(|w| w.slvpendingen().enabled().slvdeselen().enabled());

let options = dma::transfer::TransferOptions::default();
self.dma_ch
.as_mut()
.unwrap()
.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, request, options);
.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, buf, options);

poll_fn(|cx| {
let i2c = self.bus.i2c();
let dma = self.dma_ch.as_ref().unwrap();
self.bus.waker().register(cx.waker());
self.dma_ch.as_ref().unwrap().get_waker().register(cx.waker());
dma.get_waker().register(cx.waker());

//check for readyness
if i2c.stat().read().slvpending().bit_is_set() {
let stat = i2c.stat().read();
// Did master send a stop?
if stat.slvdesel().is_deselected() {
return Poll::Ready(());
}

if i2c.stat().read().slvdesel().bit_is_set() {
i2c.stat().write(|w| w.slvdesel().deselected());
// Does SW need to intervene?
if stat.slvpending().is_pending() {
return Poll::Ready(());
}

// Only check DMA status if we are not expecting a stop
if !expect_stop && !self.dma_ch.as_ref().unwrap().is_active() {
// Did we complete the DMA transfer and does the master still have more data for us?
if !dma.is_active() && stat.slvstate().is_slave_receive() {
return Poll::Ready(());
}

Poll::Pending
})
.await;

// Disable interrupts
i2c.intenclr()
.write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit());
// Complete DMA transaction and get transfer count
let xfer_count = self.abort_dma(buf.len());
let stat = i2c.stat().read();
// We got a stop from master, either way this transaction is
// completed
if stat.slvdesel().is_deselected() {
// Clear the deselected bit
i2c.stat().write(|w| w.slvdesel().deselected());

return Ok(Response::Complete(xfer_count));
} else if stat.slvstate().is_slave_address() {
// We are addressed again, so this must be a restart
return Ok(Response::Complete(xfer_count));
} else if stat.slvstate().is_slave_receive() {
// That was a partial transaction, the master want to send more
// data
return Ok(Response::Pending(xfer_count));
}

Ok(())
Err(TransferError::ReadFail.into())
}

/// Respond to commands from the I2C Master asynchronously
pub async fn respond(&mut self, response: &[u8]) -> Result<()> {
/// Respond to read command from master
/// User must provide enough data to complete the transaction or else
/// we will get stuck in this function
pub async fn respond_to_read(&mut self, buf: &[u8]) -> Result<Response> {
let i2c = self.bus.i2c();
self.block_until_addressed().await?;

// Verify that we are ready for transmit after addressed
// Verify that we are ready for transmit
if !i2c.stat().read().slvstate().is_slave_transmit() {
return Err(TransferError::WriteFail.into());
}

// Enable DMA
i2c.slvctl().write(|w| w.slvdma().enabled());

// Enable interrupt
// Enable interrupts
i2c.intenset()
.write(|w| w.slvpendingen().set_bit().slvdeselen().set_bit());
.write(|w| w.slvpendingen().enabled().slvdeselen().enabled());

let options = dma::transfer::TransferOptions::default();
self.dma_ch
.as_mut()
.unwrap()
.write_to_peripheral(response, i2c.slvdat().as_ptr() as *mut u8, options);
.write_to_peripheral(buf, i2c.slvdat().as_ptr() as *mut u8, options);

poll_fn(|cx| {
let i2c = self.bus.i2c();
let dma = self.dma_ch.as_ref().unwrap();
self.bus.waker().register(cx.waker());
self.dma_ch.as_ref().unwrap().get_waker().register(cx.waker());
dma.get_waker().register(cx.waker());

if i2c.stat().read().slvpending().bit_is_set() {
let stat = i2c.stat().read();
// Master sent a nack or stop
if stat.slvdesel().is_deselected() {
return Poll::Ready(());
}
// We need SW intervention
if stat.slvpending().is_pending() {
return Poll::Ready(());
}

Poll::Pending
})
.await;

// Complete DMA transaction and get transfer count
let xfer_count = self.abort_dma(buf.len());
let stat = i2c.stat().read();

// we got a nack or a stopfrom master, either way this transaction is
// completed
if stat.slvdesel().is_deselected() {
// clear the deselect bit
i2c.stat().write(|w| w.slvdesel().deselected());

return Ok(Response::Complete(xfer_count));
}

if i2c.stat().read().slvdesel().bit_is_set() {
i2c.stat().write(|w| w.slvdesel().deselected());
// We should not get here
Err(TransferError::WriteFail.into())
}

async fn poll_sw_action(&self) {
let i2c = self.bus.i2c();

i2c.intenset()
.write(|w| w.slvpendingen().enabled().slvdeselen().enabled());

poll_fn(|cx: &mut core::task::Context<'_>| {
self.bus.waker().register(cx.waker());

let stat = i2c.stat().read();
if stat.slvdesel().is_deselected() {
return Poll::Ready(());
}
if stat.slvpending().is_pending() {
return Poll::Ready(());
}

Poll::Pending
})
.await;
}

// Disable interrupts
i2c.intenclr()
.write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit());
/// Complete DMA and return bytes transfer
fn abort_dma(&self, xfer_size: usize) -> usize {
// abort DMA if DMA is not compelted
let dma = self.dma_ch.as_ref().unwrap();
let remain_xfer_count = dma.get_xfer_count();
let mut xfer_count = xfer_size;
if dma.is_active() && remain_xfer_count != 0x3FF {
xfer_count -= remain_xfer_count as usize + 1;
dma.abort();
}

Ok(())
xfer_count
}
}

0 comments on commit 9f8c6a4

Please sign in to comment.