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    /// # Safety
153    ///
154    /// The caller must start hardware ownership using this prepared backing
155    /// and later return it only after hardware is quiesced.
156    pub unsafe fn into_in_flight(self) -> InFlightDma {
157        InFlightDma {
158            prepared: ManuallyDrop::new(self),
159        }
160    }
161}
162
163/// DMA backing currently owned by a hardware request.
164///
165/// Dropping this object intentionally leaks the backing as a last-resort
166/// quarantine: safe callers must not observe memory reuse while hardware could
167/// still be accessing it.
168pub struct InFlightDma {
169    prepared: ManuallyDrop<PreparedDma>,
170}
171
172impl InFlightDma {
173    pub fn len(&self) -> NonZeroUsize {
174        self.prepared.len()
175    }
176
177    pub fn direction(&self) -> DmaDirection {
178        self.prepared.direction()
179    }
180
181    pub fn domain_id(&self) -> DmaDomainId {
182        self.prepared.domain_id()
183    }
184
185    pub fn cpu_ptr(&self) -> NonNull<u8> {
186        self.prepared.cpu_ptr()
187    }
188
189    pub fn dma_addr(&self) -> DmaAddr {
190        self.prepared.dma_addr()
191    }
192
193    pub fn segment(&self) -> DmaSegment {
194        self.prepared.segment()
195    }
196
197    /// # Safety
198    ///
199    /// The caller must have stopped DMA bus-master access and any command/data
200    /// engine that can touch this exact in-flight backing.
201    pub unsafe fn complete_after_quiesce(mut self) -> CompletedDma {
202        let prepared = unsafe { ManuallyDrop::take(&mut self.prepared) };
203        if matches!(
204            prepared.buffer.direction(),
205            DmaDirection::FromDevice | DmaDirection::Bidirectional
206        ) {
207            prepared.buffer.complete_for_cpu_all();
208        }
209        CompletedDma {
210            buffer: prepared.buffer,
211        }
212    }
213
214    pub fn quarantine(mut self) -> QuarantinedDma {
215        let prepared = unsafe { ManuallyDrop::take(&mut self.prepared) };
216        QuarantinedDma {
217            prepared: ManuallyDrop::new(prepared),
218        }
219    }
220}
221
222/// DMA backing completed by hardware and visible to CPU again.
223pub struct CompletedDma {
224    buffer: CpuDmaBuffer,
225}
226
227impl CompletedDma {
228    pub fn len(&self) -> NonZeroUsize {
229        self.buffer.len()
230    }
231
232    pub const fn direction(&self) -> DmaDirection {
233        self.buffer.direction()
234    }
235
236    pub fn copy_from_device_to_slice(&self, dst: &mut [u8]) {
237        self.buffer.copy_from_device_to_slice(dst);
238    }
239
240    pub fn into_cpu_buffer(self) -> CpuDmaBuffer {
241        self.buffer
242    }
243}
244
245/// DMA backing that cannot yet be safely recycled.
246///
247/// This type deliberately has no accessor to recover the CPU buffer. Dropping
248/// it leaks the backing, preserving the safety invariant.
249pub struct QuarantinedDma {
250    prepared: ManuallyDrop<PreparedDma>,
251}
252
253impl QuarantinedDma {
254    pub fn len(&self) -> NonZeroUsize {
255        self.prepared.len()
256    }
257
258    pub fn domain_id(&self) -> DmaDomainId {
259        self.prepared.domain_id()
260    }
261}