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