Skip to main content

esp_hal/dma/
aligned.rs

1//! Helper types for DMA buffers.
2
3use core::ops::{Deref, DerefMut};
4
5use procmacros::doc_replace;
6
7#[cfg(dma_can_access_psram)]
8use crate::soc::is_valid_psram_address;
9use crate::{
10    dma::{DmaAlignmentError, DmaBufError},
11    soc::is_valid_ram_address,
12};
13
14/// DMA appropriate wrapper type for internal memory values.
15///
16/// The value wrapped in this type is guaranteed to be safely useable
17/// as a DMA buffer or descriptor array, meaning DMA or cache management
18/// operations will not corrupt surrounding data.
19///
20/// [`DmaAlignedMut`] is a reference type that carries this guarantee.
21// ESP32-P4 internal memory is cached, enforce alignment to avoid memory
22// corruption. Technically only needed for IN buffers and descriptor lists.
23#[derive(Debug)]
24#[cfg_attr(feature = "defmt", derive(defmt::Format))]
25#[cfg_attr(soc_internal_memory_cached, repr(C, align(64)))] // dcache cache line
26#[cfg_attr(not(soc_internal_memory_cached), repr(C, align(4)))] // Worst-case word alignment
27#[instability::unstable]
28pub struct InternalMemory<T>(T);
29
30impl<T> InternalMemory<T> {
31    /// Creates a new value aligned for DMA operations in internal memory.
32    #[instability::unstable]
33    pub const fn new(init: T) -> Self {
34        Self(init)
35    }
36
37    /// Returns a const pointer to the wrapped value.
38    ///
39    /// The returned pointer is aligned appropriately for DMA and points at the
40    /// same address that the DMA engine will access.
41    #[instability::unstable]
42    pub const fn as_ptr(&self) -> *const T {
43        &raw const self.0
44    }
45
46    /// Returns a [`DmaAlignedMut`] to the underlying value.
47    ///
48    /// # Panics
49    ///
50    /// Panics if the value is not located at a valid internal RAM address.
51    #[instability::unstable]
52    pub fn get_mut(&mut self) -> DmaAlignedMut<'_, T> {
53        assert!(is_valid_ram_address(&raw const self.0 as usize));
54        DmaAlignedMut(&mut self.0)
55    }
56
57    /// Returns a [`DmaAlignedRef`] to the underlying value.
58    ///
59    /// The reference can be used to read the value and to invalidate its cache
60    /// lines, but not to mutate or write it back. This allows polling
61    /// DMA-written state (e.g. a descriptor ownership bit) through a shared
62    /// borrow.
63    ///
64    /// # Panics
65    ///
66    /// Panics if the value is not located at a valid internal RAM address.
67    #[instability::unstable]
68    pub fn get_ref(&self) -> DmaAlignedRef<'_, T> {
69        assert!(is_valid_ram_address(&raw const self.0 as usize));
70        DmaAlignedRef(&self.0)
71    }
72}
73
74/// Returns the DMA alignment (applying to both the start address and the
75/// length) required by the memory region containing `addr`, or `None` if
76/// `addr` is not in a DMA-capable region.
77///
78/// Both the address and size of a DMA buffer must be a multiple of this value
79/// so that cache maintenance on the buffer cannot corrupt neighbouring data.
80pub(crate) fn region_dma_alignment(addr: usize) -> Option<usize> {
81    if is_valid_ram_address(addr) {
82        return Some(core::mem::align_of::<InternalMemory<()>>());
83    }
84
85    #[cfg(dma_can_access_psram)]
86    if is_valid_psram_address(addr) {
87        return Some(cfg_select! {
88            // TODO(esp32p4): PSRAM is cached through the L2 cache,
89            // whose line size is configurable (64 or 128
90            // bytes) and is not encoded anywhere yet. Assume the
91            // larger, always-safe value until the L2 line size is
92            // available.
93            soc_internal_memory_cached => 128,
94            esp32s31 => 64,
95            any(esp32, esp32c5, esp32c61) => 32, /* TODO: fixed 32-bytes, metadata-ify */
96            _ => crate::soc::CONFIG_DATA_CACHE_LINE_SIZE,
97        });
98    }
99
100    None
101}
102
103/// Validates that the value at `addr` spanning `size` bytes lives in a
104/// DMA-capable memory region and is aligned correctly for that region.
105fn validate_dma_alignment(addr: usize, size: usize) -> Result<(), DmaBufError> {
106    // Zero-sized values never alias a cache line, so any address is fine.
107    if size == 0 {
108        return Ok(());
109    }
110
111    let Some(alignment) = region_dma_alignment(addr) else {
112        return Err(DmaBufError::UnsupportedMemoryRegion);
113    };
114
115    if !size.is_multiple_of(alignment) {
116        return Err(DmaBufError::InvalidAlignment(DmaAlignmentError::Size));
117    }
118    if !addr.is_multiple_of(alignment) {
119        return Err(DmaBufError::InvalidAlignment(DmaAlignmentError::Address));
120    }
121
122    Ok(())
123}
124
125/// Returns `true` if the value at `addr` spanning `size` bytes occupies a
126/// cached memory region and therefore needs explicit cache maintenance.
127#[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
128fn region_needs_cache_op(addr: usize, size: usize) -> bool {
129    // Do not do cache operations on zero-sized values.
130    if size == 0 {
131        return false;
132    }
133
134    let mut in_cached_region = false;
135
136    #[cfg(soc_internal_memory_cached)]
137    {
138        in_cached_region |= is_valid_ram_address(addr);
139    }
140    #[cfg(dma_can_access_psram)]
141    {
142        in_cached_region |= is_valid_psram_address(addr);
143    }
144
145    in_cached_region
146}
147
148/// A mutable reference to an [`InternalMemory`] object.
149#[derive(Debug)]
150#[cfg_attr(feature = "defmt", derive(defmt::Format))]
151#[instability::unstable]
152pub struct DmaAlignedMut<'a, T>(&'a mut T)
153where
154    T: ?Sized;
155
156impl<'a, T: ?Sized> DmaAlignedMut<'a, T> {
157    #[doc_replace("align_req" => {
158        cfg(soc_internal_memory_cached) => "64",
159        _ => "4"
160    })]
161    /// Creates a new [`DmaAlignedMut`] from a mutable variable, if it's
162    /// provably compatible.
163    ///
164    /// In internal memory, the address and size of the variable
165    /// must be at least __align_req__ byte aligned.
166    #[instability::unstable]
167    pub fn new(ptr: &'a mut T) -> Result<Self, DmaBufError> {
168        let addr = ptr as *mut T as *mut () as usize;
169        validate_dma_alignment(addr, core::mem::size_of_val(ptr))?;
170        Ok(Self(ptr))
171    }
172
173    /// Creates a new [`DmaAlignedMut`] from *any* mutable slice.
174    ///
175    /// # Safety
176    ///
177    /// The caller must ensure that the reference is properly aligned for the memory region it
178    /// occupies and the cachelines occupied by the reference do not overlap with any other data
179    /// that can be corrupted by a cacheline invalidation operation.
180    #[instability::unstable]
181    pub const unsafe fn new_unchecked(ptr: &'a mut T) -> Self {
182        Self(ptr)
183    }
184
185    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
186    fn do_cache_op(&self) -> bool {
187        region_needs_cache_op(
188            self.0 as *const T as *const () as usize,
189            core::mem::size_of_val(self.0),
190        )
191    }
192
193    /// Writes back the data from the cache to memory.
194    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
195    #[instability::unstable]
196    pub fn writeback(&mut self) {
197        if !self.do_cache_op() {
198            return;
199        }
200
201        // SAFETY: we own the cachelines and can't trash anything else
202        unsafe {
203            crate::soc::cache_writeback_addr(
204                self.0 as *const T as *const () as u32,
205                core::mem::size_of_val(self.0) as u32,
206            );
207        }
208    }
209
210    /// Invalidates the cache lines for this data.
211    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
212    #[instability::unstable]
213    pub fn invalidate(&self) {
214        if !self.do_cache_op() {
215            return;
216        }
217
218        // SAFETY: we own the cachelines and can't trash anything else
219        unsafe {
220            crate::soc::cache_invalidate_addr(
221                self.0 as *const T as *const () as u32,
222                core::mem::size_of_val(self.0) as u32,
223            );
224        }
225    }
226
227    /// Converts this object into a mutable reference.
228    pub fn into_inner(self) -> &'a mut T {
229        self.0
230    }
231
232    /// Reborrows this object with a different lifetime.
233    pub fn reborrow<'b>(&'b mut self) -> DmaAlignedMut<'b, T> {
234        DmaAlignedMut(self.0)
235    }
236}
237
238impl<'a, T, const N: usize> DmaAlignedMut<'a, [T; N]> {
239    /// Converts this object into a slice reference.
240    pub fn unsize(self) -> DmaAlignedMut<'a, [T]> {
241        DmaAlignedMut(self.0)
242    }
243}
244
245impl<'a, T: ?Sized> Deref for DmaAlignedMut<'a, T> {
246    type Target = T;
247
248    fn deref(&self) -> &Self::Target {
249        self.0
250    }
251}
252
253impl<'a, T: ?Sized> DerefMut for DmaAlignedMut<'a, T> {
254    fn deref_mut(&mut self) -> &mut Self::Target {
255        self.0
256    }
257}
258
259/// A shared reference to an [`InternalMemory`] object.
260///
261/// Unlike [`DmaAlignedMut`], a [`DmaAlignedRef`] only allows *reading* the
262/// value and *invalidating* its cache lines (discarding the CPU-cached copy so
263/// a subsequent read observes data written by DMA). It can neither mutate the
264/// value nor write it back, so it can be obtained from a shared borrow and used
265/// to poll DMA-written, interior-mutable state (e.g. a descriptor's ownership
266/// bit) without exclusive access.
267#[derive(Debug)]
268#[cfg_attr(feature = "defmt", derive(defmt::Format))]
269#[instability::unstable]
270pub struct DmaAlignedRef<'a, T>(&'a T)
271where
272    T: ?Sized;
273
274impl<'a, T: ?Sized> DmaAlignedRef<'a, T> {
275    #[doc_replace("align_req" => {
276        cfg(soc_internal_memory_cached) => "64",
277        _ => "4"
278    })]
279    /// Creates a new [`DmaAlignedRef`] from a shared reference, if it's
280    /// provably compatible.
281    ///
282    /// In internal memory, the address and size of the value
283    /// must be at least __align_req__ byte aligned.
284    #[instability::unstable]
285    pub fn new(ptr: &'a T) -> Result<Self, DmaBufError> {
286        let addr = ptr as *const T as *const () as usize;
287        validate_dma_alignment(addr, core::mem::size_of_val(ptr))?;
288        Ok(Self(ptr))
289    }
290
291    /// Creates a new [`DmaAlignedRef`] from *any* shared reference.
292    ///
293    /// # Safety
294    ///
295    /// The caller must ensure that the reference is properly aligned for the memory region it
296    /// occupies and the cachelines occupied by the reference do not overlap with any other data
297    /// that can be corrupted by a cacheline invalidation operation.
298    #[instability::unstable]
299    pub const unsafe fn new_unchecked(ptr: &'a T) -> Self {
300        Self(ptr)
301    }
302
303    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
304    fn do_cache_op(&self) -> bool {
305        region_needs_cache_op(
306            self.0 as *const T as *const () as usize,
307            core::mem::size_of_val(self.0),
308        )
309    }
310
311    /// Invalidates the cache lines for this data.
312    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
313    #[instability::unstable]
314    pub fn invalidate(&self) {
315        if !self.do_cache_op() {
316            return;
317        }
318
319        // SAFETY: the wrapped value owns the cachelines it occupies, so
320        // invalidating them cannot discard CPU writes to neighbouring data.
321        unsafe {
322            crate::soc::cache_invalidate_addr(
323                self.0 as *const T as *const () as u32,
324                core::mem::size_of_val(self.0) as u32,
325            );
326        }
327    }
328
329    /// Writes back the cached copy of this data to memory.
330    ///
331    /// This only flushes the CPU cache to memory (so a DMA engine reading from
332    /// memory observes the latest CPU writes); it does not mutate the value, so
333    /// it is sound to perform through a shared reference.
334    #[cfg(any(soc_internal_memory_cached, dma_can_access_psram))]
335    #[instability::unstable]
336    pub fn writeback(&self) {
337        if !self.do_cache_op() {
338            return;
339        }
340
341        // SAFETY: the wrapped value owns the cachelines it occupies, so writing
342        // them back cannot affect neighbouring data.
343        unsafe {
344            crate::soc::cache_writeback_addr(
345                self.0 as *const T as *const () as u32,
346                core::mem::size_of_val(self.0) as u32,
347            );
348        }
349    }
350
351    /// Converts this object into a shared reference.
352    pub fn into_inner(self) -> &'a T {
353        self.0
354    }
355}
356
357impl<'a, T: ?Sized> Deref for DmaAlignedRef<'a, T> {
358    type Target = T;
359
360    fn deref(&self) -> &Self::Target {
361        self.0
362    }
363}