#![allow(unsafe_code)]
#![cfg_attr(
feature = "nightly",
feature(
type_privacy_lints,
non_exhaustive_omitted_patterns_lint,
strict_provenance
)
)]
#![cfg_attr(
feature = "nightly",
warn(
fuzzy_provenance_casts,
lossy_provenance_casts,
unnameable_types,
non_exhaustive_omitted_patterns,
clippy::empty_enum_variants_with_brackets
)
)]
#![doc = include_str!("../README.md")]
use core::{error, ffi::c_void, fmt, ptr, slice};
use std::{
io,
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
};
use rustix::{
fs::fstat,
mm::{mmap, munmap, MapFlags, ProtFlags},
param::page_size,
};
use tracing::{debug, debug_span, trace, trace_span, warn};
mod ioctl;
use ioctl::{
dma_buf_begin_cpu_read_access, dma_buf_begin_cpu_readwrite_access,
dma_buf_begin_cpu_write_access, dma_buf_end_cpu_read_access, dma_buf_end_cpu_readwrite_access,
dma_buf_end_cpu_write_access,
};
#[derive(Debug)]
pub struct DmaBuf(OwnedFd);
impl DmaBuf {
#[expect(
clippy::unwrap_in_result,
reason = "The kernel doesn't use the same types between stat and munmap, but it's not something one can recover from."
)]
pub fn memory_map(self) -> io::Result<MappedDmaBuf> {
debug!("Mapping DMA-Buf buffer with File Descriptor {:#?}", self.0);
let len = usize::try_from(fstat(&self.0)?.st_size)
.expect("Buffer size can't fit into mmap argument length")
.next_multiple_of(page_size());
trace!("Valid buffer, size {len}");
let mapping_ptr = unsafe {
mmap(
ptr::null_mut(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED,
&self.0,
0,
)
}
.map(<*mut c_void>::cast::<u8>)?;
trace!("Memory Mapping Done");
Ok(MappedDmaBuf {
buf: self,
len,
mmap: mapping_ptr,
})
}
}
pub struct MappedDmaBuf {
buf: DmaBuf,
len: usize,
mmap: *mut u8,
}
#[derive(Debug)]
pub enum BufferError {
FdAccess {
reason: String,
source: io::Error,
},
Closure(Box<dyn error::Error>),
}
impl fmt::Display for BufferError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BufferError::FdAccess { reason, .. } => {
f.write_fmt(format_args!("Could not access the buffer: {reason}"))
}
BufferError::Closure(error) => {
f.write_fmt(format_args!("The closure returned an error: {error}"))
}
}
}
}
impl error::Error for BufferError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
BufferError::FdAccess { source, .. } => Some(source),
BufferError::Closure(error) => Some(error.as_ref()),
}
}
}
impl MappedDmaBuf {
fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.mmap, self.len) }
}
fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.mmap, self.len) }
}
pub fn read<A, F, R>(&self, f: F, arg: Option<A>) -> Result<R, BufferError>
where
F: Fn(&[u8], Option<A>) -> Result<R, Box<dyn error::Error>>,
{
trace_span!("Buffer Read Access").in_scope(|| {
trace_span!("dma-buf begin access ioctl")
.in_scope(|| dma_buf_begin_cpu_read_access(self.buf.as_fd()))?;
let ret = debug_span!("Closure Execution").in_scope(|| {
let bytes = self.as_slice();
f(bytes, arg)
.inspect(|_| debug!("Closure done without error"))
.map_err(|e| {
warn!("Closure encountered an error {}", e);
BufferError::Closure(e)
})
});
trace_span!("dma-buf end access ioctl")
.in_scope(|| dma_buf_end_cpu_read_access(self.buf.as_fd()))?;
ret
})
}
pub fn readwrite<A, F, R>(&mut self, f: F, arg: Option<A>) -> Result<R, BufferError>
where
F: Fn(&mut [u8], Option<A>) -> Result<R, Box<dyn error::Error>>,
{
trace_span!("Buffer Read / Write Access").in_scope(|| {
trace_span!("dma-buf begin access ioctl")
.in_scope(|| dma_buf_begin_cpu_readwrite_access(self.buf.as_fd()))?;
let ret = debug_span!("Closure Execution").in_scope(|| {
let bytes = self.as_slice_mut();
f(bytes, arg)
.inspect(|_| debug!("Closure done without error"))
.map_err(|e| {
warn!("Closure encountered an error {}", e);
BufferError::Closure(e)
})
});
trace_span!("dma-buf end access ioctl")
.in_scope(|| dma_buf_end_cpu_readwrite_access(self.buf.as_fd()))?;
ret
})
}
pub fn write<A, F>(&mut self, f: F, arg: Option<A>) -> Result<(), BufferError>
where
F: Fn(&mut [u8], Option<A>) -> Result<(), Box<dyn error::Error>>,
{
trace_span!("Buffer Write Access").in_scope(|| {
trace_span!("dma-buf begin access ioctl")
.in_scope(|| dma_buf_begin_cpu_write_access(self.buf.as_fd()))?;
let ret = debug_span!("Closure Execution").in_scope(|| {
let bytes = self.as_slice_mut();
f(bytes, arg)
.inspect(|()| debug!("Closure done without error"))
.map_err(|e| {
warn!("Closure encountered an error {}", e);
BufferError::Closure(e)
})
});
trace_span!("dma-buf end access ioctl")
.in_scope(|| dma_buf_end_cpu_write_access(self.buf.as_fd()))?;
ret
})
}
}
impl From<OwnedFd> for DmaBuf {
fn from(owned: OwnedFd) -> Self {
Self(owned)
}
}
impl AsFd for DmaBuf {
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for DmaBuf {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl AsFd for MappedDmaBuf {
fn as_fd(&self) -> BorrowedFd<'_> {
self.buf.as_fd()
}
}
impl AsRawFd for MappedDmaBuf {
fn as_raw_fd(&self) -> RawFd {
self.buf.as_raw_fd()
}
}
impl FromRawFd for DmaBuf {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
debug!("Importing DMABuf from File Descriptor {}", fd);
Self(unsafe { OwnedFd::from_raw_fd(fd) })
}
}
impl fmt::Debug for MappedDmaBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MappedDmaBuf")
.field("DmaBuf", &self.buf)
.field("len", &self.len)
.field("address", &self.mmap)
.finish()
}
}
impl Drop for MappedDmaBuf {
fn drop(&mut self) {
if unsafe { munmap(self.mmap.cast::<c_void>(), self.len) }.is_err() {
warn!("unmap failed!");
}
}
}