Skip to main content

drmkit_core/
vblank.rs

1// SPDX-FileCopyrightText: (c) 2026 Joel Winarske
2// SPDX-License-Identifier: MIT
3
4//! The CRTC's vblank counter, as `drmCrtcGetSequence` reads it.
5//!
6//! The `drm` crate binds `DRM_IOCTL_WAIT_VBLANK`, whose sequence is 32 bits,
7//! but not `DRM_IOCTL_CRTC_GET_SEQUENCE`, which is what upstream reads. The
8//! wider counter is the one to compare against: a 32-bit one wraps in a little
9//! over two years at 60 Hz, and a comparison across the wrap goes backwards.
10
11use std::os::fd::AsFd;
12
13use drm_ffi::drm_sys::drm_crtc_get_sequence;
14
15use crate::error::{CoreError, Result};
16
17/// `DRM_IOCTL_CRTC_GET_SEQUENCE`, i.e. `DRM_IOWR(0x3b, struct
18/// drm_crtc_get_sequence)` with `DRM_IOCTL_BASE == 'd'`.
19const CRTC_GET_SEQUENCE: rustix::ioctl::Opcode =
20    rustix::ioctl::opcode::read_write::<drm_crtc_get_sequence>(b'd', 0x3b);
21
22/// The CRTC's current vblank sequence.
23///
24/// Port of `drmCrtcGetSequence` without the timestamp. The count moves once per
25/// vblank while the CRTC is active, so a flip committed when it read `n` has
26/// landed once it reads more than `n`.
27///
28/// # Errors
29///
30/// [`CoreError::Io`] if the ioctl fails: `EINVAL` for a CRTC id the device
31/// does not have, `EOPNOTSUPP` on a driver without vblank support.
32pub fn crtc_sequence(device: &impl AsFd, crtc_id: u32) -> Result<u64> {
33    let mut data = drm_crtc_get_sequence {
34        crtc_id,
35        ..Default::default()
36    };
37    // SAFETY: CRTC_GET_SEQUENCE is DRM_IOWR(0x3b, struct drm_crtc_get_sequence)
38    // and `drm_crtc_get_sequence` is the bindgen definition of exactly that
39    // struct, so the opcode and the value type agree. The descriptor is
40    // borrowed for the duration of the call.
41    unsafe {
42        rustix::ioctl::ioctl(
43            device.as_fd(),
44            rustix::ioctl::Updater::<CRTC_GET_SEQUENCE, drm_crtc_get_sequence>::new(&mut data),
45        )
46    }
47    .map_err(CoreError::from_errno)?;
48    Ok(data.sequence)
49}
50
51/// Whether an event -- a page-flip completion, a vblank -- is queued on the
52/// device, without reading it.
53///
54/// A zero-timeout `poll`, so the event stays for whoever dispatches it. The
55/// kernel queues a flip event only once the flip has landed, which makes this
56/// the second sign of a landed flip beside [`crtc_sequence`]: a driver that
57/// completes the flip inside the commit (the SA8155P) moves the sequence
58/// before the caller can read it, and not again until the next commit
59/// (drm-cxx `42287bd`). `false` if the poll fails.
60#[must_use]
61pub fn event_pending(device: &impl AsFd) -> bool {
62    let mut fds = [rustix::event::PollFd::new(
63        device,
64        rustix::event::PollFlags::IN,
65    )];
66    let zero = rustix::event::Timespec {
67        tv_sec: 0,
68        tv_nsec: 0,
69    };
70    rustix::event::poll(&mut fds, Some(&zero))
71        .is_ok_and(|ready| ready > 0 && fds[0].revents().contains(rustix::event::PollFlags::IN))
72}