1
0
Fork 0
forked from wry/wry
wry/src/drm/drm.rs
2022-03-09 14:01:21 +01:00

395 lines
12 KiB
Rust

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<Ustring, DrmError> {
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<Ustring, DrmError> {
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<OwnedFd, DrmError> {
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<Self, DrmError> {
Ok(Self {
fd: reopen(fd, allow_downgrade)?,
})
}
pub fn raw(&self) -> c::c_int {
self.fd.raw()
}
pub fn dup_unprivileged(&self) -> Result<Self, DrmError> {
Self::new(self.fd.raw(), true)
}
pub fn get_device(&self) -> Result<DrmDevice, DrmError> {
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<Item = (c::c_int, &'a CStr)> + '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<Self::Item> {
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()
}
}
}