• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
31 pub(crate) mod util;
32 
33 pub mod buffer;
34 pub mod control;
35 pub mod node;
36 
37 use std::ffi::{OsStr, OsString};
38 use std::time::Duration;
39 use std::{
40     io,
41     os::unix::{ffi::OsStringExt, io::AsFd},
42 };
43 
44 use rustix::io::Errno;
45 
46 use crate::util::*;
47 
48 pub 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 /// ```
97 pub 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)
acquire_master_lock(&self) -> io::Result<()>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.
release_master_lock(&self) -> io::Result<()>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.")]
generate_auth_token(&self) -> io::Result<AuthToken>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.
authenticate_auth_token(&self, token: AuthToken) -> io::Result<()>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.
set_client_capability(&self, cap: ClientCapability, enable: bool) -> io::Result<()>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.
get_bus_id(&self) -> io::Result<OsString>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
authenticated(&self) -> io::Result<bool>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.
get_driver_capability(&self, cap: DriverCapability) -> io::Result<u64>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)]
get_driver(&self) -> io::Result<Driver>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.
wait_vblank( &self, target_sequence: VblankWaitTarget, flags: VblankWaitFlags, high_crtc: u32, user_data: usize, ) -> io::Result<VblankWaitReply>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)]
243 pub struct AuthToken(u32);
244 
245 /// Driver version of a device.
246 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
247 pub 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 
258 impl Driver {
259     /// Name of driver
name(&self) -> &OsStr260     pub fn name(&self) -> &OsStr {
261         self.name.as_ref()
262     }
263 
264     /// Date driver was published
date(&self) -> &OsStr265     pub fn date(&self) -> &OsStr {
266         self.date.as_ref()
267     }
268 
269     /// Driver description
description(&self) -> &OsStr270     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)]
279 pub 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)]
315 pub 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)]
344 pub 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 
351 bitflags::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)]
364 pub struct VblankWaitReply {
365     frame: u32,
366     time: Option<Duration>,
367 }
368 
369 impl VblankWaitReply {
370     /// Sequence of the frame
frame(&self) -> u32371     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
time(&self) -> Option<Duration>377     pub fn time(&self) -> Option<Duration> {
378         self.time
379     }
380 }
381