dma_buf/
lib.rs

1// Copyright 2020-2021, Cerno
2// Licensed under the MIT License
3// See the LICENSE file or <http://opensource.org/licenses/MIT>
4
5#![allow(unsafe_code)]
6#![cfg_attr(
7    feature = "nightly",
8    feature(
9        type_privacy_lints,
10        non_exhaustive_omitted_patterns_lint,
11        strict_provenance
12    )
13)]
14#![cfg_attr(
15    feature = "nightly",
16    warn(
17        fuzzy_provenance_casts,
18        lossy_provenance_casts,
19        unnameable_types,
20        non_exhaustive_omitted_patterns,
21        clippy::empty_enum_variants_with_brackets
22    )
23)]
24#![doc = include_str!("../README.md")]
25
26use core::{error, ffi::c_void, fmt, ptr, slice};
27use std::{
28    io,
29    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
30};
31
32use rustix::{
33    fs::fstat,
34    mm::{mmap, munmap, MapFlags, ProtFlags},
35    param::page_size,
36};
37use tracing::{debug, debug_span, trace, trace_span, warn};
38
39mod ioctl;
40use ioctl::{
41    dma_buf_begin_cpu_read_access, dma_buf_begin_cpu_readwrite_access,
42    dma_buf_begin_cpu_write_access, dma_buf_end_cpu_read_access, dma_buf_end_cpu_readwrite_access,
43    dma_buf_end_cpu_write_access,
44};
45
46/// A DMA-Buf buffer
47#[derive(Debug)]
48pub struct DmaBuf(OwnedFd);
49
50impl DmaBuf {
51    /// Maps a `DmaBuf` for the CPU to access it
52    ///
53    /// # Panics
54    ///
55    /// If the buffer size reported by the kernel (`i64`) cannot fit into an `usize`.
56    ///
57    /// # Errors
58    ///
59    /// Will return an error if either the Buffer's length can't be retrieved, or if the mmap call
60    /// fails.
61    #[expect(
62        clippy::unwrap_in_result,
63        reason = "The kernel doesn't use the same types between stat and munmap, but it's not something one can recover from."
64    )]
65    pub fn memory_map(self) -> io::Result<MappedDmaBuf> {
66        debug!("Mapping DMA-Buf buffer with File Descriptor {:#?}", self.0);
67
68        let len = usize::try_from(fstat(&self.0)?.st_size)
69            .expect("Buffer size can't fit into mmap argument length")
70            .next_multiple_of(page_size());
71        trace!("Valid buffer, size {len}");
72
73        // SAFETY: It's unclear at this point what the exact safety requirements from mmap are, but
74        // our fd is valid and the length is aligned, so that's something.
75        let mapping_ptr = unsafe {
76            mmap(
77                ptr::null_mut(),
78                len,
79                ProtFlags::READ | ProtFlags::WRITE,
80                MapFlags::SHARED,
81                &self.0,
82                0,
83            )
84        }
85        .map(<*mut c_void>::cast::<u8>)?;
86
87        trace!("Memory Mapping Done");
88
89        Ok(MappedDmaBuf {
90            buf: self,
91            len,
92            mmap: mapping_ptr,
93        })
94    }
95}
96
97/// A `DmaBuf` mapped in memory
98pub struct MappedDmaBuf {
99    buf: DmaBuf,
100    len: usize,
101    mmap: *mut u8,
102}
103
104/// Error type to access a [`MappedDmaBuf`]
105#[derive(Debug)]
106pub enum BufferError {
107    /// An Error occured while accessing the buffer file descriptor
108    FdAccess {
109        /// Description of the Error
110        reason: String,
111
112        /// Source of the Error
113        source: io::Error,
114    },
115
116    /// An Error occured in the closure
117    Closure(Box<dyn error::Error>),
118}
119
120impl fmt::Display for BufferError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            BufferError::FdAccess { reason, .. } => {
124                f.write_fmt(format_args!("Could not access the buffer: {reason}"))
125            }
126            BufferError::Closure(error) => {
127                f.write_fmt(format_args!("The closure returned an error: {error}"))
128            }
129        }
130    }
131}
132
133impl error::Error for BufferError {
134    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
135        match self {
136            BufferError::FdAccess { source, .. } => Some(source),
137            BufferError::Closure(error) => Some(error.as_ref()),
138        }
139    }
140}
141
142impl MappedDmaBuf {
143    fn as_slice(&self) -> &[u8] {
144        // SAFETY: We know that the pointer is valid, and the buffer length is at least equal to
145        // self.len bytes. The backing buffer won't be mutated by the kernel, our structure is the
146        // sole owner of the pointer, and it won't be mutated in our code either, so we're safe.
147        unsafe { slice::from_raw_parts(self.mmap, self.len) }
148    }
149
150    fn as_slice_mut(&mut self) -> &mut [u8] {
151        // SAFETY: We know that the pointer is valid, and the buffer length is at least equal to
152        // self.len bytes. The backing buffer won't be mutated by the kernel, our structure is the
153        // sole owner of the pointer, and it won't be mutated in our code either, so we're safe.
154        unsafe { slice::from_raw_parts_mut(self.mmap, self.len) }
155    }
156
157    /// Calls a closure to read the buffer content
158    ///
159    /// DMA-Buf requires the user-space to call the `DMA_BUF_IOCTL_SYNC` ioctl before and after any
160    /// CPU access to a buffer in order to maintain the cache coherency. The closure will be run
161    /// with those primitives called for a read access from the CPU.
162    ///
163    /// The result of the closure will be returned.
164    ///
165    /// # Errors
166    ///
167    /// Will return [Error] if the underlying ioctl or the closure fails
168    pub fn read<A, F, R>(&self, f: F, arg: Option<A>) -> Result<R, BufferError>
169    where
170        F: Fn(&[u8], Option<A>) -> Result<R, Box<dyn error::Error>>,
171    {
172        trace_span!("Buffer Read Access").in_scope(|| {
173            trace_span!("dma-buf begin access ioctl")
174                .in_scope(|| dma_buf_begin_cpu_read_access(self.buf.as_fd()))?;
175
176            let ret = debug_span!("Closure Execution").in_scope(|| {
177                let bytes = self.as_slice();
178
179                f(bytes, arg)
180                    .inspect(|_| debug!("Closure done without error"))
181                    .map_err(|e| {
182                        warn!("Closure encountered an error {}", e);
183                        BufferError::Closure(e)
184                    })
185            });
186
187            trace_span!("dma-buf end access ioctl")
188                .in_scope(|| dma_buf_end_cpu_read_access(self.buf.as_fd()))?;
189
190            ret
191        })
192    }
193
194    /// Calls a closure to read from and write to the buffer content
195    ///
196    /// DMA-Buf requires the user-space to call the `DMA_BUF_IOCTL_SYNC` ioctl before and after any
197    /// CPU access to a buffer in order to maintain the cache coherency. The closure will be run
198    /// with those primitives called for a read and write access from the CPU.
199    ///
200    /// The result of the closure will be returned on success. On failure, the closure must return
201    /// `Error::Closure`
202    ///
203    /// # Errors
204    ///
205    /// Will return [Error] if the underlying ioctl or the closure fails
206    pub fn readwrite<A, F, R>(&mut self, f: F, arg: Option<A>) -> Result<R, BufferError>
207    where
208        F: Fn(&mut [u8], Option<A>) -> Result<R, Box<dyn error::Error>>,
209    {
210        trace_span!("Buffer Read / Write Access").in_scope(|| {
211            trace_span!("dma-buf begin access ioctl")
212                .in_scope(|| dma_buf_begin_cpu_readwrite_access(self.buf.as_fd()))?;
213
214            let ret = debug_span!("Closure Execution").in_scope(|| {
215                let bytes = self.as_slice_mut();
216
217                f(bytes, arg)
218                    .inspect(|_| debug!("Closure done without error"))
219                    .map_err(|e| {
220                        warn!("Closure encountered an error {}", e);
221                        BufferError::Closure(e)
222                    })
223            });
224
225            trace_span!("dma-buf end access ioctl")
226                .in_scope(|| dma_buf_end_cpu_readwrite_access(self.buf.as_fd()))?;
227
228            ret
229        })
230    }
231
232    /// Calls a closure to read from and write to the buffer content
233    ///
234    /// DMA-Buf requires the user-space to call the `DMA_BUF_IOCTL_SYNC` ioctl before and after any
235    /// CPU access to a buffer in order to maintain the cache coherency. The closure will be run
236    /// with those primitives called for a read and write access from the CPU.
237    ///
238    /// The closure must return () on success. On failure, the closure must return `Error::Closure`.
239    ///
240    /// # Errors
241    ///
242    /// Will return [Error] if the underlying ioctl or the closure fails
243    pub fn write<A, F>(&mut self, f: F, arg: Option<A>) -> Result<(), BufferError>
244    where
245        F: Fn(&mut [u8], Option<A>) -> Result<(), Box<dyn error::Error>>,
246    {
247        trace_span!("Buffer Write Access").in_scope(|| {
248            trace_span!("dma-buf begin access ioctl")
249                .in_scope(|| dma_buf_begin_cpu_write_access(self.buf.as_fd()))?;
250
251            let ret = debug_span!("Closure Execution").in_scope(|| {
252                let bytes = self.as_slice_mut();
253
254                f(bytes, arg)
255                    .inspect(|()| debug!("Closure done without error"))
256                    .map_err(|e| {
257                        warn!("Closure encountered an error {}", e);
258                        BufferError::Closure(e)
259                    })
260            });
261
262            trace_span!("dma-buf end access ioctl")
263                .in_scope(|| dma_buf_end_cpu_write_access(self.buf.as_fd()))?;
264
265            ret
266        })
267    }
268}
269
270impl From<OwnedFd> for DmaBuf {
271    fn from(owned: OwnedFd) -> Self {
272        Self(owned)
273    }
274}
275
276impl AsFd for DmaBuf {
277    fn as_fd(&self) -> BorrowedFd<'_> {
278        self.0.as_fd()
279    }
280}
281
282impl AsRawFd for DmaBuf {
283    fn as_raw_fd(&self) -> RawFd {
284        self.0.as_raw_fd()
285    }
286}
287
288impl AsFd for MappedDmaBuf {
289    fn as_fd(&self) -> BorrowedFd<'_> {
290        self.buf.as_fd()
291    }
292}
293
294impl AsRawFd for MappedDmaBuf {
295    fn as_raw_fd(&self) -> RawFd {
296        self.buf.as_raw_fd()
297    }
298}
299
300impl FromRawFd for DmaBuf {
301    unsafe fn from_raw_fd(fd: RawFd) -> Self {
302        debug!("Importing DMABuf from File Descriptor {}", fd);
303
304        // SAFETY: We're just forwarding the FromRawFd implementation to our inner OwnerFd type.
305        // We're having exactly the same safety guarantees.
306        Self(unsafe { OwnedFd::from_raw_fd(fd) })
307    }
308}
309
310impl fmt::Debug for MappedDmaBuf {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        f.debug_struct("MappedDmaBuf")
313            .field("DmaBuf", &self.buf)
314            .field("len", &self.len)
315            .field("address", &self.mmap)
316            .finish()
317    }
318}
319
320impl Drop for MappedDmaBuf {
321    fn drop(&mut self) {
322        // SAFETY: It's not clear what rustix expects from a safety perspective, but our pointer is
323        // valid, and is a void pointer at least.
324        if unsafe { munmap(self.mmap.cast::<c_void>(), self.len) }.is_err() {
325            warn!("unmap failed!");
326        }
327    }
328}