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