Skip to main content

dma_api/
def.rs

1use core::{alloc::Layout, cmp::PartialOrd, num::NonZeroU64, ptr::NonNull};
2
3use derive_more::{
4    Add, AddAssign, Debug, Display, Div, From, Into, Mul, MulAssign, Sub, SubAssign,
5};
6
7#[derive(
8    Debug,
9    Display,
10    Clone,
11    Copy,
12    PartialEq,
13    Eq,
14    PartialOrd,
15    Hash,
16    From,
17    Into,
18    Add,
19    AddAssign,
20    Mul,
21    MulAssign,
22    Sub,
23    SubAssign,
24    Div,
25)]
26#[debug("{}", format_args!("{_0:#X}"))]
27#[display("{}", format_args!("{_0:#X}"))]
28pub struct DmaAddr(u64);
29
30impl DmaAddr {
31    pub fn as_u64(&self) -> u64 {
32        self.0
33    }
34
35    pub fn checked_add(&self, rhs: u64) -> Option<Self> {
36        self.0.checked_add(rhs).map(DmaAddr)
37    }
38}
39
40impl PartialEq<u64> for DmaAddr {
41    fn eq(&self, other: &u64) -> bool {
42        self.0 == *other
43    }
44}
45
46impl PartialOrd<u64> for DmaAddr {
47    fn partial_cmp(&self, other: &u64) -> Option<core::cmp::Ordering> {
48        self.0.partial_cmp(other)
49    }
50}
51
52/// Identity of the address domain used by one DMA device.
53///
54/// Drivers use this to reject already-prepared DMA buffers that were prepared
55/// for a different device/IOMMU domain.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub enum DmaDomainId {
58    /// Device addresses are physical addresses shared by direct-mapped devices.
59    Direct,
60    /// Device addresses are translated in the identified IOMMU domain.
61    Translated(NonZeroU64),
62}
63
64/// Device-visible DMA constraints.
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct DmaConstraints {
67    pub addr_mask: u64,
68    pub align: usize,
69    pub boundary: Option<usize>,
70    pub max_segment_size: Option<usize>,
71}
72
73/// Cache-coherency relationship between one DMA device and the CPU.
74///
75/// This is a device property supplied by firmware or the platform bus. It is
76/// independent from address-mask and segment-layout constraints.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
78pub enum DmaCoherency {
79    /// CPU and device observe the same cacheable mapping without explicit
80    /// cache maintenance.
81    Coherent,
82    /// CPU ownership transitions require cache maintenance or a coherent CPU
83    /// mapping supplied by the DMA backend.
84    NonCoherent,
85}
86
87impl DmaConstraints {
88    pub const fn new(addr_mask: u64) -> Self {
89        Self {
90            addr_mask,
91            align: 1,
92            boundary: None,
93            max_segment_size: None,
94        }
95    }
96
97    pub const fn with_align(mut self, align: usize) -> Self {
98        self.align = if align == 0 { 1 } else { align };
99        self
100    }
101
102    pub const fn with_boundary(mut self, boundary: usize) -> Self {
103        self.boundary = Some(if boundary == 0 { 1 } else { boundary });
104        self
105    }
106
107    pub const fn with_max_segment_size(mut self, max_segment_size: usize) -> Self {
108        self.max_segment_size = Some(max_segment_size);
109        self
110    }
111}
112
113/// Complete device-scoped DMA capability metadata.
114///
115/// This value deliberately contains no OS backend. It can cross portable
116/// driver boundaries without exposing platform implementation details.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub struct DmaDeviceInfo {
119    domain: DmaDomainId,
120    coherency: DmaCoherency,
121    constraints: DmaConstraints,
122}
123
124impl DmaDeviceInfo {
125    pub const fn new(
126        domain: DmaDomainId,
127        coherency: DmaCoherency,
128        constraints: DmaConstraints,
129    ) -> Self {
130        Self {
131            domain,
132            coherency,
133            constraints,
134        }
135    }
136
137    pub const fn domain(self) -> DmaDomainId {
138        self.domain
139    }
140
141    pub const fn coherency(self) -> DmaCoherency {
142        self.coherency
143    }
144
145    pub const fn constraints(self) -> DmaConstraints {
146        self.constraints
147    }
148
149    pub const fn with_constraints(self, constraints: DmaConstraints) -> Self {
150        Self {
151            constraints,
152            ..self
153        }
154    }
155}
156
157/// DMA transfer direction.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
159pub enum DmaDirection {
160    /// CPU writes, device reads.
161    ToDevice,
162    /// Device writes, CPU reads.
163    FromDevice,
164    /// CPU and device may both read/write.
165    Bidirectional,
166}
167
168#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
169pub enum DmaError {
170    #[error("DMA allocation failed")]
171    NoMemory,
172    #[error("Invalid layout")]
173    LayoutError(#[from] core::alloc::LayoutError),
174    #[error("DMA address {addr} does not match device mask {mask:#X}")]
175    DmaMaskNotMatch { addr: DmaAddr, mask: u64 },
176    #[error("DMA align mismatch: required={required:#X}, but address={address}")]
177    AlignMismatch { required: usize, address: DmaAddr },
178    #[error("DMA segment size {size:#X} exceeds max segment size {max:#X}")]
179    SegmentTooLarge { size: usize, max: usize },
180    #[error("DMA address range crosses boundary {boundary:#X}: addr={addr}, size={size:#X}")]
181    BoundaryCross {
182        addr: DmaAddr,
183        size: usize,
184        boundary: usize,
185    },
186    #[error("Null pointer provided for DMA mapping")]
187    NullPointer,
188    #[error("Zero-sized buffer cannot be used for DMA")]
189    ZeroSizedBuffer,
190    #[error("DMA coherent allocation could not be released and was quarantined")]
191    CoherentReleaseFailed,
192}
193
194/// Marker for plain data that can be safely stored in typed DMA buffers.
195///
196/// # Safety
197///
198/// Implementors must be `Copy`, have no invalid all-zero bit pattern, and must
199/// not own resources or references whose validity can be broken by raw device
200/// writes.
201pub unsafe trait DmaPod: Copy {}
202
203unsafe impl<T: Copy> DmaPod for T {}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206pub struct DmaAllocHandle {
207    pub(crate) cpu_addr: NonNull<u8>,
208    pub(crate) allocation_addr: NonNull<u8>,
209    pub(crate) dma_addr: DmaAddr,
210    pub(crate) layout: Layout,
211}
212
213impl DmaAllocHandle {
214    /// Creates a handle from its CPU-visible and allocator-owned addresses.
215    ///
216    /// # Safety
217    ///
218    /// `cpu_addr` and `allocation_addr` must refer to the same live physical
219    /// allocation described by `layout`. `cpu_addr` must remain the only CPU
220    /// mapping exposed to the allocation owner until deallocation, and
221    /// `dma_addr` must be the device-visible address for those pages.
222    pub unsafe fn new(
223        cpu_addr: NonNull<u8>,
224        allocation_addr: NonNull<u8>,
225        dma_addr: DmaAddr,
226        layout: Layout,
227    ) -> Self {
228        Self {
229            cpu_addr,
230            allocation_addr,
231            dma_addr,
232            layout,
233        }
234    }
235
236    pub fn size(&self) -> usize {
237        self.layout.size()
238    }
239
240    pub fn align(&self) -> usize {
241        self.layout.align()
242    }
243
244    pub fn as_ptr(&self) -> NonNull<u8> {
245        self.cpu_addr
246    }
247
248    /// Returns the allocator-owned address required by the DMA backend when
249    /// releasing this handle.
250    pub fn allocation_ptr(&self) -> NonNull<u8> {
251        self.allocation_addr
252    }
253
254    pub fn dma_addr(&self) -> DmaAddr {
255        self.dma_addr
256    }
257
258    pub fn layout(&self) -> Layout {
259        self.layout
260    }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
264pub struct DmaMapHandle {
265    pub(crate) cpu_addr: NonNull<u8>,
266    pub(crate) dma_addr: DmaAddr,
267    pub(crate) layout: Layout,
268    pub(crate) bounce_ptr: Option<NonNull<u8>>,
269}
270
271impl DmaMapHandle {
272    /// # Safety
273    ///
274    /// `cpu_addr` must point to the caller-owned mapped buffer for the mapping
275    /// lifetime. `bounce_ptr`, when present, must point to a live bounce buffer
276    /// described by `layout`.
277    pub unsafe fn new(
278        cpu_addr: NonNull<u8>,
279        dma_addr: DmaAddr,
280        layout: Layout,
281        bounce_ptr: Option<NonNull<u8>>,
282    ) -> Self {
283        Self {
284            cpu_addr,
285            dma_addr,
286            layout,
287            bounce_ptr,
288        }
289    }
290
291    pub fn size(&self) -> usize {
292        self.layout.size()
293    }
294
295    pub fn align(&self) -> usize {
296        self.layout.align()
297    }
298
299    pub fn as_ptr(&self) -> NonNull<u8> {
300        self.cpu_addr
301    }
302
303    pub fn dma_addr(&self) -> DmaAddr {
304        self.dma_addr
305    }
306
307    pub fn layout(&self) -> Layout {
308        self.layout
309    }
310
311    pub fn bounce_ptr(&self) -> Option<NonNull<u8>> {
312        self.bounce_ptr
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn coherent_handle_keeps_cpu_alias_and_allocator_address_distinct() {
322        let alias = NonNull::new(0x8000_usize as *mut u8).unwrap();
323        let allocation = NonNull::new(0x4000_usize as *mut u8).unwrap();
324        let layout = Layout::from_size_align(0x1000, 0x1000).unwrap();
325        let handle = unsafe { DmaAllocHandle::new(alias, allocation, 0x2000_u64.into(), layout) };
326
327        assert_eq!(handle.as_ptr(), alias);
328        assert_eq!(handle.allocation_ptr(), allocation);
329        assert_eq!(handle.dma_addr(), DmaAddr::from(0x2000_u64));
330    }
331}