| 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 | |