use crate::allocator::Allocator;
use crate::allocator::AllocatorError;
use crate::allocator::BufferObject;
use crate::allocator::BufferUsage;
use crate::allocator::MappedBuffer;
use crate::format::Format;
use crate::gfx_api::SyncFile;
use crate::utils::clonecell::CloneCell;
use crate::utils::compat::IoctlNumber;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::once::Once;
use crate::utils::page_size::page_size;
use crate::utils::ptr_ext::PtrExt;
use crate::video::LINEAR_MODIFIER;
use crate::video::LINEAR_STRIDE_ALIGN;
use crate::video::Modifier;
use crate::video::dmabuf::DmaBuf;
use crate::video::dmabuf::DmaBufIds;
use crate::video::dmabuf::DmaBufPlane;
use crate::video::dmabuf::PlaneVec;
use crate::video::drm::Drm;
use jay_algorithms::oserror::OsError;
use jay_algorithms::oserror::OsErrorExt;
use jay_algorithms::oserror::OsErrorExt2;
use std::ptr;
use std::rc::Rc;
use thiserror::Error;
use uapi::_IOW;
use uapi::OwnedFd;
use uapi::c;
use uapi::c::F_SEAL_SHRINK;
use uapi::c::MAP_SHARED;
use uapi::c::MFD_ALLOW_SEALING;
use uapi::c::O_RDONLY;
use uapi::c::PROT_READ;
use uapi::c::PROT_WRITE;
use uapi::c::ioctl;
use uapi::c::mmap;
use uapi::c::munmap;
use uapi::map_err;
use uapi::open;
#[derive(Debug, Error)]
pub enum UdmabufError {
#[error("Could not open /dev/udmabuf")]
Open(#[source] OsError),
#[error("Only the linear modifier can be allocated")]
Modifier,
#[error("Could not create a memfd")]
Memfd(#[source] OsError),
#[error("Size calculation overflowed")]
Overflow,
#[error("Could not resize the memfd")]
Truncate(#[source] OsError),
#[error("Could not seal the memfd")]
Seal(#[source] OsError),
#[error("Could not create a dmabuf")]
CreateDmabuf(#[source] OsError),
#[error("Only a single plane is supported")]
Planes,
#[error("Stride is invalid")]
Stride,
#[error("Could not stat the dmabuf")]
Stat(#[source] OsError),
#[error("Dmabuf is too small for required size")]
Size,
#[error("Could not map dmabuf")]
Map(#[source] OsError),
}
#[derive(Default)]
pub struct UdmabufHolder {
logged: Once,
udmabuf: CloneCell<Option<Option<Rc<Udmabuf>>>>,
}
impl UdmabufHolder {
pub fn get(&self) -> Option<Rc<Udmabuf>> {
if let Some(u) = self.udmabuf.get() {
return u;
}
match Udmabuf::new() {
Ok(u) => {
log::info!("Opened /dev/udmabuf");
let u = Rc::new(u);
self.udmabuf.set(Some(Some(u.clone())));
Some(u)
}
Err(e) => {
self.logged.exec(|| {
log::warn!("Unable to open /dev/udmabuf: {}", ErrorFmt(&e));
});
if not_matches!(e, UdmabufError::Open(OsError(c::EPERM))) {
self.udmabuf.set(Some(None));
}
None
}
}
}
}
pub struct Udmabuf {
fd: OwnedFd,
}
impl Udmabuf {
pub fn new() -> Result<Self, UdmabufError> {
let fd = open("/dev/udmabuf", O_RDONLY, 0).map_os_err(UdmabufError::Open)?;
Ok(Self { fd })
}
pub fn create_dmabuf_from_memfd(
&self,
memfd: &OwnedFd,
offset: usize,
size: usize,
) -> Result<OwnedFd, UdmabufError> {
let mut cmd = udmabuf_create {
memfd: memfd.raw() as u32,
flags: UDMABUF_FLAGS_CLOEXEC,
offset: offset as u64,
size: size as u64,
};
let dmabuf = unsafe { ioctl(self.fd.raw(), UDMABUF_CREATE, &mut cmd) };
let dmabuf = map_err!(dmabuf)
.map(OwnedFd::new)
.map_os_err(UdmabufError::CreateDmabuf)?;
Ok(dmabuf)
}
pub fn create_dmabuf(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
) -> Result<Rc<DmaBuf>, UdmabufError> {
Ok(self.create_bo(dma_buf_ids, width, height, format)?.buf)
}
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
) -> Result<UdmabufBo, UdmabufError> {
let height = height as u64;
let width = width as u64;
if height > 1 << 16 || width > 1 << 16 {
return Err(UdmabufError::Overflow);
}
let stride = (width * format.bpp as u64).next_multiple_of(LINEAR_STRIDE_ALIGN);
let size_mask = page_size() as u64 - 1;
let size = (height * stride + size_mask) & !size_mask;
let memfd =
uapi::memfd_create("udmabuf", MFD_ALLOW_SEALING).map_os_err(UdmabufError::Memfd)?;
uapi::ftruncate(memfd.raw(), size as _).map_os_err(UdmabufError::Truncate)?;
uapi::fcntl_add_seals(memfd.raw(), F_SEAL_SHRINK).map_os_err(UdmabufError::Seal)?;
let dmabuf = self.create_dmabuf_from_memfd(&memfd, 0, size as _)?;
let mut planes = PlaneVec::new();
planes.push(DmaBufPlane {
offset: 0,
stride: stride as _,
fd: Rc::new(dmabuf),
});
let dmabuf = DmaBuf::new(
dma_buf_ids,
width as _,
height as _,
format,
LINEAR_MODIFIER,
planes,
);
Ok(UdmabufBo {
buf: dmabuf,
size: size as _,
})
}
}
impl Allocator for Udmabuf {
fn drm(&self) -> Option<&Drm> {
None
}
fn create_bo(
&self,
dma_buf_ids: &DmaBufIds,
width: i32,
height: i32,
format: &'static Format,
modifiers: &[Modifier],
_usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
if !modifiers.contains(&LINEAR_MODIFIER) {
return Err(UdmabufError::Modifier.into());
}
Ok(Rc::new(self.create_bo(
dma_buf_ids,
width,
height,
format,
)?))
}
fn import_dmabuf(
&self,
dmabuf: &Rc<DmaBuf>,
_usage: BufferUsage,
) -> Result<Rc<dyn BufferObject>, AllocatorError> {
if dmabuf.planes.len() != 1 {
return Err(UdmabufError::Planes.into());
}
if dmabuf.modifier != LINEAR_MODIFIER {
return Err(UdmabufError::Modifier.into());
}
let plane = &dmabuf.planes[0];
let height = dmabuf.height as u64;
let width = dmabuf.width as u64;
let stride = plane.stride as u64;
let offset = plane.offset as u64;
if height > 1 << 16 || width > 1 << 16 {
return Err(UdmabufError::Overflow.into());
}
if stride < width * dmabuf.format.bpp as u64 {
return Err(UdmabufError::Stride.into());
}
let size = offset + stride * height;
if usize::try_from(size).is_err() {
return Err(UdmabufError::Overflow.into());
}
let stat = uapi::fstat(plane.fd.raw()).map_os_err(UdmabufError::Stat)?;
if (stat.st_size as u64) < size {
return Err(UdmabufError::Size.into());
}
Ok(Rc::new(UdmabufBo {
buf: dmabuf.clone(),
size: size as usize,
}))
}
}
struct UdmabufBo {
buf: Rc<DmaBuf>,
size: usize,
}
impl BufferObject for UdmabufBo {
fn dmabuf(&self) -> &Rc<DmaBuf> {
&self.buf
}
fn map_read(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
self.map_write()
}
fn map_write(self: Rc<Self>) -> Result<Box<dyn MappedBuffer>, AllocatorError> {
let plane = &self.buf.planes[0];
unsafe {
let res = mmap(
ptr::null_mut(),
self.size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
plane.fd.raw(),
0,
);
if res == c::MAP_FAILED {
return Err(UdmabufError::Map(OsError::default()).into());
}
let offset = plane.offset as _;
let data =
std::slice::from_raw_parts_mut((res as *mut u8).add(offset), self.size - offset);
Ok(Box::new(UdmabufMap {
data,
stride: plane.stride as _,
ptr: res,
len: self.size,
_bo: self,
}))
}
}
fn take_initial_sync(&self) -> Option<SyncFile> {
None
}
}
struct UdmabufMap {
_bo: Rc<UdmabufBo>,
data: *mut [u8],
stride: i32,
ptr: *mut c::c_void,
len: usize,
}
impl Drop for UdmabufMap {
fn drop(&mut self) {
unsafe {
let res = munmap(self.ptr, self.len);
if let Err(e) = map_err!(res).to_os_error() {
log::error!("Could not unmap udmabuf: {e}");
}
}
}
}
impl MappedBuffer for UdmabufMap {
unsafe fn data(&self) -> &[u8] {
unsafe { self.data.deref() }
}
fn data_ptr(&self) -> *mut u8 {
self.data as _
}
fn stride(&self) -> i32 {
self.stride
}
}
impl From<UdmabufError> for AllocatorError {
fn from(value: UdmabufError) -> Self {
Self(Box::new(value))
}
}
#[repr(C)]
#[derive(Debug)]
struct udmabuf_create {
memfd: u32,
flags: u32,
offset: u64,
size: u64,
}
const UDMABUF_FLAGS_CLOEXEC: u32 = 0x01;
const UDMABUF_CREATE: IoctlNumber = _IOW::<udmabuf_create>(b'u' as u64, 0x42) as IoctlNumber;