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}