dma: add Mem2Mem to support memory to memory transfer (#1738)

* dma: add Mem2Mem to support memory to memory transfer

* fmt

* update CHANGELOG

* removed some debugging

* use "gdma" as the selector for support

* fix empty else

* clippy

* Mem2Mem::new now accepts the peripheral to use

* mark Mem2Mem::new() unsafe

* fmt :-/

* add Mem2MemN values for gdma on non-esp32s3
tested on esp32c3,esp32c6 (will have an esp32h2 in a few days)

* support the esp32c2 (esp8684)

* DmaEligible trait providing dma peripheral value & safe constructor for Mem2Mem dma.

* added hil-test for Mem2Mem

* fmt dma_mem2mem test

* remove `debug!()`

* reset the mem2mem bit (mem_trans_en) in in_conf0 on drop
This commit is contained in:
liebman 2024-07-03 11:56:37 -06:00 committed by GitHub
parent 4f9dc960c6
commit cc7077624c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 438 additions and 13 deletions

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ESP32-S3: Expose optional HSYNC input in LCD_CAM (#1707)
- ESP32-C6: Support lp-core as wake-up source (#1723)
- Add support for GPIO wake-up source (#1724)
- dma: add Mem2Mem to support memory to memory transfer (#1738)
- Add `uart` wake source (#1727)
### Fixed

View File

@ -88,6 +88,13 @@ impl<const N: u8> RegisterAccess for Channel<N> {
// nothing special to be done here
}
#[cfg(gdma)]
fn set_mem2mem_mode(value: bool) {
Self::ch()
.in_conf0()
.modify(|_, w| w.mem_trans_en().bit(value));
}
fn set_out_burstmode(burst_mode: bool) {
Self::ch().out_conf0().modify(|_, w| {
w.out_data_burst_en()
@ -606,3 +613,151 @@ impl<'d> Dma<'d> {
}
}
}
pub use m2m::*;
mod m2m {
use embedded_dma::{ReadBuffer, WriteBuffer};
use crate::dma::{
dma_private::{DmaSupport, DmaSupportRx},
Channel,
ChannelTypes,
DescriptorChain,
DmaDescriptor,
DmaEligible,
DmaError,
DmaPeripheral,
DmaTransferRx,
RxPrivate,
TxPrivate,
};
/// 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, C, MODE>
where
C: ChannelTypes,
MODE: crate::Mode,
{
channel: Channel<'d, C, MODE>,
tx_chain: DescriptorChain,
rx_chain: DescriptorChain,
peripheral: DmaPeripheral,
}
impl<'d, C, MODE> Mem2Mem<'d, C, MODE>
where
C: ChannelTypes,
MODE: crate::Mode,
{
/// Create a new Mem2Mem instance.
pub fn new(
mut channel: Channel<'d, C, MODE>,
peripheral: impl DmaEligible,
tx_descriptors: &'static mut [DmaDescriptor],
rx_descriptors: &'static mut [DmaDescriptor],
) -> Self {
channel.tx.init_channel();
channel.rx.init_channel();
Mem2Mem {
channel,
peripheral: peripheral.dma_peripheral(),
tx_chain: DescriptorChain::new(tx_descriptors),
rx_chain: DescriptorChain::new(rx_descriptors),
}
}
/// Create a new Mem2Mem instance.
///
/// # Safety
///
/// You must insure that your not using DMA for the same peripheral and
/// that your the only one using the DmaPeripheral.
pub unsafe fn new_unsafe(
mut channel: Channel<'d, C, MODE>,
peripheral: DmaPeripheral,
tx_descriptors: &'static mut [DmaDescriptor],
rx_descriptors: &'static mut [DmaDescriptor],
) -> Self {
channel.tx.init_channel();
channel.rx.init_channel();
Mem2Mem {
channel,
peripheral,
tx_chain: DescriptorChain::new(tx_descriptors),
rx_chain: DescriptorChain::new(rx_descriptors),
}
}
/// Start a memory to memory transfer.
pub fn start_transfer<'t, TXBUF, RXBUF>(
&mut self,
tx_buffer: &'t TXBUF,
rx_buffer: &'t mut RXBUF,
) -> Result<DmaTransferRx<Self>, DmaError>
where
TXBUF: ReadBuffer<Word = u8>,
RXBUF: WriteBuffer<Word = u8>,
{
let (tx_ptr, tx_len) = unsafe { tx_buffer.read_buffer() };
let (rx_ptr, rx_len) = unsafe { rx_buffer.write_buffer() };
self.tx_chain.fill_for_tx(false, tx_ptr, tx_len)?;
self.rx_chain.fill_for_rx(false, rx_ptr, rx_len)?;
unsafe {
self.channel
.tx
.prepare_transfer_without_start(self.peripheral, &self.tx_chain)?;
self.channel
.rx
.prepare_transfer_without_start(self.peripheral, &self.rx_chain)?;
self.channel.rx.set_mem2mem_mode(true);
}
self.channel.tx.start_transfer()?;
self.channel.rx.start_transfer()?;
Ok(DmaTransferRx::new(self))
}
}
impl<'d, C, MODE> Drop for Mem2Mem<'d, C, MODE>
where
C: ChannelTypes,
MODE: crate::Mode,
{
fn drop(&mut self) {
self.channel.rx.set_mem2mem_mode(false);
}
}
impl<'d, C, MODE> DmaSupport for Mem2Mem<'d, C, MODE>
where
C: ChannelTypes,
MODE: crate::Mode,
{
fn peripheral_wait_dma(&mut self, _is_tx: bool, _is_rx: bool) {
while !self.channel.rx.is_done() {}
}
fn peripheral_dma_stop(&mut self) {
unreachable!("unsupported")
}
}
impl<'d, C, MODE> DmaSupportRx for Mem2Mem<'d, C, MODE>
where
C: ChannelTypes,
MODE: crate::Mode,
{
type RX = C::Rx<'d>;
fn rx(&mut self) -> &mut Self::RX {
&mut self.channel.rx
}
fn chain(&mut self) -> &mut DescriptorChain {
&mut self.tx_chain
}
}
}

View File

@ -49,7 +49,7 @@
//! For convenience you can use the [crate::dma_buffers] macro.
#![warn(missing_docs)]
use core::{marker::PhantomData, ptr::addr_of_mut, sync::atomic::compiler_fence};
use core::{fmt::Debug, marker::PhantomData, ptr::addr_of_mut, sync::atomic::compiler_fence};
bitfield::bitfield! {
#[doc(hidden)]
@ -63,8 +63,19 @@ bitfield::bitfield! {
owner, set_owner: 31;
}
impl Debug for DmaDescriptorFlags {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DmaDescriptorFlags")
.field("size", &self.size())
.field("length", &self.length())
.field("suc_eof", &self.suc_eof())
.field("owner", &self.owner())
.finish()
}
}
/// A DMA transfer descriptor.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct DmaDescriptor {
pub(crate) flags: DmaDescriptorFlags,
pub(crate) buffer: *mut u8,
@ -370,27 +381,45 @@ pub enum DmaPriority {
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[doc(hidden)]
pub enum DmaPeripheral {
Spi2 = 0,
Spi2 = 0,
#[cfg(any(pdma, esp32s3))]
Spi3 = 1,
Spi3 = 1,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem1 = 1,
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
Uhci0 = 2,
Uhci0 = 2,
#[cfg(any(esp32, esp32s2, esp32c3, esp32c6, esp32h2, esp32s3))]
I2s0 = 3,
I2s0 = 3,
#[cfg(any(esp32, esp32s3))]
I2s1 = 4,
I2s1 = 4,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem4 = 4,
#[cfg(esp32s3)]
LcdCam = 5,
LcdCam = 5,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem5 = 5,
#[cfg(not(esp32c2))]
Aes = 6,
Aes = 6,
#[cfg(gdma)]
Sha = 7,
Sha = 7,
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
Adc = 8,
Adc = 8,
#[cfg(esp32s3)]
Rmt = 9,
Rmt = 9,
#[cfg(parl_io)]
ParlIo = 9,
ParlIo = 9,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem10 = 10,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem11 = 11,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem12 = 12,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem13 = 13,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem14 = 14,
#[cfg(any(esp32c6, esp32h2))]
Mem2Mem15 = 15,
}
#[derive(PartialEq, PartialOrd)]
@ -408,6 +437,16 @@ impl From<u32> for Owner {
}
}
/// Marks channels as useable for SPI
#[doc(hidden)]
pub trait DmaEligible {
/// The DMA peripheral
const DMA_PERIPHERAL: DmaPeripheral;
fn dma_peripheral(&self) -> DmaPeripheral {
Self::DMA_PERIPHERAL
}
}
/// Marks channels as useable for SPI
#[doc(hidden)]
pub trait SpiPeripheral: PeripheralMarker {}
@ -458,6 +497,7 @@ pub trait Tx: TxPrivate {}
pub trait PeripheralMarker {}
#[doc(hidden)]
#[derive(Debug)]
pub struct DescriptorChain {
pub(crate) descriptors: &'static mut [DmaDescriptor],
}
@ -877,6 +917,9 @@ pub trait RxPrivate: crate::private::Sealed {
fn start_transfer(&mut self) -> Result<(), DmaError>;
#[cfg(gdma)]
fn set_mem2mem_mode(&mut self, value: bool);
fn listen_ch_in_done(&self);
fn clear_ch_in_done(&self);
@ -1042,6 +1085,11 @@ where
self.rx_impl.start_transfer()
}
#[cfg(gdma)]
fn set_mem2mem_mode(&mut self, value: bool) {
R::set_mem2mem_mode(value);
}
fn listen_ch_in_done(&self) {
R::listen_ch_in_done();
}
@ -1411,6 +1459,8 @@ where
#[doc(hidden)]
pub trait RegisterAccess: crate::private::Sealed {
fn init_channel();
#[cfg(gdma)]
fn set_mem2mem_mode(value: bool);
fn set_out_burstmode(burst_mode: bool);
fn set_out_priority(priority: DmaPriority);
fn clear_out_interrupts();

View File

@ -204,6 +204,16 @@ where
impl<T> crate::private::Sealed for &mut T where T: crate::private::Sealed {}
mod peripheral_macros {
#[doc(hidden)]
#[macro_export]
macro_rules! impl_dma_eligible {
($name:ident,$dma:ident) => {
impl $crate::dma::DmaEligible for $name {
const DMA_PERIPHERAL: $crate::dma::DmaPeripheral = $crate::dma::DmaPeripheral::$dma;
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! peripherals {
@ -215,6 +225,49 @@ mod peripheral_macros {
$(
$crate::create_peripheral!($(#[$cfg])? $name <= $from_pac);
)*
$crate::impl_dma_eligible!(SPI2,Spi2);
#[cfg(any(pdma, esp32s3))]
$crate::impl_dma_eligible!(SPI3,Spi3);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM1,Mem2Mem1);
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
$crate::impl_dma_eligible!(UHCI0,Uhci0);
#[cfg(any(esp32, esp32s2, esp32c3, esp32c6, esp32h2, esp32s3))]
$crate::impl_dma_eligible!(I2S0,I2s0);
#[cfg(any(esp32, esp32s3))]
$crate::impl_dma_eligible!(I2S1,I2s1);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM4,Mem2Mem4);
#[cfg(esp32s3)]
$crate::impl_dma_eligible!(LCD_CAM,LcdCam);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM5,Mem2Mem5);
#[cfg(not(esp32c2))]
$crate::impl_dma_eligible!(AES,Aes);
#[cfg(gdma)]
$crate::impl_dma_eligible!(SHA,Sha);
#[cfg(any(esp32c3, esp32c6, esp32h2, esp32s3))]
$crate::impl_dma_eligible!(ADC1,Adc);
#[cfg(any(esp32c3, esp32s3))]
$crate::impl_dma_eligible!(ADC2,Adc);
#[cfg(esp32s3)]
$crate::impl_dma_eligible!(RMT,Rmt);
#[cfg(parl_io)]
$crate::impl_dma_eligible!(PARL_IO,ParlIo);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM10,Mem2Mem10);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM11,Mem2Mem11);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM12,Mem2Mem12);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM13,Mem2Mem13);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM14,Mem2Mem14);
#[cfg(any(esp32c6, esp32h2))]
$crate::impl_dma_eligible!(MEM2MEM15,Mem2Mem15);
}
#[allow(non_snake_case)]

View File

@ -48,4 +48,11 @@ crate::peripherals! {
UART1 <= UART1,
WIFI <= virtual,
XTS_AES <= XTS_AES,
MEM2MEM1 <= virtual,
MEM2MEM2 <= virtual,
MEM2MEM3 <= virtual,
MEM2MEM4 <= virtual,
MEM2MEM5 <= virtual,
MEM2MEM6 <= virtual,
MEM2MEM8 <= virtual,
}

View File

@ -87,4 +87,13 @@ crate::peripherals! {
UHCI0 <= UHCI0,
USB_DEVICE <= USB_DEVICE,
WIFI <= virtual,
MEM2MEM1 <= virtual,
MEM2MEM4 <= virtual,
MEM2MEM5 <= virtual,
MEM2MEM10 <= virtual,
MEM2MEM11 <= virtual,
MEM2MEM12 <= virtual,
MEM2MEM13 <= virtual,
MEM2MEM14 <= virtual,
MEM2MEM15 <= virtual,
}

View File

@ -77,4 +77,13 @@ crate::peripherals! {
UART1 <= UART1,
UHCI0 <= UHCI0,
USB_DEVICE <= USB_DEVICE,
MEM2MEM1 <= virtual,
MEM2MEM4 <= virtual,
MEM2MEM5 <= virtual,
MEM2MEM10 <= virtual,
MEM2MEM11 <= virtual,
MEM2MEM12 <= virtual,
MEM2MEM13 <= virtual,
MEM2MEM14 <= virtual,
MEM2MEM15 <= virtual,
}

View File

@ -0,0 +1,78 @@
//! Uses DMA to copy memory to memory.
//% FEATURES: esp-hal/log
//% CHIPS: esp32s3 esp32c2 esp32c3 esp32c6 esp32h2
#![no_std]
#![no_main]
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
delay::Delay,
dma::{Dma, DmaPriority, Mem2Mem},
dma_buffers,
peripherals::Peripherals,
prelude::*,
system::SystemControl,
};
use log::{error, info};
const DATA_SIZE: usize = 1024 * 10;
#[entry]
fn main() -> ! {
esp_println::logger::init_logger(log::LevelFilter::Info);
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
let delay = Delay::new(&clocks);
let (tx_buffer, tx_descriptors, mut rx_buffer, rx_descriptors) = dma_buffers!(DATA_SIZE);
let dma = Dma::new(peripherals.DMA);
let channel = dma.channel0.configure(false, DmaPriority::Priority0);
#[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
let dma_peripheral = peripherals.SPI2;
#[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
let dma_peripheral = peripherals.MEM2MEM1;
let mut mem2mem = Mem2Mem::new(channel, dma_peripheral, tx_descriptors, rx_descriptors);
for i in 0..core::mem::size_of_val(tx_buffer) {
tx_buffer[i] = (i % 256) as u8;
}
info!("Starting transfer of {} bytes", DATA_SIZE);
let result = mem2mem.start_transfer(&tx_buffer, &mut rx_buffer);
match result {
Ok(dma_wait) => {
info!("Transfer started");
dma_wait.wait().unwrap();
info!("Transfer completed, comparing buffer");
let mut error = false;
for i in 0..core::mem::size_of_val(tx_buffer) {
if rx_buffer[i] != tx_buffer[i] {
error!(
"Error: tx_buffer[{}] = {}, rx_buffer[{}] = {}",
i, tx_buffer[i], i, rx_buffer[i]
);
error = true;
break;
}
}
if !error {
info!("Buffers are equal");
}
info!("Done");
}
Err(e) => {
error!("start_transfer: Error: {:?}", e);
}
}
loop {
delay.delay(2.secs());
}
}

View File

@ -24,6 +24,10 @@ harness = false
name = "delay"
harness = false
[[test]]
name = "dma_mem2mem"
harness = false
[[test]]
name = "ecc"
harness = false

View File

@ -0,0 +1,59 @@
//! DMA Mem2Mem Tests
//% CHIPS: esp32s3 esp32c2 esp32c3 esp32c6 esp32h2
#![no_std]
#![no_main]
use defmt_rtt as _;
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
dma::{Dma, DmaPriority, Mem2Mem},
dma_buffers,
peripherals::Peripherals,
system::SystemControl,
};
const DATA_SIZE: usize = 1024 * 10;
#[cfg(test)]
#[embedded_test::tests]
mod tests {
use defmt::assert_eq;
use super::*;
#[init]
fn init() {}
#[test]
fn test_internal_mem2mem() {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();
let (tx_buffer, tx_descriptors, mut rx_buffer, rx_descriptors) = dma_buffers!(DATA_SIZE);
let dma = Dma::new(peripherals.DMA);
let channel = dma.channel0.configure(false, DmaPriority::Priority0);
#[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
let dma_peripheral = peripherals.SPI2;
#[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
let dma_peripheral = peripherals.MEM2MEM1;
let mut mem2mem = Mem2Mem::new(channel, dma_peripheral, tx_descriptors, rx_descriptors);
for i in 0..core::mem::size_of_val(tx_buffer) {
tx_buffer[i] = (i % 256) as u8;
}
let dma_wait = mem2mem.start_transfer(&tx_buffer, &mut rx_buffer).unwrap();
dma_wait.wait().unwrap();
// explicitly drop to insure the mem2mem bit is not left set as this causes
// subsequent dma tests to fail.
drop(mem2mem);
for i in 0..core::mem::size_of_val(tx_buffer) {
assert_eq!(rx_buffer[i], tx_buffer[i]);
}
}
}