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 | extern crate core; |
31 | |
32 | extern crate drm_ffi; |
33 | |
34 | extern crate drm_fourcc; |
35 | extern crate nix; |
36 | |
37 | extern crate bytemuck; |
38 | |
39 | pub(crate) mod util; |
40 | |
41 | pub mod buffer; |
42 | pub mod control; |
43 | |
44 | use std::ffi::{OsStr, OsString}; |
45 | use std::os::unix::{ |
46 | ffi::OsStringExt, |
47 | io::{AsFd, AsRawFd}, |
48 | }; |
49 | use std::time::Duration; |
50 | |
51 | pub use drm_ffi::result::SystemError; |
52 | use 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 | /// ``` |
103 | pub 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)] |
247 | pub struct AuthToken(u32); |
248 | |
249 | /// Driver version of a device. |
250 | #[derive (Debug, Clone, Hash, PartialEq, Eq)] |
251 | pub 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 | |
260 | impl 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)] |
281 | pub 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)] |
313 | pub 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)] |
324 | pub 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 | |
331 | bitflags::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)] |
343 | pub struct VblankWaitReply { |
344 | frame: u32, |
345 | time: Option<Duration>, |
346 | } |
347 | |
348 | impl 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 | |