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) |
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)] |
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 |
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)] |
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 |
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 | } |
381 | |