Skip to main content

dma_api/
owned.rs

1use core::{mem::ManuallyDrop, num::NonZeroUsize, ptr::NonNull};
2
3use crate::{ContiguousArray, DeviceDma, DmaAddr, DmaDirection, DmaDomainId, DmaError};
4
5/// One device-visible DMA segment owned by a prepared request.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct DmaSegment {
8    pub addr: DmaAddr,
9    pub len: NonZeroUsize,
10}
11
12impl DmaSegment {
13    pub const fn new(addr: DmaAddr, len: NonZeroUsize) -> Self {
14        Self { addr, len }
15    }
16}
17
18/// CPU-owned contiguous DMA buffer that can be prepared for one async request.
19pub struct CpuDmaBuffer {
20    backing: ContiguousArray<u8>,
21    direction: DmaDirection,
22    domain: DmaDomainId,
23}
24
25impl CpuDmaBuffer {
26    pub fn new_zero(
27        device: &DeviceDma,
28        len: NonZeroUsize,
29        align: usize,
30        direction: DmaDirection,
31    ) -> Result<Self, DmaError> {
32        let backing =
33            device.contiguous_array_zero_with_align(len.get(), align.max(1), direction)?;
34        Ok(Self::from_contiguous(backing))
35    }
36
37    pub fn from_contiguous(backing: ContiguousArray<u8>) -> Self {
38        assert!(
39            !backing.is_empty(),
40            "CpuDmaBuffer backing must be non-empty"
41        );
42        let direction = backing.direction();
43        let domain = backing.domain_id();
44        Self {
45            backing,
46            direction,
47            domain,
48        }
49    }
50
51    pub fn len(&self) -> NonZeroUsize {
52        NonZeroUsize::new(self.backing.bytes_len())
53            .expect("CpuDmaBuffer never owns zero-sized backing")
54    }
55
56    pub fn is_empty(&self) -> bool {
57        false
58    }
59
60    pub const fn direction(&self) -> DmaDirection {
61        self.direction
62    }
63
64    pub const fn domain_id(&self) -> DmaDomainId {
65        self.domain
66    }
67
68    pub fn cpu_ptr(&self) -> NonNull<u8> {
69        self.backing.as_ptr()
70    }
71
72    pub fn dma_addr(&self) -> DmaAddr {
73        self.backing.dma_addr()
74    }
75
76    pub fn segment(&self) -> DmaSegment {
77        DmaSegment::new(self.dma_addr(), self.len())
78    }
79
80    pub fn as_slice_cpu(&self) -> &[u8] {
81        self.backing.as_slice_cpu()
82    }
83
84    /// # Safety
85    ///
86    /// The caller must ensure no device can access this buffer while the
87    /// returned mutable CPU slice is used.
88    pub unsafe fn as_mut_slice_cpu(&mut self) -> &mut [u8] {
89        unsafe { self.backing.as_mut_slice_cpu() }
90    }
91
92    pub fn copy_to_device_from_slice(&mut self, src: &[u8]) {
93        self.backing.copy_to_device_from_slice(src);
94    }
95
96    pub fn copy_from_device_to_slice(&self, dst: &mut [u8]) {
97        self.backing.copy_from_device_to_slice(dst);
98    }
99
100    pub fn prepare_for_device_all(&self) {
101        self.backing.prepare_for_device_all();
102    }
103
104    pub fn complete_for_cpu_all(&self) {
105        self.backing.complete_for_cpu_all();
106    }
107
108    pub fn prepare_for_device(self) -> PreparedDma {
109        self.prepare_for_device_all();
110        PreparedDma { buffer: self }
111    }
112}
113
114/// DMA backing prepared for device access but not yet owned by hardware.
115pub struct PreparedDma {
116    buffer: CpuDmaBuffer,
117}
118
119impl PreparedDma {
120    pub fn len(&self) -> NonZeroUsize {
121        self.buffer.len()
122    }
123
124    pub const fn direction(&self) -> DmaDirection {
125        self.buffer.direction()
126    }
127
128    pub const fn domain_id(&self) -> DmaDomainId {
129        self.buffer.domain_id()
130    }
131
132    pub fn cpu_ptr(&self) -> NonNull<u8> {
133        self.buffer.cpu_ptr()
134    }
135
136    pub fn dma_addr(&self) -> DmaAddr {
137        self.buffer.dma_addr()
138    }
139
140    pub fn segment(&self) -> DmaSegment {
141        self.buffer.segment()
142    }
143
144    pub fn segments(&self) -> [DmaSegment; 1] {
145        [self.segment()]
146    }
147
148    pub fn into_cpu_buffer(self) -> CpuDmaBuffer {
149        self.buffer
150    }
151
152    /// Returns backing for a request rejected before hardware submission.
153    ///
154    /// This is not request cancellation: callers may use it only while the
155    /// prepared buffer has never been transferred to device ownership.
156    pub fn complete_without_device(self) -> CompletedDma {
157        if matches!(
158            self.buffer.direction(),
159            DmaDirection::FromDevice | DmaDirection::Bidirectional
160        ) {
161            self.buffer.complete_for_cpu_all();
162        }
163        CompletedDma {
164            buffer: self.buffer,
165        }
166    }
167
168    /// # Safety
169    ///
170    /// The caller must start hardware ownership using this prepared backing
171    /// and later return it only after hardware is quiesced.
172    pub unsafe fn into_in_flight(self) -> InFlightDma {
173        InFlightDma {
174            prepared: ManuallyDrop::new(self),
175        }
176    }
177}
178
179/// DMA backing currently owned by a hardware request.
180///
181/// Dropping this object intentionally leaks the backing as a last-resort
182/// quarantine: safe callers must not observe memory reuse while hardware could
183/// still be accessing it.
184pub struct InFlightDma {
185    prepared: ManuallyDrop<PreparedDma>,
186}
187
188impl InFlightDma {
189    pub fn len(&self) -> NonZeroUsize {
190        self.prepared.len()
191    }
192
193    pub fn direction(&self) -> DmaDirection {
194        self.prepared.direction()
195    }
196
197    pub fn domain_id(&self) -> DmaDomainId {
198        self.prepared.domain_id()
199    }
200
201    pub fn cpu_ptr(&self) -> NonNull<u8> {
202        self.prepared.cpu_ptr()
203    }
204
205    pub fn dma_addr(&self) -> DmaAddr {
206        self.prepared.dma_addr()
207    }
208
209    pub fn segment(&self) -> DmaSegment {
210        self.prepared.segment()
211    }
212
213    /// # Safety
214    ///
215    /// The caller must have stopped DMA bus-master access and any command/data
216    /// engine that can touch this exact in-flight backing.
217    pub unsafe fn complete_after_quiesce(mut self) -> CompletedDma {
218        let prepared = unsafe { ManuallyDrop::take(&mut self.prepared) };
219        if matches!(
220            prepared.buffer.direction(),
221            DmaDirection::FromDevice | DmaDirection::Bidirectional
222        ) {
223            prepared.buffer.complete_for_cpu_all();
224        }
225        CompletedDma {
226            buffer: prepared.buffer,
227        }
228    }
229
230    pub fn quarantine(mut self) -> QuarantinedDma {
231        let prepared = unsafe { ManuallyDrop::take(&mut self.prepared) };
232        QuarantinedDma {
233            prepared: ManuallyDrop::new(prepared),
234        }
235    }
236}
237
238/// DMA backing completed by hardware and visible to CPU again.
239pub struct CompletedDma {
240    buffer: CpuDmaBuffer,
241}
242
243impl CompletedDma {
244    pub fn len(&self) -> NonZeroUsize {
245        self.buffer.len()
246    }
247
248    pub const fn direction(&self) -> DmaDirection {
249        self.buffer.direction()
250    }
251
252    pub fn copy_from_device_to_slice(&self, dst: &mut [u8]) {
253        self.buffer.copy_from_device_to_slice(dst);
254    }
255
256    pub fn into_cpu_buffer(self) -> CpuDmaBuffer {
257        self.buffer
258    }
259}
260
261/// DMA backing that cannot yet be safely recycled.
262///
263/// This type deliberately has no accessor to recover the CPU buffer. Dropping
264/// it leaks the backing, preserving the safety invariant.
265pub struct QuarantinedDma {
266    prepared: ManuallyDrop<PreparedDma>,
267}
268
269impl QuarantinedDma {
270    pub fn len(&self) -> NonZeroUsize {
271        self.prepared.len()
272    }
273
274    pub fn domain_id(&self) -> DmaDomainId {
275        self.prepared.domain_id()
276    }
277}