Lines
70.21 %
Functions
100 %
use core::{
mem::ManuallyDrop,
ops::{Deref, DerefMut},
};
use enumset::EnumSet;
#[cfg(dma_mem2mem_requires_peripheral)]
use crate::dma::DmaEligiblePeripheral;
use crate::{
Async,
Blocking,
DriverMode,
dma::{
BurstConfig,
Channel,
ChannelRx,
ChannelTx,
DmaChannel,
DmaDescriptor,
DmaError,
DmaPeripheral,
DmaRxBuf,
DmaRxBuffer,
DmaRxInterrupt,
DmaTxBuf,
DmaTxBuffer,
DmaTxInterrupt,
aligned::DmaAlignedMut,
},
/// A DMA channel singleton that supports memory-to-memory transfers on this chip.
///
/// Only channels listed in device metadata (`mem2mem = true`) implement this trait.
/// Use [`Mem2Mem::new`] to construct a transfer engine from such a channel.
#[diagnostic::on_unimplemented(
message = "this DMA channel does not support memory-to-memory transfers",
note = "Use a channel with `mem2mem = true` in device metadata. See `Mem2Mem::new`."
)]
pub trait Mem2MemCapableChannel<'d>: DmaChannel {
#[doc(hidden)]
type Erased: DmaChannel + From<Self>;
/// Peripheral selector programmed for memory-to-memory on this channel.
#[cfg(not(dma_mem2mem_requires_peripheral))]
fn mem2mem_id(&self) -> DmaPeripheral;
#[allow(private_interfaces)]
fn into_channel(self) -> ErasedChannel<'d, Blocking>;
}
// Type-erased version of Channel/ChannelRx/ChannelTx
for_each_mem2mem_channel! {
(engines $( ($engine:literal, $variant:ident, $any_ch:ident) ),* ) => {
struct ErasedChannel<'d, Dm: DriverMode> {
rx: ErasedChannelRx<'d, Dm>,
tx: ErasedChannelTx<'d, Dm>,
enum ErasedChannelRx<'d, Dm: DriverMode> {
$(
$variant(ChannelRx<Dm, <crate::dma::$any_ch<'d> as DmaChannel>::Rx>),
)*
impl<Dm: DriverMode> ErasedChannelRx<'_, Dm> {
delegate::delegate! {
to match self {
$( Self::$variant(channel) => channel, )*
} {
fn has_error(&self) -> bool;
fn pending_in_interrupts(&self) -> EnumSet<DmaRxInterrupt>;
fn runtime_ensure_compatible(&self, peripheral: DmaPeripheral);
fn set_mem2mem_mode(&mut self, value: bool);
fn start_transfer(&mut self) -> Result<(), DmaError>;
fn stop_transfer(&mut self);
unsafe fn prepare_transfer<BUF: DmaRxBuffer>(
&mut self,
peri: DmaPeripheral,
buffer: &mut BUF,
) -> Result<(), DmaError>;
impl<'d> ErasedChannelRx<'d, Blocking> {
fn into_async(self) -> ErasedChannelRx<'d, Async> {
match self {
$( Self::$variant(channel) => ErasedChannelRx::$variant(channel.into_async()), )*
impl<'d> ErasedChannelRx<'d, Async> {
fn into_blocking(self) -> ErasedChannelRx<'d, Blocking> {
$( Self::$variant(channel) => ErasedChannelRx::$variant(channel.into_blocking()), )*
enum ErasedChannelTx<'d, Dm: DriverMode> {
$variant(ChannelTx<Dm, <crate::dma::$any_ch<'d> as DmaChannel>::Tx>),
impl<Dm: DriverMode> ErasedChannelTx<'_, Dm> {
fn pending_out_interrupts(&self) -> EnumSet<DmaTxInterrupt>;
unsafe fn prepare_transfer<BUF: DmaTxBuffer>(
impl<'d> ErasedChannelTx<'d, Blocking> {
fn into_async(self) -> ErasedChannelTx<'d, Async> {
$( Self::$variant(channel) => ErasedChannelTx::$variant(channel.into_async()), )*
impl<'d> ErasedChannelTx<'d, Async> {
fn into_blocking(self) -> ErasedChannelTx<'d, Blocking> {
$( Self::$variant(channel) => ErasedChannelTx::$variant(channel.into_blocking()), )*
($engine:literal, $variant:ident, $any_ch:ident, $($hw:literal, $id:literal),+) => {
impl<'d> Mem2MemCapableChannel<'d> for crate::dma::$any_ch<'d> {
type Erased = Self;
fn mem2mem_id(&self) -> DmaPeripheral {
match self.channel_index() {
$( $hw => DmaPeripheral($id), )+
ch => panic!(
"Channel {} does not support memory-to-memory transfers",
ch
),
fn into_channel(self) -> ErasedChannel<'d, Blocking> {
let channel = Channel::new(self);
ErasedChannel {
rx: ErasedChannelRx::$variant(channel.rx),
tx: ErasedChannelTx::$variant(channel.tx),
($engine:literal, $variant:ident, $any_ch:ident, $ch:ident, $id:literal) => {
impl<'d> Mem2MemCapableChannel<'d> for crate::peripherals::$ch<'d> {
type Erased = crate::dma::$any_ch<'d>;
DmaPeripheral($id)
let channel = Channel::new(crate::dma::$any_ch::from(self));
/// DMA Memory to Memory pseudo-Peripheral
/// This is a pseudo-peripheral that allows for memory to memory transfers.
/// It is not a real peripheral, but a way to use the DMA engine for memory
/// to memory transfers.
pub struct Mem2Mem<'d, Dm>
where
Dm: DriverMode,
{
/// RX Half
pub rx: Mem2MemRx<'d, Dm>,
/// TX Half
pub tx: Mem2MemTx<'d, Dm>,
impl<'d> Mem2Mem<'d, Blocking> {
/// Creates a new [`Mem2Mem`] instance.
pub fn new<CH>(
channel: CH,
#[cfg(dma_mem2mem_requires_peripheral)] peripheral: impl DmaEligiblePeripheral<CH::Erased>,
) -> Self
CH: Mem2MemCapableChannel<'d>,
let dma_peri = cfg_select! {
dma_mem2mem_requires_peripheral => peripheral.dma_peripheral(),
_ => channel.mem2mem_id(),
Self::new_inner(channel, dma_peri)
/// # Safety
/// The caller must ensure that DMA is not used for the same peripheral,
/// that this is the only user of the peripheral, and that the peripheral is
/// compatible with the channel.
pub unsafe fn new_unsafe<CH>(channel: CH, peripheral: DmaPeripheral) -> Self
Self::new_inner(channel, peripheral)
/// Converts Mem2Mem to an async Mem2Mem.
pub fn into_async(self) -> Mem2Mem<'d, Async> {
Mem2Mem {
rx: self.rx.into_async(),
tx: self.tx.into_async(),
fn new_inner(channel: impl Mem2MemCapableChannel<'d>, peripheral: DmaPeripheral) -> Self {
let mut channel = channel.into_channel();
channel.rx.runtime_ensure_compatible(peripheral);
#[cfg(dma_supports_mem2mem)]
channel.rx.set_mem2mem_mode(true);
rx: Mem2MemRx {
channel: channel.rx,
peripheral,
tx: Mem2MemTx {
channel: channel.tx,
/// Shortcut to create a [SimpleMem2Mem]
pub fn with_descriptors(
self,
rx_descriptors: &'d mut [DmaDescriptor],
tx_descriptors: &'d mut [DmaDescriptor],
config: BurstConfig,
) -> Result<SimpleMem2Mem<'d, Blocking>, DmaError> {
SimpleMem2Mem::new(self, rx_descriptors, tx_descriptors, config)
/// The RX half of [Mem2Mem].
pub struct Mem2MemRx<'d, Dm>
channel: ErasedChannelRx<'d, Dm>,
peripheral: DmaPeripheral,
impl<'d> Mem2MemRx<'d, Blocking> {
/// Converts Mem2MemRx to an async Mem2MemRx.
pub fn into_async(self) -> Mem2MemRx<'d, Async> {
Mem2MemRx {
channel: self.channel.into_async(),
peripheral: self.peripheral,
impl<'d, Dm> Mem2MemRx<'d, Dm>
/// Starts the RX half of a memory to memory transfer.
pub fn receive<BUF>(
mut self,
mut buf: BUF,
) -> Result<Mem2MemRxTransfer<'d, BUF, Dm>, (DmaError, Self, BUF)>
BUF: DmaRxBuffer,
let result = unsafe {
self.channel
.prepare_transfer(self.peripheral, &mut buf)
.and_then(|_| self.channel.start_transfer())
if let Err(e) = result {
return Err((e, self, buf));
Ok(Mem2MemRxTransfer {
m2m: ManuallyDrop::new(self),
buf_view: ManuallyDrop::new(buf.into_view()),
})
/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory RX
/// transfer.
pub struct Mem2MemRxTransfer<'d, BUF, Dm>
m2m: ManuallyDrop<Mem2MemRx<'d, Dm>>,
buf_view: ManuallyDrop<BUF::View>,
impl<'d, BUF, Dm> Mem2MemRxTransfer<'d, BUF, Dm>
/// Returns whether [`Self::wait`] will not block.
pub fn is_done(&self) -> bool {
let done_interrupts = DmaRxInterrupt::DescriptorError | DmaRxInterrupt::DescriptorEmpty;
!self
.m2m
.channel
.pending_in_interrupts()
.is_disjoint(done_interrupts)
/// Waits for the transfer to stop and returns the peripheral and buffer.
pub fn wait(self) -> (Result<(), DmaError>, Mem2MemRx<'d, Dm>, BUF::Final) {
while !self.is_done() {}
let (m2m, view) = self.release();
let result = if m2m.channel.has_error() {
Err(DmaError::DescriptorError)
} else {
Ok(())
(result, m2m, BUF::from_view(view))
/// Stops this transfer on the spot and returns the peripheral and buffer.
pub fn stop(self) -> (Mem2MemRx<'d, Dm>, BUF::Final) {
let (mut m2m, view) = self.release();
m2m.channel.stop_transfer();
(m2m, BUF::from_view(view))
fn release(mut self) -> (Mem2MemRx<'d, Dm>, BUF::View) {
// SAFETY: Since forget is called on self, we know that self.m2m and
// self.buf_view won't be touched again.
let m2m = ManuallyDrop::take(&mut self.m2m);
let view = ManuallyDrop::take(&mut self.buf_view);
(m2m, view)
core::mem::forget(self);
result
impl<'d, BUF, Dm> Deref for Mem2MemRxTransfer<'d, BUF, Dm>
type Target = BUF::View;
fn deref(&self) -> &Self::Target {
&self.buf_view
impl<'d, BUF, Dm> DerefMut for Mem2MemRxTransfer<'d, BUF, Dm>
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buf_view
impl<'d, BUF, Dm> Drop for Mem2MemRxTransfer<'d, BUF, Dm>
fn drop(&mut self) {
self.m2m.channel.stop_transfer();
// SAFETY: This is Drop, we know that self.m2m and self.buf_view
// won't be touched again.
let view = unsafe {
ManuallyDrop::drop(&mut self.m2m);
ManuallyDrop::take(&mut self.buf_view)
let _ = BUF::from_view(view);
/// The TX half of [Mem2Mem].
pub struct Mem2MemTx<'d, Dm>
channel: ErasedChannelTx<'d, Dm>,
impl<'d> Mem2MemTx<'d, Blocking> {
/// Converts Mem2MemTx to an async Mem2MemTx.
pub fn into_async(self) -> Mem2MemTx<'d, Async> {
Mem2MemTx {
impl<'d, Dm> Mem2MemTx<'d, Dm>
/// Starts the TX half of a memory to memory transfer.
pub fn send<BUF>(
) -> Result<Mem2MemTxTransfer<'d, BUF, Dm>, (DmaError, Self, BUF)>
BUF: DmaTxBuffer,
Ok(Mem2MemTxTransfer {
/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory TX
pub struct Mem2MemTxTransfer<'d, BUF, Dm>
m2m: ManuallyDrop<Mem2MemTx<'d, Dm>>,
impl<'d, BUF, Dm> Mem2MemTxTransfer<'d, BUF, Dm>
let done_interrupts = DmaTxInterrupt::DescriptorError | DmaTxInterrupt::TotalEof;
.pending_out_interrupts()
pub fn wait(self) -> (Result<(), DmaError>, Mem2MemTx<'d, Dm>, BUF::Final) {
pub fn stop(self) -> (Mem2MemTx<'d, Dm>, BUF::Final) {
fn release(mut self) -> (Mem2MemTx<'d, Dm>, BUF::View) {
impl<'d, BUF, Dm> Deref for Mem2MemTxTransfer<'d, BUF, Dm>
impl<'d, BUF, Dm> DerefMut for Mem2MemTxTransfer<'d, BUF, Dm>
impl<'d, BUF, Dm> Drop for Mem2MemTxTransfer<'d, BUF, Dm>
/// A simple and easy to use wrapper around [SimpleMem2Mem].
/// More complex memory to memory transfers should use [Mem2Mem] directly.
pub struct SimpleMem2Mem<'d, Dm>
state: State<'d, Dm>,
enum State<'d, Dm: DriverMode> {
Idle(
Mem2Mem<'d, Dm>,
DmaAlignedMut<'d, [DmaDescriptor]>,
Active(
Mem2MemRxTransfer<'d, DmaRxBuf, Dm>,
Mem2MemTxTransfer<'d, DmaTxBuf, Dm>,
InUse,
impl<'d, Dm> SimpleMem2Mem<'d, Dm>
/// Creates a new [SimpleMem2Mem].
pub fn new(
mem2mem: Mem2Mem<'d, Dm>,
) -> Result<Self, DmaError> {
if rx_descriptors.is_empty() || tx_descriptors.is_empty() {
return Err(DmaError::OutOfDescriptors);
// Safety: descriptors are aligned to what the DMA requires, and we don't call invalidate on
// these slices.
let rx_descriptors = unsafe { DmaAlignedMut::new_unchecked(rx_descriptors) };
let tx_descriptors = unsafe { DmaAlignedMut::new_unchecked(tx_descriptors) };
Ok(Self {
state: State::Idle(mem2mem, rx_descriptors, tx_descriptors),
config,
/// Starts a memory to memory transfer.
pub fn start_transfer(
rx_buffer: &mut [u8],
tx_buffer: &[u8],
) -> Result<SimpleMem2MemTransfer<'_, 'd, Dm>, DmaError> {
let State::Idle(mem2mem, mut rx_descriptors, mut tx_descriptors) =
core::mem::replace(&mut self.state, State::InUse)
else {
panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar");
// Raise these buffers to 'static. This is not safe, bad things will happen if
// the user calls core::mem::forget on SimpleMem2MemTransfer. This is
// just the unfortunate consequence of doing DMA without enforcing
// 'static.
let rx_buffer = DmaAlignedMut::new(unsafe {
core::slice::from_raw_parts_mut(rx_buffer.as_mut_ptr(), rx_buffer.len())
})?;
let tx_buffer = unsafe {
DmaAlignedMut::new_unchecked(core::slice::from_raw_parts_mut(
tx_buffer.as_ptr() as _,
tx_buffer.len(),
))
let rx_descriptors = unsafe {
rx_descriptors.as_mut_ptr(),
rx_descriptors.len(),
let tx_descriptors = unsafe {
tx_descriptors.as_mut_ptr(),
tx_descriptors.len(),
// Note: The ESP32-S2 insists that RX is started before TX. Contrary to the TRM
// and every other chip.
let dma_rx_buf = unwrap!(
DmaRxBuf::new_with_config(rx_descriptors, rx_buffer, self.config),
"There's no way to get the descriptors back yet"
);
let rx = match mem2mem.rx.receive(dma_rx_buf) {
Ok(rx) => rx,
Err((err, rx, buf)) => {
let (rx_descriptors, _rx_buffer) = buf.split();
self.state = State::Idle(
Mem2Mem { rx, tx: mem2mem.tx },
rx_descriptors,
tx_descriptors,
return Err(err);
let dma_tx_buf = unwrap!(
DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config),
let tx = match mem2mem.tx.send(dma_tx_buf) {
Ok(tx) => tx,
Err((err, tx, buf)) => {
let (tx_descriptors, _tx_buffer) = buf.split();
let (rx, buf) = rx.stop();
self.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors);
self.state = State::Active(rx, tx);
Ok(SimpleMem2MemTransfer(self))
impl<Dm> Drop for SimpleMem2Mem<'_, Dm>
if !matches!(&mut self.state, State::Idle(_, _, _)) {
/// Represents an ongoing (or potentially finished) DMA Memory-to-Memory
pub struct SimpleMem2MemTransfer<'a, 'd, Dm>(&'a mut SimpleMem2Mem<'d, Dm>)
Dm: DriverMode;
impl<Dm> SimpleMem2MemTransfer<'_, '_, Dm>
let State::Active(rx, tx) = &self.0.state else {
unreachable!()
// Wait for transmission to finish, and wait for the RX channel to receive the
// one and only EOF that DmaTxBuf will send.
tx.is_done()
&& rx
.contains(DmaRxInterrupt::SuccessfulEof)
/// Waits for the transfer to finish.
pub fn wait(self) -> Result<(), DmaError> {
impl<Dm> Drop for SimpleMem2MemTransfer<'_, '_, Dm>
let State::Active(rx, tx) = core::mem::replace(&mut self.0.state, State::InUse) else {
let (tx, dma_tx_buf) = tx.stop();
let (rx, dma_rx_buf) = rx.stop();
let (tx_descriptors, _tx_buffer) = dma_tx_buf.split();
let (rx_descriptors, _rx_buffer) = dma_rx_buf.split();
self.0.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors);