Skip to content

Commit

Permalink
Add syncobj support and poll-based example
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
CirrusNeptune committed Jul 10, 2023
1 parent a7d1dc1 commit 04cc5df
Show file tree
Hide file tree
Showing 9 changed files with 582 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "MIT"
authors = ["Tyler Slabinski <[email protected]>", "Victoria Brekenfeld <[email protected]>"]
exclude = [".gitignore", ".github"]
rust-version = "1.63"
resolver = "2" # Required to separate dev-dependencies.nix features

[dependencies]
bitflags = "1"
Expand All @@ -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"
Expand Down
38 changes: 38 additions & 0 deletions drm-ffi/src/ioctl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
1 change: 1 addition & 0 deletions drm-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
262 changes: 262 additions & 0 deletions drm-ffi/src/syncobj.rs
Original file line number Diff line number Diff line change
@@ -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<drm_syncobj_create, Error> {
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<drm_syncobj_destroy, Error> {
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<drm_syncobj_handle, Error> {
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<drm_syncobj_handle, Error> {
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<drm_syncobj_wait, Error> {
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<drm_syncobj_array, Error> {
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<drm_syncobj_array, Error> {
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<drm_syncobj_timeline_wait, Error> {
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<drm_syncobj_timeline_array, Error> {
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<drm_syncobj_transfer, Error> {
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<drm_syncobj_timeline_array, Error> {
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)
}
59 changes: 59 additions & 0 deletions examples/syncobj.rs
Original file line number Diff line number Diff line change
@@ -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<SyncFile, SystemError> {
// 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();
}
Loading

0 comments on commit 04cc5df

Please sign in to comment.