Skip to main content

dma_api/op/
mod.rs

1use core::{num::NonZeroUsize, ptr::NonNull};
2
3use mbarrier::mb;
4
5use crate::{DmaAllocHandle, DmaCoherency, DmaConstraints, DmaDirection, DmaError, DmaMapHandle};
6
7cfg_if::cfg_if! {
8    if #[cfg(target_arch = "aarch64")] {
9        #[path = "aarch64.rs"]
10        pub mod arch;
11    } else{
12        #[path = "nop.rs"]
13        pub mod arch;
14    }
15}
16
17pub trait DmaOp: Sync + Send + 'static {
18    fn page_size(&self) -> usize;
19
20    /// Allocates a device-visible contiguous DMA address range.
21    ///
22    /// The returned CPU mapping is normal memory. Non-coherent platforms must
23    /// use `sync_alloc_for_device` and `sync_alloc_for_cpu` to transfer
24    /// ownership between CPU and device.
25    ///
26    /// # Safety
27    ///
28    /// Implementations must return a live allocation described by `layout`,
29    /// with a DMA address range satisfying `constraints`, and that allocation
30    /// must remain valid until `dealloc_contiguous`.
31    unsafe fn alloc_contiguous(
32        &self,
33        constraints: DmaConstraints,
34        layout: core::alloc::Layout,
35    ) -> Option<DmaAllocHandle>;
36
37    /// # Safety
38    ///
39    /// Must be paired with `alloc_contiguous`.
40    unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle);
41
42    /// Creates a coherent CPU mapping for a non-coherent DMA device.
43    ///
44    /// `DeviceDma` calls this branch only when the device is non-coherent.
45    /// Coherent devices retain the normal mapping returned by
46    /// `alloc_contiguous`. Ordering barriers remain the driver's responsibility.
47    ///
48    /// # Safety
49    ///
50    /// Implementations must return a live allocation described by `layout`,
51    /// with a DMA address range satisfying `constraints`, and with the backend's
52    /// coherent mapping policy applied until `dealloc_coherent`. When the CPU
53    /// mapping is an alias, the handle must retain the original allocation
54    /// address privately for release.
55    unsafe fn alloc_coherent(
56        &self,
57        constraints: DmaConstraints,
58        layout: core::alloc::Layout,
59    ) -> Option<DmaAllocHandle>;
60
61    /// # Safety
62    ///
63    /// Must be paired with `alloc_coherent`. The handle is consumed even when
64    /// this operation fails. On failure the implementation must quarantine the
65    /// allocation instead of returning its storage to the allocator.
66    unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) -> Result<(), DmaError>;
67
68    /// Maps an existing caller-owned buffer for streaming DMA.
69    ///
70    /// # Safety
71    ///
72    /// `addr..addr + size` must remain live until `unmap_streaming`, and CPU
73    /// access while the device owns the mapping must follow the sync contract.
74    unsafe fn map_streaming(
75        &self,
76        constraints: DmaConstraints,
77        addr: NonNull<u8>,
78        size: NonZeroUsize,
79        direction: DmaDirection,
80    ) -> Result<DmaMapHandle, DmaError>;
81
82    /// # Safety
83    ///
84    /// Must be paired with `map_streaming`.
85    unsafe fn unmap_streaming(&self, handle: DmaMapHandle);
86
87    fn flush(&self, addr: NonNull<u8>, size: usize) {
88        mb();
89        arch::flush(addr, size)
90    }
91
92    fn invalidate(&self, addr: NonNull<u8>, size: usize) {
93        arch::invalidate(addr, size);
94        mb();
95    }
96
97    fn flush_invalidate(&self, addr: NonNull<u8>, size: usize) {
98        mb();
99        arch::flush_invalidate(addr, size);
100        mb();
101    }
102
103    fn sync_alloc_for_device(
104        &self,
105        handle: &DmaAllocHandle,
106        offset: usize,
107        size: usize,
108        direction: DmaDirection,
109    ) {
110        if matches!(
111            direction,
112            DmaDirection::ToDevice | DmaDirection::Bidirectional
113        ) {
114            self.flush(unsafe { handle.as_ptr().add(offset) }, size);
115        } else if matches!(direction, DmaDirection::FromDevice) {
116            self.invalidate(unsafe { handle.as_ptr().add(offset) }, size);
117        }
118    }
119
120    fn sync_alloc_for_cpu(
121        &self,
122        handle: &DmaAllocHandle,
123        offset: usize,
124        size: usize,
125        direction: DmaDirection,
126    ) {
127        if matches!(
128            direction,
129            DmaDirection::FromDevice | DmaDirection::Bidirectional
130        ) {
131            self.invalidate(unsafe { handle.as_ptr().add(offset) }, size);
132        }
133    }
134
135    fn sync_map_for_device(
136        &self,
137        handle: &DmaMapHandle,
138        offset: usize,
139        size: usize,
140        direction: DmaDirection,
141        coherency: DmaCoherency,
142    ) {
143        let source = unsafe { handle.as_ptr().add(offset) };
144        if let Some(map_virt) = handle.bounce_ptr()
145            && map_virt != handle.as_ptr()
146        {
147            let target = unsafe { map_virt.add(offset) };
148            if matches!(
149                direction,
150                DmaDirection::ToDevice | DmaDirection::Bidirectional
151            ) {
152                unsafe {
153                    target
154                        .as_ptr()
155                        .copy_from_nonoverlapping(source.as_ptr(), size);
156                }
157                if coherency == DmaCoherency::NonCoherent {
158                    self.flush(target, size);
159                }
160            } else if matches!(direction, DmaDirection::FromDevice)
161                && coherency == DmaCoherency::NonCoherent
162            {
163                self.invalidate(target, size);
164            }
165            return;
166        }
167
168        if coherency == DmaCoherency::Coherent {
169            return;
170        }
171
172        match direction {
173            DmaDirection::ToDevice => self.flush(source, size),
174            DmaDirection::FromDevice => self.invalidate(source, size),
175            DmaDirection::Bidirectional => self.flush_invalidate(source, size),
176        }
177    }
178
179    fn sync_map_for_cpu(
180        &self,
181        handle: &DmaMapHandle,
182        offset: usize,
183        size: usize,
184        direction: DmaDirection,
185        coherency: DmaCoherency,
186    ) {
187        if !matches!(
188            direction,
189            DmaDirection::FromDevice | DmaDirection::Bidirectional
190        ) {
191            return;
192        }
193
194        let target = unsafe { handle.as_ptr().add(offset) };
195        if let Some(map_virt) = handle.bounce_ptr()
196            && map_virt != handle.as_ptr()
197        {
198            let source = unsafe { map_virt.add(offset) };
199            if coherency == DmaCoherency::NonCoherent {
200                self.invalidate(source, size);
201            }
202            unsafe {
203                target
204                    .as_ptr()
205                    .copy_from_nonoverlapping(source.as_ptr(), size);
206            }
207            return;
208        }
209
210        if coherency == DmaCoherency::NonCoherent {
211            self.invalidate(target, size);
212        }
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    fn dma_op_direction_matching_hold_for_test() -> bool {
219        // Test that DmaDirection variants work correctly for sync operations
220        use crate::DmaDirection;
221
222        let to_device = DmaDirection::ToDevice;
223        let from_device = DmaDirection::FromDevice;
224        let bidirectional = DmaDirection::Bidirectional;
225
226        // Verify all directions are distinct
227        assert!(to_device != from_device);
228        assert!(from_device != bidirectional);
229        assert!(to_device != bidirectional);
230
231        true
232    }
233
234    fn dma_op_constraints_and_error_types_hold_for_test() -> bool {
235        // Test DmaConstraints and DmaError types
236        use crate::DmaError;
237
238        // Test DmaError variants exist
239        let _no_memory = DmaError::NoMemory;
240
241        true
242    }
243
244    fn dma_op_sync_direction_branches_hold_for_test() -> bool {
245        // Test that all DmaDirection branches are covered in sync logic
246        use crate::DmaDirection;
247
248        // Test ToDevice matches
249        assert!(matches!(DmaDirection::ToDevice, DmaDirection::ToDevice));
250        assert!(!matches!(DmaDirection::ToDevice, DmaDirection::FromDevice));
251        assert!(!matches!(
252            DmaDirection::ToDevice,
253            DmaDirection::Bidirectional
254        ));
255
256        // Test FromDevice matches
257        assert!(matches!(DmaDirection::FromDevice, DmaDirection::FromDevice));
258        assert!(!matches!(DmaDirection::FromDevice, DmaDirection::ToDevice));
259        assert!(!matches!(
260            DmaDirection::FromDevice,
261            DmaDirection::Bidirectional
262        ));
263
264        // Test Bidirectional matches both
265        assert!(matches!(
266            DmaDirection::Bidirectional,
267            DmaDirection::Bidirectional
268        ));
269        assert!(!matches!(
270            DmaDirection::Bidirectional,
271            DmaDirection::ToDevice
272        ));
273        assert!(!matches!(
274            DmaDirection::Bidirectional,
275            DmaDirection::FromDevice
276        ));
277
278        // Test combined patterns
279        assert!(matches!(
280            DmaDirection::ToDevice,
281            DmaDirection::ToDevice | DmaDirection::Bidirectional
282        ));
283        assert!(!matches!(
284            DmaDirection::FromDevice,
285            DmaDirection::ToDevice | DmaDirection::Bidirectional
286        ));
287        assert!(matches!(
288            DmaDirection::Bidirectional,
289            DmaDirection::ToDevice | DmaDirection::Bidirectional
290        ));
291
292        true
293    }
294
295    #[test]
296    fn dma_op_direction_matching_hold() {
297        assert!(dma_op_direction_matching_hold_for_test());
298    }
299
300    #[test]
301    fn dma_op_constraints_and_error_types_hold() {
302        assert!(dma_op_constraints_and_error_types_hold_for_test());
303    }
304
305    #[test]
306    fn dma_op_sync_direction_branches_hold() {
307        assert!(dma_op_sync_direction_branches_hold_for_test());
308    }
309}