pub mod constants;
use std::error::Error;
use std::fmt::{self, Debug, Display, Formatter};
use std::io;
use std::os::unix::io::AsFd;
use std::path::{Path, PathBuf};
use rustix::fs::{fstat, major, minor, stat, Dev as dev_t, Stat};
use crate::node::constants::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DrmNode {
dev: dev_t,
ty: NodeType,
}
impl DrmNode {
pub fn from_file<A: AsFd>(file: A) -> Result<DrmNode, CreateDrmNodeError> {
let stat = fstat(file).map_err(Into::<io::Error>::into)?;
DrmNode::from_stat(stat)
}
pub fn from_path<A: AsRef<Path>>(path: A) -> Result<DrmNode, CreateDrmNodeError> {
let stat = stat(path.as_ref()).map_err(Into::<io::Error>::into)?;
DrmNode::from_stat(stat)
}
pub fn from_stat(stat: Stat) -> Result<DrmNode, CreateDrmNodeError> {
let dev = stat.st_rdev;
DrmNode::from_dev_id(dev)
}
pub fn from_dev_id(dev: dev_t) -> Result<Self, CreateDrmNodeError> {
if !is_device_drm(dev) {
return Err(CreateDrmNodeError::NotDrmNode);
}
let ty = NodeType::from_dev_id(dev)?;
Ok(DrmNode { dev, ty })
}
pub fn ty(&self) -> NodeType {
self.ty
}
pub fn dev_id(&self) -> dev_t {
self.dev
}
pub fn dev_path(&self) -> Option<PathBuf> {
node_path(self, self.ty).ok()
}
pub fn dev_path_with_type(&self, ty: NodeType) -> Option<PathBuf> {
node_path(self, ty).ok()
}
pub fn node_with_type(&self, ty: NodeType) -> Option<Result<DrmNode, CreateDrmNodeError>> {
self.dev_path_with_type(ty).map(DrmNode::from_path)
}
pub fn major(&self) -> u32 {
major(self.dev_id())
}
pub fn minor(&self) -> u32 {
minor(self.dev_id())
}
pub fn has_render(&self) -> bool {
#[cfg(target_os = "linux")]
{
node_path(self, NodeType::Render).is_ok()
}
#[cfg(target_os = "freebsd")]
{
false
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
{
false
}
}
}
impl Display for DrmNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.ty.minor_name_prefix(), minor(self.dev_id()))
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum NodeType {
Primary,
Control,
Render,
}
impl NodeType {
const MINOR_OFFSET: u32 = 6;
#[cfg(not(target_os = "linux"))]
const MINOR_MASK: u32 = 0b11 << Self::MINOR_OFFSET;
pub fn minor_name_prefix(&self) -> &'static str {
match self {
NodeType::Primary => PRIMARY_NAME,
NodeType::Control => CONTROL_NAME,
NodeType::Render => RENDER_NAME,
}
}
fn from_dev_id(dev: dev_t) -> Result<Self, CreateDrmNodeError> {
Ok(match minor(dev) >> Self::MINOR_OFFSET {
0 => Self::Primary,
1 => Self::Control,
2 => Self::Render,
_ => return Err(CreateDrmNodeError::NotDrmNode),
})
}
#[cfg(not(target_os = "linux"))]
fn minor_index(&self) -> u32 {
match self {
NodeType::Primary => 0,
NodeType::Control => 1,
NodeType::Render => 2,
}
}
#[cfg(not(target_os = "linux"))]
fn minor_base(&self) -> u32 {
self.minor_index() << Self::MINOR_OFFSET
}
}
impl Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(self, f)
}
}
#[derive(Debug)]
pub enum CreateDrmNodeError {
Io(io::Error),
NotDrmNode,
}
impl Display for CreateDrmNodeError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => Display::fmt(err, f),
Self::NotDrmNode => {
f.write_str("the provided file descriptor does not refer to a DRM node")
}
}
}
}
impl Error for CreateDrmNodeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(err) => Some(err),
Self::NotDrmNode => None,
}
}
}
impl From<io::Error> for CreateDrmNodeError {
#[inline]
fn from(err: io::Error) -> Self {
CreateDrmNodeError::Io(err)
}
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
fn devname(dev: dev_t) -> Option<String> {
use std::os::raw::c_char;
let mut dev_name = vec![0u8; 255];
let buf: *mut c_char = unsafe {
libc::devname_r(
dev,
libc::S_IFCHR, dev_name.as_mut_ptr() as *mut c_char,
dev_name.len() as _,
)
};
if buf.is_null() {
return None;
}
unsafe { dev_name.set_len(libc::strlen(buf)) };
Some(String::from_utf8(dev_name).expect("Returned device name is not valid utf8"))
}
#[cfg(target_os = "linux")]
pub fn is_device_drm(dev: dev_t) -> bool {
let path = format!("/sys/dev/char/{}:{}/device/drm", major(dev), minor(dev));
stat(path.as_str()).is_ok()
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub fn is_device_drm(dev: dev_t) -> bool {
devname(dev).map_or(false, |dev_name| {
dev_name.starts_with("drm/")
|| dev_name.starts_with("dri/card")
|| dev_name.starts_with("dri/control")
|| dev_name.starts_with("dri/renderD")
})
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly")))]
pub fn is_device_drm(dev: dev_t) -> bool {
major(dev) == DRM_MAJOR
}
pub fn path_to_type<P: AsRef<Path>>(path: P, ty: NodeType) -> io::Result<PathBuf> {
let stat = stat(path.as_ref()).map_err(Into::<io::Error>::into)?;
dev_path(stat.st_rdev, ty)
}
pub fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
dev_path(node.dev, ty)
}
#[cfg(target_os = "linux")]
pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
use std::fs;
use std::io::ErrorKind;
if !is_device_drm(dev) {
return Err(io::Error::new(
ErrorKind::NotFound,
format!("{}:{} is no DRM device", major(dev), minor(dev)),
));
}
let read = fs::read_dir(format!(
"/sys/dev/char/{}:{}/device/drm",
major(dev),
minor(dev)
))?;
for entry in read.flatten() {
let name = entry.file_name();
let name = name.to_string_lossy();
if name.starts_with(ty.minor_name_prefix()) {
let path = Path::new("/dev/dri").join(&*name);
if path.exists() {
return Ok(path);
}
}
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Could not find node of type {} from DRM device {}:{}",
ty,
major(dev),
minor(dev)
),
))
}
#[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
use std::io::ErrorKind;
if !is_device_drm(dev) {
return Err(io::Error::new(
ErrorKind::NotFound,
format!("{}:{} is no DRM device", major(dev), minor(dev)),
));
}
if let Some(dev_name) = devname(dev) {
let suffix = dev_name.trim_start_matches(|c: char| !c.is_numeric());
if let Ok(old_id) = suffix.parse::<u32>() {
let id = old_id & !NodeType::MINOR_MASK | ty.minor_base();
let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id));
if path.exists() {
return Ok(path);
}
}
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Could not find node of type {} from DRM device {}:{}",
ty,
major(dev),
minor(dev)
),
))
}
#[cfg(not(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly")))]
pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
use std::io::ErrorKind;
if !is_device_drm(dev) {
return Err(io::Error::new(
ErrorKind::NotFound,
format!("{}:{} is no DRM device", major(dev), minor(dev)),
));
}
let old_id = minor(dev);
let id = old_id & !NodeType::MINOR_MASK | ty.minor_base();
let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id));
if path.exists() {
return Ok(path);
}
Err(io::Error::new(
ErrorKind::NotFound,
format!(
"Could not find node of type {} for DRM device {}:{}",
ty,
major(dev),
minor(dev)
),
))
}