drm/
lib.rs

1//! A safe interface to the Direct Rendering Manager subsystem found in various
2//! operating systems.
3//!
4//! # Summary
5//!
6//! The Direct Rendering Manager (DRM) is subsystem found in various operating
7//! systems that exposes graphical functionality to userspace processes. It can
8//! be used to send data and commands to a GPU driver that implements the
9//! interface.
10//!
11//! Userspace processes can access the DRM by opening a 'device node' (usually
12//! found in `/dev/dri/*`) and using various `ioctl` commands on the open file
13//! descriptor. Most processes use the libdrm library (part of the mesa project)
14//! to execute these commands. This crate takes a more direct approach,
15//! bypassing libdrm and executing the commands directly and doing minimal
16//! abstraction to keep the interface safe.
17//!
18//! While the DRM subsystem exposes many powerful GPU interfaces, it is not
19//! recommended for rendering or GPGPU operations. There are many standards made
20//! for these use cases, and they are far more fitting for those sort of tasks.
21//!
22//! ## Usage
23//!
24//! To begin using this crate, the [`Device`] trait must be
25//! implemented. See the trait's [example section](trait@Device#example) for
26//! details on how to implement it.
27//!
28
29#![warn(missing_docs)]
30
31pub(crate) mod util;
32
33pub mod buffer;
34pub mod control;
35
36use std::ffi::{OsStr, OsString};
37use std::time::Duration;
38use std::{
39    io,
40    os::unix::{ffi::OsStringExt, io::AsFd},
41};
42
43use rustix::io::Errno;
44
45use crate::util::*;
46
47pub use drm_ffi::{DRM_CLOEXEC as CLOEXEC, DRM_RDWR as RDWR};
48
49/// This trait should be implemented by any object that acts as a DRM device. It
50/// is a prerequisite for using any DRM functionality.
51///
52/// This crate does not provide a concrete device object due to the various ways
53/// it can be implemented. The user of this crate is expected to implement it
54/// themselves and derive this trait as necessary. The example below
55/// demonstrates how to do this using a small wrapper.
56///
57/// # Example
58///
59/// ```
60/// use drm::Device;
61///
62/// use std::fs::File;
63/// use std::fs::OpenOptions;
64///
65/// use std::os::unix::io::AsFd;
66/// use std::os::unix::io::BorrowedFd;
67///
68/// #[derive(Debug)]
69/// /// A simple wrapper for a device node.
70/// struct Card(File);
71///
72/// /// Implementing [`AsFd`] is a prerequisite to implementing the traits found
73/// /// in this crate. Here, we are just calling [`File::as_fd()`] on the inner
74/// /// [`File`].
75/// impl AsFd for Card {
76///     fn as_fd(&self) -> BorrowedFd<'_> {
77///         self.0.as_fd()
78///     }
79/// }
80///
81/// /// With [`AsFd`] implemented, we can now implement [`drm::Device`].
82/// impl Device for Card {}
83///
84/// impl Card {
85///     /// Simple helper method for opening a [`Card`].
86///     fn open() -> Self {
87///         let mut options = OpenOptions::new();
88///         options.read(true);
89///         options.write(true);
90///
91///         // The normal location of the primary device node on Linux
92///         Card(options.open("/dev/dri/card0").unwrap())
93///     }
94/// }
95/// ```
96pub trait Device: AsFd {
97    /// Acquires the DRM Master lock for this process.
98    ///
99    /// # Notes
100    ///
101    /// Acquiring the DRM Master is done automatically when the primary device
102    /// node is opened. If you opened the primary device node and did not
103    /// acquire the lock, another process likely has the lock.
104    ///
105    /// This function is only available to processes with CAP_SYS_ADMIN
106    /// privileges (usually as root)
107    fn acquire_master_lock(&self) -> io::Result<()> {
108        drm_ffi::auth::acquire_master(self.as_fd())?;
109        Ok(())
110    }
111
112    /// Releases the DRM Master lock for another process to use.
113    fn release_master_lock(&self) -> io::Result<()> {
114        drm_ffi::auth::release_master(self.as_fd())?;
115        Ok(())
116    }
117
118    /// Generates an [`AuthToken`] for this process.
119    #[deprecated(note = "Consider opening a render node instead.")]
120    fn generate_auth_token(&self) -> io::Result<AuthToken> {
121        let token = drm_ffi::auth::get_magic_token(self.as_fd())?;
122        Ok(AuthToken(token.magic))
123    }
124
125    /// Authenticates an [`AuthToken`] from another process.
126    fn authenticate_auth_token(&self, token: AuthToken) -> io::Result<()> {
127        drm_ffi::auth::auth_magic_token(self.as_fd(), token.0)?;
128        Ok(())
129    }
130
131    /// Requests the driver to expose or hide certain capabilities. See
132    /// [`ClientCapability`] for more information.
133    fn set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()> {
134        drm_ffi::set_capability(self.as_fd(), cap as u64, enable)?;
135        Ok(())
136    }
137
138    /// Gets the bus ID of this device.
139    fn get_bus_id(&self) -> io::Result<OsString> {
140        let mut buffer = Vec::new();
141        let _ = drm_ffi::get_bus_id(self.as_fd(), Some(&mut buffer))?;
142        let bus_id = OsString::from_vec(buffer);
143
144        Ok(bus_id)
145    }
146
147    /// Check to see if our [`AuthToken`] has been authenticated
148    /// by the DRM Master
149    fn authenticated(&self) -> io::Result<bool> {
150        let client = drm_ffi::get_client(self.as_fd(), 0)?;
151        Ok(client.auth == 1)
152    }
153
154    /// Gets the value of a capability.
155    fn get_driver_capability(&self, cap: DriverCapability) -> io::Result<u64> {
156        let cap = drm_ffi::get_capability(self.as_fd(), cap as u64)?;
157        Ok(cap.value)
158    }
159
160    /// # Possible errors:
161    ///   - `EFAULT`: Kernel could not copy fields into userspace
162    #[allow(missing_docs)]
163    fn get_driver(&self) -> io::Result<Driver> {
164        let mut name = Vec::new();
165        let mut date = Vec::new();
166        let mut desc = Vec::new();
167
168        let _ = drm_ffi::get_version(
169            self.as_fd(),
170            Some(&mut name),
171            Some(&mut date),
172            Some(&mut desc),
173        )?;
174
175        let name = OsString::from_vec(unsafe { transmute_vec(name) });
176        let date = OsString::from_vec(unsafe { transmute_vec(date) });
177        let desc = OsString::from_vec(unsafe { transmute_vec(desc) });
178
179        let driver = Driver { name, date, desc };
180
181        Ok(driver)
182    }
183
184    /// Waits for a vblank.
185    fn wait_vblank(
186        &self,
187        target_sequence: VblankWaitTarget,
188        flags: VblankWaitFlags,
189        high_crtc: u32,
190        user_data: usize,
191    ) -> io::Result<VblankWaitReply> {
192        use drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_HIGH_CRTC_MASK;
193        use drm_ffi::_DRM_VBLANK_HIGH_CRTC_SHIFT;
194
195        let high_crtc_mask = _DRM_VBLANK_HIGH_CRTC_MASK >> _DRM_VBLANK_HIGH_CRTC_SHIFT;
196        if (high_crtc & !high_crtc_mask) != 0 {
197            return Err(Errno::INVAL.into());
198        }
199
200        let (sequence, wait_type) = match target_sequence {
201            VblankWaitTarget::Absolute(n) => {
202                (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_ABSOLUTE)
203            }
204            VblankWaitTarget::Relative(n) => {
205                (n, drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_RELATIVE)
206            }
207        };
208
209        let type_ = wait_type | (high_crtc << _DRM_VBLANK_HIGH_CRTC_SHIFT) | flags.bits();
210        let reply = drm_ffi::wait_vblank(self.as_fd(), type_, sequence, user_data)?;
211
212        let time = match (reply.tval_sec, reply.tval_usec) {
213            (0, 0) => None,
214            (sec, usec) => Some(Duration::new(sec as u64, (usec * 1000) as u32)),
215        };
216
217        Ok(VblankWaitReply {
218            frame: reply.sequence,
219            time,
220        })
221    }
222}
223
224/// An authentication token, unique to the file descriptor of the device.
225///
226/// This token can be sent to another process that owns the DRM Master lock to
227/// allow unprivileged use of the device, such as rendering.
228///
229/// # Deprecation Notes
230///
231/// This method of authentication is somewhat deprecated. Accessing unprivileged
232/// functionality is best done by opening a render node. However, some other
233/// processes may still use this method of authentication. Therefore, we still
234/// provide functionality for generating and authenticating these tokens.
235#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
236pub struct AuthToken(u32);
237
238/// Driver version of a device.
239#[derive(Debug, Clone, Hash, PartialEq, Eq)]
240pub struct Driver {
241    /// Name of the driver
242    pub name: OsString,
243    /// Date driver was published
244    pub date: OsString,
245    /// Driver description
246    pub desc: OsString,
247}
248
249impl Driver {
250    /// Name of driver
251    pub fn name(&self) -> &OsStr {
252        self.name.as_ref()
253    }
254
255    /// Date driver was published
256    pub fn date(&self) -> &OsStr {
257        self.date.as_ref()
258    }
259
260    /// Driver description
261    pub fn description(&self) -> &OsStr {
262        self.desc.as_ref()
263    }
264}
265
266/// Used to check which capabilities your graphics driver has.
267#[allow(clippy::upper_case_acronyms)]
268#[repr(u64)]
269#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
270pub enum DriverCapability {
271    /// DumbBuffer support for scanout
272    DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64,
273    /// Unknown
274    VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64,
275    /// Preferred depth to use for dumb buffers
276    DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64,
277    /// Unknown
278    DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64,
279    /// PRIME handles are supported
280    Prime = drm_ffi::DRM_CAP_PRIME as u64,
281    /// Unknown
282    MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64,
283    /// Asynchronous page flipping support
284    ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64,
285    /// Width of cursor buffers
286    CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64,
287    /// Height of cursor buffers
288    CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64,
289    /// Create framebuffers with modifiers
290    AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64,
291    /// Unknown
292    PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64,
293    /// Uses the CRTC's ID in vblank events
294    CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64,
295    /// SyncObj support
296    SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64,
297    /// Timeline SyncObj support
298    TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64,
299}
300
301/// Used to enable/disable capabilities for the process.
302#[repr(u64)]
303#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
304pub enum ClientCapability {
305    /// The driver provides 3D screen control
306    Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64,
307    /// The driver provides more plane types for modesetting
308    UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64,
309    /// The driver provides atomic modesetting
310    Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64,
311}
312
313/// Used to specify a vblank sequence to wait for
314#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
315pub enum VblankWaitTarget {
316    /// Wait for a specific vblank sequence number
317    Absolute(u32),
318    /// Wait for a given number of vblanks
319    Relative(u32),
320}
321
322bitflags::bitflags! {
323    /// Flags to alter the behaviour when waiting for a vblank
324    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
325    pub struct VblankWaitFlags : u32 {
326        /// Send event instead of blocking
327        const EVENT = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_EVENT;
328        /// If missed, wait for next vblank
329        const NEXT_ON_MISS = drm_ffi::drm_vblank_seq_type::_DRM_VBLANK_NEXTONMISS;
330    }
331}
332
333/// Data returned from a vblank wait
334#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
335pub struct VblankWaitReply {
336    frame: u32,
337    time: Option<Duration>,
338}
339
340impl VblankWaitReply {
341    /// Sequence of the frame
342    pub fn frame(&self) -> u32 {
343        self.frame
344    }
345
346    /// Time at which the vblank occurred. [`None`] if an asynchronous event was
347    /// requested
348    pub fn time(&self) -> Option<Duration> {
349        self.time
350    }
351}