use crate::utils::bitflags::BitflagsExt; use crate::utils::debug_fn::debug_fn; use crate::utils::ptr_ext::PtrExt; use bstr::ByteSlice; use std::ffi::{CStr, CString}; use std::fmt::{Debug, Formatter}; use std::ptr; use thiserror::Error; use uapi::c::c_char; use uapi::{c, Errno, OwnedFd, Ustring}; #[derive(Debug, Error)] pub enum DrmError { #[error("Could not reopen a node")] ReopenNode(#[source] crate::utils::oserror::OsError), #[error("Could not retrieve the render node name")] RenderNodeName, #[error("Could not retrieve the device node name")] DeviceNodeName, #[error("Could not retrieve device")] GetDevice(#[source] crate::utils::oserror::OsError), } #[allow(dead_code)] const DRM_NODE_PRIMARY: c::c_int = 0; #[allow(dead_code)] const DRM_NODE_CONTROL: c::c_int = 1; pub const DRM_NODE_RENDER: c::c_int = 2; const DRM_NODE_MAX: c::c_int = 3; const DRM_BUS_PCI: c::c_int = 0; const DRM_BUS_USB: c::c_int = 1; const DRM_BUS_PLATFORM: c::c_int = 2; const DRM_BUS_HOST1X: c::c_int = 3; #[link(name = "drm")] extern "C" { fn drmIsMaster(fd: c::c_int) -> c::c_int; fn drmModeCreateLease( fd: c::c_int, o: *const u32, num_objects: c::c_int, flags: c::c_int, lessee_id: *mut u32, ) -> c::c_int; fn drmGetNodeTypeFromFd(fd: c::c_int) -> c::c_int; fn drmGetRenderDeviceNameFromFd(fd: c::c_int) -> *mut c::c_char; fn drmGetDeviceNameFromFd2(fd: c::c_int) -> *mut c::c_char; fn drmFreeDevice(device: *mut *mut drmDevice); fn drmGetDevice(fd: c::c_int, device: *mut *mut drmDevice) -> c::c_int; } fn render_node_name(fd: c::c_int) -> Result { unsafe { let name = drmGetRenderDeviceNameFromFd(fd); if name.is_null() { Err(DrmError::RenderNodeName) } else { Ok(CString::from_raw(name).into()) } } } fn device_node_name(fd: c::c_int) -> Result { unsafe { let name = drmGetDeviceNameFromFd2(fd); if name.is_null() { Err(DrmError::DeviceNodeName) } else { Ok(CString::from_raw(name).into()) } } } fn reopen(fd: c::c_int, allow_downgrade: bool) -> Result { unsafe { if drmIsMaster(fd) != 0 { let mut lessee = 0; let lease_fd = drmModeCreateLease(fd, ptr::null(), 0, c::O_CLOEXEC, &mut lessee); if lease_fd >= 0 { return Ok(OwnedFd::new(lease_fd)); } } let path = if drmGetNodeTypeFromFd(fd) == DRM_NODE_RENDER { uapi::format_ustr!("/proc/self/fd/{}", fd) } else if allow_downgrade { render_node_name(fd)? } else { device_node_name(fd)? }; match uapi::open(&path, c::O_RDWR | c::O_CLOEXEC, 0) { Ok(f) => Ok(f), Err(e) => Err(DrmError::ReopenNode(e.into())), } } } pub struct Drm { fd: OwnedFd, } impl Drm { pub fn new(fd: c::c_int, allow_downgrade: bool) -> Result { Ok(Self { fd: reopen(fd, allow_downgrade)?, }) } pub fn raw(&self) -> c::c_int { self.fd.raw() } pub fn dup_unprivileged(&self) -> Result { Self::new(self.fd.raw(), true) } pub fn get_device(&self) -> Result { unsafe { let mut dev = ptr::null_mut(); if drmGetDevice(self.fd.raw(), &mut dev) < 0 { return Err(DrmError::GetDevice(Errno::default().into())); } Ok(DrmDevice { dev }) } } } #[repr(C)] struct drmPciBusInfo { domain: u16, bus: u8, dev: u8, func: u8, } #[repr(C)] struct drmUsbBusInfo { bus: u8, dev: u8, } const DRM_PLATFORM_DEVICE_NAME_LEN: usize = 512; #[repr(C)] struct drmPlatformBusInfo { fullname: [c::c_char; DRM_PLATFORM_DEVICE_NAME_LEN], } const DRM_HOST1X_DEVICE_NAME_LEN: usize = 512; #[repr(C)] struct drmHost1xBusInfo { fullname: [c::c_char; DRM_HOST1X_DEVICE_NAME_LEN], } #[repr(C)] union businfo { pci: *mut drmPciBusInfo, usb: *mut drmUsbBusInfo, platform: *mut drmPlatformBusInfo, host1x: *mut drmHost1xBusInfo, } #[repr(C)] struct drmPciDeviceInfo { vendor_id: u16, device_id: u16, subvendor_id: u16, subdevice_id: u16, revision_id: u8, } #[repr(C)] struct drmUsbDeviceInfo { vendor: u16, product: u16, } #[repr(C)] struct drmPlatformDeviceInfo { compatible: *mut *mut c::c_char, } #[repr(C)] struct drmHost1xDeviceInfo { compatible: *mut *mut c::c_char, } #[repr(C)] union deviceinfo { pci: *mut drmPciDeviceInfo, usb: *mut drmUsbDeviceInfo, platform: *mut drmPlatformDeviceInfo, host1x: *mut drmHost1xDeviceInfo, } #[repr(C)] struct drmDevice { nodes: *mut *mut c::c_char, available_nodes: c::c_int, bustype: c::c_int, businfo: businfo, deviceinfo: deviceinfo, } pub struct DrmDevice { dev: *mut drmDevice, } impl Drop for DrmDevice { fn drop(&mut self) { unsafe { drmFreeDevice(&mut self.dev); } } } impl DrmDevice { pub fn nodes<'a>(&'a self) -> impl Iterator + 'a { struct Iter<'a> { next: usize, dev: &'a DrmDevice, } impl<'a> Iterator for Iter<'a> { type Item = (c::c_int, &'a CStr); fn next(&mut self) -> Option { unsafe { let dev = self.dev.dev.deref(); while self.next < DRM_NODE_MAX as _ { let idx = self.next; self.next += 1; if dev.available_nodes.contains(1 << idx) { return Some((idx as _, CStr::from_ptr(*dev.nodes.add(idx)))); } } None } } } Iter { next: 0, dev: self } } } impl Debug for DrmDevice { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { struct StrStr<'a> { v: &'a [*mut c::c_char], } impl Debug for StrStr<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut list = f.debug_list(); for &v in self.v { if v.is_null() { list.entry(&v); } else { unsafe { list.entry(&CStr::from_ptr(v)); } } } list.finish() } } impl<'a> StrStr<'a> { fn from_nt(nt: *const *mut c_char) -> Self { unsafe { let mut num = 0; let mut tmp = nt; while !tmp.deref().is_null() { num += 1; tmp = tmp.add(1); } Self { v: std::slice::from_raw_parts(nt, num), } } } } let mut ds = f.debug_struct("DrmDevice"); unsafe { let dev = self.dev.deref(); let nodes = std::slice::from_raw_parts(dev.nodes, DRM_NODE_MAX as _); ds.field( "available_nodes", &debug_fn(|f| write!(f, "0b{:b}", dev.available_nodes)), ); ds.field("nodes", &StrStr { v: nodes }); ds.field("bustype", &dev.bustype); match dev.bustype { DRM_BUS_PCI => { ds.field( "businfo", &debug_fn(|f| { let pci = dev.businfo.pci.deref(); f.debug_struct("drmPciBusInfo") .field("domain", &pci.domain) .field("bus", &pci.bus) .field("dev", &pci.dev) .field("func", &pci.func) .finish() }), ); ds.field( "deviceinfo", &debug_fn(|f| { let pci = dev.deviceinfo.pci.deref(); f.debug_struct("drmPciDeviceInfo") .field("vendor_id", &pci.vendor_id) .field("device_id", &pci.device_id) .field("subvendor_id", &pci.subvendor_id) .field("subdevice_id", &pci.subdevice_id) .field("revision_id", &pci.revision_id) .finish() }), ); } DRM_BUS_USB => { ds.field( "businfo", &debug_fn(|f| { let usb = dev.businfo.usb.deref(); f.debug_struct("drmUsbBusInfo") .field("bus", &usb.bus) .field("dev", &usb.dev) .finish() }), ); ds.field( "deviceinfo", &debug_fn(|f| { let usb = dev.deviceinfo.usb.deref(); f.debug_struct("drmUsbDeviceInfo") .field("vendor", &usb.vendor) .field("product", &usb.product) .finish() }), ); } DRM_BUS_PLATFORM => { ds.field( "businfo", &debug_fn(|f| { let platform = dev.businfo.platform.deref(); f.debug_struct("drmPlatformBusInfo") .field( "fullname", &CStr::from_ptr(platform.fullname.as_ptr()) .to_bytes() .as_bstr(), ) .finish() }), ); ds.field( "deviceinfo", &debug_fn(|f| { let platform = dev.deviceinfo.platform.deref(); f.debug_struct("drmPlatformDeviceInfo") .field("compatible", &StrStr::from_nt(platform.compatible)) .finish() }), ); } DRM_BUS_HOST1X => { ds.field( "businfo", &debug_fn(|f| { let host1x = dev.businfo.host1x.deref(); f.debug_struct("drmHost1xBusInfo") .field( "fullname", &CStr::from_ptr(host1x.fullname.as_ptr()) .to_bytes() .as_bstr(), ) .finish() }), ); ds.field( "deviceinfo", &debug_fn(|f| { let host1x = dev.deviceinfo.host1x.deref(); f.debug_struct("drmHost1xDeviceInfo") .field("compatible", &StrStr::from_nt(host1x.compatible)) .finish() }), ); } _ => {} } ds.finish() } } }