diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..76e80c3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,351 @@ +//! 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 + } +} |