diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 445a5ec..ebcfdc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: command: update - uses: actions-rs/toolchain@v1 with: - toolchain: 1.65.0 + toolchain: 1.66.0 profile: minimal components: clippy default: true @@ -88,12 +88,12 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git - key: ${{ runner.os }}-cargo-rust_1.65.0-${{ hashFiles('**/Cargo.toml') }} + key: ${{ runner.os }}-cargo-rust_1.66.0-${{ hashFiles('**/Cargo.toml') }} - name: Build cache uses: actions/cache@v3 with: path: target - key: ${{ runner.os }}-build-rust_1.65.0-check-${{ hashFiles('**/Cargo.toml') }} + key: ${{ runner.os }}-build-rust_1.66.0-check-${{ hashFiles('**/Cargo.toml') }} - name: Clippy uses: actions-rs/clippy-check@v1 with: diff --git a/Cargo.toml b/Cargo.toml index 56dc0c8..77a15ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ version = "0.12.0" license = "MIT" authors = ["Tyler Slabinski ", "Victoria Brekenfeld "] exclude = [".gitignore", ".github"] -rust-version = "1.65" +rust-version = "1.66" edition = "2021" [dependencies] diff --git a/drm-ffi/Cargo.toml b/drm-ffi/Cargo.toml index 8561e53..927a53a 100644 --- a/drm-ffi/Cargo.toml +++ b/drm-ffi/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/Smithay/drm-rs" version = "0.8.0" license = "MIT" authors = ["Tyler Slabinski "] -rust-version = "1.65" +rust-version = "1.66" edition = "2021" [dependencies] diff --git a/drm-ffi/drm-sys/Cargo.toml b/drm-ffi/drm-sys/Cargo.toml index e040b26..4e3b722 100644 --- a/drm-ffi/drm-sys/Cargo.toml +++ b/drm-ffi/drm-sys/Cargo.toml @@ -6,7 +6,7 @@ version = "0.7.0" authors = ["Tyler Slabinski "] license = "MIT" build = "build.rs" -rust-version = "1.65" +rust-version = "1.66" edition = "2021" [features] diff --git a/drm-ffi/src/mode.rs b/drm-ffi/src/mode.rs index 1b30b8f..2fe66d3 100644 --- a/drm-ffi/src/mode.rs +++ b/drm-ffi/src/mode.rs @@ -396,7 +396,7 @@ pub fn get_connector( } else { &tmp_mode as *const _ as _ }, - count_modes: if force_probe { 0 } else { 1 }, + count_modes: u32::from(!force_probe), ..Default::default() }; @@ -427,13 +427,7 @@ pub fn get_connector( prop_values_ptr: map_ptr!(&prop_values), count_modes: match &modes { Some(b) => b.capacity() as _, - None => { - if force_probe { - 0 - } else { - 1 - } - } + None => u32::from(!force_probe), }, count_props: map_len!(&props), count_encoders: map_len!(&encoders), diff --git a/src/lib.rs b/src/lib.rs index f5299a7..8bbe60c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ pub(crate) mod util; pub mod buffer; pub mod control; +pub mod node; use std::ffi::{OsStr, OsString}; use std::time::Duration; diff --git a/src/node/constants.rs b/src/node/constants.rs new file mode 100644 index 0000000..4828842 --- /dev/null +++ b/src/node/constants.rs @@ -0,0 +1,45 @@ +//! OS-Specific DRM constants. + +/// DRM major value. +#[cfg(target_os = "dragonfly")] +pub const DRM_MAJOR: u32 = 145; + +/// DRM major value. +#[cfg(target_os = "netbsd")] +pub const DRM_MAJOR: u32 = 34; + +/// DRM major value. +#[cfg(all(target_os = "openbsd", target_arch = "x86"))] +pub const DRM_MAJOR: u32 = 88; + +/// DRM major value. +#[cfg(all(target_os = "openbsd", not(target_arch = "x86")))] +pub const DRM_MAJOR: u32 = 87; + +/// DRM major value. +#[cfg(not(any(target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd")))] +pub const DRM_MAJOR: u32 = 226; + +/// Primary DRM node prefix. +#[cfg(not(target_os = "openbsd"))] +pub const PRIMARY_NAME: &str = "card"; + +/// Primary DRM node prefix. +#[cfg(target_os = "openbsd")] +pub const PRIMARY_NAME: &str = "drm"; + +/// Control DRM node prefix. +#[cfg(not(target_os = "openbsd"))] +pub const CONTROL_NAME: &str = "controlD"; + +/// Control DRM node prefix. +#[cfg(target_os = "openbsd")] +pub const CONTROL_NAME: &str = "drmC"; + +/// Render DRM node prefix. +#[cfg(not(target_os = "openbsd"))] +pub const RENDER_NAME: &str = "renderD"; + +/// Render DRM node prefix. +#[cfg(target_os = "openbsd")] +pub const RENDER_NAME: &str = "drmR"; diff --git a/src/node/mod.rs b/src/node/mod.rs new file mode 100644 index 0000000..97ed146 --- /dev/null +++ b/src/node/mod.rs @@ -0,0 +1,382 @@ +//! Module for abstractions on drm device nodes. + +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::*; + +/// A node which refers to a DRM device. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DrmNode { + dev: dev_t, + ty: NodeType, +} + +impl DrmNode { + /// Creates a DRM node from an open drm device. + pub fn from_file(file: A) -> Result { + let stat = fstat(file).map_err(Into::::into)?; + DrmNode::from_stat(stat) + } + + /// Creates a DRM node from path. + pub fn from_path>(path: A) -> Result { + let stat = stat(path.as_ref()).map_err(Into::::into)?; + DrmNode::from_stat(stat) + } + + /// Creates a DRM node from a file stat. + pub fn from_stat(stat: Stat) -> Result { + let dev = stat.st_rdev; + DrmNode::from_dev_id(dev) + } + + /// Creates a DRM node from a [`dev_t`]. + pub fn from_dev_id(dev: dev_t) -> Result { + if !is_device_drm(dev) { + return Err(CreateDrmNodeError::NotDrmNode); + } + + // The type of the DRM node is determined by the minor number ranges: + // 0 - 63 -> Primary + // 64 - 127 -> Control + // 128 - 255 -> Render + let ty = match minor(dev) >> 6 { + 0 => NodeType::Primary, + 1 => NodeType::Control, + 2 => NodeType::Render, + _ => return Err(CreateDrmNodeError::NotDrmNode), + }; + + Ok(DrmNode { dev, ty }) + } + + /// Returns the type of the DRM node. + pub fn ty(&self) -> NodeType { + self.ty + } + + /// Returns the device_id of the underlying DRM node. + pub fn dev_id(&self) -> dev_t { + self.dev + } + + /// Returns the path of the open device if possible. + pub fn dev_path(&self) -> Option { + node_path(self, self.ty).ok() + } + + /// Returns the path of the specified node type matching the device, if available. + pub fn dev_path_with_type(&self, ty: NodeType) -> Option { + node_path(self, ty).ok() + } + + /// Returns a new node of the specified node type matching the device, if available. + pub fn node_with_type(&self, ty: NodeType) -> Option> { + self.dev_path_with_type(ty).map(DrmNode::from_path) + } + + /// Returns the major device number of the DRM device. + pub fn major(&self) -> u32 { + major(self.dev_id()) + } + + /// Returns the minor device number of the DRM device. + pub fn minor(&self) -> u32 { + minor(self.dev_id()) + } + + /// Returns whether the DRM device has render nodes. + pub fn has_render(&self) -> bool { + #[cfg(target_os = "linux")] + { + node_path(self, NodeType::Render).is_ok() + } + + // TODO: More robust checks on non-linux. + + #[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())) + } +} + +/// A type of node +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum NodeType { + /// A primary node may be used to allocate buffers. + /// + /// If no other node is present, this may be used to post a buffer to an output with mode-setting. + Primary, + + /// A control node may be used for mode-setting. + /// + /// This is almost never used since no DRM API for control nodes is available yet. + Control, + + /// A render node may be used by a client to allocate buffers. + /// + /// Mode-setting is not possible with a render node. + Render, +} + +impl NodeType { + /// Returns a string representing the prefix of a minor device's name. + /// + /// For example, on Linux with a primary node, the returned string would be `card`. + pub fn minor_name_prefix(&self) -> &'static str { + match self { + NodeType::Primary => PRIMARY_NAME, + NodeType::Control => CONTROL_NAME, + NodeType::Render => RENDER_NAME, + } + } + + #[cfg(not(target_os = "linux"))] + fn minor_base(&self) -> u32 { + match self { + NodeType::Primary => 0, + NodeType::Control => 64, + NodeType::Render => 128, + } + } +} + +impl Display for NodeType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(self, f) + } +} + +/// An error that may occur when creating a [`DrmNode`] from a file descriptor. +#[derive(Debug)] +pub enum CreateDrmNodeError { + /// Some underlying IO error occured while trying to create a DRM node. + Io(io::Error), + + /// The provided file descriptor does not refer to a DRM node. + 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 for CreateDrmNodeError { + #[inline] + fn from(err: io::Error) -> Self { + CreateDrmNodeError::Io(err) + } +} + +#[cfg(target_os = "freebsd")] +fn devname(dev: dev_t) -> Option { + use std::os::raw::{c_char, c_int}; + + // Matching value of SPECNAMELEN in FreeBSD 13+ + let mut dev_name = vec![0u8; 255]; + + let buf: *mut c_char = unsafe { + libc::devname_r( + dev, + libc::S_IFCHR, // Must be S_IFCHR or S_IFBLK + dev_name.as_mut_ptr() as *mut c_char, + dev_name.len() as c_int, + ) + }; + + // Buffer was too small (weird issue with the size of buffer) or the device could not be named. + if buf.is_null() { + return None; + } + + // SAFETY: The buffer written to by devname_r is guaranteed to be NUL terminated. + unsafe { dev_name.set_len(libc::strlen(buf)) }; + + Some(String::from_utf8(dev_name).expect("Returned device name is not valid utf8")) +} + +/// Returns if the given device by major:minor pair is a DRM device. +#[cfg(target_os = "linux")] +pub fn is_device_drm(dev: dev_t) -> bool { + // We `stat` the path rather than comparing the major to support dynamic device numbers: + // https://gitlab.freedesktop.org/mesa/drm/-/commit/f8392583418aef5e27bfed9989aeb601e20cc96d + let path = format!("/sys/dev/char/{}:{}/device/drm", major(dev), minor(dev)); + stat(path.as_str()).is_ok() +} + +/// Returns if the given device by major:minor pair is a DRM device. +#[cfg(target_os = "freebsd")] +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") + }) +} + +/// Returns if the given device by major:minor pair is a DRM device. +#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] +pub fn is_device_drm(dev: dev_t) -> bool { + major(dev) == DRM_MAJOR +} + +/// Returns the path of a specific type of node from the same DRM device as another path of the same node. +pub fn path_to_type>(path: P, ty: NodeType) -> io::Result { + let stat = stat(path.as_ref()).map_err(Into::::into)?; + dev_path(stat.st_rdev, ty) +} + +/// Returns the path of a specific type of node from the same DRM device as an existing [`DrmNode`]. +pub fn node_path(node: &DrmNode, ty: NodeType) -> io::Result { + dev_path(node.dev, ty) +} + +/// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. +#[cfg(target_os = "linux")] +pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { + 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(); + + // Only 1 primary, control and render node may exist simultaneously, so the + // first occurrence is good enough. + 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) + ), + )) +} + +/// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. +#[cfg(target_os = "freebsd")] +fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { + // Based on libdrm `drmGetMinorNameForFD`. Should be updated if the code + // there is replaced with anything more sensible... + + 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::() { + let id_mask = 0b11_1111; + let id = old_id & id_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) + ), + )) +} + +/// Returns the path of a specific type of node from the DRM device described by major and minor device numbers. +#[cfg(not(any(target_os = "linux", target_os = "freebsd")))] +fn dev_path(dev: dev_t, ty: NodeType) -> io::Result { + 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_mask = 0b11_1111; + let id = old_id & id_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) + ), + )) +}