| //! A safe interface to the Direct Rendering Manager subsystem found in various |
| //! operating systems. |
| //! |
| //! # Summary |
| //! |
| //! The Direct Rendering Manager (DRM) is subsystem found in various operating |
| //! systems that exposes graphical functionality to userspace processes. It can |
| //! be used to send data and commands to a GPU driver that implements the |
| //! interface. |
| //! |
| //! Userspace processes can access the DRM by opening a 'device node' (usually |
| //! found in `/dev/dri/*`) and using various `ioctl` commands on the open file |
| //! descriptor. Most processes use the libdrm library (part of the mesa project) |
| //! to execute these commands. This crate takes a more direct approach, |
| //! bypassing libdrm and executing the commands directly and doing minimal |
| //! abstraction to keep the interface safe. |
| //! |
| //! While the DRM subsystem exposes many powerful GPU interfaces, it is not |
| //! recommended for rendering or GPGPU operations. There are many standards made |
| //! for these use cases, and they are far more fitting for those sort of tasks. |
| //! |
| //! ## Usage |
| //! |
| //! To begin using this crate, the [`Device`] trait must be |
| //! implemented. See the trait's [example section](trait@Device#example) for |
| //! details on how to implement it. |
| //! |
| |
| #![warn(missing_docs)] |
| |
| pub(crate) mod util; |
| |
| pub mod buffer; |
| pub mod control; |
| |
| use std::ffi::{OsStr, OsString}; |
| use std::time::Duration; |
| use std::{ |
| io, |
| os::unix::{ffi::OsStringExt, io::AsFd}, |
| }; |
| |
| use rustix::io::Errno; |
| |
| use crate::util::*; |
| |
| pub use drm_ffi::{DRM_CLOEXEC as CLOEXEC, DRM_RDWR as RDWR}; |
| |
| /// This trait should be implemented by any object that acts as a DRM device. It |
| /// is a prerequisite for using any DRM functionality. |
| /// |
| /// This crate does not provide a concrete device object due to the various ways |
| /// it can be implemented. The user of this crate is expected to implement it |
| /// themselves and derive this trait as necessary. The example below |
| /// demonstrates how to do this using a small wrapper. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use drm::Device; |
| /// |
| /// use std::fs::File; |
| /// use std::fs::OpenOptions; |
| /// |
| /// use std::os::unix::io::AsFd; |
| /// use std::os::unix::io::BorrowedFd; |
| /// |
| /// #[derive(Debug)] |
| /// /// A simple wrapper for a device node. |
| /// struct Card(File); |
| /// |
| /// /// Implementing [`AsFd`] is a prerequisite to implementing the traits found |
| /// /// in this crate. Here, we are just calling [`File::as_fd()`] on the inner |
| /// /// [`File`]. |
| /// impl AsFd for Card { |
| /// fn as_fd(&self) -> BorrowedFd<'_> { |
| /// self.0.as_fd() |
| /// } |
| /// } |
| /// |
| /// /// With [`AsFd`] implemented, we can now implement [`drm::Device`]. |
| /// impl Device for Card {} |
| /// |
| /// impl Card { |
| /// /// Simple helper method for opening a [`Card`]. |
| /// fn open() -> Self { |
| /// let mut options = OpenOptions::new(); |
| /// options.read(true); |
| /// options.write(true); |
| /// |
| /// // The normal location of the primary device node on Linux |
| /// Card(options.open("/dev/dri/card0").unwrap()) |
| /// } |
| /// } |
| /// ``` |
| pub trait Device: AsFd { |
| /// Acquires the DRM Master lock for this process. |
| /// |
| /// # Notes |
| /// |
| /// Acquiring the DRM Master is done automatically when the primary device |
| /// node is opened. If you opened the primary device node and did not |
| /// acquire the lock, another process likely has the lock. |
| /// |
| /// This function is only available to processes with CAP_SYS_ADMIN |
| /// privileges (usually as root) |
| fn acquire_master_lock(&self) -> io::Result<()> { |
| drm_ffi::auth::acquire_master(self.as_fd())?; |
| Ok(()) |
| } |
| |
| /// Releases the DRM Master lock for another process to use. |
| fn release_master_lock(&self) -> io::Result<()> { |
| drm_ffi::auth::release_master(self.as_fd())?; |
| Ok(()) |
| } |
| |
| /// Generates an [`AuthToken`] for this process. |
| #[deprecated(note = "Consider opening a render node instead.")] |
| fn generate_auth_token(&self) -> io::Result<AuthToken> { |
| let token = drm_ffi::auth::get_magic_token(self.as_fd())?; |
| Ok(AuthToken(token.magic)) |
| } |
| |
| /// Authenticates an [`AuthToken`] from another process. |
| fn authenticate_auth_token(&self, token: AuthToken) -> io::Result<()> { |
| drm_ffi::auth::auth_magic_token(self.as_fd(), token.0)?; |
| Ok(()) |
| } |
| |
| /// Requests the driver to expose or hide certain capabilities. See |
| /// [`ClientCapability`] for more information. |
| fn set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()> { |
| drm_ffi::set_capability(self.as_fd(), cap as u64, enable)?; |
| Ok(()) |
| } |
| |
| /// Gets the bus ID of this device. |
| fn get_bus_id(&self) -> io::Result<OsString> { |
| let mut buffer = Vec::new(); |
| let _ = drm_ffi::get_bus_id(self.as_fd(), Some(&mut buffer))?; |
| let bus_id = OsString::from_vec(buffer); |
| |
| Ok(bus_id) |
| } |
| |
| /// Check to see if our [`AuthToken`] has been authenticated |
| /// by the DRM Master |
| fn authenticated(&self) -> io::Result<bool> { |
| let client = drm_ffi::get_client(self.as_fd(), 0)?; |
| Ok(client.auth == 1) |
| } |
| |
| /// Gets the value of a capability. |
| fn get_driver_capability(&self, cap: DriverCapability) -> io::Result<u64> { |
| let cap = drm_ffi::get_capability(self.as_fd(), cap as u64)?; |
| Ok(cap.value) |
| } |
| |
| /// # Possible errors: |
| /// - `EFAULT`: Kernel could not copy fields into userspace |
| #[allow(missing_docs)] |
| fn get_driver(&self) -> io::Result<Driver> { |
| let mut name = Vec::new(); |
| let mut date = Vec::new(); |
| let mut desc = Vec::new(); |
| |
| let _ = drm_ffi::get_version( |
| self.as_fd(), |
| Some(&mut name), |
| Some(&mut date), |
| Some(&mut desc), |
| )?; |
| |
| let name = OsString::from_vec(unsafe { transmute_vec(name) }); |
| let date = OsString::from_vec(unsafe { transmute_vec(date) }); |
| let desc = OsString::from_vec(unsafe { transmute_vec(desc) }); |
| |
| let driver = Driver { name, date, desc }; |
| |
| Ok(driver) |
| } |
| |
| /// Waits for a vblank. |
| fn wait_vblank( |
| &self, |
| target_sequence: VblankWaitTarget, |
| flags: VblankWaitFlags, |
| high_crtc: u32, |
| user_data: usize, |
| ) -> io::Result<VblankWaitReply> { |
| use drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_HIGH_CRTC_MASK; |
| use drm_ffi::_DRM_VBLANK_HIGH_CRTC_SHIFT; |
| |
| let high_crtc_mask = _DRM_VBLANK_HIGH_CRTC_MASK >> _DRM_VBLANK_HIGH_CRTC_SHIFT; |
| if (high_crtc & !high_crtc_mask) != 0 { |
| return Err(Errno::INVAL.into()); |
| } |
| |
| let (sequence, wait_type) = match target_sequence { |
| VblankWaitTarget::Absolute(n) => { |
| (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_ABSOLUTE) |
| } |
| VblankWaitTarget::Relative(n) => { |
| (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_RELATIVE) |
| } |
| }; |
| |
| let type_ = wait_type | (high_crtc << _DRM_VBLANK_HIGH_CRTC_SHIFT) | flags.bits(); |
| let reply = drm_ffi::wait_vblank(self.as_fd(), type_, sequence, user_data)?; |
| |
| let time = match (reply.tval_sec, reply.tval_usec) { |
| (0, 0) => None, |
| (sec, usec) => Some(Duration::new(sec as u64, (usec * 1000) as u32)), |
| }; |
| |
| Ok(VblankWaitReply { |
| frame: reply.sequence, |
| time, |
| }) |
| } |
| } |
| |
| /// An authentication token, unique to the file descriptor of the device. |
| /// |
| /// This token can be sent to another process that owns the DRM Master lock to |
| /// allow unprivileged use of the device, such as rendering. |
| /// |
| /// # Deprecation Notes |
| /// |
| /// This method of authentication is somewhat deprecated. Accessing unprivileged |
| /// functionality is best done by opening a render node. However, some other |
| /// processes may still use this method of authentication. Therefore, we still |
| /// provide functionality for generating and authenticating these tokens. |
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| pub struct AuthToken(u32); |
| |
| /// Driver version of a device. |
| #[derive(Debug, Clone, Hash, PartialEq, Eq)] |
| pub struct Driver { |
| /// Name of the driver |
| pub name: OsString, |
| /// Date driver was published |
| pub date: OsString, |
| /// Driver description |
| pub desc: OsString, |
| } |
| |
| impl Driver { |
| /// Name of driver |
| pub fn name(&self) -> &OsStr { |
| self.name.as_ref() |
| } |
| |
| /// Date driver was published |
| pub fn date(&self) -> &OsStr { |
| self.date.as_ref() |
| } |
| |
| /// Driver description |
| pub fn description(&self) -> &OsStr { |
| self.desc.as_ref() |
| } |
| } |
| |
| /// Used to check which capabilities your graphics driver has. |
| #[allow(clippy::upper_case_acronyms)] |
| #[repr(u64)] |
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| pub enum DriverCapability { |
| /// DumbBuffer support for scanout |
| DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64, |
| /// Unknown |
| VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64, |
| /// Preferred depth to use for dumb buffers |
| DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64, |
| /// Unknown |
| DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64, |
| /// PRIME handles are supported |
| Prime = drm_ffi::DRM_CAP_PRIME as u64, |
| /// Unknown |
| MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64, |
| /// Asynchronous page flipping support |
| ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64, |
| /// Width of cursor buffers |
| CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64, |
| /// Height of cursor buffers |
| CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64, |
| /// Create framebuffers with modifiers |
| AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64, |
| /// Unknown |
| PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64, |
| /// Uses the CRTC's ID in vblank events |
| CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64, |
| /// SyncObj support |
| SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64, |
| /// Timeline SyncObj support |
| TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64, |
| } |
| |
| /// Used to enable/disable capabilities for the process. |
| #[repr(u64)] |
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| pub enum ClientCapability { |
| /// The driver provides 3D screen control |
| Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64, |
| /// The driver provides more plane types for modesetting |
| UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64, |
| /// The driver provides atomic modesetting |
| Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64, |
| } |
| |
| /// Used to specify a vblank sequence to wait for |
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| pub enum VblankWaitTarget { |
| /// Wait for a specific vblank sequence number |
| Absolute(u32), |
| /// Wait for a given number of vblanks |
| Relative(u32), |
| } |
| |
| bitflags::bitflags! { |
| /// Flags to alter the behaviour when waiting for a vblank |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] |
| pub struct VblankWaitFlags : u32 { |
| /// Send event instead of blocking |
| const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT; |
| /// If missed, wait for next vblank |
| const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS; |
| } |
| } |
| |
| /// Data returned from a vblank wait |
| #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] |
| pub struct VblankWaitReply { |
| frame: u32, |
| time: Option<Duration>, |
| } |
| |
| impl VblankWaitReply { |
| /// Sequence of the frame |
| pub fn frame(&self) -> u32 { |
| self.frame |
| } |
| |
| /// Time at which the vblank occurred. [`None`] if an asynchronous event was |
| /// requested |
| pub fn time(&self) -> Option<Duration> { |
| self.time |
| } |
| } |