1
//! Helper types for DMA buffers.
2

            
3
use core::ops::{Deref, DerefMut};
4

            
5
use procmacros::doc_replace;
6

            
7
#[cfg(dma_can_access_psram)]
8
use crate::soc::is_valid_psram_address;
9
use 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]
28
pub struct InternalMemory<T>(T);
29

            
30
impl<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
323
        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 neighboring data.
80
pub(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.
105
17
fn validate_dma_alignment(addr: usize, size: usize) -> Result<(), DmaBufError> {
106
    // Zero-sized values never alias a cache line, so any address is fine.
107
17
    if size == 0 {
108
        return Ok(());
109
    }
110

            
111
17
    let Some(alignment) = region_dma_alignment(addr) else {
112
        return Err(DmaBufError::UnsupportedMemoryRegion);
113
    };
114

            
115
17
    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
17
}
124

            
125
/// Returns whether 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))]
128
fn 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]
152
pub struct DmaAlignedMut<'a, T>(&'a mut T)
153
where
154
    T: ?Sized;
155

            
156
impl<'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 is
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
2
        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

            
238
impl<'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

            
245
impl<'a, T: ?Sized> Deref for DmaAlignedMut<'a, T> {
246
    type Target = T;
247

            
248
    fn deref(&self) -> &Self::Target {
249
99
        self.0
250
    }
251
}
252

            
253
impl<'a, T: ?Sized> DerefMut for DmaAlignedMut<'a, T> {
254
    fn deref_mut(&mut self) -> &mut Self::Target {
255
675
        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]
270
pub struct DmaAlignedRef<'a, T>(&'a T)
271
where
272
    T: ?Sized;
273

            
274
impl<'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 is
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

            
357
impl<'a, T: ?Sized> Deref for DmaAlignedRef<'a, T> {
358
    type Target = T;
359

            
360
    fn deref(&self) -> &Self::Target {
361
        self.0
362
    }
363
}