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