1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! A safe interface to the Direct Rendering Manager subsystem found in various
//! operating systems.
//!
//! # Summary
//!
//! The Direct Rendering Manager (DRM) is subsystem found in various operating
//! systems that exposes graphical functionality to userspace processes. It can
//! be used to send data and commands to a GPU driver that implements the
//! interface.
//!
//! Userspace processes can access the DRM by opening a 'device node' (usually
//! found in `/dev/dri/*`) and using various `ioctl` commands on the open file
//! descriptor. Most processes use the libdrm library (part of the mesa project)
//! to execute these commands. This crate takes a more direct approach,
//! bypassing libdrm and executing the commands directly and doing minimal
//! abstraction to keep the interface safe.
//!
//! While the DRM subsystem exposes many powerful GPU interfaces, it is not
//! recommended for rendering or GPGPU operations. There are many standards made
//! for these use cases, and they are far more fitting for those sort of tasks.
//!
//! ## Usage
//!
//! To begin using this crate, the [`Device`] trait must be
//! implemented. See the trait's [example section](trait@Device#example) for
//! details on how to implement it.
//!

#![warn(missing_docs)]
extern crate core;

extern crate drm_ffi;

extern crate drm_fourcc;
extern crate nix;

pub(crate) mod util;

pub mod buffer;
pub mod control;

use std::os::unix::io::AsRawFd;

pub use drm_ffi::result::SystemError;
use util::*;

/// This trait should be implemented by any object that acts as a DRM device. It
/// is a prerequisite for using any DRM functionality.
///
/// This crate does not provide a concrete device object due to the various ways
/// it can be implemented. The user of this crate is expected to implement it
/// themselves and derive this trait as necessary. The example below
/// demonstrates how to do this using a small wrapper.
///
/// # Example
///
/// ```
/// extern crate drm;
///
/// use drm::Device;
///
/// use std::fs::File;
/// use std::fs::OpenOptions;
///
/// use std::os::unix::io::RawFd;
/// use std::os::unix::io::AsRawFd;
///
/// #[derive(Debug)]
/// /// A simple wrapper for a device node.
/// struct Card(File);
///
/// /// Implementing [`AsRawFd`] is a prerequisite to implementing the traits found
/// /// in this crate. Here, we are just calling [`File::as_raw_fd()`] on the inner
/// /// [`File`].
/// impl AsRawFd for Card {
///     fn as_raw_fd(&self) -> RawFd {
///         self.0.as_raw_fd()
///     }
/// }
///
/// /// With [`AsRawFd`] implemented, we can now implement [`drm::Device`].
/// impl Device for Card {}
///
/// impl Card {
///     /// Simple helper method for opening a [`Card`].
///     fn open() -> Self {
///         let mut options = OpenOptions::new();
///         options.read(true);
///         options.write(true);
///
///         // The normal location of the primary device node on Linux
///         Card(options.open("/dev/dri/card0").unwrap())
///     }
/// }
/// ```
pub trait Device: AsRawFd {
    /// Acquires the DRM Master lock for this process.
    ///
    /// # Notes
    ///
    /// Acquiring the DRM Master is done automatically when the primary device
    /// node is opened. If you opened the primary device node and did not
    /// acquire the lock, another process likely has the lock.
    ///
    /// This function is only available to processes with CAP_SYS_ADMIN
    /// privileges (usually as root)
    fn acquire_master_lock(&self) -> Result<(), SystemError> {
        drm_ffi::auth::acquire_master(self.as_raw_fd())?;
        Ok(())
    }

    /// Releases the DRM Master lock for another process to use.
    fn release_master_lock(&self) -> Result<(), SystemError> {
        drm_ffi::auth::release_master(self.as_raw_fd())?;
        Ok(())
    }

    /// Generates an [`AuthToken`] for this process.
    #[deprecated(note = "Consider opening a render node instead.")]
    fn generate_auth_token(&self) -> Result<AuthToken, SystemError> {
        let token = drm_ffi::auth::get_magic_token(self.as_raw_fd())?;
        Ok(AuthToken(token.magic))
    }

    /// Authenticates an [`AuthToken`] from another process.
    fn authenticate_auth_token(&self, token: AuthToken) -> Result<(), SystemError> {
        drm_ffi::auth::auth_magic_token(self.as_raw_fd(), token.0)?;
        Ok(())
    }

    /// Requests the driver to expose or hide certain capabilities. See
    /// [`ClientCapability`] for more information.
    fn set_client_capability(
        &self,
        cap: ClientCapability,
        enable: bool,
    ) -> Result<(), SystemError> {
        drm_ffi::set_capability(self.as_raw_fd(), cap as u64, enable)?;
        Ok(())
    }

    /// Gets the [`BusID`] of this device.
    fn get_bus_id(&self) -> Result<BusID, SystemError> {
        let mut buffer = [0u8; 32];

        let buffer_len;

        let _busid = {
            let mut slice = &mut buffer[..];
            let busid = drm_ffi::get_bus_id(self.as_raw_fd(), Some(&mut slice))?;
            buffer_len = slice.len();
            busid
        };

        let bus_id = BusID(SmallOsString::from_u8_buffer(buffer, buffer_len));

        Ok(bus_id)
    }

    /// Check to see if our [`AuthToken`] has been authenticated
    /// by the DRM Master
    fn authenticated(&self) -> Result<bool, SystemError> {
        let client = drm_ffi::get_client(self.as_raw_fd(), 0)?;
        Ok(client.auth == 1)
    }

    /// Gets the value of a capability.
    fn get_driver_capability(&self, cap: DriverCapability) -> Result<u64, SystemError> {
        let cap = drm_ffi::get_capability(self.as_raw_fd(), cap as u64)?;
        Ok(cap.value)
    }

    /// # Possible errors:
    ///   - [`SystemError::MemoryFault`]: Kernel could not copy fields into userspace
    #[allow(missing_docs)]
    fn get_driver(&self) -> Result<Driver, SystemError> {
        let mut name = [0i8; 32];
        let mut date = [0i8; 32];
        let mut desc = [0i8; 32];

        let name_len;
        let date_len;
        let desc_len;

        let _version = {
            let mut name_slice = &mut name[..];
            let mut date_slice = &mut date[..];
            let mut desc_slice = &mut desc[..];

            let version = drm_ffi::get_version(
                self.as_raw_fd(),
                Some(&mut name_slice),
                Some(&mut date_slice),
                Some(&mut desc_slice),
            )?;

            name_len = name_slice.len();
            date_len = date_slice.len();
            desc_len = desc_slice.len();

            version
        };

        let name = SmallOsString::from_i8_buffer(name, name_len);
        let date = SmallOsString::from_i8_buffer(date, date_len);
        let desc = SmallOsString::from_i8_buffer(desc, desc_len);

        let driver = Driver { name, date, desc };

        Ok(driver)
    }
}

/// An authentication token, unique to the file descriptor of the device.
///
/// This token can be sent to another process that owns the DRM Master lock to
/// allow unprivileged use of the device, such as rendering.
///
/// # Deprecation Notes
///
/// This method of authentication is somewhat deprecated. Accessing unprivileged
/// functionality is best done by opening a render node. However, some other
/// processes may still use this method of authentication. Therefore, we still
/// provide functionality for generating and authenticating these tokens.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct AuthToken(u32);

/// Bus ID of a device.
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct BusID(SmallOsString);

impl AsRef<OsStr> for BusID {
    fn as_ref(&self) -> &OsStr {
        self.0.as_ref()
    }
}

/// Driver version of a device.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct Driver {
    name: SmallOsString,
    date: SmallOsString,
    desc: SmallOsString,
}

impl Driver {
    /// Name of driver
    pub fn name(&self) -> &OsStr {
        self.name.as_ref()
    }

    /// Date driver was published
    pub fn date(&self) -> &OsStr {
        self.date.as_ref()
    }

    /// Driver description
    pub fn description(&self) -> &OsStr {
        self.desc.as_ref()
    }
}

/// Used to check which capabilities your graphics driver has.
#[allow(clippy::upper_case_acronyms)]
#[repr(u64)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum DriverCapability {
    /// DumbBuffer support for scanout
    DumbBuffer = drm_ffi::DRM_CAP_DUMB_BUFFER as u64,
    /// Unknown
    VBlankHighCRTC = drm_ffi::DRM_CAP_VBLANK_HIGH_CRTC as u64,
    /// Preferred depth to use for dumb buffers
    DumbPreferredDepth = drm_ffi::DRM_CAP_DUMB_PREFERRED_DEPTH as u64,
    /// Unknown
    DumbPreferShadow = drm_ffi::DRM_CAP_DUMB_PREFER_SHADOW as u64,
    /// PRIME handles are supported
    Prime = drm_ffi::DRM_CAP_PRIME as u64,
    /// Unknown
    MonotonicTimestamp = drm_ffi::DRM_CAP_TIMESTAMP_MONOTONIC as u64,
    /// Asynchronous page flipping support
    ASyncPageFlip = drm_ffi::DRM_CAP_ASYNC_PAGE_FLIP as u64,
    /// Width of cursor buffers
    CursorWidth = drm_ffi::DRM_CAP_CURSOR_WIDTH as u64,
    /// Height of cursor buffers
    CursorHeight = drm_ffi::DRM_CAP_CURSOR_HEIGHT as u64,
    /// Create framebuffers with modifiers
    AddFB2Modifiers = drm_ffi::DRM_CAP_ADDFB2_MODIFIERS as u64,
    /// Unknown
    PageFlipTarget = drm_ffi::DRM_CAP_PAGE_FLIP_TARGET as u64,
    /// Uses the CRTC's ID in vblank events
    CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64,
    /// SyncObj support
    SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64,
}

/// Used to enable/disable capabilities for the process.
#[repr(u64)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum ClientCapability {
    /// The driver provides 3D screen control
    Stereo3D = drm_ffi::DRM_CLIENT_CAP_STEREO_3D as u64,
    /// The driver provides more plane types for modesetting
    UniversalPlanes = drm_ffi::DRM_CLIENT_CAP_UNIVERSAL_PLANES as u64,
    /// The driver provides atomic modesetting
    Atomic = drm_ffi::DRM_CLIENT_CAP_ATOMIC as u64,
}