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}