From 04cc5df546c3bf260de8ae15ea7e2dc5e7bfb266 Mon Sep 17 00:00:00 2001 From: Cirrus Date: Mon, 10 Jul 2023 12:08:06 -0700 Subject: [PATCH] Add syncobj support and poll-based example * syncobj::Handle and syncobj::SyncFile types added * These types may be used in device-specific Card implementations to add multiple-event asynchronicity of command submissions. --- Cargo.toml | 6 + drm-ffi/src/ioctl.rs | 38 ++++++ drm-ffi/src/lib.rs | 1 + drm-ffi/src/syncobj.rs | 262 +++++++++++++++++++++++++++++++++++++++++ examples/syncobj.rs | 59 ++++++++++ examples/utils/mod.rs | 1 + src/control/mod.rs | 135 +++++++++++++++++++++ src/control/syncobj.rs | 78 ++++++++++++ src/lib.rs | 2 + 9 files changed, 582 insertions(+) create mode 100644 drm-ffi/src/syncobj.rs create mode 100644 examples/syncobj.rs create mode 100644 src/control/syncobj.rs diff --git a/Cargo.toml b/Cargo.toml index 8463492a..d0e323a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" authors = ["Tyler Slabinski ", "Victoria Brekenfeld "] exclude = [".gitignore", ".github"] rust-version = "1.63" +resolver = "2" # Required to separate dev-dependencies.nix features [dependencies] bitflags = "1" @@ -19,6 +20,11 @@ version = "0.26.0" default-features = false features = ["mman"] +[dev-dependencies.nix] +version = "0.26.0" +default-features = false +features = ["mman", "poll"] + [dev-dependencies] image = { version = "^0.23.14", default-features = false, features = ["png"] } rustyline = "^8.0.0" diff --git a/drm-ffi/src/ioctl.rs b/drm-ffi/src/ioctl.rs index e012762b..7b33ce63 100644 --- a/drm-ffi/src/ioctl.rs +++ b/drm-ffi/src/ioctl.rs @@ -205,3 +205,41 @@ pub(crate) mod gem { /// Converts a dma-buf file descriptor into a buffer handle. ioctl_readwrite!(prime_fd_to_handle, DRM_IOCTL_BASE, 0x2e, drm_prime_handle); } + +pub(crate) mod syncobj { + use drm_sys::*; + + /// Creates a syncobj. + ioctl_readwrite!(create, DRM_IOCTL_BASE, 0xBF, drm_syncobj_create); + /// Destroys a syncobj. + ioctl_readwrite!(destroy, DRM_IOCTL_BASE, 0xC0, drm_syncobj_destroy); + /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. + ioctl_readwrite!(handle_to_fd, DRM_IOCTL_BASE, 0xC1, drm_syncobj_handle); + /// Imports a file descriptor exported by [`handle_to_fd`] back into a process-local handle. + ioctl_readwrite!(fd_to_handle, DRM_IOCTL_BASE, 0xC2, drm_syncobj_handle); + /// Waits for one or more syncobjs to become signalled. + ioctl_readwrite!(wait, DRM_IOCTL_BASE, 0xC3, drm_syncobj_wait); + /// Resets (un-signals) one or more syncobjs. + ioctl_readwrite!(reset, DRM_IOCTL_BASE, 0xC4, drm_syncobj_array); + /// Signals one or more syncobjs. + ioctl_readwrite!(signal, DRM_IOCTL_BASE, 0xC5, drm_syncobj_array); + + /// Waits for one or more specific timeline syncobj points. + ioctl_readwrite!( + timeline_wait, + DRM_IOCTL_BASE, + 0xCA, + drm_syncobj_timeline_wait + ); + /// Queries for state of one or more timeline syncobjs. + ioctl_readwrite!(query, DRM_IOCTL_BASE, 0xCB, drm_syncobj_timeline_array); + /// Transfers one timeline syncobj point to another. + ioctl_readwrite!(transfer, DRM_IOCTL_BASE, 0xCC, drm_syncobj_transfer); + /// Signals one or more specific timeline syncobj points. + ioctl_readwrite!( + timeline_signal, + DRM_IOCTL_BASE, + 0xCD, + drm_syncobj_timeline_array + ); +} diff --git a/drm-ffi/src/lib.rs b/drm-ffi/src/lib.rs index 23787c32..35a98213 100644 --- a/drm-ffi/src/lib.rs +++ b/drm-ffi/src/lib.rs @@ -20,6 +20,7 @@ pub mod gem; pub mod ioctl; pub mod mode; pub mod result; +pub mod syncobj; use nix::libc::*; use std::os::unix::io::RawFd; diff --git a/drm-ffi/src/syncobj.rs b/drm-ffi/src/syncobj.rs new file mode 100644 index 00000000..0904b978 --- /dev/null +++ b/drm-ffi/src/syncobj.rs @@ -0,0 +1,262 @@ +//! +//! Bindings for DRM sync objects +//! + +use drm_sys::*; +use ioctl; + +use result::SystemError as Error; +use std::os::unix::io::RawFd; + +/// Creates a syncobj. +pub fn create(fd: RawFd, signaled: bool) -> Result { + let mut args = drm_syncobj_create { + handle: 0, + flags: if signaled { + DRM_SYNCOBJ_CREATE_SIGNALED + } else { + 0 + }, + }; + + unsafe { + ioctl::syncobj::create(fd, &mut args)?; + } + + Ok(args) +} + +/// Destroys a syncobj. +pub fn destroy(fd: RawFd, handle: u32) -> Result { + let mut args = drm_syncobj_destroy { handle, pad: 0 }; + + unsafe { + ioctl::syncobj::destroy(fd, &mut args)?; + } + + Ok(args) +} + +/// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. +pub fn handle_to_fd( + fd: RawFd, + handle: u32, + export_sync_file: bool, +) -> Result { + let mut args = drm_syncobj_handle { + handle, + flags: if export_sync_file { + DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE + } else { + 0 + }, + fd: 0, + pad: 0, + }; + + unsafe { + ioctl::syncobj::handle_to_fd(fd, &mut args)?; + } + + Ok(args) +} + +/// Imports a file descriptor exported by [`handle_to_fd`] back into a process-local handle. +pub fn fd_to_handle( + fd: RawFd, + syncobj_fd: RawFd, + import_sync_file: bool, +) -> Result { + let mut args = drm_syncobj_handle { + handle: 0, + flags: if import_sync_file { + DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE + } else { + 0 + }, + fd: syncobj_fd, + pad: 0, + }; + + unsafe { + ioctl::syncobj::fd_to_handle(fd, &mut args)?; + } + + Ok(args) +} + +/// Waits for one or more syncobjs to become signalled. +pub fn wait( + fd: RawFd, + handles: &[u32], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, +) -> Result { + let mut args = drm_syncobj_wait { + handles: handles.as_ptr() as _, + timeout_nsec, + count_handles: handles.len() as _, + flags: if wait_all { + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL + } else { + 0 + } | if wait_for_submit { + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT + } else { + 0 + }, + first_signaled: 0, + pad: 0, + }; + + unsafe { + ioctl::syncobj::wait(fd, &mut args)?; + } + + Ok(args) +} + +/// Resets (un-signals) one or more syncobjs. +pub fn reset(fd: RawFd, handles: &[u32]) -> Result { + let mut args = drm_syncobj_array { + handles: handles.as_ptr() as _, + count_handles: handles.len() as _, + pad: 0, + }; + + unsafe { + ioctl::syncobj::reset(fd, &mut args)?; + } + + Ok(args) +} + +/// Signals one or more syncobjs. +pub fn signal(fd: RawFd, handles: &[u32]) -> Result { + let mut args = drm_syncobj_array { + handles: handles.as_ptr() as _, + count_handles: handles.len() as _, + pad: 0, + }; + + unsafe { + ioctl::syncobj::signal(fd, &mut args)?; + } + + Ok(args) +} + +/// Waits for one or more specific timeline syncobj points. +pub fn timeline_wait( + fd: RawFd, + handles: &[u32], + points: &[u64], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, + wait_available: bool, +) -> Result { + debug_assert_eq!(handles.len(), points.len()); + + let mut args = drm_syncobj_timeline_wait { + handles: handles.as_ptr() as _, + points: points.as_ptr() as _, + timeout_nsec, + count_handles: handles.len() as _, + flags: if wait_all { + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL + } else { + 0 + } | if wait_for_submit { + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT + } else { + 0 + } | if wait_available { + DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE + } else { + 0 + }, + first_signaled: 0, + pad: 0, + }; + + unsafe { + ioctl::syncobj::timeline_wait(fd, &mut args)?; + } + + Ok(args) +} + +/// Queries for state of one or more timeline syncobjs. +pub fn query( + fd: RawFd, + handles: &[u32], + points: &mut [u64], + last_submitted: bool, +) -> Result { + debug_assert_eq!(handles.len(), points.len()); + + let mut args = drm_syncobj_timeline_array { + handles: handles.as_ptr() as _, + points: points.as_ptr() as _, + count_handles: handles.len() as _, + flags: if last_submitted { + DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED + } else { + 0 + }, + }; + + unsafe { + ioctl::syncobj::query(fd, &mut args)?; + } + + Ok(args) +} + +/// Transfers one timeline syncobj point to another. +pub fn transfer( + fd: RawFd, + src_handle: u32, + dst_handle: u32, + src_point: u64, + dst_point: u64, +) -> Result { + let mut args = drm_syncobj_transfer { + src_handle, + dst_handle, + src_point, + dst_point, + flags: 0, + pad: 0, + }; + + unsafe { + ioctl::syncobj::transfer(fd, &mut args)?; + } + + Ok(args) +} + +/// Signals one or more specific timeline syncobj points. +pub fn timeline_signal( + fd: RawFd, + handles: &[u32], + points: &[u64], +) -> Result { + debug_assert_eq!(handles.len(), points.len()); + + let mut args = drm_syncobj_timeline_array { + handles: handles.as_ptr() as _, + points: points.as_ptr() as _, + count_handles: handles.len() as _, + flags: 0, + }; + + unsafe { + ioctl::syncobj::timeline_signal(fd, &mut args)?; + } + + Ok(args) +} diff --git a/examples/syncobj.rs b/examples/syncobj.rs new file mode 100644 index 00000000..de33ff9b --- /dev/null +++ b/examples/syncobj.rs @@ -0,0 +1,59 @@ +extern crate drm; +extern crate image; +extern crate nix; + +/// Check the `util` module to see how the `Card` structure is implemented. +pub mod utils; + +use nix::poll::PollFlags; +use std::os::fd::AsRawFd; +use utils::*; + +use drm::control::syncobj::SyncFile; +use drm::SystemError; + +impl Card { + fn simulate_command_submission(&self) -> Result { + // Create a temporary syncobj to receive the command fence. + let syncobj = self.create_syncobj(false)?; + + let sync_file = { + // Fake a command submission by signalling the syncobj immediately. The kernel + // attaches a null fence object which is always signalled. Other than this, there + // isn't a good way to create and signal a fence object from user-mode, so an actual + // device is required to test this properly. + // + // For a real device, the syncobj handle should be passed to a command submission + // which is expected to set a fence to be signalled upon completion. + self.syncobj_signal(&[syncobj.into()])?; + + // Export fence set by previous ioctl to file descriptor. + self.syncobj_to_fd(syncobj, true) + }; + + // The sync file descriptor constitutes ownership of the fence, so the syncobj can be + // safely destroyed. + self.destroy_syncobj(syncobj)?; + + sync_file + } +} + +fn main() { + let card = Card::open_global(); + let sync_file = card.simulate_command_submission().unwrap(); + + // Poll for readability. The DRM fence object will directly wake the thread when signalled. + // + // Alternatively, Tokio's AsyncFd may be used like so: + // + // use tokio::io::{Interest, unix::AsyncFd}; + // let afd = AsyncFd::with_interest(sync_file, Interest::READABLE).unwrap(); + // let future = async move { afd.readable().await.unwrap().retain_ready() }; + // future.await; + let mut poll_fds = [nix::poll::PollFd::new( + sync_file.as_raw_fd(), + PollFlags::POLLIN, + )]; + nix::poll::poll(&mut poll_fds, -1).unwrap(); +} diff --git a/examples/utils/mod.rs b/examples/utils/mod.rs index 2b0032dc..172747ae 100644 --- a/examples/utils/mod.rs +++ b/examples/utils/mod.rs @@ -55,6 +55,7 @@ pub mod capabilities { DC::PageFlipTarget, DC::CRTCInVBlankEvent, DC::SyncObj, + DC::TimelineSyncObj, ]; } diff --git a/src/control/mod.rs b/src/control/mod.rs index 46144806..c7cda9d0 100644 --- a/src/control/mod.rs +++ b/src/control/mod.rs @@ -41,6 +41,7 @@ pub mod dumbbuffer; pub mod encoder; pub mod framebuffer; pub mod plane; +pub mod syncobj; pub mod property; @@ -797,6 +798,140 @@ pub trait Device: super::Device { Ok(()) } + /// Creates a syncobj. + fn create_syncobj(&self, signalled: bool) -> Result { + let info = ffi::syncobj::create(self.as_fd().as_raw_fd(), signalled)?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Destroys a syncobj. + fn destroy_syncobj(&self, handle: syncobj::Handle) -> Result<(), SystemError> { + ffi::syncobj::destroy(self.as_fd().as_raw_fd(), handle.into())?; + Ok(()) + } + + /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. + fn syncobj_to_fd( + &self, + handle: syncobj::Handle, + export_sync_file: bool, + ) -> Result { + use std::os::fd::FromRawFd; + let info = + ffi::syncobj::handle_to_fd(self.as_fd().as_raw_fd(), handle.into(), export_sync_file)?; + Ok(unsafe { syncobj::SyncFile::from_raw_fd(info.fd) }) + } + + /// Imports a file descriptor exported by [`Self::syncobj_to_fd`] back into a process-local handle. + fn fd_to_syncobj( + &self, + fd: RawFd, + import_sync_file: bool, + ) -> Result { + let info = ffi::syncobj::fd_to_handle(self.as_fd().as_raw_fd(), fd, import_sync_file)?; + Ok(from_u32(info.handle).unwrap()) + } + + /// Waits for one or more syncobjs to become signalled. + fn syncobj_wait( + &self, + handles: &[syncobj::Handle], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, + ) -> Result { + let info = ffi::syncobj::wait( + self.as_fd().as_raw_fd(), + bytemuck::cast_slice(handles), + timeout_nsec, + wait_all, + wait_for_submit, + )?; + Ok(info.first_signaled) + } + + /// Resets (un-signals) one or more syncobjs. + fn syncobj_reset(&self, handles: &[syncobj::Handle]) -> Result<(), SystemError> { + ffi::syncobj::reset(self.as_fd().as_raw_fd(), bytemuck::cast_slice(handles))?; + Ok(()) + } + + /// Signals one or more syncobjs. + fn syncobj_signal(&self, handles: &[syncobj::Handle]) -> Result<(), SystemError> { + ffi::syncobj::signal(self.as_fd().as_raw_fd(), bytemuck::cast_slice(handles))?; + Ok(()) + } + + /// Waits for one or more specific timeline syncobj points. + fn syncobj_timeline_wait( + &self, + handles: &[syncobj::Handle], + points: &[u64], + timeout_nsec: i64, + wait_all: bool, + wait_for_submit: bool, + wait_available: bool, + ) -> Result { + let info = ffi::syncobj::timeline_wait( + self.as_fd().as_raw_fd(), + bytemuck::cast_slice(handles), + points, + timeout_nsec, + wait_all, + wait_for_submit, + wait_available, + )?; + Ok(info.first_signaled) + } + + /// Queries for state of one or more timeline syncobjs. + fn syncobj_timeline_query( + &self, + handles: &[syncobj::Handle], + points: &mut [u64], + last_submitted: bool, + ) -> Result<(), SystemError> { + ffi::syncobj::query( + self.as_fd().as_raw_fd(), + bytemuck::cast_slice(handles), + points, + last_submitted, + )?; + Ok(()) + } + + /// Transfers one timeline syncobj point to another. + fn syncobj_timeline_transfer( + &self, + src_handle: syncobj::Handle, + dst_handle: syncobj::Handle, + src_point: u64, + dst_point: u64, + ) -> Result<(), SystemError> { + ffi::syncobj::transfer( + self.as_fd().as_raw_fd(), + src_handle.into(), + dst_handle.into(), + src_point, + dst_point, + )?; + Ok(()) + } + + /// Signals one or more specific timeline syncobj points. + fn syncobj_timeline_signal( + &self, + handles: &[syncobj::Handle], + points: &[u64], + ) -> Result<(), SystemError> { + ffi::syncobj::timeline_signal( + self.as_fd().as_raw_fd(), + bytemuck::cast_slice(handles), + points, + )?; + Ok(()) + } + /// Receive pending events fn receive_events(&self) -> Result where diff --git a/src/control/syncobj.rs b/src/control/syncobj.rs new file mode 100644 index 00000000..02adfd5c --- /dev/null +++ b/src/control/syncobj.rs @@ -0,0 +1,78 @@ +//! # SyncObj +//! +//! A SyncObj is a binding point for the DRM subsystem to attach single-use fences which are +//! signalled when a device task completes. They are typically provided as optional arguments to +//! device-specific command submission IOCTLs. In practice, they are used to implement Vulkan +//! fence objects. +//! +//! After a submission IOCTL sets a fence into a SyncObj, it may be exported as a sync file +//! descriptor. This sync file may be epoll()'d for EPOLLIN to implement asynchronous waiting on +//! multiple events. This file descriptor is also compatible with [`tokio::io::unix::AsyncFd`] for +//! Rust async/await integration. +//! +//! [`tokio::io::unix::AsyncFd`]: + +use control; +use std::os::fd::{AsFd, AsRawFd, FromRawFd, RawFd}; + +/// A handle to a specific syncobj +#[repr(transparent)] +#[derive(Copy, Clone, Hash, PartialEq, Eq)] +pub struct Handle(control::RawResourceHandle); + +// Safety: Handle is repr(transparent) over NonZeroU32 +unsafe impl bytemuck::ZeroableInOption for Handle {} +unsafe impl bytemuck::PodInOption for Handle {} +unsafe impl bytemuck::NoUninit for Handle {} + +impl From for control::RawResourceHandle { + fn from(handle: Handle) -> Self { + handle.0 + } +} + +impl From for u32 { + fn from(handle: Handle) -> Self { + handle.0.into() + } +} + +impl From for Handle { + fn from(handle: control::RawResourceHandle) -> Self { + Handle(handle) + } +} + +impl std::fmt::Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("syncobj::Handle").field(&self.0).finish() + } +} + +#[derive(Debug)] +/// A simple wrapper for a syncobj fd. +pub struct SyncFile(std::fs::File); + +impl FromRawFd for SyncFile { + unsafe fn from_raw_fd(fd: RawFd) -> Self { + Self(std::fs::File::from_raw_fd(fd)) + } +} + +/// Implementing [`AsFd`] is a prerequisite to implementing the traits found in this crate. +/// Here, we are just calling [`std::fs::File::as_fd()`] on the inner File. +impl AsFd for SyncFile { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { + self.0.as_fd() + } +} + +/// Implementing [`AsRawFd`] allows SyncFile to be owned by [`tokio::io::unix::AsyncFd`]; +/// thereby integrating with its async/await runtime. +/// +/// [`tokio::io::unix::AsyncFd`]: +impl AsRawFd for SyncFile { + fn as_raw_fd(&self) -> RawFd { + self.as_fd().as_raw_fd() + } +} diff --git a/src/lib.rs b/src/lib.rs index bf65c0a7..f4d6c6f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -305,6 +305,8 @@ pub enum DriverCapability { CRTCInVBlankEvent = drm_ffi::DRM_CAP_CRTC_IN_VBLANK_EVENT as u64, /// SyncObj support SyncObj = drm_ffi::DRM_CAP_SYNCOBJ as u64, + /// Timeline SyncObj support + TimelineSyncObj = drm_ffi::DRM_CAP_SYNCOBJ_TIMELINE as u64, } /// Used to enable/disable capabilities for the process.