nrf: Implement chunked DMA transfers for SPIM peripheral

On some chips (notably nrf52832), the maximum DMA transfer is 255
bytes, which has caused subtle issues while interfacing with various
devices over SPI bus.
This commit is contained in:
Priit Laes 2024-02-15 12:34:51 +02:00
parent 5220453d85
commit 50b8100fd3
2 changed files with 81 additions and 44 deletions

View file

@ -17,7 +17,7 @@ use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
use crate::gpio::sealed::Pin as _; use crate::gpio::sealed::Pin as _;
use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
use crate::interrupt::typelevel::Interrupt; use crate::interrupt::typelevel::Interrupt;
use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; use crate::util::{slice_in_ram_or, slice_ptr_len, slice_ptr_parts, slice_ptr_parts_mut};
use crate::{interrupt, pac, Peripheral}; use crate::{interrupt, pac, Peripheral};
/// SPIM error /// SPIM error
@ -25,10 +25,6 @@ use crate::{interrupt, pac, Peripheral};
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Supplied TX buffer overflows EasyDMA transmit buffer
TxBufferTooLong,
/// Supplied RX buffer overflows EasyDMA receive buffer
RxBufferTooLong,
/// EasyDMA can only read from data memory, read only buffers in flash will fail. /// EasyDMA can only read from data memory, read only buffers in flash will fail.
BufferNotInRAM, BufferNotInRAM,
} }
@ -74,9 +70,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
let s = T::state(); let s = T::state();
#[cfg(feature = "_nrf52832_anomaly_109")] #[cfg(feature = "_nrf52832_anomaly_109")]
if r.events_started.read().bits() != 0 { {
s.waker.wake(); // Ideally we should call this only during the first chunk transfer,
r.intenclr.write(|w| w.started().clear()); // but so far calling this every time doesn't seem to be causing any issues.
if r.events_started.read().bits() != 0 {
s.waker.wake();
r.intenclr.write(|w| w.started().clear());
}
} }
if r.events_end.read().bits() != 0 { if r.events_end.read().bits() != 0 {
@ -209,35 +209,39 @@ impl<'d, T: Instance> Spim<'d, T> {
spim spim
} }
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM.
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
let r = T::regs(); let r = T::regs();
// Set up the DMA write. fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) {
let (ptr, tx_len) = slice_ptr_parts(tx); if total > offset {
if tx_len > EASY_DMA_SIZE { (ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length))
return Err(Error::TxBufferTooLong); } else {
(ptr, 0)
}
} }
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
// Set up the DMA read. // Set up the DMA read.
let (ptr, rx_len) = slice_ptr_parts_mut(rx); let (ptr, len) = slice_ptr_parts_mut(rx);
if rx_len > EASY_DMA_SIZE { let (rx_ptr, rx_len) = xfer_params(ptr as _, len as _, offset, length);
return Err(Error::RxBufferTooLong); r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx_ptr) });
}
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) }); r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
// Set up the DMA write.
let (ptr, len) = slice_ptr_parts(tx);
let (tx_ptr, tx_len) = xfer_params(ptr as _, len as _, offset, length);
r.txd.ptr.write(|w| unsafe { w.ptr().bits(tx_ptr) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
/*
trace!("XFER: offset: {}, length: {}", offset, length);
trace!("RX(len: {}, ptr: {=u32:02x})", rx_len, rx_ptr as u32);
trace!("TX(len: {}, ptr: {=u32:02x})", tx_len, tx_ptr as u32);
*/
#[cfg(feature = "_nrf52832_anomaly_109")] #[cfg(feature = "_nrf52832_anomaly_109")]
{ if offset == 0 {
let s = T::state(); let s = T::state();
r.events_started.reset(); r.events_started.reset();
@ -260,21 +264,32 @@ impl<'d, T: Instance> Spim<'d, T> {
// Start SPI transaction. // Start SPI transaction.
r.tasks_start.write(|w| unsafe { w.bits(1) }); r.tasks_start.write(|w| unsafe { w.bits(1) });
Ok(())
} }
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
self.prepare(rx, tx)?; self.prepare_dma_transfer(rx, tx, offset, length);
#[cfg(feature = "_nrf52832_anomaly_109")] #[cfg(feature = "_nrf52832_anomaly_109")]
while let Poll::Pending = self.nrf52832_dma_workaround_status() {} if offset == 0 {
while self.nrf52832_dma_workaround_status().is_pending() {}
}
// Wait for 'end' event. // Wait for 'end' event.
while T::regs().events_end.read().bits() == 0 {} while T::regs().events_end.read().bits() == 0 {}
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
}
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM.
let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
self.blocking_inner_from_ram_chunk(rx, tx, offset, length);
}
Ok(()) Ok(())
} }
@ -287,22 +302,23 @@ impl<'d, T: Instance> Spim<'d, T> {
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
self.blocking_inner_from_ram(rx, tx_ram_buf) self.blocking_inner_from_ram(rx, tx_ram_buf)
} }
Err(error) => Err(error),
} }
} }
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
self.prepare(rx, tx)?; self.prepare_dma_transfer(rx, tx, offset, length);
#[cfg(feature = "_nrf52832_anomaly_109")] #[cfg(feature = "_nrf52832_anomaly_109")]
poll_fn(|cx| { if offset == 0 {
let s = T::state(); poll_fn(|cx| {
let s = T::state();
s.waker.register(cx.waker()); s.waker.register(cx.waker());
self.nrf52832_dma_workaround_status() self.nrf52832_dma_workaround_status()
}) })
.await; .await;
}
// Wait for 'end' event. // Wait for 'end' event.
poll_fn(|cx| { poll_fn(|cx| {
@ -316,7 +332,18 @@ impl<'d, T: Instance> Spim<'d, T> {
.await; .await;
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
}
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
// NOTE: RAM slice check for rx is not necessary, as a mutable
// slice can only be built from data located in RAM.
let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
self.async_inner_from_ram_chunk(rx, tx, offset, length).await;
}
Ok(()) Ok(())
} }
@ -329,7 +356,6 @@ impl<'d, T: Instance> Spim<'d, T> {
tx_ram_buf.copy_from_slice(tx); tx_ram_buf.copy_from_slice(tx);
self.async_inner_from_ram(rx, tx_ram_buf).await self.async_inner_from_ram(rx, tx_ram_buf).await
} }
Err(error) => Err(error),
} }
} }
@ -528,8 +554,6 @@ mod eh02 {
impl embedded_hal_1::spi::Error for Error { impl embedded_hal_1::spi::Error for Error {
fn kind(&self) -> embedded_hal_1::spi::ErrorKind { fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
match *self { match *self {
Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other,
} }
} }

View file

@ -4,6 +4,19 @@ use core::mem;
const SRAM_LOWER: usize = 0x2000_0000; const SRAM_LOWER: usize = 0x2000_0000;
const SRAM_UPPER: usize = 0x3000_0000; const SRAM_UPPER: usize = 0x3000_0000;
// #![feature(const_slice_ptr_len)]
// https://github.com/rust-lang/rust/issues/71146
pub(crate) fn slice_ptr_len<T>(ptr: *const [T]) -> usize {
use core::ptr::NonNull;
let ptr = ptr.cast_mut();
if let Some(ptr) = NonNull::new(ptr) {
ptr.len()
} else {
// We know ptr is null, so we know ptr.wrapping_byte_add(1) is not null.
NonNull::new(ptr.wrapping_byte_add(1)).unwrap().len()
}
}
// TODO: replace transmutes with core::ptr::metadata once it's stable // TODO: replace transmutes with core::ptr::metadata once it's stable
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) { pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) {
unsafe { mem::transmute(slice) } unsafe { mem::transmute(slice) }