From e3a8f087264b650617b74f63bf64b84d1ca58779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 15 Jun 2026 13:51:36 +0200 Subject: [PATCH 01/13] Remove unused traits --- esp-hal/src/dma/mod.rs | 141 ----------------------------------------- 1 file changed, 141 deletions(-) diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index d652e3fac2d..3a39ecc42c7 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -78,147 +78,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)] From 32de7be1194182db08171424ff2bccff1dad48ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 15 Jun 2026 13:23:20 +0200 Subject: [PATCH 02/13] SPI DMA: Remove ad-hoc static variables --- esp-hal/src/spi/master/dma.rs | 48 +++++++++++++++-------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index 7f6fe78a3f1..630a6585f2a 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::InternalMemoryBuffer; use crate::{ RegisterToggle, dma::{ @@ -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() }; + 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() + }, _ => &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(InternalMemoryCachelineAligned::new([DmaDescriptor::EMPTY; 2])), + #[cfg(all(spi_master_version = "1", spi_address_workaround))] + default_tx_buffer: UnsafeCell::new(InternalMemoryBuffer::new()), }; &DMA_STATE From 1f0cde3d41c61ad0e9456b2e9ad32715141a5753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 10 Jun 2026 13:33:03 +0200 Subject: [PATCH 03/13] Per-engine DMA priorities --- esp-hal/src/dma/engine/axi_gdma.rs | 40 +++++++--- esp-hal/src/dma/engine/gdma/ahb_v1.rs | 34 ++++++--- esp-hal/src/dma/engine/gdma/ahb_v2.rs | 34 ++++++--- esp-hal/src/dma/engine/gdma/mod.rs | 6 ++ esp-hal/src/dma/engine/mod.rs | 43 +++++++++-- esp-hal/src/dma/mod.rs | 74 +++++++------------ .../src/_build_script_utils.rs | 20 +++++ .../src/_generated_esp32.rs | 1 + .../src/_generated_esp32c2.rs | 6 ++ .../src/_generated_esp32c3.rs | 6 ++ .../src/_generated_esp32c5.rs | 7 +- .../src/_generated_esp32c6.rs | 7 +- .../src/_generated_esp32c61.rs | 7 +- .../src/_generated_esp32h2.rs | 7 +- .../src/_generated_esp32p4.rs | 10 ++- .../src/_generated_esp32s2.rs | 2 +- .../src/_generated_esp32s3.rs | 6 ++ esp-metadata/src/cfg/dma.rs | 43 ++++++++++- esp-metadata/src/lib.rs | 3 +- 19 files changed, 258 insertions(+), 98 deletions(-) diff --git a/esp-hal/src/dma/engine/axi_gdma.rs b/esp-hal/src/dma/engine/axi_gdma.rs index 1157569c4d5..dc023b76a04 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -138,12 +138,6 @@ impl RegisterAccess for AxiGdmaTxChannel<'_> { .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() @@ -191,6 +185,17 @@ impl RegisterAccess for AxiGdmaTxChannel<'_> { } } +#[cfg(axi_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AxiGdmaTxChannel<'_> { + type Priority = AxiGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); + } +} + impl TxRegisterAccess for AxiGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { self.ch() @@ -328,12 +333,6 @@ impl RegisterAccess for AxiGdmaRxChannel<'_> { .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() @@ -381,6 +380,17 @@ impl RegisterAccess for AxiGdmaRxChannel<'_> { } } +#[cfg(axi_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AxiGdmaRxChannel<'_> { + type Priority = AxiGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); + } +} + impl RxRegisterAccess for AxiGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { @@ -536,6 +546,12 @@ for_each_dma_channel! { }; } +for_each_dma_engine! { + ("AXI_GDMA", priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AXI_GDMA", AxiGdmaPriority, [$(($variant, $level)),*]); + }; +} + fn init_axi_dma_racey() { let regs = AXI_GDMA::regs(); diff --git a/esp-hal/src/dma/engine/gdma/ahb_v1.rs b/esp-hal/src/dma/engine/gdma/ahb_v1.rs index 2765838c5ed..cabcdd0e944 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v1.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v1.rs @@ -51,12 +51,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() @@ -110,6 +104,17 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { } } +#[cfg(ahb_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AhbGdmaTxChannel<'_> { + type Priority = AhbGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); + } +} + impl TxRegisterAccess for AhbGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { cfg_if::cfg_if! { @@ -286,12 +291,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() @@ -343,6 +342,17 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { } } +#[cfg(ahb_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AhbGdmaRxChannel<'_> { + type Priority = AhbGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); + } +} + impl RxRegisterAccess for AhbGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { diff --git a/esp-hal/src/dma/engine/gdma/ahb_v2.rs b/esp-hal/src/dma/engine/gdma/ahb_v2.rs index 871e5df8b35..864dabbcb10 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v2.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v2.rs @@ -50,12 +50,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() @@ -103,6 +97,17 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { } } +#[cfg(ahb_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AhbGdmaTxChannel<'_> { + type Priority = AhbGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .out_pri() + .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); + } +} + impl TxRegisterAccess for AhbGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { self.ch() @@ -252,12 +257,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() @@ -303,6 +302,17 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { } } +#[cfg(ahb_gdma_max_priority_is_set)] +impl PriorityRegisterAccess for AhbGdmaRxChannel<'_> { + type Priority = AhbGdmaPriority; + + fn set_priority(&self, priority: Self::Priority) { + self.ch() + .in_pri() + .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); + } +} + impl RxRegisterAccess for AhbGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index 23a65dd9e38..dbc4fe008d5 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -237,6 +237,12 @@ for_each_dma_channel! { }; } +for_each_dma_engine! { + ("AHB_GDMA", priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AHB_GDMA", AhbGdmaPriority, [$(($variant, $level)),*]); + }; +} + fn init_dma_racey() { // FIXME: reset/clock enable belongs to metadata use crate::RegisterToggle; diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index 760e0a7766c..be232ac02a0 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -82,11 +82,6 @@ pub trait RegisterAccess: Sealed { /// 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) {} @@ -124,6 +119,15 @@ pub trait RegisterAccess: Sealed { } } +/// Implemented by register access types whose engine supports channel priority. +pub trait PriorityRegisterAccess: RegisterAccess { + /// Engine-specific DMA channel priority. + type Priority: Copy; + + /// Set the channel priority. The larger the value, the higher the priority. + fn set_priority(&self, priority: Self::Priority); +} + #[doc(hidden)] pub trait RxRegisterAccess: RegisterAccess { #[cfg(dma_supports_mem2mem)] @@ -252,3 +256,32 @@ 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)] + #[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; diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 3a39ecc42c7..1e8f2413365 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -754,37 +754,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 { @@ -1124,9 +1093,11 @@ where Dm: DriverMode, CH: DmaRxChannel, { - /// Configure the channel. - #[cfg(dma_max_priority_is_set)] - pub fn set_priority(&mut self, priority: DmaPriority) { + /// Configure the channel priority. + pub fn set_priority(&mut self, priority: CH::Priority) + where + CH: PriorityRegisterAccess, + { self.rx_impl.set_priority(priority); } @@ -1344,18 +1315,20 @@ where Dm: DriverMode, CH: DmaTxChannel, { + /// Configure the channel priority. + pub fn set_priority(&mut self, priority: CH::Priority) + where + CH: PriorityRegisterAccess, + { + self.tx_impl.set_priority(priority); + } + /// 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, @@ -1553,13 +1526,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 { @@ -1569,11 +1535,21 @@ where } } -impl Channel +impl Channel where - CH: DmaChannel, Dm: DriverMode, + CH: DmaChannel, { + /// Configure the channel priorities. + pub fn set_priority(&mut self, priority: ::Priority) + where + CH::Rx: PriorityRegisterAccess, + CH::Tx: PriorityRegisterAccess::Priority>, + { + self.tx.set_priority(priority); + self.rx.set_priority(priority); + } + /// Asserts that the channel is compatible with the given peripheral. #[instability::unstable] pub fn runtime_ensure_compatible(&self, peripheral: DmaPeripheral) { diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index 664c505d50a..5888b71e1ba 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", @@ -5891,6 +5907,7 @@ impl Chip { "soc_has_dma_ch3", "soc_has_dma_ch4", "dma_supports_mem2mem", + "ahb_gdma_max_priority_is_set", "aes_supports_dma", "aes_dma_engine=\"AHB_GDMA\"", "sha_supports_dma", @@ -6166,6 +6183,7 @@ 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=aes_supports_dma", "cargo:rustc-cfg=aes_dma_engine=\"AHB_GDMA\"", "cargo:rustc-cfg=sha_supports_dma", @@ -6752,6 +6770,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 +6974,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)"); diff --git a/esp-metadata-generated/src/_generated_esp32.rs b/esp-metadata-generated/src/_generated_esp32.rs index 94ccfc3111f..e8a92c8908f 100644 --- a/esp-metadata-generated/src/_generated_esp32.rs +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -444,6 +444,7 @@ macro_rules! for_each_dma_engine { => {} } _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!((priorities)); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c2.rs b/esp-metadata-generated/src/_generated_esp32c2.rs index 31c010377e2..fc2665c61df 100644 --- a/esp-metadata-generated/src/_generated_esp32c2.rs +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -382,7 +382,13 @@ 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", 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!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, 5), + (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, 9)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c3.rs b/esp-metadata-generated/src/_generated_esp32c3.rs index 9907ce7b418..7ddd8662062 100644 --- a/esp-metadata-generated/src/_generated_esp32c3.rs +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -475,7 +475,13 @@ 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", 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!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, 5), + (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, 9)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs index b9b0b4ac034..729f2e2f2b8 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -520,7 +520,12 @@ 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", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs index cdac962cba5..7d4e2d66ea5 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -511,7 +511,12 @@ 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", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c61.rs b/esp-metadata-generated/src/_generated_esp32c61.rs index 3c09d27ed93..f3adb453d97 100644 --- a/esp-metadata-generated/src/_generated_esp32c61.rs +++ b/esp-metadata-generated/src/_generated_esp32c61.rs @@ -427,7 +427,12 @@ 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", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs index 5cb386964f9..fffd1bbbf9e 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -493,7 +493,12 @@ 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", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32p4.rs b/esp-metadata-generated/src/_generated_esp32p4.rs index 14d13a4079b..88ffb511432 100644 --- a/esp-metadata-generated/src/_generated_esp32p4.rs +++ b/esp-metadata-generated/src/_generated_esp32p4.rs @@ -417,7 +417,15 @@ 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!((all("AHB_GDMA"), ("AXI_GDMA"), ("VDMA"))); + _for_each_inner_dma_engine!(("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!(("AXI_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, + 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"), ("AXI_GDMA"), ("VDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, 5)]), + ("AXI_GDMA", priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), + (Priority3, 3), (Priority4, 4), (Priority5, 5)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32s2.rs b/esp-metadata-generated/src/_generated_esp32s2.rs index 019395c566e..e97c2fa95ea 100644 --- a/esp-metadata-generated/src/_generated_esp32s2.rs +++ b/esp-metadata-generated/src/_generated_esp32s2.rs @@ -476,7 +476,7 @@ macro_rules! for_each_dma_engine { _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"))); + ("COPY_DMA"))); _for_each_inner_dma_engine!((priorities)); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs index 3844dc0468d..17915f66f1a 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -498,7 +498,13 @@ 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", 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!((all("AHB_GDMA"))); + _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), + (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, 5), + (Priority6, 6), (Priority7, 7), (Priority8, 8), (Priority9, 9)]))); }; } #[macro_export] diff --git a/esp-metadata/src/cfg/dma.rs b/esp-metadata/src/cfg/dma.rs index 758b1fc565c..a7485138121 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,6 +62,7 @@ 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, @@ -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,13 @@ 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")); + } + } + // 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 +208,7 @@ impl GenericProperty for DmaEngines { let mut split = vec![]; let mut engines = vec![]; + let mut engines_with_priorities = 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 +228,17 @@ 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_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, priorities = [#(#priority_variants),*] + }); + } + let channel = engine.name.from_case(Case::Snake).to_case(Case::Pascal); let any_channel = format_ident!("{channel}Channel"); @@ -317,7 +355,10 @@ 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)], + ); // 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) ),*) ); )* }; } } From a81ae0dc12bc8f7c412e5e0e63b99ceaf81b017b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 10 Jun 2026 14:25:02 +0200 Subject: [PATCH 04/13] Implement DMA configuration for SPI master --- esp-hal/src/dma/engine/axi_gdma.rs | 4 +-- esp-hal/src/dma/engine/gdma/mod.rs | 4 +-- esp-hal/src/dma/engine/mod.rs | 2 +- esp-hal/src/spi/master/dma.rs | 32 +++++++++++++++++++ .../src/_generated_esp32c2.rs | 15 +++++---- .../src/_generated_esp32c3.rs | 15 +++++---- .../src/_generated_esp32c5.rs | 12 +++---- .../src/_generated_esp32c6.rs | 12 +++---- .../src/_generated_esp32c61.rs | 12 +++---- .../src/_generated_esp32h2.rs | 12 +++---- .../src/_generated_esp32p4.rs | 20 ++++++------ .../src/_generated_esp32s3.rs | 15 +++++---- esp-metadata/src/cfg/dma.rs | 6 +++- 13 files changed, 101 insertions(+), 60 deletions(-) diff --git a/esp-hal/src/dma/engine/axi_gdma.rs b/esp-hal/src/dma/engine/axi_gdma.rs index dc023b76a04..b14e45f6a28 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -547,8 +547,8 @@ for_each_dma_channel! { } for_each_dma_engine! { - ("AXI_GDMA", priorities = [$(($variant:ident, $level:literal)),*]) => { - impl_priority_type!("AXI_GDMA", AxiGdmaPriority, [$(($variant, $level)),*]); + ("AXI_GDMA", priority = $priority:ident, priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AXI_GDMA", $priority, [$(($variant, $level)),*]); }; } diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index dbc4fe008d5..76530090f9d 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -238,8 +238,8 @@ for_each_dma_channel! { } for_each_dma_engine! { - ("AHB_GDMA", priorities = [$(($variant:ident, $level:literal)),*]) => { - impl_priority_type!("AHB_GDMA", AhbGdmaPriority, [$(($variant, $level)),*]); + ("AHB_GDMA", priority = $priority:ident, priorities = [$(($variant:ident, $level:literal)),*]) => { + impl_priority_type!("AHB_GDMA", $priority, [$(($variant, $level)),*]); }; } diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index be232ac02a0..1451b522e34 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -261,7 +261,7 @@ pub(crate) use impl_channel_common; 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)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum $ty { $( diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index 630a6585f2a..7f19c7c96f7 100644 --- a/esp-hal/src/spi/master/dma.rs +++ b/esp-hal/src/spi/master/dma.rs @@ -1887,5 +1887,37 @@ 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>; + + // This is how we can check if the engine supports priority arbitration + for_each_dma_engine! { + ($engine, priority = $priority:ident, priorities = $_:tt) => { + /// Priority level for the SPI master DMA channel. + pub type SpiMasterDmaPriority = crate::dma::$priority; + + /// DMA channel configuration. + #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[non_exhaustive] + pub struct DmaConfig { + /// DMA RX channel priority. + /// + /// The default value is `Priority0`. + rx_ch_priority: SpiMasterDmaPriority, + + /// DMA TX channel priority. + /// + /// The default value is `Priority0`. + tx_ch_priority: SpiMasterDmaPriority, + } + + impl<'d, Dm: DriverMode> SpiDma<'d, Dm> { + /// Updates DMA channel configuration. + pub fn apply_dma_config(&mut self, config: &DmaConfig) { + self.channel.rx.set_priority(config.rx_ch_priority); + self.channel.tx.set_priority(config.tx_ch_priority); + } + } + }; + } }; } diff --git a/esp-metadata-generated/src/_generated_esp32c2.rs b/esp-metadata-generated/src/_generated_esp32c2.rs index fc2665c61df..6dc0caa913e 100644 --- a/esp-metadata-generated/src/_generated_esp32c2.rs +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -382,13 +382,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!(("AHB_GDMA")); - _for_each_inner_dma_engine!(("AHB_GDMA", 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!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", 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", 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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c3.rs b/esp-metadata-generated/src/_generated_esp32c3.rs index 7ddd8662062..b6f3b641c9e 100644 --- a/esp-metadata-generated/src/_generated_esp32c3.rs +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -475,13 +475,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!(("AHB_GDMA")); - _for_each_inner_dma_engine!(("AHB_GDMA", 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!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", 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", 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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c5.rs b/esp-metadata-generated/src/_generated_esp32c5.rs index 729f2e2f2b8..750c2903a53 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -520,12 +520,12 @@ 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", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)]))); + _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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c6.rs b/esp-metadata-generated/src/_generated_esp32c6.rs index 7d4e2d66ea5..938efdd0460 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -511,12 +511,12 @@ 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", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)]))); + _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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32c61.rs b/esp-metadata-generated/src/_generated_esp32c61.rs index f3adb453d97..eb89a892252 100644 --- a/esp-metadata-generated/src/_generated_esp32c61.rs +++ b/esp-metadata-generated/src/_generated_esp32c61.rs @@ -427,12 +427,12 @@ 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", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)]))); + _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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32h2.rs b/esp-metadata-generated/src/_generated_esp32h2.rs index fffd1bbbf9e..97e7185905c 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -493,12 +493,12 @@ 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", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)]))); + _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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32p4.rs b/esp-metadata-generated/src/_generated_esp32p4.rs index 88ffb511432..e66fc7311c1 100644 --- a/esp-metadata-generated/src/_generated_esp32p4.rs +++ b/esp-metadata-generated/src/_generated_esp32p4.rs @@ -417,15 +417,17 @@ 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", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!(("AXI_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, - 5)])); _for_each_inner_dma_engine!((all("AHB_GDMA"), ("AXI_GDMA"), ("VDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", priorities = [(Priority0, 0), - (Priority1, 1), (Priority2, 2), (Priority3, 3), (Priority4, 4), (Priority5, 5)]), - ("AXI_GDMA", priorities = [(Priority0, 0), (Priority1, 1), (Priority2, 2), - (Priority3, 3), (Priority4, 4), (Priority5, 5)]))); + _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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata-generated/src/_generated_esp32s3.rs b/esp-metadata-generated/src/_generated_esp32s3.rs index 17915f66f1a..cba63d5f68b 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -498,13 +498,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!(("AHB_GDMA")); - _for_each_inner_dma_engine!(("AHB_GDMA", 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!((all("AHB_GDMA"))); - _for_each_inner_dma_engine!((priorities("AHB_GDMA", 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", 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!((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)]))); }; } #[macro_export] diff --git a/esp-metadata/src/cfg/dma.rs b/esp-metadata/src/cfg/dma.rs index a7485138121..269756af5b2 100644 --- a/esp-metadata/src/cfg/dma.rs +++ b/esp-metadata/src/cfg/dma.rs @@ -229,13 +229,17 @@ impl GenericProperty for DmaEngines { 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, priorities = [#(#priority_variants),*] + #engine_name, priority = #priority_type, priorities = [#(#priority_variants),*] }); } From c065be269bb1431aa7c5caf0425875cce5f4224f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 17:20:31 +0200 Subject: [PATCH 05/13] . --- esp-hal/src/dma/buffers/mod.rs | 20 ++++++++ esp-hal/src/dma/buffers/scoped.rs | 2 + esp-hal/src/dma/engine/axi_gdma.rs | 49 ++++++++++-------- esp-hal/src/dma/engine/copy.rs | 16 ++++++ esp-hal/src/dma/engine/crypto.rs | 13 +++++ esp-hal/src/dma/engine/gdma/ahb_v1.rs | 38 ++++++-------- esp-hal/src/dma/engine/gdma/ahb_v2.rs | 38 ++++++-------- esp-hal/src/dma/engine/gdma/mod.rs | 11 ++++ esp-hal/src/dma/engine/i2s.rs | 13 +++++ esp-hal/src/dma/engine/mod.rs | 28 +++++----- esp-hal/src/dma/engine/spi.rs | 13 +++++ esp-hal/src/dma/mod.rs | 73 +++++++++++++++++++-------- esp-hal/src/spi/master/dma.rs | 42 +++++---------- 13 files changed, 227 insertions(+), 129 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index cd7ba5eb873..6c7472a6de2 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -384,6 +384,16 @@ pub struct Preparation { #[doc = crate::trm_markdown_link!()] pub burst_transfer: BurstConfig, + /// Maximum alignment (the "burst axis") guaranteed by this buffer's start + /// address *and* every descriptor `size`/length field, in bytes. + /// + /// This is the alignment the buffer chunked itself for; the channel uses it + /// to negotiate the effective burst at transfer setup. It does *not* include + /// the cache-line requirement (which only constrains the buffer's base and + /// length and is handled by the buffer itself), and so is never weakened by + /// the burst negotiation. + pub max_alignment: usize, + /// Configures the "check owner" feature of the DMA channel. /// /// Most DMA channels allow software to configure whether the hardware @@ -900,6 +910,7 @@ unsafe impl DmaTxBuffer for DmaRxTxBuf { #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, burst_transfer: self.burst, + max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::Out), check_owner: None, auto_write_back: false, } @@ -944,6 +955,7 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf { #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, burst_transfer: self.burst, + max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::In), check_owner: None, auto_write_back: true, } @@ -1081,6 +1093,7 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf { #[cfg(dma_can_access_psram)] accesses_psram: false, burst_transfer: self.burst, + max_alignment: self.burst.min_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. @@ -1503,6 +1516,7 @@ unsafe impl DmaTxBuffer for DmaTxStreamBuf { #[cfg(dma_can_access_psram)] accesses_psram: false, burst_transfer: self.burst, + max_alignment: self.burst.min_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. @@ -1613,6 +1627,7 @@ unsafe impl DmaTxBuffer for EmptyBuf { #[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. @@ -1651,6 +1666,7 @@ unsafe impl DmaRxBuffer for EmptyBuf { #[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. @@ -1728,6 +1744,7 @@ unsafe impl DmaTxBuffer for DmaLoopBuf { accesses_psram: false, direction: TransferDirection::Out, burst_transfer: BurstConfig::default(), + max_alignment: BurstConfig::default().min_alignment(self.buffer, TransferDirection::Out), // The DMA must not check the owner bit, as it is never set. check_owner: Some(false), @@ -1773,6 +1790,7 @@ impl NoBuffer { #[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, } @@ -1874,6 +1892,7 @@ pub(crate) unsafe fn prepare_for_tx( 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)] @@ -1985,6 +2004,7 @@ pub(crate) unsafe fn prepare_for_rx( 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)] diff --git a/esp-hal/src/dma/buffers/scoped.rs b/esp-hal/src/dma/buffers/scoped.rs index e684b73d9b8..bb855d14234 100644 --- a/esp-hal/src/dma/buffers/scoped.rs +++ b/esp-hal/src/dma/buffers/scoped.rs @@ -173,6 +173,7 @@ unsafe impl<'a> DmaTxBuffer for ScopedDmaTxBuf<'a> { #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, burst_transfer: self.burst, + max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::Out), check_owner: None, auto_write_back: false, } @@ -386,6 +387,7 @@ unsafe impl<'a> DmaRxBuffer for ScopedDmaRxBuf<'a> { #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, burst_transfer: self.burst, + max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::In), 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 b14e45f6a28..6ada351bb50 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -109,6 +109,17 @@ 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, +} + impl AxiGdmaTxChannel<'_> { #[inline(always)] fn ch(&self) -> &pac::axi_dma::OUT_CH { @@ -117,6 +128,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( @@ -185,17 +204,6 @@ impl RegisterAccess for AxiGdmaTxChannel<'_> { } } -#[cfg(axi_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AxiGdmaTxChannel<'_> { - type Priority = AxiGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); - } -} - impl TxRegisterAccess for AxiGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { self.ch() @@ -312,6 +320,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( @@ -380,17 +396,6 @@ impl RegisterAccess for AxiGdmaRxChannel<'_> { } } -#[cfg(axi_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AxiGdmaRxChannel<'_> { - type Priority = AxiGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); - } -} - impl RxRegisterAccess for AxiGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { diff --git a/esp-hal/src/dma/engine/copy.rs b/esp-hal/src/dma/engine/copy.rs index 07722433e8e..093c59b039a 100644 --- a/esp-hal/src/dma/engine/copy.rs +++ b/esp-hal/src/dma/engine/copy.rs @@ -81,7 +81,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)) @@ -254,6 +266,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)) diff --git a/esp-hal/src/dma/engine/crypto.rs b/esp-hal/src/dma/engine/crypto.rs index 24e73ec195a..a1ed9d97cac 100644 --- a/esp-hal/src/dma/engine/crypto.rs +++ b/esp-hal/src/dma/engine/crypto.rs @@ -82,7 +82,16 @@ impl CryptoDmaTxChannel<'_> { impl crate::private::Sealed for CryptoDmaTxChannel<'_> {} impl DmaTxChannel for CryptoDmaTxChannel<'_> {} +/// Configuration for a CRYPTO DMA channel half. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct CryptoDmaConfig {} + 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)) @@ -281,6 +290,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)) diff --git a/esp-hal/src/dma/engine/gdma/ahb_v1.rs b/esp-hal/src/dma/engine/gdma/ahb_v1.rs index cabcdd0e944..cd2226d199b 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( @@ -104,17 +112,6 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { } } -#[cfg(ahb_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AhbGdmaTxChannel<'_> { - type Priority = AhbGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); - } -} - impl TxRegisterAccess for AhbGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { cfg_if::cfg_if! { @@ -267,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( @@ -342,17 +347,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { } } -#[cfg(ahb_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AhbGdmaRxChannel<'_> { - type Priority = AhbGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); - } -} - impl RxRegisterAccess for AhbGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { diff --git a/esp-hal/src/dma/engine/gdma/ahb_v2.rs b/esp-hal/src/dma/engine/gdma/ahb_v2.rs index 864dabbcb10..137731d5f8d 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v2.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v2.rs @@ -26,6 +26,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( @@ -97,17 +105,6 @@ impl RegisterAccess for AhbGdmaTxChannel<'_> { } } -#[cfg(ahb_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AhbGdmaTxChannel<'_> { - type Priority = AhbGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .out_pri() - .write(|w| unsafe { w.tx_pri().bits(priority.into()) }); - } -} - impl TxRegisterAccess for AhbGdmaTxChannel<'_> { fn is_fifo_empty(&self) -> bool { self.ch() @@ -233,6 +230,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( @@ -302,17 +307,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { } } -#[cfg(ahb_gdma_max_priority_is_set)] -impl PriorityRegisterAccess for AhbGdmaRxChannel<'_> { - type Priority = AhbGdmaPriority; - - fn set_priority(&self, priority: Self::Priority) { - self.ch() - .in_pri() - .write(|w| unsafe { w.rx_pri().bits(priority.into()) }); - } -} - impl RxRegisterAccess for AhbGdmaRxChannel<'_> { #[cfg(dma_supports_mem2mem)] fn set_mem2mem_mode(&self, value: bool) { diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index 76530090f9d..777b8a07904 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -106,6 +106,17 @@ 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, +} + /// An arbitrary GDMA RX channel #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/esp-hal/src/dma/engine/i2s.rs b/esp-hal/src/dma/engine/i2s.rs index 635a4c73b77..90df20b0414 100644 --- a/esp-hal/src/dma/engine/i2s.rs +++ b/esp-hal/src/dma/engine/i2s.rs @@ -77,7 +77,16 @@ impl I2sDmaTxChannel<'_> { impl crate::private::Sealed for I2sDmaTxChannel<'_> {} impl DmaTxChannel for I2sDmaTxChannel<'_> {} +/// Configuration for an I2S DMA channel half. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct I2sDmaConfig {} + impl RegisterAccess for I2sDmaTxChannel<'_> { + type Config = I2sDmaConfig; + + fn apply_config(&self, _config: &Self::Config) {} + #[allow(private_interfaces)] fn enable(&self) -> Option { None @@ -266,6 +275,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 diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index 1451b522e34..089912bde97 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -20,19 +20,19 @@ for_each_dma_engine! { }; ("COPY_DMA") => { mod copy; - pub use copy::{CopyDmaChannel, CopyDmaRxChannel, CopyDmaTxChannel}; + pub use copy::{CopyDmaChannel, CopyDmaConfig, CopyDmaRxChannel, CopyDmaTxChannel}; }; ("CRYPTO_DMA") => { mod crypto; - pub use crypto::{CryptoDmaChannel, CryptoDmaRxChannel, CryptoDmaTxChannel}; + pub use crypto::{CryptoDmaChannel, CryptoDmaConfig, CryptoDmaRxChannel, CryptoDmaTxChannel}; }; ("I2S_DMA") => { mod i2s; - pub use i2s::{I2sDmaChannel, I2sDmaRxChannel, I2sDmaTxChannel}; + pub use i2s::{I2sDmaChannel, I2sDmaConfig, I2sDmaRxChannel, I2sDmaTxChannel}; }; ("SPI_DMA") => { mod spi; - pub use spi::{SpiDmaChannel, SpiDmaRxChannel, SpiDmaTxChannel}; + pub use spi::{SpiDmaChannel, SpiDmaConfig, SpiDmaRxChannel, SpiDmaTxChannel}; }; } @@ -68,6 +68,17 @@ 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); + #[allow(private_interfaces)] fn enable(&self) -> Option; @@ -119,15 +130,6 @@ pub trait RegisterAccess: Sealed { } } -/// Implemented by register access types whose engine supports channel priority. -pub trait PriorityRegisterAccess: RegisterAccess { - /// Engine-specific DMA channel priority. - type Priority: Copy; - - /// Set the channel priority. The larger the value, the higher the priority. - fn set_priority(&self, priority: Self::Priority); -} - #[doc(hidden)] pub trait RxRegisterAccess: RegisterAccess { #[cfg(dma_supports_mem2mem)] diff --git a/esp-hal/src/dma/engine/spi.rs b/esp-hal/src/dma/engine/spi.rs index 28d425fb6c9..75455573bd2 100644 --- a/esp-hal/src/dma/engine/spi.rs +++ b/esp-hal/src/dma/engine/spi.rs @@ -77,7 +77,16 @@ impl SpiDmaTxChannel<'_> { impl crate::private::Sealed for SpiDmaTxChannel<'_> {} impl DmaTxChannel for SpiDmaTxChannel<'_> {} +/// Configuration for a SPI DMA channel half. +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct SpiDmaConfig {} + 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! { @@ -276,6 +285,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! { diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 1e8f2413365..7b1fa053b46 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -1093,12 +1093,9 @@ where Dm: DriverMode, CH: DmaRxChannel, { - /// Configure the channel priority. - pub fn set_priority(&mut self, priority: CH::Priority) - where - CH: PriorityRegisterAccess, - { - self.rx_impl.set_priority(priority); + /// Applies a complete configuration to this channel half. + pub fn apply_config(&mut self, config: &CH::Config) { + self.rx_impl.apply_config(config); } fn do_prepare( @@ -1315,12 +1312,9 @@ where Dm: DriverMode, CH: DmaTxChannel, { - /// Configure the channel priority. - pub fn set_priority(&mut self, priority: CH::Priority) - where - CH: PriorityRegisterAccess, - { - self.tx_impl.set_priority(priority); + /// Applies a complete configuration to this channel half. + pub fn apply_config(&mut self, config: &CH::Config) { + self.tx_impl.apply_config(config); } /// Asserts that the channel is compatible with the given peripheral. @@ -1448,6 +1442,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 @@ -1540,14 +1574,13 @@ where Dm: DriverMode, CH: DmaChannel, { - /// Configure the channel priorities. - pub fn set_priority(&mut self, priority: ::Priority) - where - CH::Rx: PriorityRegisterAccess, - CH::Tx: PriorityRegisterAccess::Priority>, - { - self.tx.set_priority(priority); - self.rx.set_priority(priority); + /// 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. diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index 7f19c7c96f7..ad3a938d9d9 100644 --- a/esp-hal/src/spi/master/dma.rs +++ b/esp-hal/src/spi/master/dma.rs @@ -1888,36 +1888,18 @@ 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>; - // This is how we can check if the engine supports priority arbitration - for_each_dma_engine! { - ($engine, priority = $priority:ident, priorities = $_:tt) => { - /// Priority level for the SPI master DMA channel. - pub type SpiMasterDmaPriority = crate::dma::$priority; - - /// DMA channel configuration. - #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - #[non_exhaustive] - pub struct DmaConfig { - /// DMA RX channel priority. - /// - /// The default value is `Priority0`. - rx_ch_priority: SpiMasterDmaPriority, - - /// DMA TX channel priority. - /// - /// The default value is `Priority0`. - tx_ch_priority: SpiMasterDmaPriority, - } - - impl<'d, Dm: DriverMode> SpiDma<'d, Dm> { - /// Updates DMA channel configuration. - pub fn apply_dma_config(&mut self, config: &DmaConfig) { - self.channel.rx.set_priority(config.rx_ch_priority); - self.channel.tx.set_priority(config.tx_ch_priority); - } - } - }; + /// 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); + } } }; } From 9803b7e4a1cc7b11b0be2a71f76a008a0f7f109d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 18:16:10 +0200 Subject: [PATCH 06/13] . --- esp-hal/src/dma/engine/axi_gdma.rs | 15 +++ esp-hal/src/dma/engine/copy.rs | 7 ++ esp-hal/src/dma/engine/crypto.rs | 41 ++++++- esp-hal/src/dma/engine/gdma/mod.rs | 35 ++++++ esp-hal/src/dma/engine/i2s.rs | 41 ++++++- esp-hal/src/dma/engine/mod.rs | 111 +++++++++++++++++- esp-hal/src/dma/engine/spi.rs | 41 ++++++- esp-hal/src/dma/mod.rs | 24 +++- .../src/_build_script_utils.rs | 12 ++ .../src/_generated_esp32.rs | 11 +- .../src/_generated_esp32c2.rs | 7 +- .../src/_generated_esp32c3.rs | 7 +- .../src/_generated_esp32c5.rs | 6 +- .../src/_generated_esp32c6.rs | 6 +- .../src/_generated_esp32c61.rs | 6 +- .../src/_generated_esp32h2.rs | 6 +- .../src/_generated_esp32p4.rs | 8 ++ .../src/_generated_esp32s2.rs | 20 +++- .../src/_generated_esp32s3.rs | 10 +- esp-metadata/src/cfg/dma.rs | 72 +++++++++++- 20 files changed, 453 insertions(+), 33 deletions(-) diff --git a/esp-hal/src/dma/engine/axi_gdma.rs b/esp-hal/src/dma/engine/axi_gdma.rs index 6ada351bb50..89f3141f63b 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -118,6 +118,21 @@ pub struct AxiGdmaConfig { /// /// The default value is `Priority0`. priority: AxiGdmaPriority, + + /// Maximum burst length (applies to internal and external RAM alike). + burst: AxiGdmaBurst, +} + +impl crate::dma::DmaBurstConfig for AxiGdmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + (self.burst.bytes(), self.burst.bytes()) + } +} + +for_each_dma_engine! { + ("AXI_GDMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { + impl_burst_type!($bt, [$(($v, $b)),*]); + }; } impl AxiGdmaTxChannel<'_> { diff --git a/esp-hal/src/dma/engine/copy.rs b/esp-hal/src/dma/engine/copy.rs index 093c59b039a..4871d47e4dd 100644 --- a/esp-hal/src/dma/engine/copy.rs +++ b/esp-hal/src/dma/engine/copy.rs @@ -89,6 +89,13 @@ impl DmaTxChannel for CopyDmaTxChannel<'_> {} #[non_exhaustive] pub struct CopyDmaConfig {} +impl crate::dma::DmaBurstConfig for CopyDmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + // COPY_DMA has no burst knob; report "disabled" for both regions. + (0, 0) + } +} + impl RegisterAccess for CopyDmaTxChannel<'_> { type Config = CopyDmaConfig; diff --git a/esp-hal/src/dma/engine/crypto.rs b/esp-hal/src/dma/engine/crypto.rs index a1ed9d97cac..8adb2a12feb 100644 --- a/esp-hal/src/dma/engine/crypto.rs +++ b/esp-hal/src/dma/engine/crypto.rs @@ -18,6 +18,7 @@ use crate::{ RxRegisterAccess, TxRegisterAccess, asynch, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::{DMA_CRYPTO, Interrupt}, @@ -83,9 +84,45 @@ impl crate::private::Sealed for CryptoDmaTxChannel<'_> {} impl DmaTxChannel for CryptoDmaTxChannel<'_> {} /// Configuration for a CRYPTO DMA channel half. -#[derive(Debug, Default, Clone)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] -pub struct CryptoDmaConfig {} +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, +} + +impl crate::dma::DmaBurstConfig for CryptoDmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + cfg_if::cfg_if! { + if #[cfg(crypto_dma_separate_burst)] { + (self.internal_burst.bytes(), self.external_burst.bytes()) + } else { + (self.burst.bytes(), self.burst.bytes()) + } + } + } +} + +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; diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index 777b8a07904..f0d9701ba3b 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -115,6 +115,31 @@ pub struct AhbGdmaConfig { /// /// 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, +} + +impl crate::dma::DmaBurstConfig for AhbGdmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + cfg_if::cfg_if! { + if #[cfg(ahb_gdma_separate_burst)] { + (self.internal_burst.bytes(), self.external_burst.bytes()) + } else { + (self.burst.bytes(), self.burst.bytes()) + } + } + } } /// An arbitrary GDMA RX channel @@ -254,6 +279,16 @@ for_each_dma_engine! { }; } +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 90df20b0414..b5cad1629de 100644 --- a/esp-hal/src/dma/engine/i2s.rs +++ b/esp-hal/src/dma/engine/i2s.rs @@ -15,6 +15,7 @@ use crate::{ RegisterAccess, RxRegisterAccess, TxRegisterAccess, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::Interrupt, @@ -78,9 +79,45 @@ impl crate::private::Sealed for I2sDmaTxChannel<'_> {} impl DmaTxChannel for I2sDmaTxChannel<'_> {} /// Configuration for an I2S DMA channel half. -#[derive(Debug, Default, Clone)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] -pub struct I2sDmaConfig {} +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, +} + +impl crate::dma::DmaBurstConfig for I2sDmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + cfg_if::cfg_if! { + if #[cfg(i2s_dma_separate_burst)] { + (self.internal_burst.bytes(), self.external_burst.bytes()) + } else { + (self.burst.bytes(), self.burst.bytes()) + } + } + } +} + +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; diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index 089912bde97..18b82d57acb 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -8,6 +8,8 @@ use crate::{ private::{Internal, Sealed}, system::PeripheralGuard, }; +#[cfg(dma_can_access_psram)] +use crate::dma::{ExternalBurstConfig, InternalBurstConfig}; for_each_dma_engine! { ("AHB_GDMA") => { @@ -20,19 +22,19 @@ for_each_dma_engine! { }; ("COPY_DMA") => { mod copy; - pub use copy::{CopyDmaChannel, CopyDmaConfig, CopyDmaRxChannel, CopyDmaTxChannel}; + pub use copy::*; }; ("CRYPTO_DMA") => { mod crypto; - pub use crypto::{CryptoDmaChannel, CryptoDmaConfig, CryptoDmaRxChannel, CryptoDmaTxChannel}; + pub use crypto::*; }; ("I2S_DMA") => { mod i2s; - pub use i2s::{I2sDmaChannel, I2sDmaConfig, I2sDmaRxChannel, I2sDmaTxChannel}; + pub use i2s::*; }; ("SPI_DMA") => { mod spi; - pub use spi::{SpiDmaChannel, SpiDmaConfig, SpiDmaRxChannel, SpiDmaTxChannel}; + pub use spi::*; }; } @@ -66,12 +68,24 @@ for_each_peripheral! { }; } +/// Exposes a channel `Config`'s burst ceilings to the engine-agnostic +/// negotiation in [`RegisterAccess::prepare_burst`]. +/// +/// Implemented by every engine `Config`. Reports the largest burst length (in +/// bytes) the channel may use for internal- and external-memory transfers +/// respectively. A `0` ceiling means "burst disabled". +#[doc(hidden)] +pub trait DmaBurstConfig { + /// Returns `(internal_ceiling, external_ceiling)` in bytes. + fn burst_ceilings(&self) -> (usize, usize); +} + #[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; + type Config: Default + Clone + core::fmt::Debug + DmaBurstConfig; /// Apply the configuration to this channel half. /// @@ -79,6 +93,51 @@ pub trait RegisterAccess: Sealed { /// There is no `config()` getter and no partial/read-modify-write update path. fn apply_config(&self, config: &Self::Config); + /// Negotiates and programs the per-transfer burst for this channel half. + /// + /// The channel's stored `Config` provides per-region burst ceilings; the + /// buffer's `max_alignment` is the largest alignment it can guarantee. The + /// effective burst is the smaller of the two, clamped to each region's + /// hardware floor. This is engine-agnostic and feeds the existing + /// `set_burst_mode` / `set_ext_mem_block_size` register writers. + fn prepare_burst(&self, config: &Self::Config, max_alignment: usize) { + let (internal_ceiling, external_ceiling) = config.burst_ceilings(); + #[cfg(not(dma_can_access_psram))] + let _ = external_ceiling; + + let internal_enabled = internal_ceiling.min(max_alignment) >= 4; + + cfg_if::cfg_if! { + if #[cfg(dma_can_access_psram)] { + let effective_external = external_ceiling.min(max_alignment); + let external_memory = if effective_external >= 64 { + ExternalBurstConfig::Size64 + } else if effective_external >= 32 { + ExternalBurstConfig::Size32 + } else { + ExternalBurstConfig::Size16 + }; + let internal_memory = if internal_enabled { + InternalBurstConfig::Enabled + } else { + InternalBurstConfig::Disabled + }; + let burst = BurstConfig { external_memory, internal_memory }; + + #[cfg(dma_ext_mem_configurable_block_size)] + self.set_ext_mem_block_size(external_memory.into()); + } else { + let burst = if internal_enabled { + BurstConfig::Enabled + } else { + BurstConfig::Disabled + }; + } + } + + self.set_burst_mode(burst); + } + #[allow(private_interfaces)] fn enable(&self) -> Option; @@ -287,3 +346,45 @@ macro_rules! impl_priority_type { } #[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, )* + } + } + } + + impl Default for $ty { + fn default() -> Self { + // Metadata lists sizes ascending, so the first variant is the + // neutral default: burst disabled where the engine can disable + // it, otherwise the minimum (floor) burst. + 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 75455573bd2..6f6f9ee97e7 100644 --- a/esp-hal/src/dma/engine/spi.rs +++ b/esp-hal/src/dma/engine/spi.rs @@ -15,6 +15,7 @@ use crate::{ RegisterAccess, RxRegisterAccess, TxRegisterAccess, + impl_burst_type, }, interrupt::InterruptHandler, peripherals::Interrupt, @@ -78,9 +79,45 @@ impl crate::private::Sealed for SpiDmaTxChannel<'_> {} impl DmaTxChannel for SpiDmaTxChannel<'_> {} /// Configuration for a SPI DMA channel half. -#[derive(Debug, Default, Clone)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] -pub struct SpiDmaConfig {} +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, +} + +impl crate::dma::DmaBurstConfig for SpiDmaConfig { + fn burst_ceilings(&self) -> (usize, usize) { + cfg_if::cfg_if! { + if #[cfg(spi_dma_separate_burst)] { + (self.internal_burst.bytes(), self.external_burst.bytes()) + } else { + (self.burst.bytes(), self.burst.bytes()) + } + } + } +} + +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; diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 7b1fa053b46..a0011848843 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -1013,6 +1013,10 @@ where CH: DmaRxChannel, { pub(crate) rx_impl: CH, + /// Last-applied channel configuration. The burst ceiling stored here is + /// re-applied on every transfer (registers do not survive `reset()`), and + /// the value is carried across blocking/async conversions. + pub(crate) config: CH::Config, pub(crate) _phantom: PhantomData, pub(crate) _guard: Option, } @@ -1039,6 +1043,7 @@ where Self { rx_impl, + config: CH::Config::default(), _phantom: PhantomData, _guard, } @@ -1052,6 +1057,7 @@ where self.rx_impl.set_async(true); ChannelRx { rx_impl: self.rx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1082,6 +1088,7 @@ where self.rx_impl.set_async(false); ChannelRx { rx_impl: self.rx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1095,6 +1102,7 @@ where { /// 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); } @@ -1113,10 +1121,8 @@ 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); + .prepare_burst(&self.config, preparation.max_alignment); self.rx_impl.set_descr_burst_mode(true); self.rx_impl.set_check_owner(preparation.check_owner); @@ -1238,6 +1244,10 @@ where CH: DmaTxChannel, { pub(crate) tx_impl: CH, + /// Last-applied channel configuration. The burst ceiling stored here is + /// re-applied on every transfer (registers do not survive `reset()`), and + /// the value is carried across blocking/async conversions. + pub(crate) config: CH::Config, pub(crate) _phantom: PhantomData, pub(crate) _guard: Option, } @@ -1258,6 +1268,7 @@ where tx_impl.set_async(false); Self { tx_impl, + config: CH::Config::default(), _phantom: PhantomData, _guard, } @@ -1271,6 +1282,7 @@ where self.tx_impl.set_async(true); ChannelTx { tx_impl: self.tx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1301,6 +1313,7 @@ where self.tx_impl.set_async(false); ChannelTx { tx_impl: self.tx_impl, + config: self.config, _phantom: PhantomData, _guard: self._guard, } @@ -1314,6 +1327,7 @@ where { /// 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); } @@ -1338,10 +1352,8 @@ 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); + .prepare_burst(&self.config, preparation.max_alignment); self.tx_impl.set_descr_burst_mode(true); self.tx_impl.set_check_owner(preparation.check_owner); self.tx_impl diff --git a/esp-metadata-generated/src/_build_script_utils.rs b/esp-metadata-generated/src/_build_script_utils.rs index 5888b71e1ba..337f2cb4dba 100644 --- a/esp-metadata-generated/src/_build_script_utils.rs +++ b/esp-metadata-generated/src/_build_script_utils.rs @@ -5220,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", @@ -5456,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", @@ -5908,6 +5914,7 @@ impl Chip { "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", @@ -6184,6 +6191,7 @@ impl Chip { "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", @@ -7001,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)"); @@ -7011,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 e8a92c8908f..609fbae4d12 100644 --- a/esp-metadata-generated/src/_generated_esp32.rs +++ b/esp-metadata-generated/src/_generated_esp32.rs @@ -442,9 +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!((priorities)); + _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 6dc0caa913e..a1881942543 100644 --- a/esp-metadata-generated/src/_generated_esp32c2.rs +++ b/esp-metadata-generated/src/_generated_esp32c2.rs @@ -385,11 +385,14 @@ macro_rules! for_each_dma_engine { _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!((all("AHB_GDMA"))); + 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)]))); + (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 b6f3b641c9e..68e5765770b 100644 --- a/esp-metadata-generated/src/_generated_esp32c3.rs +++ b/esp-metadata-generated/src/_generated_esp32c3.rs @@ -478,11 +478,14 @@ macro_rules! for_each_dma_engine { _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!((all("AHB_GDMA"))); + 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)]))); + (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 750c2903a53..ead2b9b4802 100644 --- a/esp-metadata-generated/src/_generated_esp32c5.rs +++ b/esp-metadata-generated/src/_generated_esp32c5.rs @@ -522,10 +522,14 @@ macro_rules! for_each_dma_engine { => {} } _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!((all("AHB_GDMA"))); + (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 938efdd0460..f5c3e005df3 100644 --- a/esp-metadata-generated/src/_generated_esp32c6.rs +++ b/esp-metadata-generated/src/_generated_esp32c6.rs @@ -513,10 +513,14 @@ macro_rules! for_each_dma_engine { => {} } _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!((all("AHB_GDMA"))); + (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 eb89a892252..507032932b9 100644 --- a/esp-metadata-generated/src/_generated_esp32c61.rs +++ b/esp-metadata-generated/src/_generated_esp32c61.rs @@ -429,10 +429,14 @@ macro_rules! for_each_dma_engine { => {} } _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!((all("AHB_GDMA"))); + (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 97e7185905c..da9a5076c75 100644 --- a/esp-metadata-generated/src/_generated_esp32h2.rs +++ b/esp-metadata-generated/src/_generated_esp32h2.rs @@ -495,10 +495,14 @@ macro_rules! for_each_dma_engine { => {} } _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!((all("AHB_GDMA"))); + (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 e66fc7311c1..85ad35a9ee9 100644 --- a/esp-metadata-generated/src/_generated_esp32p4.rs +++ b/esp-metadata-generated/src/_generated_esp32p4.rs @@ -422,12 +422,20 @@ macro_rules! for_each_dma_engine { (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 e97c2fa95ea..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"), + _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 cba63d5f68b..34b0dabb188 100644 --- a/esp-metadata-generated/src/_generated_esp32s3.rs +++ b/esp-metadata-generated/src/_generated_esp32s3.rs @@ -501,11 +501,17 @@ macro_rules! for_each_dma_engine { _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!((all("AHB_GDMA"))); + 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)]))); + (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 269756af5b2..cae6c13a03b 100644 --- a/esp-metadata/src/cfg/dma.rs +++ b/esp-metadata/src/cfg/dma.rs @@ -67,8 +67,8 @@ pub struct DmaEngineDef { 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`. @@ -173,6 +173,14 @@ impl GenericProperty for DmaEngines { 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. @@ -209,6 +217,7 @@ impl GenericProperty for DmaEngines { 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. @@ -243,6 +252,61 @@ impl GenericProperty for DmaEngines { }); } + // 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"); @@ -361,7 +425,11 @@ impl GenericProperty for DmaEngines { let dma_engines_macro = generate_for_each_macro( "dma_engine", - &[("all", &engines), ("priorities", &engines_with_priorities)], + &[ + ("all", &engines), + ("priorities", &engines_with_priorities), + ("bursts", &engines_with_bursts), + ], ); // Always emit for_each_dma_channel_peri_pair! so drivers can call it From f19b4ebac6bb76383f8f3328863a19ce38c932a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 19:36:26 +0200 Subject: [PATCH 07/13] . --- esp-hal/src/dma/buffers/mod.rs | 368 +++++++++--------- esp-hal/src/dma/buffers/scoped.rs | 130 +++---- esp-hal/src/dma/m2m.rs | 61 ++- esp-hal/src/dma/mod.rs | 2 +- .../peripheral/dma/extmem2mem/src/main.rs | 15 +- examples/peripheral/dma/mem2mem/src/main.rs | 4 +- .../spi/loopback_dma_psram/src/main.rs | 11 +- .../src/bin/misc_non_drivers/dma_mem2mem.rs | 6 +- .../src/bin/spi_half_duplex_write_psram.rs | 18 +- 9 files changed, 305 insertions(+), 310 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index 6c7472a6de2..9de116e6005 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -240,114 +240,121 @@ const fn max(a: usize, b: usize) -> usize { if a > b { a } else { b } } +#[cfg(dma_can_access_psram)] 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; - } + pub(super) fn is_burst_enabled(self) -> bool { + self.internal_memory.is_burst_enabled() } +} - /// 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); +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)] - let alignment = max(alignment, self.external_memory as usize); +/// The strictest *mandatory* alignment for a buffer in this memory region and +/// transfer direction, *excluding* burst. +/// +/// Internal RAM is byte-addressable (word-aligned only on the ESP32), so it +/// imposes no alignment by itself — burst is what needs alignment, and burst is +/// optional and negotiated against the buffer at transfer setup. PSRAM receive +/// transfers, however, must be block-aligned by the hardware, so that floor is +/// always applied. This is the "usable anywhere" alignment of a default buffer. +fn region_alignment(buffer: &[u8], direction: TransferDirection) -> usize { + let alignment = InternalBurstConfig::DEFAULT.min_dram_alignment(direction); - alignment + 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, ExternalBurstConfig::DEFAULT.min_psram_alignment(direction)); + } + alignment + } else { + let _ = buffer; + alignment + } } +} - 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 - } +/// 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.max(1), region_alignment(buffer, 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()) - } +/// 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 = InternalBurstConfig::DEFAULT.min_dram_alignment(TransferDirection::In); + let out_alignment = InternalBurstConfig::DEFAULT.min_dram_alignment(TransferDirection::Out); + let alignment = max(in_alignment, out_alignment); - fn min_alignment(self, _buffer: &[u8], direction: TransferDirection) -> usize { - let alignment = self.min_dram_alignment(direction); + #[cfg(dma_can_access_psram)] + let alignment = max( + alignment, + ExternalBurstConfig::DEFAULT.min_psram_alignment(TransferDirection::In), + ); - 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)); - } - } - } + 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()) +} - // 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)) +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); } - 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); - } + // 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); + } - // 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); - } + Ok(()) +} - Ok(()) +fn ensure_buffer_compatible( + buffer: &[u8], + direction: TransferDirection, + alignment: usize, +) -> Result<(), DmaBufError> { + if buffer.is_empty() { + return Ok(()); } - - 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; - } + // 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; } + } - if !(is_in_dram || is_in_psram) { - return Err(DmaBufError::UnsupportedMemoryRegion); - } + if !(is_in_dram || is_in_psram) { + return Err(DmaBufError::UnsupportedMemoryRegion); + } - self.ensure_buffer_aligned(buffer, direction)?; + ensure_buffer_aligned(buffer, direction, alignment)?; - Ok(()) - } + Ok(()) } /// The direction of the DMA transfer. @@ -374,16 +381,6 @@ pub struct Preparation { #[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, - /// Maximum alignment (the "burst axis") guaranteed by this buffer's start /// address *and* every descriptor `size`/length field, in bytes. /// @@ -526,28 +523,31 @@ 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 (and, for receive buffers, its length) must + /// be a multiple of `alignment`, otherwise this fails with + /// [DmaBufError::InvalidAlignment]. Larger alignments chunk the buffer into + /// smaller descriptors and therefore need more descriptors + /// ([DmaBufError::InsufficientDescriptors] otherwise). + /// + /// The alignment is the *burst axis* only: it is the largest alignment the + /// buffer guarantees, against which a channel negotiates its effective burst + /// at transfer setup. It does not encode any engine's burst rules. The + /// mandatory per-region floor (e.g. PSRAM block size) is always applied, so + /// the effective alignment may end up larger than requested. /// /// 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. @@ -638,28 +638,30 @@ 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 chunk the buffer into smaller descriptors and therefore need + /// more descriptors ([DmaBufError::InsufficientDescriptors] otherwise). + /// + /// The alignment is the *burst axis* only: it is the largest alignment the + /// buffer guarantees, against which a channel negotiates its effective burst + /// at transfer setup. It does not encode any engine's burst rules. The + /// mandatory per-region floor (e.g. PSRAM block size) is always applied, so + /// the effective alignment may end up larger than requested. /// /// 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. @@ -753,7 +755,7 @@ pub struct DmaRxTxBuf { rx_descriptors: DescriptorSet<'static>, tx_descriptors: DescriptorSet<'static>, buffer: &'static mut [u8], - burst: BurstConfig, + alignment: usize, } impl DmaRxTxBuf { @@ -768,48 +770,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, 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( @@ -850,21 +861,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)?; + 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, - 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), - )?; + 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(()) } @@ -874,7 +881,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)); } } @@ -909,8 +916,7 @@ unsafe impl DmaTxBuffer for DmaRxTxBuf { direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::Out), + max_alignment: self.alignment, check_owner: None, auto_write_back: false, } @@ -954,8 +960,7 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf { direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::In), + max_alignment: self.alignment, check_owner: None, auto_write_back: true, } @@ -1015,7 +1020,6 @@ unsafe impl DmaRxBuffer for DmaRxTxBuf { pub struct DmaRxStreamBuf { descriptors: &'static mut [DmaDescriptor], buffer: &'static mut [u8], - burst: BurstConfig, } impl DmaRxStreamBuf { @@ -1061,11 +1065,7 @@ impl DmaRxStreamBuf { last_descriptor.set_size(size); } - Ok(Self { - descriptors, - buffer, - burst: BurstConfig::default(), - }) + Ok(Self { descriptors, buffer }) } /// Consume the buf, returning the descriptors and buffer. @@ -1092,8 +1092,7 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf { direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::In), + 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. @@ -1318,7 +1317,6 @@ 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, @@ -1368,7 +1366,6 @@ impl DmaTxStreamBuf { Ok(Self { descriptors, buffer, - burst: Default::default(), pre_filled: None, view_descriptor_idx: 0, view_descriptor_offset: 0, @@ -1515,8 +1512,7 @@ unsafe impl DmaTxBuffer for DmaTxStreamBuf { direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: false, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::Out), + 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. @@ -1626,7 +1622,6 @@ unsafe impl DmaTxBuffer for EmptyBuf { 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 @@ -1665,7 +1660,6 @@ unsafe impl DmaRxBuffer for EmptyBuf { 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 @@ -1712,7 +1706,8 @@ 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); } @@ -1743,8 +1738,7 @@ unsafe impl DmaTxBuffer for DmaLoopBuf { #[cfg(dma_can_access_psram)] accesses_psram: false, direction: TransferDirection::Out, - burst_transfer: BurstConfig::default(), - max_alignment: BurstConfig::default().min_alignment(self.buffer, TransferDirection::Out), + 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), @@ -1789,7 +1783,6 @@ impl NoBuffer { 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, @@ -1837,8 +1830,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. @@ -1891,7 +1883,6 @@ pub(crate) unsafe fn prepare_for_tx( NoBuffer(Preparation { start: descriptors.head(), direction: TransferDirection::Out, - burst_transfer: BurstConfig::DEFAULT, max_alignment: alignment, check_owner: None, auto_write_back: false, @@ -1916,8 +1907,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 @@ -2003,7 +1996,6 @@ 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, diff --git a/esp-hal/src/dma/buffers/scoped.rs b/esp-hal/src/dma/buffers/scoped.rs index bb855d14234..160de6c1158 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 @@ -172,8 +170,7 @@ unsafe impl<'a> DmaTxBuffer for ScopedDmaTxBuf<'a> { direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::Out), + max_alignment: self.alignment, check_owner: None, auto_write_back: false, } @@ -198,57 +195,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) @@ -269,16 +266,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 @@ -287,7 +282,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. @@ -386,8 +381,7 @@ unsafe impl<'a> DmaRxBuffer for ScopedDmaRxBuf<'a> { direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: is_data_in_psram, - burst_transfer: self.burst, - max_alignment: self.burst.min_alignment(self.buffer, TransferDirection::In), + max_alignment: self.alignment, check_owner: None, auto_write_back: true, } diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs index bcbac5f8298..520f6bd53e4 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,14 @@ where panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar"); }; + // Remember the descriptor slices' raw parts so we can hand them back into the + // idle state if buffer creation fails (the fallible constructors consume them + // without returning 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 +659,27 @@ 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" - ); + // Use the loosest (region-default) alignment. If the caller's buffers don't + // even satisfy that, surface the validation error rather than panicking. + 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 +694,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 a0011848843..211cb423d60 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -561,7 +561,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 ); }; diff --git a/examples/peripheral/dma/extmem2mem/src/main.rs b/examples/peripheral/dma/extmem2mem/src/main.rs index 60f6890018f..5278cd7859e 100644 --- a/examples/peripheral/dma/extmem2mem/src/main.rs +++ b/examples/peripheral/dma/extmem2mem/src/main.rs @@ -12,7 +12,7 @@ use esp_alloc as _; use esp_backtrace as _; use esp_hal::{ delay::Delay, - dma::{BurstConfig, ExternalBurstConfig, Mem2Mem}, + dma::Mem2Mem, dma_descriptors_chunk_size, main, time::Duration, @@ -66,18 +66,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..6b08abc89e5 100644 --- a/examples/peripheral/dma/mem2mem/src/main.rs +++ b/examples/peripheral/dma/mem2mem/src/main.rs @@ -8,7 +8,7 @@ use esp_backtrace as _; use esp_hal::{ delay::Delay, - dma::{BurstConfig, Mem2Mem}, + dma::Mem2Mem, dma_buffers, main, time::Duration, @@ -37,7 +37,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_mem2mem.rs b/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs index 92773434b58..3698d3f3c3c 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) { @@ -50,7 +50,7 @@ mod tests { let (rx_descriptors, tx_descriptors) = dma_descriptors!(1024, 0); match ctx .mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) + .with_descriptors(rx_descriptors, tx_descriptors) { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), @@ -62,7 +62,7 @@ mod tests { let (rx_descriptors, tx_descriptors) = dma_descriptors!(0, 1024); match ctx .mem2mem - .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) + .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(); From 3d973b7be1d58cbfd4591dfad53280166709ad12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 20:49:29 +0200 Subject: [PATCH 08/13] . --- esp-hal/src/dma/buffers/mod.rs | 77 +++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index 9de116e6005..be2547151c7 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -254,6 +254,41 @@ const fn chunk_size_for_alignment(alignment: usize) -> usize { 4096 - alignment } +/// Data cache line size of cached *internal* memory, in bytes, or `0` if +/// internal memory is not accessed through a cache. +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, +}; + +/// Data cache line size of cached *external* (PSRAM) memory, in bytes. +/// +/// On the ESP32-S2/S3 this follows the `data-cache-line-size` esp-config option. +#[cfg(dma_can_access_psram)] +const EXTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { + data_cache_line_size_64b => 64, + data_cache_line_size_32b => 32, + data_cache_line_size_16b => 16, + // 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. + _ => 128, +}; + +/// The cache line size (in bytes) of the memory `buffer` lives in, or `0` if it +/// is not accessed through a cache. DMA receive 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) { + return EXTERNAL_MEMORY_CACHE_LINE_SIZE; + } + + let _ = buffer; + INTERNAL_MEMORY_CACHE_LINE_SIZE +} + /// The strictest *mandatory* alignment for a buffer in this memory region and /// transfer direction, *excluding* burst. /// @@ -265,24 +300,21 @@ const fn chunk_size_for_alignment(alignment: usize) -> usize { fn region_alignment(buffer: &[u8], direction: TransferDirection) -> usize { let alignment = InternalBurstConfig::DEFAULT.min_dram_alignment(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, ExternalBurstConfig::DEFAULT.min_psram_alignment(direction)); - } - alignment - } else { - let _ = buffer; - alignment - } + #[cfg(dma_can_access_psram)] + if is_valid_psram_address(buffer.as_ptr() as usize) { + return max( + alignment, + ExternalBurstConfig::DEFAULT.min_psram_alignment(direction), + ); } + + alignment } /// 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.max(1), region_alignment(buffer, direction)) + max(requested, region_alignment(buffer, direction)) } /// An alignment that is compatible with any DMA channel in any direction. @@ -354,6 +386,17 @@ fn ensure_buffer_compatible( ensure_buffer_aligned(buffer, direction, alignment)?; + // A receive buffer in cached memory is invalidated after the transfer. The + // hardware/ROM rounds the invalidated range out to whole cache lines, so a + // buffer whose start is not cache-line aligned would also discard cached data + // belonging to a neighbouring allocation. Reject such buffers 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)); + } + } + Ok(()) } @@ -798,7 +841,7 @@ impl DmaRxTxBuf { }; let capacity = buf.capacity(); - buf.configure(alignment, capacity)?; + buf.configure(alignment.max(1), capacity)?; Ok(buf) } @@ -1065,7 +1108,10 @@ impl DmaRxStreamBuf { last_descriptor.set_size(size); } - Ok(Self { descriptors, buffer }) + Ok(Self { + descriptors, + buffer, + }) } /// Consume the buf, returning the descriptors and buffer. @@ -1706,8 +1752,7 @@ impl DmaLoopBuf { return Err(DmaBufError::UnsupportedMemoryRegion); } - if buffer.len() - > chunk_size_for_alignment(region_alignment(buffer, TransferDirection::Out)) + if buffer.len() > chunk_size_for_alignment(region_alignment(buffer, TransferDirection::Out)) { return Err(DmaBufError::InsufficientDescriptors); } From b7da9a8cf151a8af1ca40269e5b5a3f6edfd9f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 21:43:41 +0200 Subject: [PATCH 09/13] . --- esp-hal/src/dma/buffers/mod.rs | 94 ++++++++++--------------------- esp-hal/src/dma/buffers/scoped.rs | 2 - esp-hal/src/dma/engine/mod.rs | 24 +++----- esp-hal/src/dma/m2m.rs | 15 ++--- esp-hal/src/dma/mod.rs | 16 ++---- esp-hal/src/soc/esp32s2/mod.rs | 50 ++++++++-------- esp-hal/src/soc/esp32s3/mod.rs | 74 +++++++++++------------- 7 files changed, 109 insertions(+), 166 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index be2547151c7..dc8aec54dfc 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -254,8 +254,8 @@ const fn chunk_size_for_alignment(alignment: usize) -> usize { 4096 - alignment } -/// Data cache line size of cached *internal* memory, in bytes, or `0` if -/// internal memory is not accessed through a cache. +/// Data cache line size of cached *internal* memory, in bytes, or `1` (i.e. no +/// alignment requirement) if internal memory is not accessed through a cache. const INTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { // ESP32-P4: L1 data cache line size. // FIXME: add uncached address range? @@ -268,17 +268,16 @@ const INTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { /// On the ESP32-S2/S3 this follows the `data-cache-line-size` esp-config option. #[cfg(dma_can_access_psram)] const EXTERNAL_MEMORY_CACHE_LINE_SIZE: usize = cfg_select! { - data_cache_line_size_64b => 64, - data_cache_line_size_32b => 32, - data_cache_line_size_16b => 16, // 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. - _ => 128, + soc_internal_memory_cached => 128, + any(esp32c5, esp32c61) => 32, // TODO: metadata-ify + _ => crate::soc::CONFIG_DATA_CACHE_LINE_SIZE, }; -/// The cache line size (in bytes) of the memory `buffer` lives in, or `0` if it -/// is not accessed through a cache. DMA receive buffers must be aligned to this. +/// 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) { @@ -291,12 +290,6 @@ fn cache_line_size_for(buffer: &[u8]) -> usize { /// The strictest *mandatory* alignment for a buffer in this memory region and /// transfer direction, *excluding* burst. -/// -/// Internal RAM is byte-addressable (word-aligned only on the ESP32), so it -/// imposes no alignment by itself — burst is what needs alignment, and burst is -/// optional and negotiated against the buffer at transfer setup. PSRAM receive -/// transfers, however, must be block-aligned by the hardware, so that floor is -/// always applied. This is the "usable anywhere" alignment of a default buffer. fn region_alignment(buffer: &[u8], direction: TransferDirection) -> usize { let alignment = InternalBurstConfig::DEFAULT.min_dram_alignment(direction); @@ -308,6 +301,7 @@ fn region_alignment(buffer: &[u8], direction: TransferDirection) -> usize { ); } + let _ = buffer; alignment } @@ -372,13 +366,10 @@ fn ensure_buffer_compatible( } // 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; - } - } + let is_in_psram = cfg_select! { + dma_can_access_psram => is_slice_in_psram(buffer), + _ => false, + }; if !(is_in_dram || is_in_psram) { return Err(DmaBufError::UnsupportedMemoryRegion); @@ -386,10 +377,9 @@ fn ensure_buffer_compatible( ensure_buffer_aligned(buffer, direction, alignment)?; - // A receive buffer in cached memory is invalidated after the transfer. The - // hardware/ROM rounds the invalidated range out to whole cache lines, so a - // buffer whose start is not cache-line aligned would also discard cached data - // belonging to a neighbouring allocation. Reject such buffers up front. + // 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) { @@ -417,21 +407,14 @@ 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, - /// Maximum alignment (the "burst axis") guaranteed by this buffer's start - /// address *and* every descriptor `size`/length field, in bytes. - /// - /// This is the alignment the buffer chunked itself for; the channel uses it - /// to negotiate the effective burst at transfer setup. It does *not* include - /// the cache-line requirement (which only constrains the buffer's base and - /// length and is handled by the buffer itself), and so is never weakened by - /// the burst negotiation. + /// 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. @@ -571,17 +554,14 @@ impl DmaTxBuf { /// Creates a new [DmaTxBuf] with a caller-chosen minimum alignment. /// - /// The buffer's start address (and, for receive buffers, its length) must - /// be a multiple of `alignment`, otherwise this fails with - /// [DmaBufError::InvalidAlignment]. Larger alignments chunk the buffer into - /// smaller descriptors and therefore need more descriptors - /// ([DmaBufError::InsufficientDescriptors] otherwise). + /// 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). /// - /// The alignment is the *burst axis* only: it is the largest alignment the - /// buffer guarantees, against which a channel negotiates its effective burst - /// at transfer setup. It does not encode any engine's burst rules. The - /// mandatory per-region floor (e.g. PSRAM block size) is always applied, so - /// the effective alignment may end up larger than requested. + /// `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. @@ -688,14 +668,12 @@ impl DmaRxBuf { /// /// The buffer's start address and length must be a multiple of `alignment`, /// otherwise this fails with [DmaBufError::InvalidAlignment]. Larger - /// alignments chunk the buffer into smaller descriptors and therefore need - /// more descriptors ([DmaBufError::InsufficientDescriptors] otherwise). + /// alignments split the buffer into more descriptors + /// ([DmaBufError::InsufficientDescriptors] otherwise). /// - /// The alignment is the *burst axis* only: it is the largest alignment the - /// buffer guarantees, against which a channel negotiates its effective burst - /// at transfer setup. It does not encode any engine's burst rules. The - /// mandatory per-region floor (e.g. PSRAM block size) is always applied, so - /// the effective alignment may end up larger than requested. + /// `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. @@ -956,7 +934,6 @@ 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, max_alignment: self.alignment, @@ -1000,7 +977,6 @@ 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, max_alignment: self.alignment, @@ -1135,7 +1111,6 @@ unsafe impl DmaRxBuffer for DmaRxStreamBuf { } Preparation { start: self.descriptors.as_mut_ptr(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: false, max_alignment: region_alignment(self.buffer, TransferDirection::In), @@ -1555,7 +1530,6 @@ unsafe impl DmaTxBuffer for DmaTxStreamBuf { Preparation { start: self.descriptors.as_mut_ptr(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: false, max_alignment: region_alignment(self.buffer, TransferDirection::Out), @@ -1665,7 +1639,6 @@ unsafe impl DmaTxBuffer for EmptyBuf { Preparation { start: (&raw mut EMPTY).cast(), - direction: TransferDirection::Out, #[cfg(dma_can_access_psram)] accesses_psram: false, max_alignment: 1, @@ -1703,7 +1676,6 @@ unsafe impl DmaRxBuffer for EmptyBuf { Preparation { start: (&raw mut EMPTY).cast(), - direction: TransferDirection::In, #[cfg(dma_can_access_psram)] accesses_psram: false, max_alignment: 1, @@ -1782,7 +1754,6 @@ unsafe impl DmaTxBuffer for DmaLoopBuf { start: self.descriptor, #[cfg(dma_can_access_psram)] accesses_psram: false, - direction: TransferDirection::Out, 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), @@ -1825,7 +1796,6 @@ 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, max_alignment: self.0.max_alignment, @@ -1927,7 +1897,6 @@ pub(crate) unsafe fn prepare_for_tx( Ok(( NoBuffer(Preparation { start: descriptors.head(), - direction: TransferDirection::Out, max_alignment: alignment, check_owner: None, auto_write_back: false, @@ -2040,7 +2009,6 @@ pub(crate) unsafe fn prepare_for_rx( ( NoBuffer(Preparation { start: descriptors.head(), - direction: TransferDirection::In, max_alignment: 4096 - chunk_size, check_owner: None, auto_write_back: true, diff --git a/esp-hal/src/dma/buffers/scoped.rs b/esp-hal/src/dma/buffers/scoped.rs index 160de6c1158..de0f5f41244 100644 --- a/esp-hal/src/dma/buffers/scoped.rs +++ b/esp-hal/src/dma/buffers/scoped.rs @@ -167,7 +167,6 @@ 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, max_alignment: self.alignment, @@ -378,7 +377,6 @@ 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, max_alignment: self.alignment, diff --git a/esp-hal/src/dma/engine/mod.rs b/esp-hal/src/dma/engine/mod.rs index 18b82d57acb..6b4a2a1d789 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -1,5 +1,7 @@ use enumset::{EnumSet, EnumSetType}; +#[cfg(dma_can_access_psram)] +use crate::dma::{ExternalBurstConfig, InternalBurstConfig}; use crate::{ asynch::AtomicWaker, dma::{BurstConfig, DmaRxInterrupt, DmaTxInterrupt}, @@ -8,8 +10,6 @@ use crate::{ private::{Internal, Sealed}, system::PeripheralGuard, }; -#[cfg(dma_can_access_psram)] -use crate::dma::{ExternalBurstConfig, InternalBurstConfig}; for_each_dma_engine! { ("AHB_GDMA") => { @@ -68,12 +68,10 @@ for_each_peripheral! { }; } -/// Exposes a channel `Config`'s burst ceilings to the engine-agnostic -/// negotiation in [`RegisterAccess::prepare_burst`]. +/// Exposes a channel `Config`'s burst ceilings to [`RegisterAccess::prepare_burst`]. /// -/// Implemented by every engine `Config`. Reports the largest burst length (in -/// bytes) the channel may use for internal- and external-memory transfers -/// respectively. A `0` ceiling means "burst disabled". +/// Reports the largest burst length (bytes) for internal- and external-memory +/// transfers; `0` means burst disabled. #[doc(hidden)] pub trait DmaBurstConfig { /// Returns `(internal_ceiling, external_ceiling)` in bytes. @@ -95,11 +93,8 @@ pub trait RegisterAccess: Sealed { /// Negotiates and programs the per-transfer burst for this channel half. /// - /// The channel's stored `Config` provides per-region burst ceilings; the - /// buffer's `max_alignment` is the largest alignment it can guarantee. The - /// effective burst is the smaller of the two, clamped to each region's - /// hardware floor. This is engine-agnostic and feeds the existing - /// `set_burst_mode` / `set_ext_mem_block_size` register writers. + /// The effective burst is the smaller of the channel's configured ceiling + /// and the buffer's `max_alignment`, clamped to each region's hardware floor. fn prepare_burst(&self, config: &Self::Config, max_alignment: usize) { let (internal_ceiling, external_ceiling) = config.burst_ceilings(); #[cfg(not(dma_can_access_psram))] @@ -378,9 +373,8 @@ macro_rules! impl_burst_type { impl Default for $ty { fn default() -> Self { - // Metadata lists sizes ascending, so the first variant is the - // neutral default: burst disabled where the engine can disable - // it, otherwise the minimum (floor) burst. + // Sizes are listed ascending: the first variant is burst + // disabled, or the minimum burst where it can't be disabled. Self::$first } } diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs index 520f6bd53e4..143a80ce3bf 100644 --- a/esp-hal/src/dma/m2m.rs +++ b/esp-hal/src/dma/m2m.rs @@ -643,9 +643,8 @@ where panic!("SimpleMem2MemTransfer was forgotten with core::mem::forget or similar"); }; - // Remember the descriptor slices' raw parts so we can hand them back into the - // idle state if buffer creation fails (the fallible constructors consume them - // without returning them on error). + // 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(); @@ -659,16 +658,14 @@ 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_desc_ptr, rx_desc_len) }; - let tx_descriptors = - unsafe { core::slice::from_raw_parts_mut(tx_desc_ptr, tx_desc_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. - // Use the loosest (region-default) alignment. If the caller's buffers don't - // even satisfy that, surface the validation error rather than panicking. + // 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) => { diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 211cb423d60..115ef128cec 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -1013,9 +1013,9 @@ where CH: DmaRxChannel, { pub(crate) rx_impl: CH, - /// Last-applied channel configuration. The burst ceiling stored here is - /// re-applied on every transfer (registers do not survive `reset()`), and - /// the value is carried across blocking/async conversions. + /// 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, @@ -1111,8 +1111,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 }); @@ -1244,9 +1242,9 @@ where CH: DmaTxChannel, { pub(crate) tx_impl: CH, - /// Last-applied channel configuration. The burst ceiling stored here is - /// re-applied on every transfer (registers do not survive `reset()`), and - /// the value is carried across blocking/async conversions. + /// 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, @@ -1342,8 +1340,6 @@ where preparation: Preparation, peri: DmaPeripheral, ) -> Result<(), DmaError> { - debug_assert_eq!(preparation.direction, TransferDirection::Out); - debug!("Preparing TX transfer {:?}", preparation); trace!("First descriptor {:?}", unsafe { &*preparation.start }); 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); } From 1b08b12843e77bb6f548f71ce8e5eb05e21cc752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 23:16:26 +0200 Subject: [PATCH 10/13] . --- esp-hal/src/dma/buffers/mod.rs | 7 --- esp-hal/src/dma/engine/axi_gdma.rs | 28 ++++++--- esp-hal/src/dma/engine/copy.rs | 22 +------ esp-hal/src/dma/engine/crypto.rs | 68 ++++++++++++-------- esp-hal/src/dma/engine/gdma/ahb_v1.rs | 35 ++++++----- esp-hal/src/dma/engine/gdma/ahb_v2.rs | 33 +++++++--- esp-hal/src/dma/engine/gdma/mod.rs | 28 ++++++--- esp-hal/src/dma/engine/i2s.rs | 67 ++++++++++++-------- esp-hal/src/dma/engine/mod.rs | 89 +++++++++------------------ esp-hal/src/dma/engine/spi.rs | 67 ++++++++++++-------- esp-hal/src/dma/mod.rs | 43 +++++-------- 11 files changed, 254 insertions(+), 233 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index dc8aec54dfc..afbb1749893 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -240,13 +240,6 @@ const fn max(a: usize, b: usize) -> usize { if a > b { a } else { b } } -#[cfg(dma_can_access_psram)] -impl BurstConfig { - pub(super) fn is_burst_enabled(self) -> bool { - self.internal_memory.is_burst_enabled() - } -} - 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 diff --git a/esp-hal/src/dma/engine/axi_gdma.rs b/esp-hal/src/dma/engine/axi_gdma.rs index 89f3141f63b..6ac63a48b05 100644 --- a/esp-hal/src/dma/engine/axi_gdma.rs +++ b/esp-hal/src/dma/engine/axi_gdma.rs @@ -123,12 +123,6 @@ pub struct AxiGdmaConfig { burst: AxiGdmaBurst, } -impl crate::dma::DmaBurstConfig for AxiGdmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - (self.burst.bytes(), self.burst.bytes()) - } -} - for_each_dma_engine! { ("AXI_GDMA", single, burst = $bt:ident, bursts = [$(($v:ident, $b:literal)),*]) => { impl_burst_type!($bt, [$(($v, $b)),*]); @@ -163,8 +157,15 @@ 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 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| unsafe { w.out_burst_size_sel().bits(burst_size_sel) }); + } fn set_descr_burst_mode(&self, burst_mode: bool) { self.ch() @@ -355,8 +356,15 @@ 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 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| unsafe { w.in_burst_size_sel().bits(burst_size_sel) }); + } fn set_descr_burst_mode(&self, burst_mode: bool) { self.ch() diff --git a/esp-hal/src/dma/engine/copy.rs b/esp-hal/src/dma/engine/copy.rs index 4871d47e4dd..c3ad7f43441 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, @@ -89,13 +87,6 @@ impl DmaTxChannel for CopyDmaTxChannel<'_> {} #[non_exhaustive] pub struct CopyDmaConfig {} -impl crate::dma::DmaBurstConfig for CopyDmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - // COPY_DMA has no burst knob; report "disabled" for both regions. - (0, 0) - } -} - impl RegisterAccess for CopyDmaTxChannel<'_> { type Config = CopyDmaConfig; @@ -110,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) {} @@ -144,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 @@ -286,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) {} @@ -320,10 +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 { diff --git a/esp-hal/src/dma/engine/crypto.rs b/esp-hal/src/dma/engine/crypto.rs index 8adb2a12feb..5526326aee8 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, @@ -102,18 +100,32 @@ pub struct CryptoDmaConfig { burst: CryptoDmaBurst, } -impl crate::dma::DmaBurstConfig for CryptoDmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - cfg_if::cfg_if! { - if #[cfg(crypto_dma_separate_burst)] { - (self.internal_burst.bytes(), self.external_burst.bytes()) +/// 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 { - (self.burst.bytes(), self.burst.bytes()) - } + 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)),*]); @@ -142,10 +154,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) { @@ -196,13 +215,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 @@ -344,7 +356,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() @@ -394,13 +417,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 cd2226d199b..b6b397d773c 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v1.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v1.rs @@ -47,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) { @@ -95,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 @@ -284,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) { @@ -330,12 +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 { diff --git a/esp-hal/src/dma/engine/gdma/ahb_v2.rs b/esp-hal/src/dma/engine/gdma/ahb_v2.rs index 137731d5f8d..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 { @@ -46,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) { @@ -250,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) { diff --git a/esp-hal/src/dma/engine/gdma/mod.rs b/esp-hal/src/dma/engine/gdma/mod.rs index f0d9701ba3b..b8d3212d8b9 100644 --- a/esp-hal/src/dma/engine/gdma/mod.rs +++ b/esp-hal/src/dma/engine/gdma/mod.rs @@ -130,18 +130,32 @@ pub struct AhbGdmaConfig { burst: AhbGdmaBurst, } -impl crate::dma::DmaBurstConfig for AhbGdmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - cfg_if::cfg_if! { - if #[cfg(ahb_gdma_separate_burst)] { - (self.internal_burst.bytes(), self.external_burst.bytes()) +/// 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 { - (self.burst.bytes(), self.burst.bytes()) - } + 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))] diff --git a/esp-hal/src/dma/engine/i2s.rs b/esp-hal/src/dma/engine/i2s.rs index b5cad1629de..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, @@ -97,18 +96,32 @@ pub struct I2sDmaConfig { burst: I2sDmaBurst, } -impl crate::dma::DmaBurstConfig for I2sDmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - cfg_if::cfg_if! { - if #[cfg(i2s_dma_separate_burst)] { - (self.internal_burst.bytes(), self.external_burst.bytes()) +/// 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 { - (self.burst.bytes(), self.burst.bytes()) - } + 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)),*]); @@ -133,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) { @@ -175,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(_))) @@ -325,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() @@ -363,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 6b4a2a1d789..dc1b60f5109 100644 --- a/esp-hal/src/dma/engine/mod.rs +++ b/esp-hal/src/dma/engine/mod.rs @@ -1,10 +1,8 @@ use enumset::{EnumSet, EnumSetType}; -#[cfg(dma_can_access_psram)] -use crate::dma::{ExternalBurstConfig, InternalBurstConfig}; use crate::{ asynch::AtomicWaker, - dma::{BurstConfig, DmaRxInterrupt, DmaTxInterrupt}, + dma::{DmaRxInterrupt, DmaTxInterrupt}, interrupt::InterruptHandler, peripherals::Interrupt, private::{Internal, Sealed}, @@ -68,22 +66,12 @@ for_each_peripheral! { }; } -/// Exposes a channel `Config`'s burst ceilings to [`RegisterAccess::prepare_burst`]. -/// -/// Reports the largest burst length (bytes) for internal- and external-memory -/// transfers; `0` means burst disabled. -#[doc(hidden)] -pub trait DmaBurstConfig { - /// Returns `(internal_ceiling, external_ceiling)` in bytes. - fn burst_ceilings(&self) -> (usize, usize); -} - #[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 + DmaBurstConfig; + type Config: Default + Clone + core::fmt::Debug; /// Apply the configuration to this channel half. /// @@ -91,47 +79,13 @@ pub trait RegisterAccess: Sealed { /// There is no `config()` getter and no partial/read-modify-write update path. fn apply_config(&self, config: &Self::Config); - /// Negotiates and programs the per-transfer burst for this channel half. + /// Programs the per-transfer burst for this channel half. /// - /// The effective burst is the smaller of the channel's configured ceiling - /// and the buffer's `max_alignment`, clamped to each region's hardware floor. - fn prepare_burst(&self, config: &Self::Config, max_alignment: usize) { - let (internal_ceiling, external_ceiling) = config.burst_ceilings(); - #[cfg(not(dma_can_access_psram))] - let _ = external_ceiling; - - let internal_enabled = internal_ceiling.min(max_alignment) >= 4; - - cfg_if::cfg_if! { - if #[cfg(dma_can_access_psram)] { - let effective_external = external_ceiling.min(max_alignment); - let external_memory = if effective_external >= 64 { - ExternalBurstConfig::Size64 - } else if effective_external >= 32 { - ExternalBurstConfig::Size32 - } else { - ExternalBurstConfig::Size16 - }; - let internal_memory = if internal_enabled { - InternalBurstConfig::Enabled - } else { - InternalBurstConfig::Disabled - }; - let burst = BurstConfig { external_memory, internal_memory }; - - #[cfg(dma_ext_mem_configurable_block_size)] - self.set_ext_mem_block_size(external_memory.into()); - } else { - let burst = if internal_enabled { - BurstConfig::Enabled - } else { - BurstConfig::Disabled - }; - } - } - - self.set_burst_mode(burst); - } + /// 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; @@ -139,10 +93,6 @@ pub trait RegisterAccess: Sealed { /// 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); @@ -166,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; @@ -369,6 +316,26 @@ macro_rules! impl_burst_type { $( 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 { diff --git a/esp-hal/src/dma/engine/spi.rs b/esp-hal/src/dma/engine/spi.rs index 6f6f9ee97e7..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, @@ -97,18 +96,32 @@ pub struct SpiDmaConfig { burst: SpiDmaBurst, } -impl crate::dma::DmaBurstConfig for SpiDmaConfig { - fn burst_ceilings(&self) -> (usize, usize) { - cfg_if::cfg_if! { - if #[cfg(spi_dma_separate_burst)] { - (self.internal_burst.bytes(), self.external_burst.bytes()) +/// 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 { - (self.burst.bytes(), self.burst.bytes()) - } + 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)),*]); @@ -144,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) { @@ -186,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(_))) @@ -346,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() @@ -384,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/mod.rs b/esp-hal/src/dma/mod.rs index 115ef128cec..646779abe2f 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -981,29 +981,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)] @@ -1119,8 +1096,14 @@ where return Err(DmaError::UnsupportedMemoryRegion); } - self.rx_impl - .prepare_burst(&self.config, preparation.max_alignment); + 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); @@ -1348,8 +1331,14 @@ where return Err(DmaError::UnsupportedMemoryRegion); } - self.tx_impl - .prepare_burst(&self.config, preparation.max_alignment); + 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 From 0ae356818fd1a265b972b57d099b88e337c5f140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 12 Jun 2026 23:31:19 +0200 Subject: [PATCH 11/13] . --- esp-hal/src/dma/buffers/mod.rs | 268 ++++++++------------------------- 1 file changed, 64 insertions(+), 204 deletions(-) diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index afbb1749893..a912767528f 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -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; - } + // S2 TRM: Size, length and buffer address pointer in transmit descriptors are + // not necessarily aligned with block size. - impl Default for BurstConfig { - fn default() -> Self { - Self::DEFAULT - } - } - - 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 } } } @@ -247,51 +119,42 @@ const fn chunk_size_for_alignment(alignment: usize) -> usize { 4096 - alignment } -/// Data cache line size of cached *internal* memory, in bytes, or `1` (i.e. no -/// alignment requirement) if internal memory is not accessed through a cache. -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, -}; - -/// Data cache line size of cached *external* (PSRAM) memory, in bytes. -/// -/// On the ESP32-S2/S3 this follows the `data-cache-line-size` esp-config option. -#[cfg(dma_can_access_psram)] -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, -}; - /// 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; } let _ = buffer; + + 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 } /// 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 = InternalBurstConfig::DEFAULT.min_dram_alignment(direction); + let alignment = min_dram_alignment(direction); #[cfg(dma_can_access_psram)] if is_valid_psram_address(buffer.as_ptr() as usize) { - return max( - alignment, - ExternalBurstConfig::DEFAULT.min_psram_alignment(direction), - ); + return max(alignment, min_psram_alignment(direction)); } let _ = buffer; @@ -309,15 +172,12 @@ fn effective_alignment(buffer: &[u8], direction: TransferDirection, requested: u /// 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 = InternalBurstConfig::DEFAULT.min_dram_alignment(TransferDirection::In); - let out_alignment = InternalBurstConfig::DEFAULT.min_dram_alignment(TransferDirection::Out); + 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, - ExternalBurstConfig::DEFAULT.min_psram_alignment(TransferDirection::In), - ); + let alignment = max(alignment, min_psram_alignment(TransferDirection::In)); alignment } @@ -2021,7 +1881,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); From 9a57b116ee6cc270c6d36d5bcfe8034887f96653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 15 Jun 2026 10:50:23 +0200 Subject: [PATCH 12/13] . --- esp-hal/src/dma/aligned.rs | 72 +++++++++++++++++++ esp-hal/src/dma/buffers/mod.rs | 2 +- esp-hal/src/dma/engine/copy.rs | 1 - esp-hal/src/dma/engine/crypto.rs | 6 +- esp-hal/src/dma/engine/gdma/ahb_v1.rs | 1 - esp-hal/src/dma/mod.rs | 61 ++-------------- esp-hal/src/spi/master/dma.rs | 12 ++-- .../peripheral/dma/extmem2mem/src/main.rs | 8 +-- examples/peripheral/dma/mem2mem/src/main.rs | 8 +-- .../src/bin/misc_non_drivers/dma_mem2mem.rs | 10 +-- 10 files changed, 94 insertions(+), 87 deletions(-) create mode 100644 esp-hal/src/dma/aligned.rs diff --git a/esp-hal/src/dma/aligned.rs b/esp-hal/src/dma/aligned.rs new file mode 100644 index 00000000000..f1f79882325 --- /dev/null +++ b/esp-hal/src/dma/aligned.rs @@ -0,0 +1,72 @@ +//! Helper types for cacheline-aligned DMA buffers. + +// 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 InternalMemory(T); + +/// A shared reference to an [`InternalMemory`] that is known to be cacheline-aligned. +pub struct InternalMemoryRef<'a, T>(&'a T) +where + T: ?Sized; + +impl<'a, T: ?Sized> InternalMemoryRef<'a, T> { + /// Converts this object into a reference. + pub fn into_ref(self) -> &'a T { + self.0 + } +} + +/// A mutable reference to an [`InternalMemory`] that is known to be cacheline-aligned. +pub struct InternalMemoryMut<'a, T>(&'a mut T) +where + T: ?Sized; + +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 InternalMemory { + pub const fn new(init: T) -> Self { + Self(init) + } + + pub const fn get(&self) -> InternalMemoryRef<'_, T> { + InternalMemoryRef(&self.0) + } + + pub const fn get_mut(&mut self) -> InternalMemoryMut<'_, T> { + InternalMemoryMut(&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(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(&self) -> InternalMemoryRef<'_, [u8]> { + InternalMemoryRef(&self.0.0) + } + + pub const fn get_mut(&mut self) -> InternalMemoryMut<'_, [u8]> { + InternalMemoryMut(&mut self.0.0) + } +} diff --git a/esp-hal/src/dma/buffers/mod.rs b/esp-hal/src/dma/buffers/mod.rs index a912767528f..49375d82c12 100644 --- a/esp-hal/src/dma/buffers/mod.rs +++ b/esp-hal/src/dma/buffers/mod.rs @@ -9,7 +9,7 @@ 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}, }; diff --git a/esp-hal/src/dma/engine/copy.rs b/esp-hal/src/dma/engine/copy.rs index c3ad7f43441..2e191b70a04 100644 --- a/esp-hal/src/dma/engine/copy.rs +++ b/esp-hal/src/dma/engine/copy.rs @@ -306,7 +306,6 @@ impl RegisterAccess for CopyDmaRxChannel<'_> { } } - #[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 5526326aee8..57099009677 100644 --- a/esp-hal/src/dma/engine/crypto.rs +++ b/esp-hal/src/dma/engine/crypto.rs @@ -103,7 +103,11 @@ pub struct CryptoDmaConfig { /// 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 { +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 { diff --git a/esp-hal/src/dma/engine/gdma/ahb_v1.rs b/esp-hal/src/dma/engine/gdma/ahb_v1.rs index b6b397d773c..14d73c2e8f9 100644 --- a/esp-hal/src/dma/engine/gdma/ahb_v1.rs +++ b/esp-hal/src/dma/engine/gdma/ahb_v1.rs @@ -337,7 +337,6 @@ impl RegisterAccess for AhbGdmaRxChannel<'_> { .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true))); } - #[cfg(dma_can_access_psram)] fn can_access_psram(&self) -> bool { true diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 646779abe2f..e56f7219fd7 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -71,6 +71,7 @@ use crate::{ system::{Cpu, PeripheralGuard}, }; +pub mod aligned; mod buffers; #[cfg(dma_supports_mem2mem)] mod m2m; @@ -486,53 +487,6 @@ macro_rules! dma_circular_descriptors_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 { @@ -547,8 +501,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() } @@ -579,17 +533,14 @@ 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() }}; diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index ad3a938d9d9..4074fe3be0e 100644 --- a/esp-hal/src/spi/master/dma.rs +++ b/esp-hal/src/spi/master/dma.rs @@ -16,7 +16,7 @@ use procmacros::ram; use super::*; #[cfg(all(spi_master_version = "1", spi_address_workaround))] -use crate::dma::InternalMemoryBuffer; +use crate::dma::aligned::InternalMemoryBuffer; use crate::{ RegisterToggle, dma::{ @@ -28,11 +28,11 @@ use crate::{ DmaRxBuffer, DmaTxBuf, DmaTxBuffer, - InternalMemoryCachelineAligned, NoBuffer, ScopedDmaRxBuf, ScopedDmaTxBuf, TransferDirection, + aligned::InternalMemory, asynch::DmaRxFuture, prepare_for_rx, prepare_for_tx, @@ -264,14 +264,14 @@ impl<'d> SpiDma<'d, Blocking> { state.tx_transfer_in_progress.set(false); state.rx_transfer_in_progress.set(false); - let descriptors = unsafe { (&mut *state.descriptors.get()).get_mut() }; + 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) => unsafe { - (&mut *state.default_tx_buffer.get()).get_mut() + (&mut *state.default_tx_buffer.get()).get_mut().into_mut() }, _ => &mut [] }; @@ -1790,7 +1790,7 @@ struct DmaState { rx_buffer: UnsafeCell>>, tx_buffer: UnsafeCell>>, - descriptors: UnsafeCell>, + descriptors: UnsafeCell>, #[cfg(all(spi_master_version = "1", spi_address_workaround))] default_tx_buffer: UnsafeCell>, @@ -1843,7 +1843,7 @@ for_each_spi_master!( rx_buffer: UnsafeCell::new(MaybeUninit::uninit()), tx_buffer: UnsafeCell::new(MaybeUninit::uninit()), - descriptors: UnsafeCell::new(InternalMemoryCachelineAligned::new([DmaDescriptor::EMPTY; 2])), + descriptors: UnsafeCell::new(InternalMemory::new([DmaDescriptor::EMPTY; 2])), #[cfg(all(spi_master_version = "1", spi_address_workaround))] default_tx_buffer: UnsafeCell::new(InternalMemoryBuffer::new()), }; diff --git a/examples/peripheral/dma/extmem2mem/src/main.rs b/examples/peripheral/dma/extmem2mem/src/main.rs index 5278cd7859e..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::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!(); diff --git a/examples/peripheral/dma/mem2mem/src/main.rs b/examples/peripheral/dma/mem2mem/src/main.rs index 6b08abc89e5..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::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!(); 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 3698d3f3c3c..0fdb38d9538 100644 --- a/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs +++ b/hil-test/src/bin/misc_non_drivers/dma_mem2mem.rs @@ -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) - { + 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) - { + match ctx.mem2mem.with_descriptors(rx_descriptors, tx_descriptors) { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), } From 6a0330df93e7b8df38bd0c84b867cfc0f48e57c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 15 Jun 2026 14:55:11 +0200 Subject: [PATCH 13/13] Consume typed buffers --- esp-hal/src/aes/mod.rs | 10 +- esp-hal/src/dma/aligned.rs | 126 +++++++++++++----- esp-hal/src/dma/buffers/mod.rs | 54 +++++++- esp-hal/src/dma/mod.rs | 72 +++++++--- .../src/bin/misc_non_drivers/dma_buffers.rs | 4 +- 5 files changed, 203 insertions(+), 63 deletions(-) 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 index f1f79882325..31152b89f51 100644 --- a/esp-hal/src/dma/aligned.rs +++ b/esp-hal/src/dma/aligned.rs @@ -1,52 +1,46 @@ //! 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); -/// A shared reference to an [`InternalMemory`] that is known to be cacheline-aligned. -pub struct InternalMemoryRef<'a, T>(&'a T) -where - T: ?Sized; - -impl<'a, T: ?Sized> InternalMemoryRef<'a, T> { - /// Converts this object into a reference. - pub fn into_ref(self) -> &'a T { - self.0 - } -} - -/// A mutable reference to an [`InternalMemory`] that is known to be cacheline-aligned. -pub struct InternalMemoryMut<'a, T>(&'a mut T) -where - T: ?Sized; - -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 InternalMemory { pub const fn new(init: T) -> Self { Self(init) } - pub const fn get(&self) -> InternalMemoryRef<'_, T> { - InternalMemoryRef(&self.0) - } - 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]>); @@ -62,11 +56,79 @@ impl InternalMemoryBuffer { Self(InternalMemory::new([0u8; N])) } - pub const fn get(&self) -> InternalMemoryRef<'_, [u8]> { - InternalMemoryRef(&self.0.0) - } - 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 49375d82c12..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::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::*; @@ -390,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. @@ -503,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. @@ -633,6 +652,20 @@ pub struct DmaRxTxBuf { } 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. @@ -895,6 +928,16 @@ pub struct DmaRxStreamBuf { } 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( @@ -1197,6 +1240,15 @@ pub struct DmaTxStreamBuf { } 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( diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index e56f7219fd7..82dc428d150 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -66,6 +66,7 @@ use crate::{ Async, Blocking, DriverMode, + dma::aligned::InternalMemoryMut, interrupt::InterruptHandler, soc::is_slice_in_dram, system::{Cpu, PeripheralGuard}, @@ -316,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) }; } @@ -344,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) }; } @@ -368,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) }; } @@ -387,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) }; } @@ -413,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) @@ -437,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) }}; } @@ -457,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) @@ -480,7 +501,11 @@ 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) @@ -542,7 +567,7 @@ macro_rules! dma_descriptors_impl { static DESCRIPTORS: ConstStaticCell> = ConstStaticCell::new(InternalMemory::new([DmaDescriptor::EMPTY; COUNT])); - DESCRIPTORS.take().get_mut() + DESCRIPTORS.take().get_mut().unsize() }}; } @@ -581,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) }}; } @@ -611,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() }}; } @@ -641,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() }}; } @@ -666,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() }}; } @@ -741,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); } @@ -765,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. @@ -814,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. @@ -826,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. 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"), }