From cc7077624cce6e4dd8dd853955f3261257f2f5ee Mon Sep 17 00:00:00 2001 From: liebman Date: Wed, 3 Jul 2024 11:56:37 -0600 Subject: [PATCH] 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 --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/dma/gdma.rs | 155 +++++++++++++++++++++++++ esp-hal/src/dma/mod.rs | 76 +++++++++--- esp-hal/src/peripheral.rs | 53 +++++++++ esp-hal/src/soc/esp32c2/peripherals.rs | 7 ++ esp-hal/src/soc/esp32c6/peripherals.rs | 9 ++ esp-hal/src/soc/esp32h2/peripherals.rs | 9 ++ examples/src/bin/dma_mem2mem.rs | 78 +++++++++++++ hil-test/Cargo.toml | 4 + hil-test/tests/dma_mem2mem.rs | 59 ++++++++++ 10 files changed, 438 insertions(+), 13 deletions(-) create mode 100644 examples/src/bin/dma_mem2mem.rs create mode 100644 hil-test/tests/dma_mem2mem.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 602b93562..bb9e5d051 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -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 diff --git a/esp-hal/src/dma/gdma.rs b/esp-hal/src/dma/gdma.rs index 0449f6c71..91cec235a 100644 --- a/esp-hal/src/dma/gdma.rs +++ b/esp-hal/src/dma/gdma.rs @@ -88,6 +88,13 @@ impl RegisterAccess for Channel { // 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, DmaError> + where + TXBUF: ReadBuffer, + RXBUF: WriteBuffer, + { + 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 + } + } +} diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 88291fb9e..749d8db02 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -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 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(); diff --git a/esp-hal/src/peripheral.rs b/esp-hal/src/peripheral.rs index 59f8ad022..6aa24f308 100644 --- a/esp-hal/src/peripheral.rs +++ b/esp-hal/src/peripheral.rs @@ -204,6 +204,16 @@ where impl 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)] diff --git a/esp-hal/src/soc/esp32c2/peripherals.rs b/esp-hal/src/soc/esp32c2/peripherals.rs index 14c955f98..adf05427a 100644 --- a/esp-hal/src/soc/esp32c2/peripherals.rs +++ b/esp-hal/src/soc/esp32c2/peripherals.rs @@ -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, } diff --git a/esp-hal/src/soc/esp32c6/peripherals.rs b/esp-hal/src/soc/esp32c6/peripherals.rs index bdc3c3537..6952cdc0d 100644 --- a/esp-hal/src/soc/esp32c6/peripherals.rs +++ b/esp-hal/src/soc/esp32c6/peripherals.rs @@ -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, } diff --git a/esp-hal/src/soc/esp32h2/peripherals.rs b/esp-hal/src/soc/esp32h2/peripherals.rs index b9314259c..205c1bad4 100644 --- a/esp-hal/src/soc/esp32h2/peripherals.rs +++ b/esp-hal/src/soc/esp32h2/peripherals.rs @@ -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, } diff --git a/examples/src/bin/dma_mem2mem.rs b/examples/src/bin/dma_mem2mem.rs new file mode 100644 index 000000000..a345cceec --- /dev/null +++ b/examples/src/bin/dma_mem2mem.rs @@ -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()); + } +} diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index f05a13e4f..227247473 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -24,6 +24,10 @@ harness = false name = "delay" harness = false +[[test]] +name = "dma_mem2mem" +harness = false + [[test]] name = "ecc" harness = false diff --git a/hil-test/tests/dma_mem2mem.rs b/hil-test/tests/dma_mem2mem.rs new file mode 100644 index 000000000..3869f4a9e --- /dev/null +++ b/hil-test/tests/dma_mem2mem.rs @@ -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]); + } + } +}