Skip to main content

dma_api/
owned.rs

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