From c101bcf40cb41204d85fc7453facde042f6434dc Mon Sep 17 00:00:00 2001 From: liebman Date: Fri, 28 Jun 2024 10:55:06 -0700 Subject: [PATCH] dma: add Mem2Mem to support memory to memory transfer --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/dma/gdma.rs | 161 ++++++++++++++++++++++++++++++++ esp-hal/src/dma/mod.rs | 62 +++++++++--- examples/src/bin/dma_mem2mem.rs | 78 ++++++++++++++++ 4 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 examples/src/bin/dma_mem2mem.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 8adb6ce1894..f2f673fe53f 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - uart: Implement `embedded_io::ReadReady` for `Uart` and `UartRx` (#1702) - 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 ### Fixed diff --git a/esp-hal/src/dma/gdma.rs b/esp-hal/src/dma/gdma.rs index 0449f6c71dd..b5a3e0b6e04 100644 --- a/esp-hal/src/dma/gdma.rs +++ b/esp-hal/src/dma/gdma.rs @@ -88,6 +88,14 @@ impl RegisterAccess for Channel { // nothing special to be done here } + #[cfg(esp32s3)] + fn set_mem2mem_mode() { + debug!("set_mem2mem_mode"); + Self::ch() + .in_conf0() + .modify(|_, w| w.mem_trans_en().set_bit()); + } + fn set_out_burstmode(burst_mode: bool) { Self::ch().out_conf0().modify(|_, w| { w.out_data_burst_en() @@ -606,3 +614,156 @@ impl<'d> Dma<'d> { } } } + +pub use m2m::*; +mod m2m { + use embedded_dma::{ReadBuffer, WriteBuffer}; + + use super::dma_private::{DmaSupport, DmaSupportRx}; + use crate::dma::{ + ChannelTypes, + DescriptorChain, + DmaDescriptor, + 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. + #[cfg(esp32s3)] + pub struct Mem2Mem<'d, C, MODE> + where + C: ChannelTypes, + MODE: crate::Mode, + { + channel: crate::dma::Channel<'d, C, MODE>, + tx_chain: DescriptorChain, + rx_chain: DescriptorChain, + } + + #[cfg(esp32s3)] + impl<'d, C, MODE> Mem2Mem<'d, C, MODE> + where + C: ChannelTypes, + MODE: crate::Mode, + { + /// Create a new Mem2Mem instance. + pub fn new( + mut channel: crate::dma::Channel<'d, C, MODE>, + tx_descriptors: &'static mut [DmaDescriptor], + rx_descriptors: &'static mut [DmaDescriptor], + ) -> Self { + channel.tx.init_channel(); + channel.rx.init_channel(); + Mem2Mem { + channel, + 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() }; + debug!("tx_ptr: {:p}, tx_len: {}", tx_ptr, tx_len); + debug!("rx_ptr: {:p}, rx_len: {}", rx_ptr, rx_len); + 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(DmaPeripheral::Mem2Mem, &self.tx_chain)?; + self.channel + .rx + .prepare_transfer_without_start(DmaPeripheral::Mem2Mem, &self.rx_chain)?; + } + let result = self.channel.tx.start_transfer(); + #[cfg(feature = "debug")] + self.dump_info(); + if result.is_err() { + return Err(result.unwrap_err()); + } + self.channel.rx.start_transfer()?; + Ok(DmaTransferRx::new(self)) + } + + /// Dump register info + #[cfg(feature = "debug")] + pub fn dump_info(&self) { + debug!("TX Chain{:?}", self.tx_chain); + debug!("RX Chain{:?}", self.rx_chain); + dump_registers(); + } + } + + /// Dump register info + #[cfg(feature = "debug")] + pub fn dump_registers() { + let r = unsafe { &*esp32s3::DMA::ptr() }; + debug!("{:?}", r.ch(0).in_conf0()); + debug!("{:?}", r.ch(0).in_peri_sel()); + debug!("IN_INT_{:?}", r.ch(0).in_int().raw()); + debug!("{:?}", r.ch(0).in_link()); + debug!("{:?}", r.ch(0).out_conf0()); + debug!("{:?}", r.ch(0).out_peri_sel()); + debug!("OUT_INT_{:?}", r.ch(0).out_int().raw()); + debug!("{:?}", r.ch(0).out_link()); + debug!("{:?}", r.ch(0).outfifo_status()); + debug!("{:?}", r.ch(0).out_state()); + debug!("{:?}", r.ch(0).out_eof_des_addr()); + debug!("{:?}", r.ch(0).out_eof_bfr_des_addr()); + debug!("{:?}", r.ch(0).out_dscr()); + debug!("{:?}", r.ch(0).out_dscr_bf0()); + debug!("{:?}", r.ch(0).out_dscr_bf1()); + } + + #[cfg(esp32s3)] + 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() {} + + #[cfg(feature = "debug")] + dump_registers(); + } + + fn peripheral_dma_stop(&mut self) { + unreachable!("unsupported") + } + } + + #[cfg(esp32s3)] + 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 88291fb9eae..8f44e7a2580 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,29 @@ 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(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(esp32s3)] - LcdCam = 5, + LcdCam = 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(esp32s3)] + Mem2Mem = 63, } #[derive(PartialEq, PartialOrd)] @@ -458,6 +471,7 @@ pub trait Tx: TxPrivate {} pub trait PeripheralMarker {} #[doc(hidden)] +#[derive(Debug)] pub struct DescriptorChain { pub(crate) descriptors: &'static mut [DmaDescriptor], } @@ -948,6 +962,16 @@ where R::clear_in_interrupts(); R::reset_in(); R::set_in_descriptors(descriptors.first() as u32); + #[cfg(esp32s3)] + if peri == DmaPeripheral::Mem2Mem { + // Mem2Mem mode does not require a peripheral but if we leave this set to the + // default reset value (63) the DMA will not start and issues a + // descriptor error. So we set it to 0 to avoid this. + R::set_in_peripheral(0); + R::set_mem2mem_mode(); + } else { + } + #[cfg(not(esp32s3))] R::set_in_peripheral(peri as u8); Ok(()) @@ -1213,6 +1237,16 @@ where R::clear_out_interrupts(); R::reset_out(); R::set_out_descriptors(descriptors.first() as u32); + #[cfg(esp32s3)] + if peri == DmaPeripheral::Mem2Mem { + // Mem2Mem mode does not require a peripheral but if we leave this set to the + // default reset value (63) the DMA will not start and issues a + // descriptor error. So we set it to 0 to avoid this. + R::set_out_peripheral(0); + } else { + R::set_out_peripheral(peri as u8); + } + #[cfg(not(esp32s3))] R::set_out_peripheral(peri as u8); Ok(()) @@ -1411,6 +1445,8 @@ where #[doc(hidden)] pub trait RegisterAccess: crate::private::Sealed { fn init_channel(); + #[cfg(esp32s3)] + fn set_mem2mem_mode(); fn set_out_burstmode(burst_mode: bool); fn set_out_priority(priority: DmaPriority); fn clear_out_interrupts(); diff --git a/examples/src/bin/dma_mem2mem.rs b/examples/src/bin/dma_mem2mem.rs new file mode 100644 index 00000000000..2775bf69b8c --- /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 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::clock::ClockControl; +use esp_hal::delay::Delay; +use esp_hal::dma::Dma; +use esp_hal::dma::DmaPriority; +use esp_hal::dma::Mem2Mem; +use esp_hal::dma_buffers; +use esp_hal::peripherals::Peripherals; +use esp_hal::prelude::*; +use esp_hal::system::SystemControl; +use log::info; +use log::error; + +#[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); + + const DATA_SIZE: usize = 1024*100; + 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, + ); + + let mut mem2mem = Mem2Mem::new(channel, 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()); + } +}