Skip to content

Commit

Permalink
dma: add Mem2Mem to support memory to memory transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
liebman committed Jun 28, 2024
1 parent 8cb921f commit c101bcf
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 13 deletions.
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
161 changes: 161 additions & 0 deletions esp-hal/src/dma/gdma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ impl<const N: u8> RegisterAccess for Channel<N> {
// 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()
Expand Down Expand Up @@ -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<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() };
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
}
}
}
62 changes: 49 additions & 13 deletions esp-hal/src/dma/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -458,6 +471,7 @@ pub trait Tx: TxPrivate {}
pub trait PeripheralMarker {}

#[doc(hidden)]
#[derive(Debug)]
pub struct DescriptorChain {
pub(crate) descriptors: &'static mut [DmaDescriptor],
}
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -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();
Expand Down
78 changes: 78 additions & 0 deletions examples/src/bin/dma_mem2mem.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit c101bcf

Please sign in to comment.