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::error::syscall_ret;
18use crate::reactor::Fd;
19use crate::CoreError;
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).map_err(|_| {
63 CoreError::sys(libc::EINVAL, "drm open path contains NUL")
64 })?;
65 let fd = unsafe { libc::open(c_path.as_ptr(), O_RDWR | O_CLOEXEC) };
66 syscall_ret(fd, "open(drm)")?;
67 // SAFETY: `fd` is a freshly opened owned descriptor from `open`.
68 let fd = unsafe { Fd::from_owned_raw_fd(fd, "drm") }?;
69 Ok(Self { fd })
70 }
71
72 /// Block until the next vblank and return the monotonic instant the wait
73 /// returned.
74 ///
75 /// The reply `t_sec`/`t_usec` fields are deliberately ignored: they are
76 /// zeroed on `msm` display stacks. Only the wait-unblock instant is
77 /// trustworthy, so callers diff consecutive return values.
78 ///
79 /// ### Errors
80 /// - `EINTR`: interrupted by a signal (safe to retry).
81 /// - `EINVAL`: the card does not support vblank waits.
82 /// - `EBADF`: the card fd was closed.
83 pub fn wait_vblank(&self) -> Result<Instant, CoreError> {
84 let mut vblank = DrmWaitVBlank {
85 request_type: DRM_VBLANK_RELATIVE,
86 request_sequence: 1,
87 request_signal: 0,
88 reply_sequence: 0,
89 reply_t_sec: 0,
90 reply_t_usec: 0,
91 };
92
93 let nr = DRM_IOCTL_WAIT_VBLANK_NR;
94 let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
95 let ioctl_nr: libc::Ioctl = ((3u32 << 30)
96 | (DRM_IOCTL_BASE << 8)
97 | (nr << 0)
98 | (size << 16)) as libc::Ioctl;
99
100 // The vblank wait is a blocking ioctl; a signal (e.g. the per-thread
101 // shutdown interrupt) surfaces as EINTR and is propagated to the caller.
102 let ret = unsafe { libc::ioctl(self.fd.as_raw_fd(), ioctl_nr, &mut vblank) };
103 syscall_ret(ret, "DRM_IOCTL_WAIT_VBLANK")?;
104 Ok(Instant::now())
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn wait_vblank_ioctl_matches_known_constant() {
114 // (3 << 30) | ('d' << 8) | (0x3a) | (size(32) << 16) == 0xC020643A.
115 let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
116 let computed: libc::Ioctl =
117 ((3u32 << 30) | (DRM_IOCTL_BASE << 8) | (0x3au32) | (size << 16)) as libc::Ioctl;
118 assert_eq!(computed, 0xC020643Au32 as libc::Ioctl);
119 assert_eq!(size, 32);
120 }
121
122 #[test]
123 fn vblank_request_struct_layout_is_32_bytes() {
124 assert_eq!(std::mem::size_of::<DrmWaitVBlank>(), 32);
125 }
126}