Merge pull request #2572 from plaes/nrf-spim-chunked-dma

NRF: Implement chunked DMA transfers for SPIM
This commit is contained in:
Ulf Lilleengen 2024-02-16 17:35:53 +00:00 committed by GitHub
commit e9907de39d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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::{self, AnyPin, Pin as GpioPin, PselBits};
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};
/// SPIM error
@ -25,10 +25,6 @@ use crate::{interrupt, pac, Peripheral};
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
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.
BufferNotInRAM,
}
@ -74,9 +70,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
let s = T::state();
#[cfg(feature = "_nrf52832_anomaly_109")]
if r.events_started.read().bits() != 0 {
s.waker.wake();
r.intenclr.write(|w| w.started().clear());
{
// Ideally we should call this only during the first chunk transfer,
// 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 {
@ -209,35 +209,39 @@ impl<'d, T: Instance> Spim<'d, T> {
spim
}
fn prepare(&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.
fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
compiler_fence(Ordering::SeqCst);
let r = T::regs();
// Set up the DMA write.
let (ptr, tx_len) = slice_ptr_parts(tx);
if tx_len > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong);
fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) {
if total > offset {
(ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length))
} 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.
let (ptr, rx_len) = slice_ptr_parts_mut(rx);
if rx_len > EASY_DMA_SIZE {
return Err(Error::RxBufferTooLong);
}
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
let (ptr, len) = slice_ptr_parts_mut(rx);
let (rx_ptr, rx_len) = xfer_params(ptr as _, len as _, offset, length);
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx_ptr) });
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")]
{
if offset == 0 {
let s = T::state();
r.events_started.reset();
@ -260,21 +264,32 @@ impl<'d, T: Instance> Spim<'d, T> {
// Start SPI transaction.
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> {
self.prepare(rx, tx)?;
fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
self.prepare_dma_transfer(rx, tx, offset, length);
#[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.
while T::regs().events_end.read().bits() == 0 {}
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(())
}
@ -287,22 +302,23 @@ impl<'d, T: Instance> Spim<'d, T> {
tx_ram_buf.copy_from_slice(tx);
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> {
self.prepare(rx, tx)?;
async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
self.prepare_dma_transfer(rx, tx, offset, length);
#[cfg(feature = "_nrf52832_anomaly_109")]
poll_fn(|cx| {
let s = T::state();
if offset == 0 {
poll_fn(|cx| {
let s = T::state();
s.waker.register(cx.waker());
s.waker.register(cx.waker());
self.nrf52832_dma_workaround_status()
})
.await;
self.nrf52832_dma_workaround_status()
})
.await;
}
// Wait for 'end' event.
poll_fn(|cx| {
@ -316,7 +332,18 @@ impl<'d, T: Instance> Spim<'d, T> {
.await;
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(())
}
@ -329,7 +356,6 @@ impl<'d, T: Instance> Spim<'d, T> {
tx_ram_buf.copy_from_slice(tx);
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 {
fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
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,
}
}

View file

@ -4,6 +4,19 @@ use core::mem;
const SRAM_LOWER: usize = 0x2000_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
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) {
unsafe { mem::transmute(slice) }