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}