cloudfox-coreshift-core 2.14.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Direct Rendering Manager (DRM) vblank primitives.
//!
//! The public API is intentionally narrow: open a card node, then block on
//! `DRM_IOCTL_WAIT_VBLANK` until the next vertical blank, reporting the
//! monotonic instant the wait returned. Android's `msm` display stack zeroes
//! the ioctl reply timestamps (`t_sec`/`t_usec`), so callers must use a
//! monotonic clock taken at the moment the wait unblocks — this module does
//! exactly that and never exposes the (unreliable) reply fields.

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

use crate::CoreError;
use crate::error::syscall_ret;
use crate::fd::Fd;

const O_RDWR: i32 = libc::O_RDWR;
const O_CLOEXEC: i32 = libc::O_CLOEXEC;

// _IOC(dir, type, nr, size) = (dir << 30) | (type << 8) | (nr << 0) | (size << 16)
// DRM_IOCTL_BASE is 'd' (0x64); DRM_IOCTL_WAIT_VBLANK is _IOWR(0x3a, drm_wait_vblank).
const DRM_IOCTL_BASE: u32 = 0x64; // 'd'
const DRM_IOCTL_WAIT_VBLANK_NR: u32 = 0x3a;

// DRM_VBLANK_RELATIVE (wait relative to current counter), as used for the
// common "wake me at the next vblank" blocking call.
const DRM_VBLANK_RELATIVE: u32 = 0x1;

#[repr(C)]
#[derive(Clone, Copy)]
struct DrmWaitVBlankRequest {
    // enum drm_vblank_seq_type
    r#type: u32,
    sequence: u32,
    // __aligned_u64 signal (unsigned long on LP64)
    signal: u64,
}

#[repr(C)]
#[derive(Clone, Copy)]
struct DrmWaitVBlankReply {
    r#type: u32,
    sequence: u32,
    tval_sec: i64,
    tval_usec: i64,
}

/// `union drm_wait_vblank` from `<drm/drm.h>`. The request and reply overlay
/// each other; only the request is ever written by this module (the reply
/// timestamps are unreliable on `msm` stacks and deliberately ignored).
#[repr(C)]
#[derive(Clone, Copy)]
union DrmWaitVBlank {
    request: DrmWaitVBlankRequest,
    reply: DrmWaitVBlankReply,
}

// LP64 (aarch64/x86_64) kernel ABI: request=16 bytes, reply=24 bytes, so the
// union is 24 bytes. The previous hand-rolled 32-byte struct encoded the wrong
// `_IOC_SIZE` (0xC020643A vs the real 0xc018643a), which copy_to_user would
// round with 8 bytes of kernel-stack garbage. Fail loudly on any layout that
// does not match the LP64 header rather than silently send a wrong size.
const _: () = assert!(std::mem::size_of::<DrmWaitVBlank>() == 24);
const _: () = assert!(std::mem::size_of::<DrmWaitVBlankRequest>() == 16);

/// An open DRM card node (e.g. `/dev/dri/card0`).
///
/// The fd is `O_CLOEXEC` and owned by this wrapper; it is closed on drop.
pub struct DrmCard {
    fd: Fd,
}

impl DrmCard {
    /// Open a DRM primary node by device path.
    ///
    /// ### Errors
    /// - `ENOENT`/`ENODEV`: no such card node.
    /// - `EACCES`/`EPERM`: insufficient privilege for the device node.
    pub fn open(path: &str) -> Result<Self, CoreError> {
        let c_path = std::ffi::CString::new(path)
            .map_err(|_| CoreError::sys(libc::EINVAL, "drm open path contains NUL"))?;
        let fd = unsafe { libc::open(c_path.as_ptr(), O_RDWR | O_CLOEXEC) };
        syscall_ret(fd, "open(drm)")?;
        // SAFETY: `fd` is a freshly opened owned descriptor from `open`.
        let fd = unsafe { Fd::from_owned_raw_fd(fd, "drm") }?;
        Ok(Self { fd })
    }

    /// Block until the next vblank and return the monotonic instant the wait
    /// returned.
    ///
    /// The reply `t_sec`/`t_usec` fields are deliberately ignored: they are
    /// zeroed on `msm` display stacks. Only the wait-unblock instant is
    /// trustworthy, so callers diff consecutive return values.
    ///
    /// ### Errors
    /// - `EINTR`: interrupted by a signal (safe to retry).
    /// - `EINVAL`: the card does not support vblank waits.
    /// - `EBADF`: the card fd was closed.
    pub fn wait_vblank(&self) -> Result<Instant, CoreError> {
        let mut vblank = DrmWaitVBlank {
            request: DrmWaitVBlankRequest {
                r#type: DRM_VBLANK_RELATIVE,
                sequence: 1,
                signal: 0,
            },
        };

        let nr = DRM_IOCTL_WAIT_VBLANK_NR;
        let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
        let ioctl_nr: libc::Ioctl =
            ((3u32 << 30) | (DRM_IOCTL_BASE << 8) | nr | (size << 16)) as libc::Ioctl;

        // The vblank wait is a blocking ioctl; a signal (e.g. the per-thread
        // shutdown interrupt) surfaces as EINTR and is propagated to the caller.
        let ret = unsafe { libc::ioctl(self.fd.as_raw_fd(), ioctl_nr, &mut vblank) };
        syscall_ret(ret, "DRM_IOCTL_WAIT_VBLANK")?;
        Ok(Instant::now())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn wait_vblank_ioctl_matches_known_constant() {
        // (3 << 30) | ('d' << 8) | (0x3a) | (size(24) << 16) == 0xC018643A,
        // matching `DRM_IOCTL_WAIT_VBLANK` (`_IOWR(0x3a, union drm_wait_vblank)`)
        // on the LP64 kernel ABI where the union is 24 bytes.
        let size = std::mem::size_of::<DrmWaitVBlank>() as u32;
        let computed: libc::Ioctl =
            ((3u32 << 30) | (DRM_IOCTL_BASE << 8) | (0x3au32) | (size << 16)) as libc::Ioctl;
        assert_eq!(computed, 0xC018643Au32 as libc::Ioctl);
        assert_eq!(size, 24);
    }

    #[test]
    fn vblank_request_union_layout_is_24_bytes() {
        assert_eq!(std::mem::size_of::<DrmWaitVBlank>(), 24);
        assert_eq!(std::mem::size_of::<DrmWaitVBlankRequest>(), 16);
    }
}