diff --git a/esp-hal/src/aes/mod.rs b/esp-hal/src/aes/mod.rs index 07b09150591..455cda363dc 100644 --- a/esp-hal/src/aes/mod.rs +++ b/esp-hal/src/aes/mod.rs @@ -382,8 +382,8 @@ pub mod dma { DmaError, DmaRxBuffer, DmaTxBuffer, - InternalMemoryCachelineAligned, NoBuffer, + aligned::InternalMemory, prepare_for_rx, prepare_for_tx, }, @@ -761,7 +761,7 @@ pub mod dma { #[cfg(dma_can_access_psram)] unaligned_data_buffers: [Option; 2], - descriptors: InternalMemoryCachelineAligned<[DmaDescriptor; OUT_DESCR_COUNT + 1]>, + descriptors: InternalMemory<[DmaDescriptor; OUT_DESCR_COUNT + 1]>, } // The DMA descriptors prevent auto-implementing Sync and Send, but they can be treated as Send @@ -801,9 +801,7 @@ pub mod dma { #[cfg(dma_can_access_psram)] unaligned_data_buffers: [const { None }; 2], - descriptors: InternalMemoryCachelineAligned::new( - [DmaDescriptor::EMPTY; OUT_DESCR_COUNT + 1], - ), + descriptors: InternalMemory::new([DmaDescriptor::EMPTY; OUT_DESCR_COUNT + 1]), } } @@ -988,7 +986,7 @@ pub mod dma { work_item: &mut AesOperation, ) -> Result<(), AesDma<'d>> { let input_len = work_item.buffers.input.len(); - let (input_dscr, output_dscr) = self.descriptors.get_mut().split_at_mut(1); + let (input_dscr, output_dscr) = self.descriptors.get_mut().into_mut().split_at_mut(1); let (input_buffer, data_len) = unsafe { // This unwrap is infallible as AES-DMA devices don't have TX DMA alignment // requirements. diff --git a/esp-hal/src/dma/aligned.rs b/esp-hal/src/dma/aligned.rs new file mode 100644 index 00000000000..31152b89f51 --- /dev/null +++ b/esp-hal/src/dma/aligned.rs @@ -0,0 +1,134 @@ +//! Helper types for cacheline-aligned DMA buffers. + +use core::ops::{Deref, DerefMut}; + +use crate::{ + dma::{DmaAlignmentError, DmaBufError}, + soc::is_valid_ram_address, +}; + +/// A cacheline-aligned type wrapper. +/// +/// This type is guaranteed to be safely useable as a DMA buffer or +/// descriptor array. +/// +/// [`InternalMemoryMut`] is a reference type that carries this guarantee. +// ESP32-P4 internal memory is cached, enforce alignment to avoid memory +// corruption. Technically only needed for IN buffers and descriptor lists. +#[cfg_attr(soc_internal_memory_cached, repr(C, align(64)))] // dcache cache line +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[doc(hidden)] +pub struct InternalMemory(T); + +impl InternalMemory { + pub const fn new(init: T) -> Self { + Self(init) + } + + pub const fn get_mut(&mut self) -> InternalMemoryMut<'_, T> { + InternalMemoryMut(&mut self.0) + } +} + +/// A cacheline-aligned byte buffer. +/// +/// This type is guaranteed to be safely useable as a DMA buffer. +/// +/// [`InternalMemoryMut`] is a reference type that carries this guarantee. +// ESP32 requires word alignment for DMA buffers. +// ESP32-S2 technically supports byte-aligned DMA buffers, but the +// transfer ends up writing out of bounds. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg_attr(not(soc_internal_memory_cached), repr(C, align(4)))] +#[doc(hidden)] +pub struct InternalMemoryBuffer(InternalMemory<[u8; N]>); + +impl Default for InternalMemoryBuffer { + fn default() -> Self { + Self::new() + } +} + +impl InternalMemoryBuffer { + pub const fn new() -> Self { + Self(InternalMemory::new([0u8; N])) + } + + pub const fn get_mut(&mut self) -> InternalMemoryMut<'_, [u8]> { + InternalMemoryMut(&mut self.0.0) + } +} + +/// A mutable reference to an [`InternalMemory`] object. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct InternalMemoryMut<'a, T>(&'a mut T) +where + T: ?Sized; + +impl<'a, T: ?Sized> InternalMemoryMut<'a, T> { + /// Creates a new [`InternalMemoryMut`] from a mutable reference, if it's + /// provably compatible. + pub fn new(ptr: &'a mut T) -> Result { + let alignment = if cfg!(soc_internal_memory_cached) { + 64 // FIXME un-magic this + } else { + 4 + }; + let addr = ptr as *mut T as *mut () as usize; + + if !is_valid_ram_address(addr) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } + if !core::mem::size_of_val(ptr).is_multiple_of(alignment) { + return Err(DmaBufError::InvalidAlignment(DmaAlignmentError::Size)); + } + if !addr.is_multiple_of(alignment) { + return Err(DmaBufError::InvalidAlignment(DmaAlignmentError::Address)); + } + + Ok(Self(ptr)) + } + + /// Creates a new [`InternalMemoryMut`] from *any* mutable reference. + /// + /// # Safety + /// + /// The caller must ensure that the reference is properly aligned for [`InternalMemory`] and + /// the cachelines occupied by the reference do not overlap with any other data that can be + /// corrupted by a cacheline invalidation operation. + pub const unsafe fn new_unchecked(ptr: &'a mut T) -> Self { + Self(ptr) + } +} + +impl<'a, T: ?Sized> InternalMemoryMut<'a, T> { + /// Converts this object into a mutable reference. + pub fn into_mut(self) -> &'a mut T { + self.0 + } +} + +impl<'a, T, const N: usize> InternalMemoryMut<'a, [T; N]> { + /// Converts this object into a slice reference. + pub fn unsize(self) -> InternalMemoryMut<'a, [T]> { + InternalMemoryMut(self.0) + } +} + +impl<'a, T: ?Sized> Deref for InternalMemoryMut<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a, T: ?Sized> DerefMut for InternalMemoryMut<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index cd7ba5eb873..1ad47513825 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -6,12 +6,12 @@ use core::{ }; use super::*; -use crate::soc::is_slice_in_dram; #[cfg(dma_can_access_psram)] use crate::{ - dma::InternalMemoryBuffer, + dma::aligned::InternalMemoryBuffer, soc::{is_slice_in_psram, is_valid_psram_address, is_valid_ram_address}, }; +use crate::{dma::aligned::InternalMemoryMut, soc::is_slice_in_dram}; pub(crate) mod scoped; pub(crate) use scoped::*; @@ -53,185 +53,57 @@ impl From for DmaBufError { } } -cfg_if::cfg_if! { - if #[cfg(dma_can_access_psram)] { - /// Burst size used when transferring to and from external memory. - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum ExternalBurstConfig { - /// 16 bytes - Size16 = 16, - - /// 32 bytes - Size32 = 32, - - /// 64 bytes - Size64 = 64, - } - - impl ExternalBurstConfig { - /// The default external memory burst length. - pub const DEFAULT: Self = Self::Size16; - } - - impl Default for ExternalBurstConfig { - fn default() -> Self { - Self::DEFAULT - } - } - - /// Internal memory access burst mode. - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum InternalBurstConfig { - /// Burst mode is disabled. - Disabled, - - /// Burst mode is enabled. - Enabled, - } - - impl InternalBurstConfig { - /// The default internal burst mode configuration. - pub const DEFAULT: Self = Self::Disabled; - } - - impl Default for InternalBurstConfig { - fn default() -> Self { - Self::DEFAULT - } - } - - /// Burst transfer configuration. - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub struct BurstConfig { - /// Configures the burst size for PSRAM transfers. - /// - /// Burst mode is always enabled for PSRAM transfers. - pub external_memory: ExternalBurstConfig, - - /// Enables or disables the burst mode for internal memory transfers. - /// - /// The burst size is not configurable. - pub internal_memory: InternalBurstConfig, - } - - impl BurstConfig { - /// The default burst mode configuration. - pub const DEFAULT: Self = Self { - external_memory: ExternalBurstConfig::DEFAULT, - internal_memory: InternalBurstConfig::DEFAULT, - }; - } - - impl Default for BurstConfig { - fn default() -> Self { - Self::DEFAULT - } - } - - impl From for BurstConfig { - fn from(internal_memory: InternalBurstConfig) -> Self { - Self { - external_memory: ExternalBurstConfig::DEFAULT, - internal_memory, - } - } - } - - impl From for BurstConfig { - fn from(external_memory: ExternalBurstConfig) -> Self { - Self { - external_memory, - internal_memory: InternalBurstConfig::DEFAULT, - } - } - } +/// The mandatory PSRAM alignment for the given direction, *excluding* burst. +#[cfg(dma_can_access_psram)] +const fn min_psram_alignment(direction: TransferDirection) -> usize { + // S2 TRM: Specifically, size and buffer address pointer in receive descriptors + // should be 16-byte, 32-byte or 64-byte aligned. For data frame whose + // length is not a multiple of 16 bytes, 32 bytes, or 64 bytes, EDMA adds + // padding bytes to the end. + + // S3 TRM: Size and Address for IN transfers must be block aligned. For receive + // descriptors, if the data length received are not aligned with block size, + // GDMA will pad the data received with 0 until they are aligned to + // initiate burst transfer. You can read the length field in receive descriptors + // to obtain the length of valid data received + if matches!(direction, TransferDirection::In) { + 16 } else { - /// Burst transfer configuration. - #[derive(Clone, Copy, PartialEq, Eq, Debug)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum BurstConfig { - /// Burst mode is disabled. - Disabled, - - /// Burst mode is enabled. - Enabled, - } - - impl BurstConfig { - /// The default burst mode configuration. - pub const DEFAULT: Self = Self::Disabled; - } - - impl Default for BurstConfig { - fn default() -> Self { - Self::DEFAULT - } - } + // S2 TRM: Size, length and buffer address pointer in transmit descriptors are + // not necessarily aligned with block size. - type InternalBurstConfig = BurstConfig; + // S3 TRM: Size, length, and buffer address pointer in transmit descriptors do + // not need to be aligned. + 1 } } -#[cfg(dma_can_access_psram)] -impl ExternalBurstConfig { - const fn min_psram_alignment(self, direction: TransferDirection) -> usize { - // S2 TRM: Specifically, size and buffer address pointer in receive descriptors - // should be 16-byte, 32-byte or 64-byte aligned. For data frame whose - // length is not a multiple of 16 bytes, 32 bytes, or 64 bytes, EDMA adds - // padding bytes to the end. - - // S3 TRM: Size and Address for IN transfers must be block aligned. For receive - // descriptors, if the data length received are not aligned with block size, - // GDMA will pad the data received with 0 until they are aligned to - // initiate burst transfer. You can read the length field in receive descriptors - // to obtain the length of valid data received - if matches!(direction, TransferDirection::In) { - self as usize +/// The mandatory internal-RAM (DRAM) alignment for the given direction, +/// *excluding* burst. Size and address alignment come in pairs on current +/// hardware. +const fn min_dram_alignment(direction: TransferDirection) -> usize { + if matches!(direction, TransferDirection::In) { + // ESP32-S2 technically supports byte-aligned DMA buffers, but the + // transfer ends up writing out of bounds. + if cfg!(any(esp32, esp32s2)) { + // NOTE: The size must be word-aligned. + // NOTE: The buffer address must be word-aligned + 4 } else { - // S2 TRM: Size, length and buffer address pointer in transmit descriptors are - // not necessarily aligned with block size. - - // S3 TRM: Size, length, and buffer address pointer in transmit descriptors do - // not need to be aligned. 1 } - } -} - -impl InternalBurstConfig { - pub(super) const fn is_burst_enabled(self) -> bool { - !matches!(self, Self::Disabled) - } - - // Size and address alignment as those come in pairs on current hardware. - const fn min_dram_alignment(self, direction: TransferDirection) -> usize { - if matches!(direction, TransferDirection::In) { - if cfg!(esp32) { - // NOTE: The size must be word-aligned. - // NOTE: The buffer address must be word-aligned - 4 - } else if self.is_burst_enabled() { - // As described in "Accessing Internal Memory" paragraphs in the various TRMs. - 4 - } else { - 1 - } + } else { + // OUT transfers have no alignment requirements, except for ESP32, which is + // described below. + if cfg!(esp32) { + // SPI DMA: Burst transmission is supported. The data size for + // a single transfer must be four bytes aligned. + // I2S DMA: Burst transfer is supported. However, unlike the + // SPI DMA channels, the data size for a single transfer is + // one word, or four bytes. + 4 } else { - // OUT transfers have no alignment requirements, except for ESP32, which is - // described below. - if cfg!(esp32) { - // SPI DMA: Burst transmission is supported. The data size for - // a single transfer must be four bytes aligned. - // I2S DMA: Burst transfer is supported. However, unlike the - // SPI DMA channels, the data size for a single transfer is - // one word, or four bytes. - 4 - } else { - 1 - } + 1 } } } @@ -240,114 +112,135 @@ const fn max(a: usize, b: usize) -> usize { if a > b { a } else { b } } -impl BurstConfig { - delegate::delegate! { - #[cfg(dma_can_access_psram)] - to self.internal_memory { - pub(super) const fn min_dram_alignment(self, direction: TransferDirection) -> usize; - pub(super) fn is_burst_enabled(self) -> bool; - } +const fn chunk_size_for_alignment(alignment: usize) -> usize { + // DMA descriptors have a 12-bit field for the size/length of the buffer they + // point at. As there is no such thing as 0-byte alignment, this means the + // maximum size is 4095 bytes. + 4096 - alignment +} + +/// Cache line size (bytes) of the memory `buffer` lives in. RX buffers must be +/// aligned to this. +fn cache_line_size_for(buffer: &[u8]) -> usize { + #[cfg(dma_can_access_psram)] + if is_valid_psram_address(buffer.as_ptr() as usize) { + const EXTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { + // TODO(esp32p4): PSRAM is cached through the L2 cache, whose line size is + // configurable (64 or 128 bytes) and is not encoded anywhere yet. Assume the + // larger, always-safe value until the L2 line size is available. + soc_internal_memory_cached => 128, + any(esp32c5, esp32c61) => 32, // TODO: metadata-ify + _ => crate::soc::CONFIG_DATA_CACHE_LINE_SIZE, + }; + + return EXTERNAL_MEMORY_CACHE_LINE_SIZE; } - /// Calculates an alignment that is compatible with the current burst - /// configuration. - /// - /// This is an over-estimation so that Descriptors can be safely used with - /// any DMA channel in any direction. - pub const fn min_compatible_alignment(self) -> usize { - let in_alignment = self.min_dram_alignment(TransferDirection::In); - let out_alignment = self.min_dram_alignment(TransferDirection::Out); - let alignment = max(in_alignment, out_alignment); + let _ = buffer; - #[cfg(dma_can_access_psram)] - let alignment = max(alignment, self.external_memory as usize); + const INTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { + // ESP32-P4: L1 data cache line size. + // FIXME: add uncached address range? + soc_internal_memory_cached => 64, + _ => 1, + }; + INTERNAL_MEMORY_CACHE_LINE_SIZE +} - alignment - } +/// The strictest *mandatory* alignment for a buffer in this memory region and +/// transfer direction, *excluding* burst. +fn region_alignment(buffer: &[u8], direction: TransferDirection) -> usize { + let alignment = min_dram_alignment(direction); - const fn chunk_size_for_alignment(alignment: usize) -> usize { - // DMA descriptors have a 12-bit field for the size/length of the buffer they - // point at. As there is no such thing as 0-byte alignment, this means the - // maximum size is 4095 bytes. - 4096 - alignment + #[cfg(dma_can_access_psram)] + if is_valid_psram_address(buffer.as_ptr() as usize) { + return max(alignment, min_psram_alignment(direction)); } - /// Calculates a chunk size that is compatible with the current burst - /// configuration's alignment requirements. - /// - /// This is an over-estimation so that Descriptors can be safely used with - /// any DMA channel in any direction. - pub const fn max_compatible_chunk_size(self) -> usize { - Self::chunk_size_for_alignment(self.min_compatible_alignment()) - } + let _ = buffer; + alignment +} - fn min_alignment(self, _buffer: &[u8], direction: TransferDirection) -> usize { - let alignment = self.min_dram_alignment(direction); +/// The alignment a buffer actually guarantees, given a caller-requested +/// alignment: the larger of the request and the region's mandatory floor. +fn effective_alignment(buffer: &[u8], direction: TransferDirection, requested: usize) -> usize { + max(requested, region_alignment(buffer, direction)) +} - cfg_if::cfg_if! { - if #[cfg(dma_can_access_psram)] { - let mut alignment = alignment; - if is_valid_psram_address(_buffer.as_ptr() as usize) { - alignment = max(alignment, self.external_memory.min_psram_alignment(direction)); - } - } - } +/// An alignment that is compatible with any DMA channel in any direction. +/// +/// This is an over-estimation so that a single descriptor array can be safely +/// used regardless of the transfer direction or memory region. +pub const fn min_compatible_alignment() -> usize { + let in_alignment = min_dram_alignment(TransferDirection::In); + let out_alignment = min_dram_alignment(TransferDirection::Out); + let alignment = max(in_alignment, out_alignment); + + #[cfg(dma_can_access_psram)] + let alignment = max(alignment, min_psram_alignment(TransferDirection::In)); + + alignment +} - alignment +/// A chunk size that is compatible with any DMA channel in any direction. +/// +/// This is an over-estimation so that a single descriptor array can be safely +/// used regardless of the transfer direction or memory region. +pub const fn max_compatible_chunk_size() -> usize { + chunk_size_for_alignment(min_compatible_alignment()) +} + +fn ensure_buffer_aligned( + buffer: &[u8], + direction: TransferDirection, + alignment: usize, +) -> Result<(), DmaAlignmentError> { + if !(buffer.as_ptr() as usize).is_multiple_of(alignment) { + return Err(DmaAlignmentError::Address); } - // Note: this function ignores address alignment as we assume the buffers are - // aligned. - fn max_chunk_size_for(self, buffer: &[u8], direction: TransferDirection) -> usize { - Self::chunk_size_for_alignment(self.min_alignment(buffer, direction)) + // NB: the TRMs suggest that buffer length don't need to be aligned, but + // for IN transfers, we configure the DMA descriptors' size field, which needs + // to be aligned. + if direction == TransferDirection::In && !buffer.len().is_multiple_of(alignment) { + return Err(DmaAlignmentError::Size); } - fn ensure_buffer_aligned( - self, - buffer: &[u8], - direction: TransferDirection, - ) -> Result<(), DmaAlignmentError> { - let alignment = self.min_alignment(buffer, direction); - if !(buffer.as_ptr() as usize).is_multiple_of(alignment) { - return Err(DmaAlignmentError::Address); - } + Ok(()) +} - // NB: the TRMs suggest that buffer length don't need to be aligned, but - // for IN transfers, we configure the DMA descriptors' size field, which needs - // to be aligned. - if direction == TransferDirection::In && !buffer.len().is_multiple_of(alignment) { - return Err(DmaAlignmentError::Size); - } +fn ensure_buffer_compatible( + buffer: &[u8], + direction: TransferDirection, + alignment: usize, +) -> Result<(), DmaBufError> { + if buffer.is_empty() { + return Ok(()); + } + // buffer can be either DRAM or PSRAM (if supported) + let is_in_dram = is_slice_in_dram(buffer); + let is_in_psram = cfg_select! { + dma_can_access_psram => is_slice_in_psram(buffer), + _ => false, + }; - Ok(()) + if !(is_in_dram || is_in_psram) { + return Err(DmaBufError::UnsupportedMemoryRegion); } - fn ensure_buffer_compatible( - self, - buffer: &[u8], - direction: TransferDirection, - ) -> Result<(), DmaBufError> { - if buffer.is_empty() { - return Ok(()); - } - // buffer can be either DRAM or PSRAM (if supported) - let is_in_dram = is_slice_in_dram(buffer); - cfg_if::cfg_if! { - if #[cfg(dma_can_access_psram)]{ - let is_in_psram = is_slice_in_psram(buffer); - } else { - let is_in_psram = false; - } - } + ensure_buffer_aligned(buffer, direction, alignment)?; - if !(is_in_dram || is_in_psram) { - return Err(DmaBufError::UnsupportedMemoryRegion); + // RX buffers in cached memory are invalidated after the transfer, rounded out + // to whole cache lines. A misaligned start would discard a neighbour's cached + // data, so reject it up front. + if direction == TransferDirection::In { + let cache_line_size = cache_line_size_for(buffer); + if !(buffer.as_ptr() as usize).is_multiple_of(cache_line_size) { + return Err(DmaBufError::InvalidAlignment(DmaAlignmentError::Address)); } - - self.ensure_buffer_aligned(buffer, direction)?; - - Ok(()) } + + Ok(()) } /// The direction of the DMA transfer. @@ -367,22 +260,15 @@ pub struct Preparation { /// The descriptor the DMA will start from. pub start: *mut DmaDescriptor, - /// The direction of the DMA transfer. - pub direction: TransferDirection, - /// Must be `true` if any of the DMA descriptors contain data in PSRAM. #[cfg(dma_can_access_psram)] pub accesses_psram: bool, - /// Configures the DMA to transfer data in bursts. - /// - /// The implementation of the buffer must ensure that buffer size - /// and alignment in each descriptor is compatible with the burst - /// transfer configuration. - /// - /// For details on alignment requirements, refer to your chip's - #[doc = crate::trm_markdown_link!()] - pub burst_transfer: BurstConfig, + /// Largest alignment (the "burst axis") guaranteed by the buffer's start + /// address and every descriptor length, in bytes. The channel negotiates the + /// effective burst against this. The cache-line requirement is excluded; the + /// buffer handles that itself. + pub max_alignment: usize, /// Configures the "check owner" feature of the DMA channel. /// @@ -504,6 +390,16 @@ pub struct BufView(T); pub struct DmaTxBuf(ScopedDmaTxBuf<'static>); impl DmaTxBuf { + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + pub fn new_internal_memory( + descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + // TODO: TX buffers don't need to be cacheline aligned. + buffer: InternalMemoryMut<'static, [u8]>, + ) -> Result { + // TODO: this is the stronger guarantee, this should be the callee + Self::new(descriptors.into_mut(), buffer.into_mut()) + } + /// Creates a new [DmaTxBuf] from some descriptors and a buffer. /// /// There must be enough descriptors for the provided buffer. @@ -516,28 +412,28 @@ impl DmaTxBuf { descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], ) -> Result { - ScopedDmaTxBuf::new_with_config(descriptors, buffer, BurstConfig::default()).map(Self) + ScopedDmaTxBuf::new(descriptors, buffer).map(Self) } - /// Creates a new [DmaTxBuf] from some descriptors and a buffer. + /// Creates a new [DmaTxBuf] with a caller-chosen minimum alignment. /// - /// There must be enough descriptors for the provided buffer. - /// Depending on alignment requirements, each descriptor can handle at most - /// 4095 bytes worth of buffer. + /// The buffer's start address must be a multiple of `alignment`, otherwise + /// this fails with [DmaBufError::InvalidAlignment]. Larger alignments split + /// the buffer into more descriptors ([DmaBufError::InsufficientDescriptors] + /// otherwise). + /// + /// `alignment` is the largest alignment the buffer guarantees, against which + /// the channel negotiates its burst. The mandatory per-region floor (e.g. + /// PSRAM block size) is always applied, so the effective value may be larger. /// /// Both the descriptors and buffer must be in DMA-capable memory. /// Only DRAM is supported for descriptors. - pub fn new_with_config( + pub fn new_aligned( descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], - config: impl Into, + alignment: usize, ) -> Result { - ScopedDmaTxBuf::new_with_config(descriptors, buffer, config).map(Self) - } - - /// Configures the DMA to use burst transfers to access this buffer. - pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { - self.0.set_burst_config(burst) + ScopedDmaTxBuf::new_aligned(descriptors, buffer, alignment).map(Self) } /// Consume the buf, returning the descriptors and buffer. @@ -617,6 +513,15 @@ unsafe impl DmaTxBuffer for DmaTxBuf { pub struct DmaRxBuf(ScopedDmaRxBuf<'static>); impl DmaRxBuf { + /// Creates a new [DmaRxBuf] from some descriptors and a buffer. + pub fn new_internal_memory( + descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + buffer: InternalMemoryMut<'static, [u8]>, + ) -> Result { + // TODO: this is the stronger guarantee, this should be the callee + Self::new(descriptors.into_mut(), buffer.into_mut()) + } + /// Creates a new [DmaRxBuf] from some descriptors and a buffer. /// /// There must be enough descriptors for the provided buffer. @@ -628,28 +533,28 @@ impl DmaRxBuf { descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], ) -> Result { - ScopedDmaRxBuf::new_with_config(descriptors, buffer, BurstConfig::default()).map(Self) + ScopedDmaRxBuf::new(descriptors, buffer).map(Self) } - /// Creates a new [DmaRxBuf] from some descriptors and a buffer. + /// Creates a new [DmaRxBuf] with a caller-chosen minimum alignment. /// - /// There must be enough descriptors for the provided buffer. - /// Depending on alignment requirements, each descriptor can handle at most - /// 4092 bytes worth of buffer. + /// The buffer's start address and length must be a multiple of `alignment`, + /// otherwise this fails with [DmaBufError::InvalidAlignment]. Larger + /// alignments split the buffer into more descriptors + /// ([DmaBufError::InsufficientDescriptors] otherwise). + /// + /// `alignment` is the largest alignment the buffer guarantees, against which + /// the channel negotiates its burst. The mandatory per-region floor (e.g. + /// PSRAM block size) is always applied, so the effective value may be larger. /// /// Both the descriptors and buffer must be in DMA-capable memory. /// Only DRAM is supported for descriptors. - pub fn new_with_config( + pub fn new_aligned( descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], - config: impl Into, + alignment: usize, ) -> Result { - ScopedDmaRxBuf::new_with_config(descriptors, buffer, config).map(Self) - } - - /// Configures the DMA to use burst transfers to access this buffer. - pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { - self.0.set_burst_config(burst) + ScopedDmaRxBuf::new_aligned(descriptors, buffer, alignment).map(Self) } /// Consume the buf, returning the descriptors and buffer. @@ -743,10 +648,24 @@ pub struct DmaRxTxBuf { rx_descriptors: DescriptorSet<'static>, tx_descriptors: DescriptorSet<'static>, buffer: &'static mut [u8], - burst: BurstConfig, + alignment: usize, } impl DmaRxTxBuf { + /// Creates a new [DmaRxTxBuf] from some descriptors and a buffer. + pub fn new_internal_memory( + rx_descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + tx_descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + buffer: InternalMemoryMut<'static, [u8]>, + ) -> Result { + // TODO: this is the stronger guarantee, this should be the callee + Self::new( + rx_descriptors.into_mut(), + tx_descriptors.into_mut(), + buffer.into_mut(), + ) + } + /// Creates a new [DmaRxTxBuf] from some descriptors and a buffer. /// /// There must be enough descriptors for the provided buffer. @@ -758,48 +677,57 @@ impl DmaRxTxBuf { rx_descriptors: &'static mut [DmaDescriptor], tx_descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], + ) -> Result { + // The buffer is used in both directions, so honour the stricter floor. + let alignment = max( + region_alignment(buffer, TransferDirection::In), + region_alignment(buffer, TransferDirection::Out), + ); + Self::new_aligned(rx_descriptors, tx_descriptors, buffer, alignment) + } + + /// Creates a new [DmaRxTxBuf] with a caller-chosen minimum alignment. + /// + /// See [DmaRxBuf::new_aligned] for the meaning of `alignment`. As the buffer + /// is used in both directions, the stricter of the two directions' floors is + /// applied. + pub fn new_aligned( + rx_descriptors: &'static mut [DmaDescriptor], + tx_descriptors: &'static mut [DmaDescriptor], + buffer: &'static mut [u8], + alignment: usize, ) -> Result { let mut buf = Self { rx_descriptors: DescriptorSet::new(rx_descriptors)?, tx_descriptors: DescriptorSet::new(tx_descriptors)?, buffer, - burst: BurstConfig::default(), + alignment: 1, }; let capacity = buf.capacity(); - buf.configure(buf.burst, capacity)?; + buf.configure(alignment.max(1), capacity)?; Ok(buf) } - fn configure( - &mut self, - burst: impl Into, - length: usize, - ) -> Result<(), DmaBufError> { - let burst = burst.into(); - self.set_length_fallible(length, burst)?; + fn configure(&mut self, alignment: usize, length: usize) -> Result<(), DmaBufError> { + // The buffer is shared by both directions, so honour the stricter floor. + let alignment = max( + effective_alignment(self.buffer, TransferDirection::In, alignment), + effective_alignment(self.buffer, TransferDirection::Out, alignment), + ); + self.set_length_fallible(length, alignment)?; - self.rx_descriptors.link_with_buffer( - self.buffer, - burst.max_chunk_size_for(self.buffer, TransferDirection::In), - )?; - self.tx_descriptors.link_with_buffer( - self.buffer, - burst.max_chunk_size_for(self.buffer, TransferDirection::Out), - )?; + self.rx_descriptors + .link_with_buffer(self.buffer, chunk_size_for_alignment(alignment))?; + self.tx_descriptors + .link_with_buffer(self.buffer, chunk_size_for_alignment(alignment))?; - self.burst = burst; + self.alignment = alignment; Ok(()) } - /// Configures the DMA to use burst transfers to access this buffer. - pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { - let len = self.len(); - self.configure(burst, len) - } - /// Consume the buf, returning the rx descriptors, tx descriptors and /// buffer. pub fn split( @@ -840,21 +768,17 @@ impl DmaRxTxBuf { self.buffer } - fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + fn set_length_fallible(&mut self, len: usize, alignment: usize) -> Result<(), DmaBufError> { if len > self.capacity() { return Err(DmaBufError::BufferTooSmall); } - burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In)?; - burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out)?; - - self.rx_descriptors.set_rx_length( - len, - burst.max_chunk_size_for(self.buffer, TransferDirection::In), - )?; - self.tx_descriptors.set_tx_length( - len, - burst.max_chunk_size_for(self.buffer, TransferDirection::Out), - )?; + ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In, alignment)?; + ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out, alignment)?; + + self.rx_descriptors + .set_rx_length(len, chunk_size_for_alignment(alignment))?; + self.tx_descriptors + .set_tx_length(len, chunk_size_for_alignment(alignment))?; Ok(()) } @@ -864,7 +788,7 @@ impl DmaRxTxBuf { /// /// `len` must be less than or equal to the buffer size. pub fn set_length(&mut self, len: usize) { - unwrap!(self.set_length_fallible(len, self.burst)); + unwrap!(self.set_length_fallible(len, self.alignment)); } } @@ -896,10 +820,9 @@ unsafe impl DmaTxBuffer for DmaRxTxBuf { Preparation { start: self.tx_descriptors.head(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, + max_alignment: self.alignment, check_owner: None, auto_write_back: false, } @@ -940,10 +863,9 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf { Preparation { start: self.rx_descriptors.head(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, + max_alignment: self.alignment, check_owner: None, auto_write_back: true, } @@ -1003,10 +925,19 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf { pub struct DmaRxStreamBuf { descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], - burst: BurstConfig, } impl DmaRxStreamBuf { + /// Creates a new [DmaRxStreamBuf] evenly distributing the buffer between + /// the provided descriptors. + pub fn new_internal_memory( + descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + buffer: InternalMemoryMut<'static, [u8]>, + ) -> Result { + // TODO: this is the stronger guarantee, this should be the callee + Self::new(descriptors.into_mut(), buffer.into_mut()) + } + /// Creates a new [DmaRxStreamBuf] evenly distributing the buffer between /// the provided descriptors. pub fn new( @@ -1052,7 +983,6 @@ impl DmaRxStreamBuf { Ok(Self { descriptors, buffer, - burst: BurstConfig::default(), }) } @@ -1077,10 +1007,9 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf { } Preparation { start: self.descriptors.as_mut_ptr(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: self.burst, + max_alignment: region_alignment(self.buffer, TransferDirection::In), // Whilst we give ownership of the descriptors the DMA, the correctness of this buffer // implementation doesn't rely on the DMA checking for descriptor ownership. @@ -1305,13 +1234,21 @@ impl DmaRxStreamBufView { pub struct DmaTxStreamBuf { descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], - burst: BurstConfig, pre_filled: Option, view_descriptor_idx: usize, view_descriptor_offset: usize, } impl DmaTxStreamBuf { + /// Creates a new [DmaTxStreamBuf] from some descriptors and a buffer. + pub fn new_internal_memory( + descriptors: InternalMemoryMut<'static, [DmaDescriptor]>, + buffer: InternalMemoryMut<'static, [u8]>, + ) -> Result { + // TODO: this is the stronger guarantee, this should be the callee + Self::new(descriptors.into_mut(), buffer.into_mut()) + } + /// Creates a new [DmaTxStreamBuf] evenly distributing the buffer between /// the provided descriptors. pub fn new( @@ -1355,7 +1292,6 @@ impl DmaTxStreamBuf { Ok(Self { descriptors, buffer, - burst: Default::default(), pre_filled: None, view_descriptor_idx: 0, view_descriptor_offset: 0, @@ -1499,10 +1435,9 @@ unsafe impl DmaTxBuffer for DmaTxStreamBuf { Preparation { start: self.descriptors.as_mut_ptr(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: self.burst, + max_alignment: region_alignment(self.buffer, TransferDirection::Out), // Whilst we give ownership of the descriptors the DMA, the correctness of this buffer // implementation doesn't rely on the DMA checking for descriptor ownership. @@ -1609,10 +1544,9 @@ unsafe impl DmaTxBuffer for EmptyBuf { Preparation { start: (&raw mut EMPTY).cast(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: BurstConfig::default(), + max_alignment: 1, // As we don't give ownership of the descriptor to the DMA, it's important that the DMA // channel does *NOT* check for ownership, otherwise the channel will return an error. @@ -1647,10 +1581,9 @@ unsafe impl DmaRxBuffer for EmptyBuf { Preparation { start: (&raw mut EMPTY).cast(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: BurstConfig::default(), + max_alignment: 1, // As we don't give ownership of the descriptor to the DMA, it's important that the DMA // channel does *NOT* check for ownership, otherwise the channel will return an error. @@ -1696,7 +1629,7 @@ impl DmaLoopBuf { return Err(DmaBufError::UnsupportedMemoryRegion); } - if buffer.len() > BurstConfig::default().max_chunk_size_for(buffer, TransferDirection::Out) + if buffer.len() > chunk_size_for_alignment(region_alignment(buffer, TransferDirection::Out)) { return Err(DmaBufError::InsufficientDescriptors); } @@ -1726,8 +1659,7 @@ unsafe impl DmaTxBuffer for DmaLoopBuf { start: self.descriptor, #[cfg(dma_can_access_psram)] accesses_psram: false, - direction: TransferDirection::Out, - burst_transfer: BurstConfig::default(), + max_alignment: region_alignment(self.buffer, TransferDirection::Out), // The DMA must not check the owner bit, as it is never set. check_owner: Some(false), @@ -1769,10 +1701,9 @@ impl NoBuffer { fn prep(&self) -> Preparation { Preparation { start: self.0.start, - direction: self.0.direction, #[cfg(dma_can_access_psram)] accesses_psram: self.0.accesses_psram, - burst_transfer: self.0.burst_transfer, + max_alignment: self.0.max_alignment, check_owner: self.0.check_owner, auto_write_back: self.0.auto_write_back, } @@ -1819,8 +1750,7 @@ pub(crate) unsafe fn prepare_for_tx( mut data: NonNull<[u8]>, block_size: usize, ) -> Result<(NoBuffer, usize), DmaError> { - let alignment = - BurstConfig::DEFAULT.min_alignment(unsafe { data.as_ref() }, TransferDirection::Out); + let alignment = region_alignment(unsafe { data.as_ref() }, TransferDirection::Out); if !data.addr().get().is_multiple_of(alignment) { // ESP32 has word alignment requirement on the TX descriptors, too. @@ -1872,8 +1802,7 @@ pub(crate) unsafe fn prepare_for_tx( Ok(( NoBuffer(Preparation { start: descriptors.head(), - direction: TransferDirection::Out, - burst_transfer: BurstConfig::DEFAULT, + max_alignment: alignment, check_owner: None, auto_write_back: false, #[cfg(dma_can_access_psram)] @@ -1897,8 +1826,10 @@ pub(crate) unsafe fn prepare_for_rx( #[cfg(dma_can_access_psram)] align_buffers: &mut [Option; 2], mut data: NonNull<[u8]>, ) -> (NoBuffer, usize) { - let chunk_size = - BurstConfig::DEFAULT.max_chunk_size_for(unsafe { data.as_ref() }, TransferDirection::In); + let chunk_size = chunk_size_for_alignment(region_alignment( + unsafe { data.as_ref() }, + TransferDirection::In, + )); // The data we have to process may not be appropriate for the DMA: // - it may be improperly aligned for PSRAM @@ -1983,8 +1914,7 @@ pub(crate) unsafe fn prepare_for_rx( ( NoBuffer(Preparation { start: descriptors.head(), - direction: TransferDirection::In, - burst_transfer: BurstConfig::DEFAULT, + max_alignment: 4096 - chunk_size, check_owner: None, auto_write_back: true, #[cfg(dma_can_access_psram)] @@ -2003,7 +1933,7 @@ fn build_descriptor_list_for_psram( let data_len = data.len(); let data_addr = data.addr().get(); - let min_alignment = ExternalBurstConfig::DEFAULT.min_psram_alignment(TransferDirection::In); + let min_alignment = min_psram_alignment(TransferDirection::In); let chunk_size = 4096 - min_alignment; let mut desciptor_iter = DescriptorChainingIter::new(descriptors.descriptors); diff --git a/esp-hal/src/dma/buffers/scoped.rs b/esp-hal/src/dma/buffers/scoped.rs index e684b73d9b8..de0f5f41244 100644 --- a/esp-hal/src/dma/buffers/scoped.rs +++ b/esp-hal/src/dma/buffers/scoped.rs @@ -10,58 +10,58 @@ use super::*; pub(crate) struct ScopedDmaTxBuf<'a> { descriptors: DescriptorSet<'a>, buffer: &'a mut [u8], - burst: BurstConfig, + alignment: usize, } impl<'a> ScopedDmaTxBuf<'a> { - /// Creates a new [ScopedDmaTxBuf] from some descriptors and a buffer. + /// Creates a new [ScopedDmaTxBuf] aligned to the requirements of the memory + /// region the buffer lives in (internal or external, inferred from its + /// address). Use [ScopedDmaTxBuf::new_aligned] to request a stronger + /// alignment. + pub fn new( + descriptors: &'a mut [DmaDescriptor], + buffer: &'a mut [u8], + ) -> Result { + let alignment = region_alignment(buffer, TransferDirection::Out); + Self::new_aligned(descriptors, buffer, alignment) + } + + /// Creates a new [ScopedDmaTxBuf] with a caller-chosen minimum alignment. /// /// There must be enough descriptors for the provided buffer. - /// Depending on alignment requirements, each descriptor can handle at most + /// Depending on the alignment, each descriptor can handle at most /// 4095 bytes worth of buffer. /// /// Both the descriptors and buffer must be in DMA-capable memory. /// Only DRAM is supported for descriptors. - pub fn new_with_config( + pub fn new_aligned( descriptors: &'a mut [DmaDescriptor], buffer: &'a mut [u8], - config: impl Into, + alignment: usize, ) -> Result { let mut buf = Self { descriptors: DescriptorSet::new(descriptors)?, buffer, - burst: BurstConfig::default(), + alignment: 1, }; let capacity = buf.capacity(); - buf.configure(config, capacity)?; + buf.configure(alignment, capacity)?; Ok(buf) } - fn configure( - &mut self, - burst: impl Into, - length: usize, - ) -> Result<(), DmaBufError> { - let burst = burst.into(); - self.set_length_fallible(length, burst)?; + fn configure(&mut self, alignment: usize, length: usize) -> Result<(), DmaBufError> { + let alignment = effective_alignment(self.buffer, TransferDirection::Out, alignment); + self.set_length_fallible(length, alignment)?; - self.descriptors.link_with_buffer( - self.buffer, - burst.max_chunk_size_for(self.buffer, TransferDirection::Out), - )?; + self.descriptors + .link_with_buffer(self.buffer, chunk_size_for_alignment(alignment))?; - self.burst = burst; + self.alignment = alignment; Ok(()) } - /// Configures the DMA to use burst transfers to access this buffer. - pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { - let len = self.len(); - self.configure(burst, len) - } - /// Consume the buf, returning the descriptors and buffer. pub fn split(self) -> (&'a mut [DmaDescriptor], &'a mut [u8]) { (self.descriptors.into_inner(), self.buffer) @@ -81,16 +81,14 @@ impl<'a> ScopedDmaTxBuf<'a> { .sum::() } - fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + fn set_length_fallible(&mut self, len: usize, alignment: usize) -> Result<(), DmaBufError> { if len > self.capacity() { return Err(DmaBufError::BufferTooSmall); } - burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out)?; + ensure_buffer_compatible(&self.buffer[..len], TransferDirection::Out, alignment)?; - self.descriptors.set_tx_length( - len, - burst.max_chunk_size_for(self.buffer, TransferDirection::Out), - )?; + self.descriptors + .set_tx_length(len, chunk_size_for_alignment(alignment))?; // This only needs to be done once (after every significant length change) as // Self::prepare sets Preparation::auto_write_back to false. @@ -109,7 +107,7 @@ impl<'a> ScopedDmaTxBuf<'a> { /// The number of bytes in data must be less than or equal to the buffer /// size. pub fn set_length(&mut self, len: usize) { - unwrap!(self.set_length_fallible(len, self.burst)) + unwrap!(self.set_length_fallible(len, self.alignment)) } /// Fills the TX buffer with the bytes provided in `data` and reset the @@ -169,10 +167,9 @@ unsafe impl<'a> DmaTxBuffer for ScopedDmaTxBuf<'a> { Preparation { start: self.descriptors.head(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, + max_alignment: self.alignment, check_owner: None, auto_write_back: false, } @@ -197,57 +194,57 @@ unsafe impl<'a> DmaTxBuffer for ScopedDmaTxBuf<'a> { pub(crate) struct ScopedDmaRxBuf<'a> { descriptors: DescriptorSet<'a>, buffer: &'a mut [u8], - burst: BurstConfig, + alignment: usize, } impl<'a> ScopedDmaRxBuf<'a> { - /// Creates a new [ScopedDmaRxBuf] from some descriptors and a buffer. + /// Creates a new [ScopedDmaRxBuf] aligned to the requirements of the memory + /// region the buffer lives in (internal or external, inferred from its + /// address). Use [ScopedDmaRxBuf::new_aligned] to request a stronger + /// alignment. + pub fn new( + descriptors: &'a mut [DmaDescriptor], + buffer: &'a mut [u8], + ) -> Result { + let alignment = region_alignment(buffer, TransferDirection::In); + Self::new_aligned(descriptors, buffer, alignment) + } + + /// Creates a new [ScopedDmaRxBuf] with a caller-chosen minimum alignment. /// /// There must be enough descriptors for the provided buffer. - /// Depending on alignment requirements, each descriptor can handle at most + /// Depending on the alignment, each descriptor can handle at most /// 4092 bytes worth of buffer. /// /// Both the descriptors and buffer must be in DMA-capable memory. /// Only DRAM is supported for descriptors. - pub fn new_with_config( + pub fn new_aligned( descriptors: &'a mut [DmaDescriptor], buffer: &'a mut [u8], - config: impl Into, + alignment: usize, ) -> Result { let mut buf = Self { descriptors: DescriptorSet::new(descriptors)?, buffer, - burst: BurstConfig::default(), + alignment: 1, }; - buf.configure(config, buf.capacity())?; + buf.configure(alignment, buf.capacity())?; Ok(buf) } - fn configure( - &mut self, - burst: impl Into, - length: usize, - ) -> Result<(), DmaBufError> { - let burst = burst.into(); - self.set_length_fallible(length, burst)?; + fn configure(&mut self, alignment: usize, length: usize) -> Result<(), DmaBufError> { + let alignment = effective_alignment(self.buffer, TransferDirection::In, alignment); + self.set_length_fallible(length, alignment)?; - self.descriptors.link_with_buffer( - self.buffer, - burst.max_chunk_size_for(self.buffer, TransferDirection::In), - )?; + self.descriptors + .link_with_buffer(self.buffer, chunk_size_for_alignment(alignment))?; - self.burst = burst; + self.alignment = alignment; Ok(()) } - /// Configures the DMA to use burst transfers to access this buffer. - pub fn set_burst_config(&mut self, burst: BurstConfig) -> Result<(), DmaBufError> { - let len = self.len(); - self.configure(burst, len) - } - /// Consume the buf, returning the descriptors and buffer. pub fn split(self) -> (&'a mut [DmaDescriptor], &'a mut [u8]) { (self.descriptors.into_inner(), self.buffer) @@ -268,16 +265,14 @@ impl<'a> ScopedDmaRxBuf<'a> { .sum::() } - fn set_length_fallible(&mut self, len: usize, burst: BurstConfig) -> Result<(), DmaBufError> { + fn set_length_fallible(&mut self, len: usize, alignment: usize) -> Result<(), DmaBufError> { if len > self.capacity() { return Err(DmaBufError::BufferTooSmall); } - burst.ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In)?; + ensure_buffer_compatible(&self.buffer[..len], TransferDirection::In, alignment)?; - self.descriptors.set_rx_length( - len, - burst.max_chunk_size_for(&self.buffer[..len], TransferDirection::In), - ) + self.descriptors + .set_rx_length(len, chunk_size_for_alignment(alignment)) } /// Reset the descriptors to only receive `len` amount of bytes into this @@ -286,7 +281,7 @@ impl<'a> ScopedDmaRxBuf<'a> { /// The number of bytes in data must be less than or equal to the buffer /// size. pub fn set_length(&mut self, len: usize) { - unwrap!(self.set_length_fallible(len, self.burst)); + unwrap!(self.set_length_fallible(len, self.alignment)); } /// Returns the entire underlying buffer as a slice than can be read. @@ -382,10 +377,9 @@ unsafe impl<'a> DmaRxBuffer for ScopedDmaRxBuf<'a> { Preparation { start: self.descriptors.head(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, + max_alignment: self.alignment, check_owner: None, auto_write_back: true, } diff --git a/esp-hal/src/dma/engine/axi_gdma.rs b/esp-hal/src/dma/engine/axi_gdma.rs index 1157569c4d5..6ac63a48b05 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -109,6 +109,26 @@ impl<'d> From> for AxiGdmaTxChannel<'d> { } } +/// Configuration for an AXI GDMA channel half. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct AxiGdmaConfig { + /// Channel priority. + /// + /// The default value is `Priority0`. + priority: AxiGdmaPriority, + + /// Maximum burst length (applies to internal and external RAM alike). + burst: AxiGdmaBurst, +} + +for_each_dma_engine! { + ("AXI_GDMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; +} + impl AxiGdmaTxChannel<'_> { #[inline(always)] fn ch(&self) -> &pac::axi_dma::OUT_CH { @@ -117,6 +137,14 @@ impl AxiGdmaTxChannel<'_> { } impl RegisterAccess for AxiGdmaTxChannel<'_> { + type Config = AxiGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -129,19 +157,20 @@ impl RegisterAccess for AxiGdmaTxChannel<'_> { self.ch().out_conf0().toggle(|w, en| w.out_rst().bit(en)); } - // AXI-DMA data burst is always enabled; nothing to configure. - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} - - fn set_descr_burst_mode(&self, burst_mode: bool) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, _accesses_psram: bool) { + // `out_burst_size_sel`: 0..=4 select 8/16/32/64/128-byte bursts, which is + // exactly the negotiated burst's discriminant. Burst is always enabled and + // shared between internal and external RAM. + let burst_size_sel = config.burst.negotiate(max_alignment) as u8; self.ch() .out_conf0() - .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); + .modify(|_, w| unsafe { w.out_burst_size_sel().bits(burst_size_sel) }); } - fn set_priority(&self, priority: DmaPriority) { + fn set_descr_burst_mode(&self, burst_mode: bool) { self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority as u8) }); + .out_conf0() + .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); } fn set_peripheral(&self, peripheral: u8) { @@ -307,6 +336,14 @@ impl AxiGdmaRxChannel<'_> { } impl RegisterAccess for AxiGdmaRxChannel<'_> { + type Config = AxiGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -319,19 +356,20 @@ impl RegisterAccess for AxiGdmaRxChannel<'_> { self.ch().in_conf0().toggle(|w, en| w.in_rst().bit(en)); } - // AXI-DMA data burst is always enabled; nothing to configure. - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} - - fn set_descr_burst_mode(&self, burst_mode: bool) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, _accesses_psram: bool) { + // `in_burst_size_sel`: 0..=4 select 8/16/32/64/128-byte bursts, which is + // exactly the negotiated burst's discriminant. Burst is always enabled and + // shared between internal and external RAM. + let burst_size_sel = config.burst.negotiate(max_alignment) as u8; self.ch() .in_conf0() - .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); + .modify(|_, w| unsafe { w.in_burst_size_sel().bits(burst_size_sel) }); } - fn set_priority(&self, priority: DmaPriority) { + fn set_descr_burst_mode(&self, burst_mode: bool) { self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority as u8) }); + .in_conf0() + .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); } fn set_peripheral(&self, peripheral: u8) { @@ -536,6 +574,12 @@ for_each_dma_channel! { }; } +for_each_dma_engine! { + ("AXI_GDMA", priority = $priority:ident, priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AXI_GDMA", $priority, [$(($variant, $level)),*]); + }; +} + fn init_axi_dma_racey() { let regs = AXI_GDMA::regs(); diff --git a/esp-hal/src/dma/engine/copy.rs b/esp-hal/src/dma/engine/copy.rs index 07722433e8e..2e191b70a04 100644 --- a/esp-hal/src/dma/engine/copy.rs +++ b/esp-hal/src/dma/engine/copy.rs @@ -5,9 +5,7 @@ use crate::{ RegisterToggle, asynch::AtomicWaker, dma::{ - BurstConfig, DmaChannel, - DmaExtMemBKSize, DmaRxChannel, DmaRxInterrupt, DmaTxChannel, @@ -81,7 +79,19 @@ impl CopyDmaTxChannel<'_> { impl crate::private::Sealed for CopyDmaTxChannel<'_> {} impl DmaTxChannel for CopyDmaTxChannel<'_> {} +/// Configuration for a COPY DMA channel half. +/// +/// COPY_DMA is a mem2mem-only engine with no configurable options, so this is +/// empty. It is never exposed as a driver-facing configuration surface. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct CopyDmaConfig {} + impl RegisterAccess for CopyDmaTxChannel<'_> { + type Config = CopyDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new(Peripheral::CopyDma)) @@ -91,7 +101,7 @@ impl RegisterAccess for CopyDmaTxChannel<'_> { self.regs().conf().toggle(|w, bit| w.out_rst().bit(bit)); } - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + fn prepare_burst(&self, _config: &Self::Config, _max_alignment: usize, _accesses_psram: bool) {} fn set_descr_burst_mode(&self, _burst_mode: bool) {} @@ -125,11 +135,6 @@ impl RegisterAccess for CopyDmaTxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { - // not supported - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { false @@ -254,6 +259,10 @@ impl InterruptAccess for CopyDmaTxChannel<'_> { } impl RegisterAccess for CopyDmaRxChannel<'_> { + type Config = CopyDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new(Peripheral::CopyDma)) @@ -263,7 +272,7 @@ impl RegisterAccess for CopyDmaRxChannel<'_> { self.regs().conf().toggle(|w, bit| w.in_rst().bit(bit)); } - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + fn prepare_burst(&self, _config: &Self::Config, _max_alignment: usize, _accesses_psram: bool) {} fn set_descr_burst_mode(&self, _burst_mode: bool) {} @@ -297,11 +306,6 @@ impl RegisterAccess for CopyDmaRxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { - // not supported - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { false diff --git a/esp-hal/src/dma/engine/crypto.rs b/esp-hal/src/dma/engine/crypto.rs index 24e73ec195a..57099009677 100644 --- a/esp-hal/src/dma/engine/crypto.rs +++ b/esp-hal/src/dma/engine/crypto.rs @@ -5,9 +5,7 @@ use crate::{ RegisterToggle, asynch::AtomicWaker, dma::{ - BurstConfig, DmaChannel, - DmaExtMemBKSize, DmaPeripheral, DmaRxChannel, DmaRxInterrupt, @@ -18,6 +16,7 @@ use crate::{ RxRegisterAccess, TxRegisterAccess, asynch, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::{DMA_CRYPTO, Interrupt}, @@ -82,7 +81,70 @@ impl CryptoDmaTxChannel<'_> { impl crate::private::Sealed for CryptoDmaTxChannel<'_> {} impl DmaTxChannel for CryptoDmaTxChannel<'_> {} +/// Configuration for a CRYPTO DMA channel half. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct CryptoDmaConfig { + /// Maximum burst length for internal-RAM transfers. + #[cfg(crypto_dma_separate_burst)] + internal_burst: CryptoDmaInternalBurst, + + /// Maximum burst length (block size) for external-RAM (PSRAM) transfers. + #[cfg(crypto_dma_separate_burst)] + external_burst: CryptoDmaExternalBurst, + + /// Maximum burst length for transfers (applies to both internal and + /// external RAM, which share the same burst configuration on this chip). + #[cfg(not(crypto_dma_separate_burst))] + burst: CryptoDmaBurst, +} + +/// Whether data burst should be enabled for the burst negotiated from the +/// config and the buffer alignment. On engines with independent internal and +/// external burst configuration, `accesses_psram` selects which one applies. +fn data_burst_enabled( + config: &CryptoDmaConfig, + max_alignment: usize, + accesses_psram: bool, +) -> bool { + cfg_if::cfg_if! { + if #[cfg(crypto_dma_separate_burst)] { + let bytes = if accesses_psram { + config.external_burst.negotiate(max_alignment).bytes() + } else { + config.internal_burst.negotiate(max_alignment).bytes() + }; + bytes != 0 + } else { + let _ = accesses_psram; + config.burst.negotiate(max_alignment).bytes() != 0 + } + } +} + +/// External-memory block-size register encoding for the negotiated external +/// burst. +#[cfg(dma_ext_mem_configurable_block_size)] +fn ext_mem_block_size(config: &CryptoDmaConfig, max_alignment: usize) -> u8 { + config.external_burst.negotiate(max_alignment) as u8 +} + +for_each_dma_engine! { + ("CRYPTO_DMA", separate, internal = $it:ident, internal_bursts = [$(($iv:ident, $ib:literal)),*], external = $et:ident, external_bursts = [$(($ev:ident, $eb:literal)),*]) => { + impl_burst_type!($it, [$(($iv, $ib)),*]); + impl_burst_type!($et, [$(($ev, $eb)),*]); + }; + ("CRYPTO_DMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; +} + impl RegisterAccess for CryptoDmaTxChannel<'_> { + type Config = CryptoDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new(Peripheral::CryptoDma)) @@ -96,10 +158,17 @@ impl RegisterAccess for CryptoDmaTxChannel<'_> { }); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().conf1().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); self.regs() .conf() - .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + .modify(|_, w| w.out_data_burst_en().bit(burst_enabled)); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -150,13 +219,6 @@ impl RegisterAccess for CryptoDmaTxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { - self.regs() - .conf1() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { true @@ -281,6 +343,10 @@ impl InterruptAccess for CryptoDmaTxChannel<'_> { } impl RegisterAccess for CryptoDmaRxChannel<'_> { + type Config = CryptoDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new(Peripheral::CryptoDma)) @@ -294,7 +360,18 @@ impl RegisterAccess for CryptoDmaRxChannel<'_> { }); } - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + // PDMA configures the burst enable on the OUT path; the IN half only sets + // the external-memory block size, where that is configurable. + let _ = accesses_psram; + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().conf1().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + #[cfg(not(dma_ext_mem_configurable_block_size))] + let _ = (config, max_alignment); + } fn set_descr_burst_mode(&self, burst_mode: bool) { self.regs() @@ -344,13 +421,6 @@ impl RegisterAccess for CryptoDmaRxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { - self.regs() - .conf1() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { true diff --git a/esp-hal/src/dma/engine/gdma/ahb_v1.rs b/esp-hal/src/dma/engine/gdma/ahb_v1.rs index 2765838c5ed..14d73c2e8f9 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v1.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v1.rs @@ -27,6 +27,14 @@ impl AhbGdmaTxChannel<'_> { } impl RegisterAccess for AhbGdmaTxChannel<'_> { + type Config = AhbGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -39,10 +47,17 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { self.ch().out_conf0().toggle(|w, bit| w.out_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + #[cfg(dma_ext_mem_configurable_block_size)] + self.ch().out_conf1().modify(|_, w| unsafe { + w.out_ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); self.ch() .out_conf0() - .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + .modify(|_, w| w.out_data_burst_en().bit(burst_enabled)); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -51,12 +66,6 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); } - fn set_priority(&self, priority: DmaPriority) { - self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority as u8) }); - } - fn set_peripheral(&self, peripheral: u8) { self.ch() .out_peri_sel() @@ -93,13 +102,6 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { .modify(|_, w| w.out_check_owner().bit(check_owner.unwrap_or(true))); } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { - self.ch() - .out_conf1() - .modify(|_, w| unsafe { w.out_ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { true @@ -262,6 +264,14 @@ impl AhbGdmaRxChannel<'_> { } impl RegisterAccess for AhbGdmaRxChannel<'_> { + type Config = AhbGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -274,10 +284,17 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { self.ch().in_conf0().toggle(|w, bit| w.in_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + #[cfg(dma_ext_mem_configurable_block_size)] + self.ch().in_conf1().modify(|_, w| unsafe { + w.in_ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); self.ch() .in_conf0() - .modify(|_, w| w.in_data_burst_en().bit(burst_mode.is_burst_enabled())); + .modify(|_, w| w.in_data_burst_en().bit(burst_enabled)); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -286,12 +303,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); } - fn set_priority(&self, priority: DmaPriority) { - self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority as u8) }); - } - fn set_peripheral(&self, peripheral: u8) { self.ch() .in_peri_sel() @@ -326,13 +337,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true))); } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) { - self.ch() - .in_conf1() - .modify(|_, w| unsafe { w.in_ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { true diff --git a/esp-hal/src/dma/engine/gdma/ahb_v2.rs b/esp-hal/src/dma/engine/gdma/ahb_v2.rs index 871e5df8b35..d7b391b3d89 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v2.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v2.rs @@ -13,6 +13,17 @@ cfg_if::cfg_if! { } } +/// `*_data_burst_mode_sel` encoding for the negotiated burst: +/// 0 = single (≤4 B), 1 = INCR4 (16 B), 2 = INCR8 (32 B), 3 = INCR16 (64 B). +fn data_burst_mode_sel(config: &AhbGdmaConfig, max_alignment: usize) -> u8 { + match config.burst.negotiate(max_alignment).bytes() { + 16 => 1, + 32 => 2, + 64 => 3, + _ => 0, + } +} + impl AhbGdmaTxChannel<'_> { #[inline(always)] pub(super) fn ch(&self) -> &gdma_pac::ch::CH { @@ -26,6 +37,14 @@ impl AhbGdmaTxChannel<'_> { } impl RegisterAccess for AhbGdmaTxChannel<'_> { + type Config = AhbGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -38,10 +57,13 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { self.ch().out_conf0().toggle(|w, bit| w.out_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { - self.ch() - .out_conf0() - .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); + let mode_sel = data_burst_mode_sel(config, max_alignment); + self.ch().out_conf0().modify(|_, w| { + w.out_data_burst_en().bit(burst_enabled); + unsafe { w.out_data_burst_mode_sel().bits(mode_sel) } + }); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -50,12 +72,6 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { .modify(|_, w| w.outdscr_burst_en().bit(burst_mode)); } - fn set_priority(&self, priority: DmaPriority) { - self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority as u8) }); - } - fn set_peripheral(&self, peripheral: u8) { self.ch() .out_peri_sel() @@ -228,6 +244,14 @@ impl AhbGdmaRxChannel<'_> { } impl RegisterAccess for AhbGdmaRxChannel<'_> { + type Config = AhbGdmaConfig; + + fn apply_config(&self, config: &Self::Config) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(config.priority.into()) }); + } + #[allow(private_interfaces)] fn enable(&self) -> Option { Some(PeripheralGuard::new_with( @@ -240,10 +264,13 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { self.ch().in_conf0().toggle(|w, bit| w.in_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { - self.ch() - .in_conf0() - .modify(|_, w| w.in_data_burst_en().bit(burst_mode.is_burst_enabled())); + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); + let mode_sel = data_burst_mode_sel(config, max_alignment); + self.ch().in_conf0().modify(|_, w| { + w.in_data_burst_en().bit(burst_enabled); + unsafe { w.in_data_burst_mode_sel().bits(mode_sel) } + }); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -252,12 +279,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { .modify(|_, w| w.indscr_burst_en().bit(burst_mode)); } - fn set_priority(&self, priority: DmaPriority) { - self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority as u8) }); - } - fn set_peripheral(&self, peripheral: u8) { self.ch() .in_peri_sel() diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index 23a65dd9e38..b8d3212d8b9 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -106,6 +106,56 @@ impl<'d> DmaChannel for AhbGdmaChannel<'d> { } } +/// Configuration for an AHB GDMA channel half. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct AhbGdmaConfig { + /// Channel priority. + /// + /// The default value is `Priority0`. + priority: AhbGdmaPriority, + + /// Maximum burst length for internal-RAM transfers. + #[cfg(ahb_gdma_separate_burst)] + internal_burst: AhbGdmaInternalBurst, + + /// Maximum burst length (block size) for external-RAM (PSRAM) transfers. + #[cfg(ahb_gdma_separate_burst)] + external_burst: AhbGdmaExternalBurst, + + /// Maximum burst length for transfers (applies to both internal and + /// external RAM, which share the same burst configuration on this chip). + #[cfg(not(ahb_gdma_separate_burst))] + burst: AhbGdmaBurst, +} + +/// Whether data burst should be enabled for the burst negotiated from the +/// config and the buffer alignment. On engines with independent internal and +/// external burst configuration, `accesses_psram` selects which one applies. +fn data_burst_enabled(config: &AhbGdmaConfig, max_alignment: usize, accesses_psram: bool) -> bool { + cfg_if::cfg_if! { + if #[cfg(ahb_gdma_separate_burst)] { + let bytes = if accesses_psram { + config.external_burst.negotiate(max_alignment).bytes() + } else { + config.internal_burst.negotiate(max_alignment).bytes() + }; + bytes != 0 + } else { + let _ = accesses_psram; + config.burst.negotiate(max_alignment).bytes() != 0 + } + } +} + +/// External-memory block-size register encoding for the negotiated external +/// burst. +#[cfg(dma_ext_mem_configurable_block_size)] +fn ext_mem_block_size(config: &AhbGdmaConfig, max_alignment: usize) -> u8 { + config.external_burst.negotiate(max_alignment) as u8 +} + /// An arbitrary GDMA RX channel #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -237,6 +287,22 @@ for_each_dma_channel! { }; } +for_each_dma_engine! { + ("AHB_GDMA", priority = $priority:ident, priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AHB_GDMA", $priority, [$(($variant, $level)),*]); + }; +} + +for_each_dma_engine! { + ("AHB_GDMA", separate, internal = $it:ident, internal_bursts = [$(($iv:ident, $ib:literal)),*], external = $et:ident, external_bursts = [$(($ev:ident, $eb:literal)),*]) => { + impl_burst_type!($it, [$(($iv, $ib)),*]); + impl_burst_type!($et, [$(($ev, $eb)),*]); + }; + ("AHB_GDMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; +} + fn init_dma_racey() { // FIXME: reset/clock enable belongs to metadata use crate::RegisterToggle; diff --git a/esp-hal/src/dma/engine/i2s.rs b/esp-hal/src/dma/engine/i2s.rs index 635a4c73b77..8feed99ab03 100644 --- a/esp-hal/src/dma/engine/i2s.rs +++ b/esp-hal/src/dma/engine/i2s.rs @@ -5,7 +5,6 @@ use crate::{ RegisterToggle, asynch::AtomicWaker, dma::{ - BurstConfig, DmaChannel, DmaRxChannel, DmaRxInterrupt, @@ -15,6 +14,7 @@ use crate::{ RegisterAccess, RxRegisterAccess, TxRegisterAccess, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::Interrupt, @@ -77,7 +77,66 @@ impl I2sDmaTxChannel<'_> { impl crate::private::Sealed for I2sDmaTxChannel<'_> {} impl DmaTxChannel for I2sDmaTxChannel<'_> {} +/// Configuration for an I2S DMA channel half. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct I2sDmaConfig { + /// Maximum burst length for internal-RAM transfers. + #[cfg(i2s_dma_separate_burst)] + internal_burst: I2sDmaInternalBurst, + + /// Maximum burst length (block size) for external-RAM (PSRAM) transfers. + #[cfg(i2s_dma_separate_burst)] + external_burst: I2sDmaExternalBurst, + + /// Maximum burst length for transfers (applies to both internal and + /// external RAM, which share the same burst configuration on this chip). + #[cfg(not(i2s_dma_separate_burst))] + burst: I2sDmaBurst, +} + +/// Whether data burst should be enabled for the burst negotiated from the +/// config and the buffer alignment. On engines with independent internal and +/// external burst configuration, `accesses_psram` selects which one applies. +fn data_burst_enabled(config: &I2sDmaConfig, max_alignment: usize, accesses_psram: bool) -> bool { + cfg_if::cfg_if! { + if #[cfg(i2s_dma_separate_burst)] { + let bytes = if accesses_psram { + config.external_burst.negotiate(max_alignment).bytes() + } else { + config.internal_burst.negotiate(max_alignment).bytes() + }; + bytes != 0 + } else { + let _ = accesses_psram; + config.burst.negotiate(max_alignment).bytes() != 0 + } + } +} + +/// External-memory block-size register encoding for the negotiated external +/// burst. +#[cfg(dma_ext_mem_configurable_block_size)] +fn ext_mem_block_size(config: &I2sDmaConfig, max_alignment: usize) -> u8 { + config.external_burst.negotiate(max_alignment) as u8 +} + +for_each_dma_engine! { + ("I2S_DMA", separate, internal = $it:ident, internal_bursts = [$(($iv:ident, $ib:literal)),*], external = $et:ident, external_bursts = [$(($ev:ident, $eb:literal)),*]) => { + impl_burst_type!($it, [$(($iv, $ib)),*]); + impl_burst_type!($et, [$(($ev, $eb)),*]); + }; + ("I2S_DMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; +} + impl RegisterAccess for I2sDmaTxChannel<'_> { + type Config = I2sDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { None @@ -87,10 +146,17 @@ impl RegisterAccess for I2sDmaTxChannel<'_> { self.regs().lc_conf().toggle(|w, bit| w.out_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().lc_conf().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); self.regs() .lc_conf() - .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + .modify(|_, w| w.out_data_burst_en().bit(burst_enabled)); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -129,13 +195,6 @@ impl RegisterAccess for I2sDmaTxChannel<'_> { .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true))); } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { - self.regs() - .lc_conf() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { matches!(self.0, I2sDmaChannel(any::Inner::I2s0(_))) @@ -266,6 +325,10 @@ impl InterruptAccess for I2sDmaTxChannel<'_> { } impl RegisterAccess for I2sDmaRxChannel<'_> { + type Config = I2sDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { None @@ -275,7 +338,18 @@ impl RegisterAccess for I2sDmaRxChannel<'_> { self.regs().lc_conf().toggle(|w, bit| w.in_rst().bit(bit)); } - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + // PDMA configures the burst enable on the OUT path; the IN half only sets + // the external-memory block size, where that is configurable. + let _ = accesses_psram; + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().lc_conf().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + #[cfg(not(dma_ext_mem_configurable_block_size))] + let _ = (config, max_alignment); + } fn set_descr_burst_mode(&self, burst_mode: bool) { self.regs() @@ -313,13 +387,6 @@ impl RegisterAccess for I2sDmaRxChannel<'_> { .modify(|_, w| w.check_owner().bit(check_owner.unwrap_or(true))); } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { - self.regs() - .lc_conf() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { matches!(self.0, I2sDmaChannel(any::Inner::I2s0(_))) diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index 760e0a7766c..dc1b60f5109 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -2,7 +2,7 @@ use enumset::{EnumSet, EnumSetType}; use crate::{ asynch::AtomicWaker, - dma::{BurstConfig, DmaRxInterrupt, DmaTxInterrupt}, + dma::{DmaRxInterrupt, DmaTxInterrupt}, interrupt::InterruptHandler, peripherals::Interrupt, private::{Internal, Sealed}, @@ -20,19 +20,19 @@ for_each_dma_engine! { }; ("COPY_DMA") => { mod copy; - pub use copy::{CopyDmaChannel, CopyDmaRxChannel, CopyDmaTxChannel}; + pub use copy::*; }; ("CRYPTO_DMA") => { mod crypto; - pub use crypto::{CryptoDmaChannel, CryptoDmaRxChannel, CryptoDmaTxChannel}; + pub use crypto::*; }; ("I2S_DMA") => { mod i2s; - pub use i2s::{I2sDmaChannel, I2sDmaRxChannel, I2sDmaTxChannel}; + pub use i2s::*; }; ("SPI_DMA") => { mod spi; - pub use spi::{SpiDmaChannel, SpiDmaRxChannel, SpiDmaTxChannel}; + pub use spi::*; }; } @@ -68,25 +68,35 @@ for_each_peripheral! { #[doc(hidden)] pub trait RegisterAccess: Sealed { + /// Engine-specific channel configuration. + /// + /// Exposes only the configuration knobs (and values) this engine supports. + type Config: Default + Clone + core::fmt::Debug; + + /// Apply the configuration to this channel half. + /// + /// Write-only and total: the caller always supplies a complete `Config`. + /// There is no `config()` getter and no partial/read-modify-write update path. + fn apply_config(&self, config: &Self::Config); + + /// Programs the per-transfer burst for this channel half. + /// + /// Negotiates the configured burst ceiling(s) in `config` against the + /// buffer's guaranteed `max_alignment`. `accesses_psram` selects the + /// relevant burst on engines that configure internal and external RAM + /// independently. Infallible: buffers are validated at construction. + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool); + #[allow(private_interfaces)] fn enable(&self) -> Option; /// Reset the state machine of the channel and FIFO pointer. fn reset(&self); - /// Enable/Disable INCR burst transfer for channel reading - /// accessing data in internal RAM. - fn set_burst_mode(&self, burst_mode: BurstConfig); - /// Enable/Disable burst transfer for channel reading /// descriptors in internal RAM. fn set_descr_burst_mode(&self, burst_mode: bool); - /// The priority of the channel. The larger the value, the higher the - /// priority. - #[cfg(dma_max_priority_is_set)] - fn set_priority(&self, priority: crate::dma::DmaPriority); - /// Select a peripheral for the channel. fn set_peripheral(&self, _peripheral: u8) {} @@ -106,9 +116,6 @@ pub trait RegisterAccess: Sealed { /// descriptor. fn set_check_owner(&self, check_owner: Option); - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize); - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool; @@ -252,3 +259,93 @@ macro_rules! impl_channel_common { }; } pub(crate) use impl_channel_common; + +#[allow(unused)] +macro_rules! impl_priority_type { + ($engine:literal, $ty:ident, [$(($variant:ident, $level:literal)),*]) => { + #[doc = concat!("DMA channel priority levels for the ", $engine, " engine.")] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum $ty { + $( + #[doc = concat!("Priority level ", $level, ".")] + $variant = $level, + )* + } + + impl Default for $ty { + fn default() -> Self { + Self::Priority0 + } + } + + impl From<$ty> for u8 { + fn from(value: $ty) -> Self { + value as Self + } + } + }; +} +#[allow(unused)] +pub(crate) use impl_priority_type; + +#[allow(unused)] +macro_rules! impl_burst_type { + ($ty:ident, [($first:ident, $first_bytes:literal) $(, ($variant:ident, $bytes:literal))*]) => { + #[doc = concat!("DMA burst length options usable with the `", stringify!($ty), "` channel config.")] + /// + /// Each variant is a maximum burst length in bytes; `Disabled` (where + /// present) turns burst off. The effective burst is negotiated against + /// the buffer alignment at transfer setup. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum $ty { + #[doc = concat!($first_bytes, "-byte burst.")] + $first, + $( + #[doc = concat!($bytes, "-byte burst.")] + $variant, + )* + } + + impl $ty { + /// The burst length in bytes (`0` means burst disabled). + pub const fn bytes(self) -> usize { + match self { + Self::$first => $first_bytes, + $( Self::$variant => $bytes, )* + } + } + + /// Negotiates the effective burst: the largest supported variant + /// not exceeding this configured ceiling nor `max_alignment`. + /// + /// Falls back to the smallest supported burst if none fit. The + /// result's discriminant (`as u8`) doubles as the hardware + /// block-size register encoding where applicable. + #[allow(dead_code)] + pub(crate) const fn negotiate(self, max_alignment: usize) -> Self { + let ceiling = self.bytes(); + let budget = if ceiling < max_alignment { ceiling } else { max_alignment }; + #[allow(unused_mut)] + let mut selected = Self::$first; + $( + if $bytes <= budget { + selected = Self::$variant; + } + )* + selected + } + } + + impl Default for $ty { + fn default() -> Self { + // Sizes are listed ascending: the first variant is burst + // disabled, or the minimum burst where it can't be disabled. + Self::$first + } + } + }; +} +#[allow(unused)] +pub(crate) use impl_burst_type; diff --git a/esp-hal/src/dma/engine/spi.rs b/esp-hal/src/dma/engine/spi.rs index 28d425fb6c9..13e63fe8c8d 100644 --- a/esp-hal/src/dma/engine/spi.rs +++ b/esp-hal/src/dma/engine/spi.rs @@ -5,7 +5,6 @@ use crate::{ RegisterToggle, asynch::AtomicWaker, dma::{ - BurstConfig, DmaChannel, DmaRxChannel, DmaRxInterrupt, @@ -15,6 +14,7 @@ use crate::{ RegisterAccess, RxRegisterAccess, TxRegisterAccess, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::Interrupt, @@ -77,7 +77,66 @@ impl SpiDmaTxChannel<'_> { impl crate::private::Sealed for SpiDmaTxChannel<'_> {} impl DmaTxChannel for SpiDmaTxChannel<'_> {} +/// Configuration for a SPI DMA channel half. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct SpiDmaConfig { + /// Maximum burst length for internal-RAM transfers. + #[cfg(spi_dma_separate_burst)] + internal_burst: SpiDmaInternalBurst, + + /// Maximum burst length (block size) for external-RAM (PSRAM) transfers. + #[cfg(spi_dma_separate_burst)] + external_burst: SpiDmaExternalBurst, + + /// Maximum burst length for transfers (applies to both internal and + /// external RAM, which share the same burst configuration on this chip). + #[cfg(not(spi_dma_separate_burst))] + burst: SpiDmaBurst, +} + +/// Whether data burst should be enabled for the burst negotiated from the +/// config and the buffer alignment. On engines with independent internal and +/// external burst configuration, `accesses_psram` selects which one applies. +fn data_burst_enabled(config: &SpiDmaConfig, max_alignment: usize, accesses_psram: bool) -> bool { + cfg_if::cfg_if! { + if #[cfg(spi_dma_separate_burst)] { + let bytes = if accesses_psram { + config.external_burst.negotiate(max_alignment).bytes() + } else { + config.internal_burst.negotiate(max_alignment).bytes() + }; + bytes != 0 + } else { + let _ = accesses_psram; + config.burst.negotiate(max_alignment).bytes() != 0 + } + } +} + +/// External-memory block-size register encoding for the negotiated external +/// burst. +#[cfg(dma_ext_mem_configurable_block_size)] +fn ext_mem_block_size(config: &SpiDmaConfig, max_alignment: usize) -> u8 { + config.external_burst.negotiate(max_alignment) as u8 +} + +for_each_dma_engine! { + ("SPI_DMA", separate, internal = $it:ident, internal_bursts = [$(($iv:ident, $ib:literal)),*], external = $et:ident, external_bursts = [$(($ev:ident, $eb:literal)),*]) => { + impl_burst_type!($it, [$(($iv, $ib)),*]); + impl_burst_type!($et, [$(($ev, $eb)),*]); + }; + ("SPI_DMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; +} + impl RegisterAccess for SpiDmaTxChannel<'_> { + type Config = SpiDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { cfg_if::cfg_if! { @@ -98,10 +157,17 @@ impl RegisterAccess for SpiDmaTxChannel<'_> { self.regs().dma_conf().toggle(|w, bit| w.out_rst().bit(bit)); } - fn set_burst_mode(&self, burst_mode: BurstConfig) { + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().dma_conf().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + + let burst_enabled = data_burst_enabled(config, max_alignment, accesses_psram); self.regs() .dma_conf() - .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled())); + .modify(|_, w| w.out_data_burst_en().bit(burst_enabled)); } fn set_descr_burst_mode(&self, burst_mode: bool) { @@ -140,13 +206,6 @@ impl RegisterAccess for SpiDmaTxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { - self.regs() - .dma_conf() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_))) @@ -276,6 +335,10 @@ impl InterruptAccess for SpiDmaTxChannel<'_> { } impl RegisterAccess for SpiDmaRxChannel<'_> { + type Config = SpiDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { cfg_if::cfg_if! { @@ -296,7 +359,18 @@ impl RegisterAccess for SpiDmaRxChannel<'_> { self.regs().dma_conf().toggle(|w, bit| w.in_rst().bit(bit)); } - fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize, accesses_psram: bool) { + // PDMA configures the burst enable on the OUT path; the IN half only sets + // the external-memory block size, where that is configurable. + let _ = accesses_psram; + #[cfg(dma_ext_mem_configurable_block_size)] + self.regs().dma_conf().modify(|_, w| unsafe { + w.ext_mem_bk_size() + .bits(ext_mem_block_size(config, max_alignment)) + }); + #[cfg(not(dma_ext_mem_configurable_block_size))] + let _ = (config, max_alignment); + } fn set_descr_burst_mode(&self, burst_mode: bool) { self.regs() @@ -334,13 +408,6 @@ impl RegisterAccess for SpiDmaRxChannel<'_> { } } - #[cfg(dma_ext_mem_configurable_block_size)] - fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) { - self.regs() - .dma_conf() - .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) }); - } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_))) diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs index bcbac5f8298..143a80ce3bf 100644 --- a/esp-hal/src/dma/m2m.rs +++ b/esp-hal/src/dma/m2m.rs @@ -12,7 +12,6 @@ use crate::{ Blocking, DriverMode, dma::{ - BurstConfig, Channel, ChannelRx, ChannelTx, @@ -280,9 +279,8 @@ impl<'d> Mem2Mem<'d, Blocking> { self, rx_descriptors: &'d mut [DmaDescriptor], tx_descriptors: &'d mut [DmaDescriptor], - config: BurstConfig, ) -> Result, DmaError> { - SimpleMem2Mem::new(self, rx_descriptors, tx_descriptors, config) + SimpleMem2Mem::new(self, rx_descriptors, tx_descriptors) } } @@ -595,7 +593,6 @@ where Dm: DriverMode, { state: State<'d, Dm>, - config: BurstConfig, } enum State<'d, Dm: DriverMode> { @@ -620,14 +617,12 @@ where mem2mem: Mem2Mem<'d, Dm>, rx_descriptors: &'d mut [DmaDescriptor], tx_descriptors: &'d mut [DmaDescriptor], - config: BurstConfig, ) -> Result { if rx_descriptors.is_empty() || tx_descriptors.is_empty() { return Err(DmaError::OutOfDescriptors); } Ok(Self { state: State::Idle(mem2mem, rx_descriptors, tx_descriptors), - config, }) } } @@ -648,6 +643,13 @@ where panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar"); }; + // Save the descriptors' raw parts to restore the idle state if buffer + // creation fails (the constructors consume them on error). + let rx_desc_ptr = rx_descriptors.as_mut_ptr(); + let rx_desc_len = rx_descriptors.len(); + let tx_desc_ptr = tx_descriptors.as_mut_ptr(); + let tx_desc_len = tx_descriptors.len(); + // 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 @@ -656,20 +658,25 @@ where unsafe { core::slice::from_raw_parts_mut(rx_buffer.as_mut_ptr(), rx_buffer.len()) }; let tx_buffer = unsafe { core::slice::from_raw_parts_mut(tx_buffer.as_ptr() as _, tx_buffer.len()) }; - let rx_descriptors = unsafe { - core::slice::from_raw_parts_mut(rx_descriptors.as_mut_ptr(), rx_descriptors.len()) - }; - let tx_descriptors = unsafe { - core::slice::from_raw_parts_mut(tx_descriptors.as_mut_ptr(), tx_descriptors.len()) - }; + let rx_descriptors = unsafe { core::slice::from_raw_parts_mut(rx_desc_ptr, rx_desc_len) }; + let tx_descriptors = unsafe { core::slice::from_raw_parts_mut(tx_desc_ptr, tx_desc_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" - ); + // Loosest (region-default) alignment; surface a validation error instead + // of panicking if the buffers don't satisfy even that. + let dma_rx_buf = match DmaRxBuf::new(rx_descriptors, rx_buffer) { + Ok(buf) => buf, + Err(err) => { + let rx_descriptors = + unsafe { core::slice::from_raw_parts_mut(rx_desc_ptr, rx_desc_len) }; + let tx_descriptors = + unsafe { core::slice::from_raw_parts_mut(tx_desc_ptr, tx_desc_len) }; + self.state = State::Idle(mem2mem, rx_descriptors, tx_descriptors); + return Err(err.into()); + } + }; let rx = match mem2mem.rx.receive(dma_rx_buf) { Ok(rx) => rx, @@ -684,10 +691,21 @@ where } }; - let dma_tx_buf = unwrap!( - DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config), - "There's no way to get the descriptors back yet" - ); + let dma_tx_buf = match DmaTxBuf::new(tx_descriptors, tx_buffer) { + Ok(buf) => buf, + Err(err) => { + let (rx, buf) = rx.stop(); + let (rx_descriptors, _rx_buffer) = buf.split(); + let tx_descriptors = + unsafe { core::slice::from_raw_parts_mut(tx_desc_ptr, tx_desc_len) }; + self.state = State::Idle( + Mem2Mem { rx, tx: mem2mem.tx }, + rx_descriptors, + tx_descriptors, + ); + return Err(err.into()); + } + }; let tx = match mem2mem.tx.send(dma_tx_buf) { Ok(tx) => tx, diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index d652e3fac2d..82dc428d150 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -66,11 +66,13 @@ use crate::{ Async, Blocking, DriverMode, + dma::aligned::InternalMemoryMut, interrupt::InterruptHandler, soc::is_slice_in_dram, system::{Cpu, PeripheralGuard}, }; +pub mod aligned; mod buffers; #[cfg(dma_supports_mem2mem)] mod m2m; @@ -78,147 +80,6 @@ mod m2m; mod engine; pub use engine::*; -trait Word: crate::private::Sealed {} - -macro_rules! impl_word { - ($w:ty) => { - impl $crate::private::Sealed for $w {} - impl Word for $w {} - }; -} - -impl_word!(u8); -impl_word!(u16); -impl_word!(u32); -impl_word!(i8); -impl_word!(i16); -impl_word!(i32); - -impl crate::private::Sealed for [W; S] where W: Word {} - -impl crate::private::Sealed for &[W; S] where W: Word {} - -impl crate::private::Sealed for &[W] where W: Word {} - -impl crate::private::Sealed for &mut [W] where W: Word {} - -/// Trait for buffers that can be given to DMA for reading. -/// -/// # Safety -/// -/// Once the `read_buffer` method has been called, it is unsafe to call any -/// `&mut self` methods on this object as long as the returned value is in use -/// (by DMA). -pub unsafe trait ReadBuffer { - /// Provide a buffer usable for DMA reads. - /// - /// The return value is: - /// - /// - pointer to the start of the buffer - /// - buffer size in bytes - /// - /// # Safety - /// - /// Once this method has been called, it is unsafe to call any `&mut self` - /// methods on this object as long as the returned value is in use (by DMA). - unsafe fn read_buffer(&self) -> (*const u8, usize); -} - -unsafe impl ReadBuffer for [W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(self)) - } -} - -unsafe impl ReadBuffer for &[W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &mut [W; S] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &[W] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl ReadBuffer for &mut [W] -where - W: Word, -{ - unsafe fn read_buffer(&self) -> (*const u8, usize) { - (self.as_ptr() as *const u8, core::mem::size_of_val(*self)) - } -} - -/// Trait for buffers that can be given to DMA for writing. -/// -/// # Safety -/// -/// Once the `write_buffer` method has been called, it is unsafe to call any -/// `&mut self` methods, except for `write_buffer`, on this object as long as -/// the returned value is in use (by DMA). -pub unsafe trait WriteBuffer { - /// Provide a buffer usable for DMA writes. - /// - /// The return value is: - /// - /// - pointer to the start of the buffer - /// - buffer size in bytes - /// - /// # Safety - /// - /// Once this method has been called, it is unsafe to call any `&mut self` - /// methods, except for `write_buffer`, on this object as long as the - /// returned value is in use (by DMA). - unsafe fn write_buffer(&mut self) -> (*mut u8, usize); -} - -unsafe impl WriteBuffer for [W; S] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(self)) - } -} - -unsafe impl WriteBuffer for &mut [W; S] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) - } -} - -unsafe impl WriteBuffer for &mut [W] -where - W: Word, -{ - unsafe fn write_buffer(&mut self) -> (*mut u8, usize) { - (self.as_mut_ptr() as *mut u8, core::mem::size_of_val(*self)) - } -} - bitfield::bitfield! { /// DMA descriptor flags. #[derive(Clone, Copy, PartialEq, Eq)] @@ -456,10 +317,10 @@ pub const CHUNK_SIZE: usize = 4092; #[macro_export] macro_rules! dma_buffers { ($rx_size:expr, $tx_size:expr) => { - $crate::dma_buffers_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) + $crate::dma_buffers_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE); }; ($size:expr) => { - $crate::dma_buffers_chunk_size!($size, $crate::dma::CHUNK_SIZE) + $crate::dma_buffers!($size, $size) }; } @@ -484,7 +345,7 @@ macro_rules! dma_circular_buffers { }; ($size:expr) => { - $crate::dma_circular_buffers_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + $crate::dma_circular_buffers!($size, $size) }; } @@ -508,7 +369,7 @@ macro_rules! dma_descriptors { }; ($size:expr) => { - $crate::dma_descriptors_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + $crate::dma_descriptors!($size, $size) }; } @@ -527,12 +388,10 @@ macro_rules! dma_descriptors { /// ``` #[macro_export] macro_rules! dma_circular_descriptors { - ($rx_size:expr, $tx_size:expr) => { - $crate::dma_circular_descriptors_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) - }; + ($rx_size:expr, $tx_size:expr) => {{ $crate::dma_circular_descriptors_chunk_size!($rx_size, $tx_size, $crate::dma::CHUNK_SIZE) }}; ($size:expr) => { - $crate::dma_circular_descriptors_chunk_size!($size, $size, $crate::dma::CHUNK_SIZE) + $crate::dma_circular_descriptors!($size, $size) }; } @@ -553,7 +412,16 @@ macro_rules! dma_circular_descriptors { /// ``` #[macro_export] macro_rules! dma_buffers_chunk_size { - ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = false) }}; + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ + let (rx_buf, rx_desc, tx_buf, tx_desc) = + $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = false); + ( + rx_buf.into_mut(), + rx_desc.into_mut(), + tx_buf.into_mut(), + tx_desc.into_mut(), + ) + }}; ($size:expr, $chunk_size:expr) => { $crate::dma_buffers_chunk_size!($size, $size, $chunk_size) @@ -577,7 +445,16 @@ macro_rules! dma_buffers_chunk_size { /// ``` #[macro_export] macro_rules! dma_circular_buffers_chunk_size { - ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = true) }}; + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ + let (rx_buf, rx_desc, tx_buf, tx_desc) = + $crate::dma_buffers_impl!($rx_size, $tx_size, $chunk_size, is_circular = true); + ( + rx_buf.into_mut(), + rx_desc.into_mut(), + tx_buf.into_mut(), + tx_desc.into_mut(), + ) + }}; ($size:expr, $chunk_size:expr) => {{ $crate::dma_circular_buffers_chunk_size!($size, $size, $chunk_size) }}; } @@ -597,7 +474,11 @@ macro_rules! dma_circular_buffers_chunk_size { /// ``` #[macro_export] macro_rules! dma_descriptors_chunk_size { - ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = false) }}; + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ + let (rx, tx) = + $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = false); + (rx.into_mut(), tx.into_mut()) + }}; ($size:expr, $chunk_size:expr) => { $crate::dma_descriptors_chunk_size!($size, $size, $chunk_size) @@ -620,60 +501,17 @@ macro_rules! dma_descriptors_chunk_size { /// ``` #[macro_export] macro_rules! dma_circular_descriptors_chunk_size { - ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = true) }}; + ($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{ + let (rx, tx) = + $crate::dma_descriptors_impl!($rx_size, $tx_size, $chunk_size, is_circular = true); + (rx.into_mut(), tx.into_mut()) + }}; ($size:expr, $chunk_size:expr) => { $crate::dma_circular_descriptors_chunk_size!($size, $size, $chunk_size) }; } -// ESP32-P4 internal memory is cached, enforce alignment to avoid memory -// corruption. Technically only needed for IN buffers and descriptor lists. -#[cfg_attr(soc_internal_memory_cached, repr(C, align(64)))] // dcache cache line -#[doc(hidden)] -pub struct InternalMemoryCachelineAligned(T); - -impl InternalMemoryCachelineAligned { - pub const fn new(init: T) -> Self { - Self(init) - } - - pub const fn get(&self) -> &T { - &self.0 - } - - pub const fn get_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -// ESP32 requires word alignment for DMA buffers. -// ESP32-S2 technically supports byte-aligned DMA buffers, but the -// transfer ends up writing out of bounds. -#[cfg_attr(not(soc_internal_memory_cached), repr(C, align(4)))] -#[doc(hidden)] -pub struct InternalMemoryBuffer(InternalMemoryCachelineAligned<[u8; N]>); - -impl Default for InternalMemoryBuffer { - fn default() -> Self { - Self::new() - } -} - -impl InternalMemoryBuffer { - pub const fn new() -> Self { - Self(InternalMemoryCachelineAligned::new([0u8; N])) - } - - pub const fn get(&self) -> &[u8] { - self.0.get() - } - - pub const fn get_mut(&mut self) -> &mut [u8] { - self.0.get_mut() - } -} - #[doc(hidden)] #[macro_export] macro_rules! dma_buffers_impl { @@ -688,8 +526,8 @@ macro_rules! dma_buffers_impl { ( { #[allow(unused_braces)] - static mut BUFFER: $crate::dma::InternalMemoryBuffer<{ $size }> = - $crate::dma::InternalMemoryBuffer::new(); + static mut BUFFER: $crate::dma::aligned::InternalMemoryBuffer<{ $size }> = + $crate::dma::aligned::InternalMemoryBuffer::new(); // SAFETY: The ConstStaticCell in the descriptor part ensures there will only // be a single mutable reference to this buffer. unsafe { BUFFER.get_mut() } @@ -702,7 +540,7 @@ macro_rules! dma_buffers_impl { ($size:expr, is_circular = $circular:tt) => { $crate::dma_buffers_impl!( $size, - $crate::dma::BurstConfig::DEFAULT.max_compatible_chunk_size(), + $crate::dma::max_compatible_chunk_size(), is_circular = $circular ); }; @@ -720,19 +558,16 @@ macro_rules! dma_descriptors_impl { ($size:expr, $chunk_size:expr, is_circular = $circular:tt) => {{ use $crate::{ __macro_implementation::static_cell::ConstStaticCell, - dma::{DmaDescriptor, InternalMemoryCachelineAligned}, + dma::{DmaDescriptor, aligned::InternalMemory}, }; const COUNT: usize = $crate::dma_descriptor_count!($size, $chunk_size, is_circular = $circular); - static DESCRIPTORS: ConstStaticCell< - InternalMemoryCachelineAligned<[DmaDescriptor; COUNT]>, - > = ConstStaticCell::new(InternalMemoryCachelineAligned::new( - [DmaDescriptor::EMPTY; COUNT], - )); + static DESCRIPTORS: ConstStaticCell> = + ConstStaticCell::new(InternalMemory::new([DmaDescriptor::EMPTY; COUNT])); - DESCRIPTORS.take().get_mut() + DESCRIPTORS.take().get_mut().unsize() }}; } @@ -771,7 +606,7 @@ macro_rules! dma_tx_buffer { ($tx_size:expr) => {{ let (tx_buffer, tx_descriptors) = $crate::dma_buffers_impl!($tx_size, is_circular = false); - $crate::dma::DmaTxBuf::new(tx_descriptors, tx_buffer) + $crate::dma::DmaTxBuf::new_internal_memory(tx_descriptors, tx_buffer) }}; } @@ -801,7 +636,7 @@ macro_rules! dma_rx_stream_buffer { let (buffer, descriptors) = $crate::dma_buffers_impl!($rx_size, $chunk_size, is_circular = false); - $crate::dma::DmaRxStreamBuf::new(descriptors, buffer).unwrap() + $crate::dma::DmaRxStreamBuf::new_internal_memory(descriptors, buffer).unwrap() }}; } @@ -831,7 +666,7 @@ macro_rules! dma_tx_stream_buffer { let (buffer, descriptors) = $crate::dma_buffers_impl!($tx_size, $chunk_size, is_circular = false); - $crate::dma::DmaTxStreamBuf::new(descriptors, buffer).unwrap() + $crate::dma::DmaTxStreamBuf::new_internal_memory(descriptors, buffer).unwrap() }}; } @@ -856,7 +691,7 @@ macro_rules! dma_loop_buffer { let (buffer, descriptors) = $crate::dma_buffers_impl!($size, $size, is_circular = false); - $crate::dma::DmaLoopBuf::new(&mut descriptors[0], buffer).unwrap() + $crate::dma::DmaLoopBuf::new_internal_memory(&mut descriptors[0], buffer).unwrap() }}; } @@ -895,37 +730,6 @@ impl From for DmaError { } } -/// DMA Priorities -#[cfg(dma_max_priority_is_set)] -#[derive(Debug, Clone, Copy, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum DmaPriority { - /// The lowest priority level (Priority 0). - Priority0 = 0, - /// Priority level 1. - Priority1 = 1, - /// Priority level 2. - Priority2 = 2, - /// Priority level 3. - Priority3 = 3, - /// Priority level 4. - Priority4 = 4, - /// Priority level 5. - Priority5 = 5, - /// Priority level 6. - #[cfg(dma_max_priority = "9")] - Priority6 = 6, - /// Priority level 7. - #[cfg(dma_max_priority = "9")] - Priority7 = 7, - /// Priority level 8. - #[cfg(dma_max_priority = "9")] - Priority8 = 8, - /// Priority level 9. - #[cfg(dma_max_priority = "9")] - Priority9 = 9, -} - /// The owner bit of a DMA descriptor. #[derive(PartialEq, PartialOrd)] pub enum Owner { @@ -962,13 +766,14 @@ pub const fn descriptor_count(buffer_size: usize, chunk_size: usize, is_circular #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] struct DescriptorSet<'a> { - descriptors: &'a mut [DmaDescriptor], + descriptors: InternalMemoryMut<'a, [DmaDescriptor]>, } impl<'a> DescriptorSet<'a> { /// Creates a new `DescriptorSet` from a slice of descriptors and associates /// them with the given buffer. fn new(descriptors: &'a mut [DmaDescriptor]) -> Result { + // FIXME: we should verify that the size of the slice is also cacheline aligned if !is_slice_in_dram(descriptors) { return Err(DmaBufError::UnsupportedMemoryRegion); } @@ -986,12 +791,14 @@ impl<'a> DescriptorSet<'a> { /// The caller must ensure that the descriptors are located in a supported /// memory region. unsafe fn new_unchecked(descriptors: &'a mut [DmaDescriptor]) -> Self { - Self { descriptors } + Self { + descriptors: unsafe { InternalMemoryMut::new_unchecked(descriptors) }, + } } /// Consumes the `DescriptorSet` and returns the inner slice of descriptors. fn into_inner(self) -> &'a mut [DmaDescriptor] { - self.descriptors + self.descriptors.into_mut() } /// Returns a pointer to the first descriptor in the chain. @@ -1035,7 +842,7 @@ impl<'a> DescriptorSet<'a> { buffer: &mut [u8], chunk_size: usize, ) -> Result<(), DmaBufError> { - Self::set_up_buffer_ptrs(buffer, self.descriptors, chunk_size, false) + Self::set_up_buffer_ptrs(buffer, &mut self.descriptors, chunk_size, false) } /// Prepares descriptors for transferring `len` bytes of data. @@ -1047,7 +854,7 @@ impl<'a> DescriptorSet<'a> { chunk_size: usize, prepare: fn(&mut DmaDescriptor, usize), ) -> Result<(), DmaBufError> { - Self::set_up_descriptors(self.descriptors, len, chunk_size, false, prepare) + Self::set_up_descriptors(&mut self.descriptors, len, chunk_size, false, prepare) } /// Prepares descriptors for reading `len` bytes of data. @@ -1153,29 +960,6 @@ impl<'a> DescriptorSet<'a> { } } -/// Block size for transfers to/from PSRAM -#[cfg(dma_ext_mem_configurable_block_size)] -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum DmaExtMemBKSize { - /// External memory block size of 16 bytes. - Size16 = 0, - /// External memory block size of 32 bytes. - Size32 = 1, - /// External memory block size of 64 bytes. - Size64 = 2, -} - -#[cfg(dma_ext_mem_configurable_block_size)] -impl From for DmaExtMemBKSize { - fn from(size: ExternalBurstConfig) -> Self { - match size { - ExternalBurstConfig::Size16 => DmaExtMemBKSize::Size16, - ExternalBurstConfig::Size32 => DmaExtMemBKSize::Size32, - ExternalBurstConfig::Size64 => DmaExtMemBKSize::Size64, - } - } -} - // DMA receive channel #[non_exhaustive] #[doc(hidden)] @@ -1185,6 +969,10 @@ where CH: DmaRxChannel, { pub(crate) rx_impl: CH, + /// Last-applied channel configuration, re-applied on every transfer (the + /// burst registers do not survive `reset()`) and kept across async/blocking + /// conversions. + pub(crate) config: CH::Config, pub(crate) _phantom: PhantomData, pub(crate) _guard: Option, } @@ -1211,6 +999,7 @@ where Self { rx_impl, + config: CH::Config::default(), _phantom: PhantomData, _guard, } @@ -1224,6 +1013,7 @@ where self.rx_impl.set_async(true); ChannelRx { rx_impl: self.rx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1254,6 +1044,7 @@ where self.rx_impl.set_async(false); ChannelRx { rx_impl: self.rx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1265,10 +1056,10 @@ where Dm: DriverMode, CH: DmaRxChannel, { - /// Configure the channel. - #[cfg(dma_max_priority_is_set)] - pub fn set_priority(&mut self, priority: DmaPriority) { - self.rx_impl.set_priority(priority); + /// Applies a complete configuration to this channel half. + pub fn apply_config(&mut self, config: &CH::Config) { + self.config = config.clone(); + self.rx_impl.apply_config(config); } fn do_prepare( @@ -1276,8 +1067,6 @@ where preparation: Preparation, peri: DmaPeripheral, ) -> Result<(), DmaError> { - debug_assert_eq!(preparation.direction, TransferDirection::In); - debug!("Preparing RX transfer {:?}", preparation); trace!("First descriptor {:?}", unsafe { &*preparation.start }); @@ -1286,10 +1075,14 @@ where return Err(DmaError::UnsupportedMemoryRegion); } - #[cfg(dma_ext_mem_configurable_block_size)] - self.rx_impl - .set_ext_mem_block_size(preparation.burst_transfer.external_memory.into()); - self.rx_impl.set_burst_mode(preparation.burst_transfer); + self.rx_impl.prepare_burst( + &self.config, + preparation.max_alignment, + cfg_select! { + dma_can_access_psram => preparation.accesses_psram, + _ => false, + }, + ); self.rx_impl.set_descr_burst_mode(true); self.rx_impl.set_check_owner(preparation.check_owner); @@ -1411,6 +1204,10 @@ where CH: DmaTxChannel, { pub(crate) tx_impl: CH, + /// Last-applied channel configuration, re-applied on every transfer (the + /// burst registers do not survive `reset()`) and kept across async/blocking + /// conversions. + pub(crate) config: CH::Config, pub(crate) _phantom: PhantomData, pub(crate) _guard: Option, } @@ -1431,6 +1228,7 @@ where tx_impl.set_async(false); Self { tx_impl, + config: CH::Config::default(), _phantom: PhantomData, _guard, } @@ -1444,6 +1242,7 @@ where self.tx_impl.set_async(true); ChannelTx { tx_impl: self.tx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1474,6 +1273,7 @@ where self.tx_impl.set_async(false); ChannelTx { tx_impl: self.tx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1485,25 +1285,23 @@ where Dm: DriverMode, CH: DmaTxChannel, { + /// Applies a complete configuration to this channel half. + pub fn apply_config(&mut self, config: &CH::Config) { + self.config = config.clone(); + self.tx_impl.apply_config(config); + } + /// Asserts that the channel is compatible with the given peripheral. #[allow(dead_code)] pub(crate) fn runtime_ensure_compatible(&self, peripheral: DmaPeripheral) { self.tx_impl.runtime_ensure_compatible(peripheral); } - /// Configure the channel priority. - #[cfg(dma_max_priority_is_set)] - pub fn set_priority(&mut self, priority: DmaPriority) { - self.tx_impl.set_priority(priority); - } - fn do_prepare( &mut self, preparation: Preparation, peri: DmaPeripheral, ) -> Result<(), DmaError> { - debug_assert_eq!(preparation.direction, TransferDirection::Out); - debug!("Preparing TX transfer {:?}", preparation); trace!("First descriptor {:?}", unsafe { &*preparation.start }); @@ -1512,10 +1310,14 @@ where return Err(DmaError::UnsupportedMemoryRegion); } - #[cfg(dma_ext_mem_configurable_block_size)] - self.tx_impl - .set_ext_mem_block_size(preparation.burst_transfer.external_memory.into()); - self.tx_impl.set_burst_mode(preparation.burst_transfer); + self.tx_impl.prepare_burst( + &self.config, + preparation.max_alignment, + cfg_select! { + dma_can_access_psram => preparation.accesses_psram, + _ => false, + }, + ); self.tx_impl.set_descr_burst_mode(true); self.tx_impl.set_check_owner(preparation.check_owner); self.tx_impl @@ -1616,6 +1418,46 @@ where } } +/// Configuration for a full DMA channel (both halves). +/// +/// Composes the per-half engine configurations, so the RX and TX halves can be +/// configured independently (e.g. with different priorities) in a single value +/// passed to [`Channel::apply_config`]. +pub struct DmaChannelConfig { + /// Configuration for the RX (receive) half. + pub rx: ::Config, + /// Configuration for the TX (transmit) half. + pub tx: ::Config, +} + +// Manual impls: deriving would wrongly require `CH: Default`/`Clone`/`Debug`. +impl Default for DmaChannelConfig { + fn default() -> Self { + Self { + rx: Default::default(), + tx: Default::default(), + } + } +} + +impl Clone for DmaChannelConfig { + fn clone(&self) -> Self { + Self { + rx: self.rx.clone(), + tx: self.tx.clone(), + } + } +} + +impl core::fmt::Debug for DmaChannelConfig { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DmaChannelConfig") + .field("rx", &self.rx) + .field("tx", &self.tx) + .finish() + } +} + /// DMA Channel #[non_exhaustive] pub struct Channel @@ -1694,13 +1536,6 @@ where } } - /// Configure the channel priorities. - #[cfg(dma_max_priority_is_set)] - pub fn set_priority(&mut self, priority: DmaPriority) { - self.tx.set_priority(priority); - self.rx.set_priority(priority); - } - /// Converts a blocking channel to an async channel. pub fn into_async(self) -> Channel { Channel { @@ -1710,11 +1545,20 @@ where } } -impl Channel +impl Channel where - CH: DmaChannel, Dm: DriverMode, + CH: DmaChannel, { + /// Applies a complete configuration to both halves of the channel. + /// + /// The RX and TX halves can be configured independently via the `rx` and + /// `tx` fields of [`DmaChannelConfig`]. + pub fn apply_config(&mut self, config: &DmaChannelConfig) { + self.rx.apply_config(&config.rx); + self.tx.apply_config(&config.tx); + } + /// Asserts that the channel is compatible with the given peripheral. #[instability::unstable] pub fn runtime_ensure_compatible(&self, peripheral: DmaPeripheral) { diff --git a/esp-hal/src/soc/esp32s2/mod.rs b/esp-hal/src/soc/esp32s2/mod.rs index 153abd0077d..167a0b51105 100644 --- a/esp-hal/src/soc/esp32s2/mod.rs +++ b/esp-hal/src/soc/esp32s2/mod.rs @@ -50,6 +50,25 @@ pub unsafe fn cache_invalidate_addr(addr: u32, size: u32) { } } +pub(crate) const CONFIG_INSTRUCTION_CACHE_SIZE: usize = cfg_select! { + instruction_cache_size_8kb => 0, + instruction_cache_size_16kb => 1, +}; +pub(crate) const CONFIG_INSTRUCTION_CACHE_LINE_SIZE: usize = cfg_select! { + instruction_cache_line_size_16b => 0, + instruction_cache_line_size_32b => 1, +}; + +pub(crate) const CONFIG_DATA_CACHE_SIZE: usize = cfg_select! { + data_cache_size_0kb => 0, // doesn't matter according to esp-idf + data_cache_size_8kb => 0, + data_cache_size_16kb => 1, +}; +pub(crate) const CONFIG_DATA_CACHE_LINE_SIZE: usize = cfg_select! { + data_cache_line_size_16b => 0, + data_cache_line_size_32b => 1, +}; + #[crate::ram] pub(crate) unsafe fn configure_cpu_caches() { // Set up caches. Doesn't work when put in `configure_cpu_caches`. @@ -107,29 +126,6 @@ pub(crate) unsafe fn configure_cpu_caches() { const CACHE_4WAYS_ASSOC: u32 = 0; - const CONFIG_ESP32S2_INSTRUCTION_CACHE_SIZE: u32 = match () { - _ if cfg!(instruction_cache_size_8kb) => 0, - _ if cfg!(instruction_cache_size_16kb) => 1, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S2_INSTRUCTION_CACHE_LINE_SIZE: u32 = match () { - _ if cfg!(instruction_cache_line_size_16b) => 0, - _ if cfg!(instruction_cache_line_size_32b) => 1, - _ => core::unreachable!(), - }; - - const CONFIG_ESP32S2_DATA_CACHE_SIZE: u32 = match () { - _ if cfg!(data_cache_size_0kb) => 0, // doesn't matter according to esp-idf - _ if cfg!(data_cache_size_8kb) => 0, - _ if cfg!(data_cache_size_16kb) => 1, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S2_DATA_CACHE_LINE_SIZE: u32 = match () { - _ if cfg!(data_cache_line_size_16b) => 0, - _ if cfg!(data_cache_line_size_32b) => 1, - _ => core::unreachable!(), - }; - #[derive(Clone, Copy, Debug)] enum CacheLayout { Invalid = 0, @@ -171,17 +167,17 @@ pub(crate) unsafe fn configure_cpu_caches() { unsafe { Cache_Set_ICache_Mode( - CONFIG_ESP32S2_INSTRUCTION_CACHE_SIZE, + CONFIG_INSTRUCTION_CACHE_SIZE as u32, CACHE_4WAYS_ASSOC, - CONFIG_ESP32S2_INSTRUCTION_CACHE_LINE_SIZE, + CONFIG_INSTRUCTION_CACHE_LINE_SIZE as u32, ); Cache_Invalidate_ICache_All(); Cache_Resume_ICache(0); Cache_Set_DCache_Mode( - CONFIG_ESP32S2_DATA_CACHE_SIZE, + CONFIG_DATA_CACHE_SIZE as u32, CACHE_4WAYS_ASSOC, - CONFIG_ESP32S2_DATA_CACHE_LINE_SIZE, + CONFIG_DATA_CACHE_LINE_SIZE as u32, ); Cache_Invalidate_DCache_All(); Cache_Enable_DCache(0); diff --git a/esp-hal/src/soc/esp32s3/mod.rs b/esp-hal/src/soc/esp32s3/mod.rs index ac0fecacdf9..3b80062ba77 100644 --- a/esp-hal/src/soc/esp32s3/mod.rs +++ b/esp-hal/src/soc/esp32s3/mod.rs @@ -23,6 +23,34 @@ pub(crate) fn i2s_sclk_frequency() -> u32 { clocks::pll_160m_frequency() } +pub(crate) const CONFIG_INSTRUCTION_CACHE_SIZE: usize = cfg_select! { + instruction_cache_size_32kb => 0x8000, + instruction_cache_size_16kb => 0x4000, +}; +pub(crate) const CONFIG_ICACHE_ASSOCIATED_WAYS: usize = cfg_select! { + icache_associated_ways_8 => 8, + icache_associated_ways_4 => 4, +}; +pub(crate) const CONFIG_INSTRUCTION_CACHE_LINE_SIZE: usize = cfg_select! { + instruction_cache_line_size_32b => 32, + instruction_cache_line_size_16b => 16, +}; + +pub(crate) const CONFIG_DATA_CACHE_SIZE: usize = cfg_select! { + data_cache_size_64kb => 0x10000, + data_cache_size_32kb => 0x8000, + data_cache_size_16kb => 0x4000, +}; +pub(crate) const CONFIG_DCACHE_ASSOCIATED_WAYS: usize = cfg_select! { + dcache_associated_ways_8 => 8, + dcache_associated_ways_4 => 4, +}; +pub(crate) const CONFIG_DATA_CACHE_LINE_SIZE: usize = cfg_select! { + data_cache_line_size_64b => 64, + data_cache_line_size_32b => 32, + data_cache_line_size_16b => 16, +}; + #[unsafe(link_section = ".rwtext")] pub(crate) unsafe fn configure_cpu_caches() { // this is just the bare minimum we need to run code from flash @@ -47,46 +75,12 @@ pub(crate) unsafe fn configure_cpu_caches() { ); } - const CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE: u32 = match () { - _ if cfg!(instruction_cache_size_32kb) => 0x8000, - _ if cfg!(instruction_cache_size_16kb) => 0x4000, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS: u8 = match () { - _ if cfg!(icache_associated_ways_8) => 8, - _ if cfg!(icache_associated_ways_4) => 4, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE: u8 = match () { - _ if cfg!(instruction_cache_line_size_32b) => 32, - _ if cfg!(instruction_cache_line_size_16b) => 16, - _ => core::unreachable!(), - }; - - const CONFIG_ESP32S3_DATA_CACHE_SIZE: u32 = match () { - _ if cfg!(data_cache_size_64kb) => 0x10000, - _ if cfg!(data_cache_size_32kb) => 0x8000, - _ if cfg!(data_cache_size_16kb) => 0x4000, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS: u8 = match () { - _ if cfg!(dcache_associated_ways_8) => 8, - _ if cfg!(dcache_associated_ways_4) => 4, - _ => core::unreachable!(), - }; - const CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE: u8 = match () { - _ if cfg!(data_cache_line_size_64b) => 64, - _ if cfg!(data_cache_line_size_32b) => 32, - _ if cfg!(data_cache_line_size_16b) => 16, - _ => core::unreachable!(), - }; - // Configure the mode of instruction cache: cache size, cache line size. unsafe { rom_config_instruction_cache_mode( - CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE, - CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS, - CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE, + CONFIG_INSTRUCTION_CACHE_SIZE as u32, + CONFIG_ICACHE_ASSOCIATED_WAYS as u8, + CONFIG_INSTRUCTION_CACHE_LINE_SIZE as u8, ); } @@ -94,9 +88,9 @@ pub(crate) unsafe fn configure_cpu_caches() { unsafe { Cache_Suspend_DCache(); rom_config_data_cache_mode( - CONFIG_ESP32S3_DATA_CACHE_SIZE, - CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS, - CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE, + CONFIG_DATA_CACHE_SIZE as u32, + CONFIG_DCACHE_ASSOCIATED_WAYS as u8, + CONFIG_DATA_CACHE_LINE_SIZE as u8, ); Cache_Resume_DCache(0); } diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index 7f6fe78a3f1..4074fe3be0e 100644 --- a/esp-hal/src/spi/master/dma.rs +++ b/esp-hal/src/spi/master/dma.rs @@ -15,6 +15,8 @@ use enumset::EnumSet; use procmacros::ram; use super::*; +#[cfg(all(spi_master_version = "1", spi_address_workaround))] +use crate::dma::aligned::InternalMemoryBuffer; use crate::{ RegisterToggle, dma::{ @@ -26,11 +28,11 @@ use crate::{ DmaRxBuffer, DmaTxBuf, DmaTxBuffer, - InternalMemoryCachelineAligned, NoBuffer, ScopedDmaRxBuf, ScopedDmaTxBuf, TransferDirection, + aligned::InternalMemory, asynch::DmaRxFuture, prepare_for_rx, prepare_for_tx, @@ -257,44 +259,25 @@ impl<'d> SpiDma<'d, Blocking> { let channel = Channel::new(channel); channel.runtime_ensure_compatible(spi.spi.dma_peripheral()); - for_each_spi_master!((all $($inst:tt),*) => { - const SPI_NUM: usize = 0 $(+ { stringify!($inst); 1 })*; - };); - let id = if spi.info() == unsafe { crate::peripherals::SPI2::steal().info() } { - 0 - } else { - 1 - }; - let state = spi.spi.dma_state(); state.tx_transfer_in_progress.set(false); state.rx_transfer_in_progress.set(false); - static mut TX_DESCRIPTORS: InternalMemoryCachelineAligned<[DmaDescriptor; SPI_NUM]> = - InternalMemoryCachelineAligned::new([DmaDescriptor::EMPTY; SPI_NUM]); - static mut RX_DESCRIPTORS: InternalMemoryCachelineAligned<[DmaDescriptor; SPI_NUM]> = - InternalMemoryCachelineAligned::new([DmaDescriptor::EMPTY; SPI_NUM]); + let descriptors = unsafe { (&mut *state.descriptors.get()).get_mut().into_mut() }; + descriptors.fill(DmaDescriptor::EMPTY); + + let (tx_descriptors, rx_descriptors) = descriptors.split_at_mut(0); let tx_buffer = cfg_select! { - all(spi_master_version = "1", spi_address_workaround) => {{ - use crate::dma::InternalMemoryBuffer; - static mut BUFFERS: [InternalMemoryBuffer<4>; SPI_NUM] = [const { InternalMemoryBuffer::new() }; SPI_NUM]; - unsafe { BUFFERS[id].get_mut() } - }} + all(spi_master_version = "1", spi_address_workaround) => unsafe { + (&mut *state.default_tx_buffer.get()).get_mut().into_mut() + }, _ => &mut [] }; - #[allow(static_mut_refs)] - let rx_buffer = unwrap!(DmaRxBuf::new( - core::slice::from_mut(unsafe { &mut (RX_DESCRIPTORS.get_mut()[id]) }), - &mut [] - )); - #[allow(static_mut_refs)] - let tx_buffer = unwrap!(DmaTxBuf::new( - core::slice::from_mut(unsafe { &mut (TX_DESCRIPTORS.get_mut()[id]) }), - tx_buffer - )); + let rx_buffer = unwrap!(DmaRxBuf::new(rx_descriptors, &mut [])); + let tx_buffer = unwrap!(DmaTxBuf::new(tx_descriptors, tx_buffer)); // The buffers must be set up when creating the driver. unsafe { (&mut *state.tx_buffer.get()).write(tx_buffer.into_scoped()) }; @@ -1806,6 +1789,11 @@ struct DmaState { rx_buffer: UnsafeCell>>, tx_buffer: UnsafeCell>>, + + descriptors: UnsafeCell>, + + #[cfg(all(spi_master_version = "1", spi_address_workaround))] + default_tx_buffer: UnsafeCell>, } impl DmaState { @@ -1854,6 +1842,10 @@ for_each_spi_master!( rx_buffer: UnsafeCell::new(MaybeUninit::uninit()), tx_buffer: UnsafeCell::new(MaybeUninit::uninit()), + + descriptors: UnsafeCell::new(InternalMemory::new([DmaDescriptor::EMPTY; 2])), + #[cfg(all(spi_master_version = "1", spi_address_workaround))] + default_tx_buffer: UnsafeCell::new(InternalMemoryBuffer::new()), }; &DMA_STATE @@ -1895,5 +1887,19 @@ with_spi_master_dma_engine! { // Proxy type so that the type-erased DMA channel can be named in the driver, regardless of the DMA engine. type SpiMasterErased<'d> = crate::dma::$any_channel<'d>; + + /// DMA channel configuration for the SPI master driver. + /// + /// This is the underlying DMA channel's own configuration type; the RX + /// and TX halves (with their engine-specific knobs, e.g. priority) are + /// configured independently via its `rx` / `tx` fields. + pub type DmaConfig<'d> = crate::dma::DmaChannelConfig>; + + impl<'d, Dm: DriverMode> SpiDma<'d, Dm> { + /// Updates DMA channel configuration. + pub fn apply_dma_config(&mut self, config: &DmaConfig<'d>) { + self.channel.apply_config(config); + } + } }; } diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index 664c505d50a..337f2cb4dba 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -877,6 +877,7 @@ impl Chip { "dma_gdma_version_is_set", "soc_has_dma_ch0", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "sha_supports_dma", "sha_dma_engine=\"AHB_GDMA\"", "spi_master_supports_dma", @@ -1048,6 +1049,7 @@ impl Chip { "cargo:rustc-cfg=dma_gdma_version_is_set", "cargo:rustc-cfg=soc_has_dma_ch0", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=sha_supports_dma", "cargo:rustc-cfg=sha_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=spi_master_supports_dma", @@ -1361,6 +1363,7 @@ impl Chip { "soc_has_dma_ch1", "soc_has_dma_ch2", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -1591,6 +1594,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch1", "cargo:rustc-cfg=soc_has_dma_ch2", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -1947,6 +1951,7 @@ impl Chip { "soc_has_dma_ch1", "soc_has_dma_ch2", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -2215,6 +2220,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch1", "cargo:rustc-cfg=soc_has_dma_ch2", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -2645,6 +2651,7 @@ impl Chip { "soc_has_dma_ch1", "soc_has_dma_ch2", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -2938,6 +2945,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch1", "cargo:rustc-cfg=soc_has_dma_ch2", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -3319,6 +3327,7 @@ impl Chip { "soc_has_dma_ch0", "soc_has_dma_ch1", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "sha_supports_dma", "sha_dma_engine=\"AHB_GDMA\"", "spi_master_supports_dma", @@ -3525,6 +3534,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch0", "cargo:rustc-cfg=soc_has_dma_ch1", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=sha_supports_dma", "cargo:rustc-cfg=sha_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=spi_master_supports_dma", @@ -3907,6 +3917,7 @@ impl Chip { "soc_has_dma_ch1", "soc_has_dma_ch2", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -4164,6 +4175,7 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch1", "cargo:rustc-cfg=soc_has_dma_ch2", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -4512,6 +4524,8 @@ impl Chip { "soc_has_vdma_ch2", "soc_has_vdma_ch3", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", + "axi_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AXI_GDMA\"", "sha_supports_dma", @@ -4731,6 +4745,8 @@ impl Chip { "cargo:rustc-cfg=soc_has_vdma_ch2", "cargo:rustc-cfg=soc_has_vdma_ch3", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", + "cargo:rustc-cfg=axi_gdma_max_priority_is_set", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AXI_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -5204,6 +5220,9 @@ impl Chip { "soc_has_dma_crypto", "soc_has_dma_copy", "dma_supports_mem2mem", + "spi_dma_separate_burst", + "i2s_dma_separate_burst", + "crypto_dma_separate_burst", "spi_master_supports_dma", "spi_master_dma_engine=\"SPI_DMA\"", "spi_slave_supports_dma", @@ -5440,6 +5459,9 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_crypto", "cargo:rustc-cfg=soc_has_dma_copy", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=spi_dma_separate_burst", + "cargo:rustc-cfg=i2s_dma_separate_burst", + "cargo:rustc-cfg=crypto_dma_separate_burst", "cargo:rustc-cfg=spi_master_supports_dma", "cargo:rustc-cfg=spi_master_dma_engine=\"SPI_DMA\"", "cargo:rustc-cfg=spi_slave_supports_dma", @@ -5891,6 +5913,8 @@ impl Chip { "soc_has_dma_ch3", "soc_has_dma_ch4", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", + "ahb_gdma_separate_burst", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -6166,6 +6190,8 @@ impl Chip { "cargo:rustc-cfg=soc_has_dma_ch3", "cargo:rustc-cfg=soc_has_dma_ch4", "cargo:rustc-cfg=dma_supports_mem2mem", + "cargo:rustc-cfg=ahb_gdma_max_priority_is_set", + "cargo:rustc-cfg=ahb_gdma_separate_burst", "cargo:rustc-cfg=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -6752,6 +6778,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(dma_gdma_version_is_set)"); println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch0)"); println!("cargo:rustc-check-cfg=cfg(dma_supports_mem2mem)"); + println!("cargo:rustc-check-cfg=cfg(ahb_gdma_max_priority_is_set)"); println!("cargo:rustc-check-cfg=cfg(sha_supports_dma)"); println!("cargo:rustc-check-cfg=cfg(dma_max_priority_is_set)"); println!("cargo:rustc-check-cfg=cfg(ecc_zero_extend_writes)"); @@ -6955,6 +6982,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(soc_has_vdma_ch1)"); println!("cargo:rustc-check-cfg=cfg(soc_has_vdma_ch2)"); println!("cargo:rustc-check-cfg=cfg(soc_has_vdma_ch3)"); + println!("cargo:rustc-check-cfg=cfg(axi_gdma_max_priority_is_set)"); println!("cargo:rustc-check-cfg=cfg(mipi_dsi_supports_dma)"); println!("cargo:rustc-check-cfg=cfg(ethernet_mii_via_gpio_matrix)"); println!("cargo:rustc-check-cfg=cfg(soc_internal_memory_cached)"); @@ -6981,6 +7009,9 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(dma_ext_mem_configurable_block_size)"); println!("cargo:rustc-check-cfg=cfg(soc_has_dma_crypto)"); println!("cargo:rustc-check-cfg=cfg(soc_has_dma_copy)"); + println!("cargo:rustc-check-cfg=cfg(spi_dma_separate_burst)"); + println!("cargo:rustc-check-cfg=cfg(i2s_dma_separate_burst)"); + println!("cargo:rustc-check-cfg=cfg(crypto_dma_separate_burst)"); println!("cargo:rustc-check-cfg=cfg(soc_has_clock_node_ref_tick_ck8m)"); println!("cargo:rustc-check-cfg=cfg(spi_master_has_octal)"); println!("cargo:rustc-check-cfg=cfg(esp32s3)"); @@ -6991,6 +7022,7 @@ pub fn emit_check_cfg_directives() { println!("cargo:rustc-check-cfg=cfg(camera_driver_supported)"); println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch3)"); println!("cargo:rustc-check-cfg=cfg(soc_has_dma_ch4)"); + println!("cargo:rustc-check-cfg=cfg(ahb_gdma_separate_burst)"); println!("cargo:rustc-check-cfg=cfg(rmt_supports_dma)"); println!("cargo:rustc-check-cfg=cfg(lcd_cam_supports_dma)"); println!("cargo:rustc-check-cfg=cfg(psram_octal_spi)"); diff --git a/esp-metadata-generated/src/_generated_esp32.rs b/esp-metadata-generated/src/_generated_esp32.rs index 94ccfc3111f..609fbae4d12 100644 --- a/esp-metadata-generated/src/_generated_esp32.rs +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -442,8 +442,14 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("SPI_DMA")); - _for_each_inner_dma_engine!(("I2S_DMA")); - _for_each_inner_dma_engine!((all("SPI_DMA"), ("I2S_DMA"))); + _for_each_inner_dma_engine!(("I2S_DMA")); _for_each_inner_dma_engine!(("SPI_DMA", + single, burst = SpiDmaBurst, bursts = [(Disabled, 0), (Size4, 4)])); + _for_each_inner_dma_engine!(("I2S_DMA", single, burst = I2sDmaBurst, bursts = + [(Disabled, 0), (Size4, 4)])); _for_each_inner_dma_engine!((all("SPI_DMA"), + ("I2S_DMA"))); _for_each_inner_dma_engine!((priorities)); + _for_each_inner_dma_engine!((bursts("SPI_DMA", single, burst = SpiDmaBurst, + bursts = [(Disabled, 0), (Size4, 4)]), ("I2S_DMA", single, burst = I2sDmaBurst, + bursts = [(Disabled, 0), (Size4, 4)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c2.rs b/esp-metadata-generated/src/_generated_esp32c2.rs index 31c010377e2..a1881942543 100644 --- a/esp-metadata-generated/src/_generated_esp32c2.rs +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -382,7 +382,17 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, + 9)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Disabled, 0), (Size4, 4)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), + (Priority9, 9)]))); _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst + = AhbGdmaBurst, bursts = [(Disabled, 0), (Size4, 4)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c3.rs b/esp-metadata-generated/src/_generated_esp32c3.rs index 9907ce7b418..68e5765770b 100644 --- a/esp-metadata-generated/src/_generated_esp32c3.rs +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -475,7 +475,17 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, + 9)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Disabled, 0), (Size4, 4)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), + (Priority9, 9)]))); _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst + = AhbGdmaBurst, bursts = [(Disabled, 0), (Size4, 4)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs index b9b0b4ac034..ead2b9b4802 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -520,7 +520,16 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = + AhbGdmaBurst, bursts = [(Size4, 4), (Size16, 16), (Size32, 32)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]))); + _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Size4, 4), (Size16, 16), (Size32, 32)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs index cdac962cba5..f5c3e005df3 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -511,7 +511,16 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = + AhbGdmaBurst, bursts = [(Disabled, 0), (Size4, 4)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]))); + _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Disabled, 0), (Size4, 4)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c61.rs b/esp-metadata-generated/src/_generated_esp32c61.rs index 3c09d27ed93..507032932b9 100644 --- a/esp-metadata-generated/src/_generated_esp32c61.rs +++ b/esp-metadata-generated/src/_generated_esp32c61.rs @@ -427,7 +427,16 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = + AhbGdmaBurst, bursts = [(Size4, 4), (Size16, 16), (Size32, 32)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]))); + _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Size4, 4), (Size16, 16), (Size32, 32)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs index 5cb386964f9..da9a5076c75 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -493,7 +493,16 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5)])); _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = + AhbGdmaBurst, bursts = [(Disabled, 0), (Size4, 4)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]))); + _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Disabled, 0), (Size4, 4)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32p4.rs b/esp-metadata-generated/src/_generated_esp32p4.rs index 14d13a4079b..85ad35a9ee9 100644 --- a/esp-metadata-generated/src/_generated_esp32p4.rs +++ b/esp-metadata-generated/src/_generated_esp32p4.rs @@ -417,7 +417,25 @@ macro_rules! for_each_dma_engine { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); _for_each_inner_dma_engine!(("AXI_GDMA")); _for_each_inner_dma_engine!(("VDMA")); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5)])); _for_each_inner_dma_engine!(("AXI_GDMA", priority = + AxiGdmaPriority, priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), + (Priority3, 3), (Priority4, 4), (Priority5, 5)])); + _for_each_inner_dma_engine!(("AHB_GDMA", single, burst = AhbGdmaBurst, bursts = + [(Disabled, 0), (Size4, 4), (Size16, 16), (Size32, 32)])); + _for_each_inner_dma_engine!(("AXI_GDMA", single, burst = AxiGdmaBurst, bursts = + [(Size8, 8), (Size16, 16), (Size32, 32), (Size64, 64), (Size128, 128)])); _for_each_inner_dma_engine!((all("AHB_GDMA"), ("AXI_GDMA"), ("VDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]), ("AXI_GDMA", priority = AxiGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5)]))); + _for_each_inner_dma_engine!((bursts("AHB_GDMA", single, burst = AhbGdmaBurst, + bursts = [(Disabled, 0), (Size4, 4), (Size16, 16), (Size32, 32)]), ("AXI_GDMA", + single, burst = AxiGdmaBurst, bursts = [(Size8, 8), (Size16, 16), (Size32, 32), + (Size64, 64), (Size128, 128)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32s2.rs b/esp-metadata-generated/src/_generated_esp32s2.rs index 019395c566e..e9ccc6fcd6d 100644 --- a/esp-metadata-generated/src/_generated_esp32s2.rs +++ b/esp-metadata-generated/src/_generated_esp32s2.rs @@ -475,8 +475,26 @@ macro_rules! for_each_dma_engine { _for_each_inner_dma_engine!(("I2S_DMA")); _for_each_inner_dma_engine!(("CRYPTO_DMA")); _for_each_inner_dma_engine!(("COPY_DMA")); - _for_each_inner_dma_engine!((all("SPI_DMA"), ("I2S_DMA"), ("CRYPTO_DMA"), - ("COPY_DMA"))); + _for_each_inner_dma_engine!(("SPI_DMA", separate, internal = SpiDmaInternalBurst, + internal_bursts = [(Disabled, 0), (Size4, 4)], external = SpiDmaExternalBurst, + external_bursts = [(Size16, 16), (Size32, 32), (Size64, 64)])); + _for_each_inner_dma_engine!(("I2S_DMA", separate, internal = I2sDmaInternalBurst, + internal_bursts = [(Disabled, 0), (Size4, 4)], external = I2sDmaExternalBurst, + external_bursts = [(Size16, 16), (Size32, 32), (Size64, 64)])); + _for_each_inner_dma_engine!(("CRYPTO_DMA", separate, internal = + CryptoDmaInternalBurst, internal_bursts = [(Disabled, 0), (Size4, 4)], external = + CryptoDmaExternalBurst, external_bursts = [(Size16, 16), (Size32, 32), (Size64, + 64)])); _for_each_inner_dma_engine!((all("SPI_DMA"), ("I2S_DMA"), ("CRYPTO_DMA"), + ("COPY_DMA"))); _for_each_inner_dma_engine!((priorities)); + _for_each_inner_dma_engine!((bursts("SPI_DMA", separate, internal = + SpiDmaInternalBurst, internal_bursts = [(Disabled, 0), (Size4, 4)], external = + SpiDmaExternalBurst, external_bursts = [(Size16, 16), (Size32, 32), (Size64, + 64)]), ("I2S_DMA", separate, internal = I2sDmaInternalBurst, internal_bursts = + [(Disabled, 0), (Size4, 4)], external = I2sDmaExternalBurst, external_bursts = + [(Size16, 16), (Size32, 32), (Size64, 64)]), ("CRYPTO_DMA", separate, internal = + CryptoDmaInternalBurst, internal_bursts = [(Disabled, 0), (Size4, 4)], external = + CryptoDmaExternalBurst, external_bursts = [(Size16, 16), (Size32, 32), (Size64, + 64)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs index 3844dc0468d..34b0dabb188 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -498,7 +498,20 @@ macro_rules! for_each_dma_engine { ($($pattern:tt => $code:tt;)*) => { macro_rules! _for_each_inner_dma_engine { $(($pattern) => $code;)* ($other : tt) => {} } _for_each_inner_dma_engine!(("AHB_GDMA")); - _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!(("AHB_GDMA", priority = AhbGdmaPriority, priorities = + [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), + (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, + 9)])); _for_each_inner_dma_engine!(("AHB_GDMA", separate, internal = + AhbGdmaInternalBurst, internal_bursts = [(Disabled, 0), (Size4, 4)], external = + AhbGdmaExternalBurst, external_bursts = [(Size16, 16), (Size32, 32), (Size64, + 64)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priority = AhbGdmaPriority, + priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), (Priority3, 3), + (Priority4, 4), (Priority5, 5), (Priority6, 6), (Priority7, 7), (Priority8, 8), + (Priority9, 9)]))); _for_each_inner_dma_engine!((bursts("AHB_GDMA", separate, + internal = AhbGdmaInternalBurst, internal_bursts = [(Disabled, 0), (Size4, 4)], + external = AhbGdmaExternalBurst, external_bursts = [(Size16, 16), (Size32, 32), + (Size64, 64)]))); }; } #[macro_export] diff --git a/esp-metadata/src/cfg/dma.rs b/esp-metadata/src/cfg/dma.rs index 758b1fc565c..cae6c13a03b 100644 --- a/esp-metadata/src/cfg/dma.rs +++ b/esp-metadata/src/cfg/dma.rs @@ -52,6 +52,9 @@ pub struct DmaPeripheralInstance { pub dma_id: u32, } +/// DMA engines that expose configurable channel priority through esp-hal. +const PRIORITY_CAPABLE_ENGINES: &[&str] = &["AHB_GDMA", "AXI_GDMA"]; + /// A single DMA engine with its channels. #[derive(Debug, Clone, serde::Deserialize)] #[serde(deny_unknown_fields)] @@ -59,12 +62,13 @@ pub struct DmaEngineDef { /// The name of the engine (e.g. `"AHB_GDMA"`, `"SPI_DMA"`, `"I2S_DMA"`). pub name: String, + /// Maximum configurable channel priority level (inclusive), when supported. #[serde(default)] pub max_priority: Option, /// Configurable internal RAM burst sizes, when they differ from external RAM. + /// `0` means "burst disabled"; non-zero values are byte burst lengths. #[serde(default)] - #[expect(dead_code)] pub internal_ram_burst_sizes: Vec, /// Configurable burst sizes, when supported. Implies `can_access_psram = true`. @@ -90,6 +94,21 @@ pub struct DmaEngineDef { pub struct DmaEngines(pub Vec); impl DmaEngines { + pub(crate) fn validate_max_priority(&self) -> Result<()> { + for engine in &self.0 { + let requires = PRIORITY_CAPABLE_ENGINES.contains(&engine.name.as_str()); + match (requires, engine.max_priority) { + (true, None) => bail!("DMA engine '{}': max_priority is required", engine.name), + (false, Some(max)) => bail!( + "DMA engine '{}': max_priority = {max} is not supported", + engine.name + ), + _ => {} + } + } + Ok(()) + } + pub(crate) fn validate_mem2mem(&self, requires_peripheral: bool) -> Result<()> { for engine in &self.0 { let mut seen_ids = std::collections::HashSet::new(); @@ -149,6 +168,21 @@ impl GenericProperty for DmaEngines { cfgs.push("dma.supports_mem2mem".to_string()); } + for engine in &self.0 { + if engine.max_priority.is_some() { + let prefix = engine.name.to_ascii_lowercase(); + cfgs.push(format!("{prefix}_max_priority_is_set")); + } + + // When internal-RAM burst sizes are listed *and* (external) burst + // sizes are listed, the engine exposes independent internal/external + // burst configuration; its `Config` carries separate fields. + if !engine.internal_ram_burst_sizes.is_empty() && !engine.burst_sizes.is_empty() { + let prefix = engine.name.to_ascii_lowercase(); + cfgs.push(format!("{prefix}_separate_burst")); + } + } + // Emit a DMA-support cfg symbol for each unique driver listed in any engine. let mut seen = std::collections::HashSet::new(); for engine in &self.0 { @@ -182,6 +216,8 @@ impl GenericProperty for DmaEngines { let mut split = vec![]; let mut engines = vec![]; + let mut engines_with_priorities = vec![]; + let mut engines_with_bursts = vec![]; let mut engine_channels = vec![]; let mut engine_any_channels = vec![]; // One entry per (channel, peripheral) pair from DMA `compatible_with` lists. @@ -201,6 +237,76 @@ impl GenericProperty for DmaEngines { let engine_name = engine.name.as_str(); engines.push(quote! { #engine_name }); + if let Some(max) = engine.max_priority { + let priority_type = format_ident!( + "{}Priority", + engine.name.from_case(Case::Snake).to_case(Case::Pascal) + ); + let priority_variants = (0..=max).map(|n| { + let variant = format_ident!("Priority{n}"); + let level = number(n); + quote! { (#variant, #level) } + }); + engines_with_priorities.push(quote! { + #engine_name, priority = #priority_type, priorities = [#(#priority_variants),*] + }); + } + + // Channel-level burst capability used for the per-engine `*Burst` + // ceiling enum(s) and the `do_prepare` burst negotiation. + // + // A burst list is a set of byte lengths the channel may be set to. + // A `0` entry means the engine has a burst-enable bit (burst can be + // disabled); its absence means burst is always on (the minimum size + // is the floor). + // + // When *both* an internal-RAM list and a (external) burst list are + // present, the engine exposes them independently: two enums + // (`*InternalBurst` / `*ExternalBurst`) and two `Config` fields. + // When only one list is present there is no internal/external + // distinction, so a single `*Burst` enum / `burst` field is emitted + // (it applies to both regions). Engines with no burst knob at all + // (e.g. COPY_DMA) produce no entry. + let pascal = engine.name.from_case(Case::Snake).to_case(Case::Pascal); + let burst_variants = |sizes: &[u32]| { + sizes + .iter() + .map(|&n| { + let variant = if n == 0 { + format_ident!("Disabled") + } else { + format_ident!("Size{n}") + }; + let bytes = number(n); + quote! { (#variant, #bytes) } + }) + .collect::>() + }; + let has_internal = !engine.internal_ram_burst_sizes.is_empty(); + let has_external = !engine.burst_sizes.is_empty(); + if has_internal && has_external { + let int_ty = format_ident!("{pascal}InternalBurst"); + let ext_ty = format_ident!("{pascal}ExternalBurst"); + let int_variants = burst_variants(&engine.internal_ram_burst_sizes); + let ext_variants = burst_variants(&engine.burst_sizes); + engines_with_bursts.push(quote! { + #engine_name, separate, + internal = #int_ty, internal_bursts = [#(#int_variants),*], + external = #ext_ty, external_bursts = [#(#ext_variants),*] + }); + } else if has_internal || has_external { + let burst_type = format_ident!("{pascal}Burst"); + let sizes = if has_internal { + &engine.internal_ram_burst_sizes + } else { + &engine.burst_sizes + }; + let variants = burst_variants(sizes); + engines_with_bursts.push(quote! { + #engine_name, single, burst = #burst_type, bursts = [#(#variants),*] + }); + } + let channel = engine.name.from_case(Case::Snake).to_case(Case::Pascal); let any_channel = format_ident!("{channel}Channel"); @@ -317,7 +423,14 @@ impl GenericProperty for DmaEngines { None }; - let dma_engines_macro = generate_for_each_macro("dma_engine", &[("all", &engines)]); + let dma_engines_macro = generate_for_each_macro( + "dma_engine", + &[ + ("all", &engines), + ("priorities", &engines_with_priorities), + ("bursts", &engines_with_bursts), + ], + ); // Always emit for_each_dma_channel_peri_pair! so drivers can call it // unconditionally. On GDMA chips it expands to nothing. diff --git a/esp-metadata/src/lib.rs b/esp-metadata/src/lib.rs index edcb73ad037..bb1923dd41b 100644 --- a/esp-metadata/src/lib.rs +++ b/esp-metadata/src/lib.rs @@ -420,6 +420,7 @@ impl Config { } if let Some(dma) = self.device.peri_config.dma.as_ref() { + dma.engines.validate_max_priority()?; dma.engines .validate_mem2mem(dma.mem2mem_requires_peripheral)?; } @@ -903,7 +904,7 @@ fn generate_for_each_macro(name: &str, branches: &[Branch<'_>]) -> TokenStream { // } // } // ``` - #( #inner!( (#repeat_names #( (#repeat_branches) ),*) ); )* + #( #inner!( (#repeat_names #( (#repeat_branches) ),*) ); )* }; } } diff --git a/examples/peripheral/dma/extmem2mem/src/main.rs b/examples/peripheral/dma/extmem2mem/src/main.rs index 60f6890018f..295067f3200 100644 --- a/examples/peripheral/dma/extmem2mem/src/main.rs +++ b/examples/peripheral/dma/extmem2mem/src/main.rs @@ -10,13 +10,7 @@ extern crate alloc; use aligned::{A64, Aligned}; use esp_alloc as _; use esp_backtrace as _; -use esp_hal::{ - delay::Delay, - dma::{BurstConfig, ExternalBurstConfig, Mem2Mem}, - dma_descriptors_chunk_size, - main, - time::Duration, -}; +use esp_hal::{delay::Delay, dma::Mem2Mem, dma_descriptors_chunk_size, main, time::Duration}; use log::{error, info}; esp_bootloader_esp_idf::esp_app_desc!(); @@ -66,18 +60,7 @@ fn main() -> ! { }; let mut mem2mem = mem2mem - .with_descriptors( - rx_descriptors, - tx_descriptors, - BurstConfig { - external_memory: if cfg!(feature = "esp32s2") { - ExternalBurstConfig::Size32 - } else { - ExternalBurstConfig::Size64 - }, - internal_memory: Default::default(), - }, - ) + .with_descriptors(rx_descriptors, tx_descriptors) .unwrap(); for i in 0..core::mem::size_of_val(extram_buffer) { diff --git a/examples/peripheral/dma/mem2mem/src/main.rs b/examples/peripheral/dma/mem2mem/src/main.rs index 37ed59c3f6a..e2ffa76388c 100644 --- a/examples/peripheral/dma/mem2mem/src/main.rs +++ b/examples/peripheral/dma/mem2mem/src/main.rs @@ -6,13 +6,7 @@ #![no_main] use esp_backtrace as _; -use esp_hal::{ - delay::Delay, - dma::{BurstConfig, Mem2Mem}, - dma_buffers, - main, - time::Duration, -}; +use esp_hal::{delay::Delay, dma::Mem2Mem, dma_buffers, main, time::Duration}; use log::{error, info}; esp_bootloader_esp_idf::esp_app_desc!(); @@ -37,7 +31,7 @@ fn main() -> ! { }; let mut mem2mem = mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, BurstConfig::default()) + .with_descriptors(rx_descriptors, tx_descriptors) .unwrap(); for i in 0..size_of_val(tx_buffer) { diff --git a/examples/peripheral/spi/loopback_dma_psram/src/main.rs b/examples/peripheral/spi/loopback_dma_psram/src/main.rs index 2a0df715549..b854ae39623 100644 --- a/examples/peripheral/spi/loopback_dma_psram/src/main.rs +++ b/examples/peripheral/spi/loopback_dma_psram/src/main.rs @@ -22,7 +22,7 @@ extern crate alloc; use esp_backtrace as _; use esp_hal::{ delay::Delay, - dma::{DmaRxBuf, DmaTxBuf, ExternalBurstConfig}, + dma::{DmaRxBuf, DmaTxBuf}, main, spi::{ Mode, @@ -49,8 +49,8 @@ macro_rules! dma_alloc_buffer { } const DMA_BUFFER_SIZE: usize = 8192; -const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size64; -const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize; +const DMA_ALIGNMENT: usize = 64; +const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT; #[main] fn main() -> ! { @@ -74,15 +74,14 @@ fn main() -> ! { let (_, tx_descriptors) = esp_hal::dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE); - let tx_buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize); + let tx_buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT); info!( "TX: {:p} len {} ({} descripters)", tx_buffer.as_ptr(), tx_buffer.len(), tx_descriptors.len() ); - let mut dma_tx_buf = - DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, DMA_ALIGNMENT).unwrap(); + let mut dma_tx_buf = DmaTxBuf::new_aligned(tx_descriptors, tx_buffer, DMA_ALIGNMENT).unwrap(); let (rx_buffer, rx_descriptors, _, _) = esp_hal::dma_buffers!(DMA_BUFFER_SIZE, 0); info!( "RX: {:p} len {} ({} descripters)", diff --git a/hil-test/src/bin/misc_non_drivers/dma_buffers.rs b/hil-test/src/bin/misc_non_drivers/dma_buffers.rs index 0c4b138c414..a69f846d314 100644 --- a/hil-test/src/bin/misc_non_drivers/dma_buffers.rs +++ b/hil-test/src/bin/misc_non_drivers/dma_buffers.rs @@ -110,7 +110,7 @@ mod tests { fn test_dma_rx_stream_buf_insufficient_descriptors() { let (buffer, descriptors) = esp_hal::dma_buffers_impl!(BUFFER_SIZE, CHUNK_SIZE * 2, is_circular = false); - match DmaRxStreamBuf::new(descriptors, buffer) { + match DmaRxStreamBuf::new_internal_memory(descriptors, buffer) { Err(DmaBufError::InsufficientDescriptors) => (), _ => core::panic!("expected InsufficientDescriptors"), } @@ -120,7 +120,7 @@ mod tests { fn test_dma_tx_stream_buf_insufficient_descriptors() { let (buffer, descriptors) = esp_hal::dma_buffers_impl!(BUFFER_SIZE, CHUNK_SIZE * 2, is_circular = false); - match DmaTxStreamBuf::new(descriptors, buffer) { + match DmaTxStreamBuf::new_internal_memory(descriptors, buffer) { Err(DmaBufError::InsufficientDescriptors) => (), _ => core::panic!("expected InsufficientDescriptors"), } diff --git a/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs b/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs index 92773434b58..0fdb38d9538 100644 --- a/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs +++ b/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs @@ -32,7 +32,7 @@ mod tests { let mut mem2mem = ctx .mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) + .with_descriptors(rx_descriptors, tx_descriptors) .unwrap(); for i in 0..core::mem::size_of_val(tx_buffer) { @@ -48,10 +48,7 @@ mod tests { #[test] fn test_mem2mem_errors_zero_tx(ctx: Context) { let (rx_descriptors, tx_descriptors) = dma_descriptors!(1024, 0); - match ctx - .mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) - { + match ctx.mem2mem.with_descriptors(rx_descriptors, tx_descriptors) { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), } @@ -60,10 +57,7 @@ mod tests { #[test] fn test_mem2mem_errors_zero_rx(ctx: Context) { let (rx_descriptors, tx_descriptors) = dma_descriptors!(0, 1024); - match ctx - .mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) - { + match ctx.mem2mem.with_descriptors(rx_descriptors, tx_descriptors) { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), } diff --git a/hil-test/src/bin/spi_half_duplex_write_psram.rs b/hil-test/src/bin/spi_half_duplex_write_psram.rs index 5840f16e8fd..7727938113b 100644 --- a/hil-test/src/bin/spi_half_duplex_write_psram.rs +++ b/hil-test/src/bin/spi_half_duplex_write_psram.rs @@ -11,7 +11,7 @@ use defmt::error; use esp_alloc as _; use esp_hal::{ Blocking, - dma::{DmaRxBuf, DmaTxBuf, ExternalBurstConfig}, + dma::{DmaRxBuf, DmaTxBuf}, dma_buffers, dma_descriptors_chunk_size, gpio::{Flex, interconnect::InputSignal}, @@ -93,12 +93,12 @@ mod tests { #[test] fn test_spi_writes_are_correctly_by_pcnt(ctx: Context) { const DMA_BUFFER_SIZE: usize = 4; - const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size32; - const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize; + const DMA_ALIGNMENT: usize = 32; + const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT; let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE); - let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize); - let mut dma_tx_buf = DmaTxBuf::new_with_config(descriptors, buffer, DMA_ALIGNMENT).unwrap(); + let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT); + let mut dma_tx_buf = DmaTxBuf::new_aligned(descriptors, buffer, DMA_ALIGNMENT).unwrap(); let unit = ctx.pcnt_unit; let mut spi = ctx.spi; @@ -143,12 +143,12 @@ mod tests { #[test] fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) { const DMA_BUFFER_SIZE: usize = 4; - const DMA_ALIGNMENT: ExternalBurstConfig = ExternalBurstConfig::Size32; // matches dcache line size - const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize; // 64 byte aligned + const DMA_ALIGNMENT: usize = 32; // matches dcache line size + const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT; // 64 byte aligned let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE); - let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize); - let dma_tx_buf = DmaTxBuf::new_with_config(descriptors, buffer, DMA_ALIGNMENT).unwrap(); + let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT); + let dma_tx_buf = DmaTxBuf::new_aligned(descriptors, buffer, DMA_ALIGNMENT).unwrap(); let (rx, rxd, _, _) = dma_buffers!(1, 0); let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap();