Skip to main content

coreshift_core/
drm.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Direct Rendering Manager (DRM) vblank primitives.
6//!
7//! The public API is intentionally narrow: open a card node, then block on
8//! `DRM_IOCTL_WAIT_VBLANK` until the next vertical blank, reporting the
9//! monotonic instant the wait returned. Android's `msm` display stack zeroes
10//! the ioctl reply timestamps (`t_sec`/`t_usec`), so callers must use a
11//! monotonic clock taken at the moment the wait unblocks — this module does
12//! exactly that and never exposes the (unreliable) reply fields.
13
14use std::os::unix::io::AsRawFd;
15use std::time::Instant;
16
17use crate::CoreError;
18use crate::error::syscall_ret;
19use crate::reactor::Fd;
20
21const O_RDWR: i32 = libc::O_RDWR;
22const O_CLOEXEC: i32 = libc::O_CLOEXEC;
23
24// _IOC(dir, type, nr, size) = (dir << 30) | (type << 8) | (nr << 0) | (size << 16)
25// DRM_IOCTL_BASE is 'd' (0x64); DRM_IOCTL_WAIT_VBLANK is _IOWR(0x3a, drm_wait_vblank).
26const DRM_IOCTL_BASE: u32 = 0x64; // 'd'
27const DRM_IOCTL_WAIT_VBLANK_NR: u32 = 0x3a;
28
29// DRM_VBLANK_RELATIVE (wait relative to current counter), as used for the
30// common "wake me at the next vblank" blocking call.
31const DRM_VBLANK_RELATIVE: u32 = 0x1;
32
33#[repr(C)]
34#[derive(Clone, Copy)]
35struct DrmWaitVBlank {
36    // union drm_wait_vblank_request
37    request_type: u32,
38    request_sequence: u32,
39    request_signal: u64, // __aligned_u64
40    // union drm_wait_vblank_reply
41    reply_sequence: u32,
42    reply_t_sec: u32,
43    reply_t_usec: u32,
44}
45
46const _: () = assert!(std::mem::size_of::<DrmWaitVBlank>() == 32);
47
48/// An open DRM card node (e.g. `/dev/dri/card0`).
49///
50/// The fd is `O_CLOEXEC` and owned by this wrapper; it is closed on drop.
51pub struct DrmCard {
52    fd: Fd,
53}
54
55impl DrmCard {
56    /// Open a DRM primary node by device path.
57    ///
58    /// ### Errors
59    /// - `ENOENT`/`ENODEV`: no such card node.
60    /// - `EACCES`/`EPERM`: insufficient privilege for the device node.
61    pub fn open(path: &str) -> Result<Self, CoreError> {
62        let c_path = std::ffi::CString::new(path)
63            .map_err(|_| CoreError::sys(libc::EINVAL, "drm open path contains NUL"))?;
64        let fd = unsafe { libc::open(c_path.as_ptr(), O_RDWR | O_CLOEXEC) };
65        syscall_ret(fd, "open(drm)")?;
66        // SAFETY: `fd` is a freshly opened owned descriptor from `open`.
67        let fd = unsafe { Fd::from_owned_raw_fd(fd, "drm") }?;
68        Ok(Self { fd })
69    }
70
71    /// Block until the next vblank and return the monotonic instant the wait
72    /// returned.
73    ///
74    /// The reply `t_sec`/`t_usec` fields are deliberately ignored: they are
75    /// zeroed on `msm` display stacks. Only the wait-unblock instant is
76    /// trustworthy, so callers diff consecutive return values.
77    ///
78    /// ### Errors
79    /// - `EINTR`: interrupted by a signal (safe to retry).
80    /// - `EINVAL`: the card does not support vblank waits.
81    /// - `EBADF`: the card fd was closed.
82    pub fn wait_vblank(&self) -> Result<Instant, CoreError> {
83        let mut vblank = DrmWaitVBlank {
84            request_type: DRM_VBLANK_RELATIVE,
85            request_sequence: 1,
86            request_signal: 0,
87            reply_sequence: 0,
88            reply_t_sec: 0,
89            reply_t_usec: 0,
90        };
91
92        let nr = DRM_IOCTL_WAIT_VBLANK_NR;
93        let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
94        let ioctl_nr: libc::Ioctl =
95            ((3u32 << 30) | (DRM_IOCTL_BASE << 8) | nr | (size << 16)) as libc::Ioctl;
96
97        // The vblank wait is a blocking ioctl; a signal (e.g. the per-thread
98        // shutdown interrupt) surfaces as EINTR and is propagated to the caller.
99        let ret = unsafe { libc::ioctl(self.fd.as_raw_fd(), ioctl_nr, &mut vblank) };
100        syscall_ret(ret, "DRM_IOCTL_WAIT_VBLANK")?;
101        Ok(Instant::now())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn wait_vblank_ioctl_matches_known_constant() {
111        // (3 << 30) | ('d' << 8) | (0x3a) | (size(32) << 16) == 0xC020643A.
112        let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
113        let computed: libc::Ioctl =
114            ((3u32 << 30) | (DRM_IOCTL_BASE << 8) | (0x3au32) | (size << 16)) as libc::Ioctl;
115        assert_eq!(computed, 0xC020643Au32 as libc::Ioctl);
116        assert_eq!(size, 32);
117    }
118
119    #[test]
120    fn vblank_request_struct_layout_is_32_bytes() {
121        assert_eq!(std::mem::size_of::<DrmWaitVBlank>(), 32);
122    }
123}