use std::{
os::fd::{AsFd, AsRawFd, OwnedFd},
time::Duration,
};
use crate::{
bindings::{
DRM_CAP_SYNCOBJ, DRM_CAP_SYNCOBJ_TIMELINE, DrmGetCap, DrmSyncobjCreate, DrmSyncobjDestroy,
DrmSyncobjFdToHandle, DrmSyncobjHandleToFd, DrmSyncobjTimelineQuery,
DrmSyncobjTimelineSignal, DrmSyncobjTimelineWait, DrmSyncobjTransfer, RawDrmSyncobjHandle,
SyncobjCreateFlags, SyncobjFdToHandleFlags, SyncobjHandleToFdFlags,
SyncobjTimelineQueryFlags, SyncobjTimelineSignalFlags, SyncobjTransferFlags,
SyncobjWaitFlags,
},
render_node::DrmRenderNode,
};
#[derive(Debug)]
pub struct TimelineSyncObj {
handle: RawDrmSyncobjHandle,
render_node: DrmRenderNode,
}
impl TimelineSyncObj {
pub fn export_sync_file_point(&self, point: u64) -> rustix::io::Result<OwnedFd> {
if self.render_node.direct_syncfile {
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjHandleToFd {
handle: self.handle,
flags: SyncobjHandleToFdFlags::EXPORT_SYNC_FILE
| SyncobjHandleToFdFlags::TIMELINE,
fd: -1,
_padding: 0,
point,
},
)
}
} else {
let tmp = TimelineSyncObj::new(&self.render_node)?;
self.transfer_point(&tmp, point, 0)?;
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjHandleToFd {
handle: tmp.handle,
flags: SyncobjHandleToFdFlags::EXPORT_SYNC_FILE,
fd: -1,
_padding: 0,
point: 0,
},
)
}
}
}
pub fn import_sync_file_point(
&self,
sync_file: impl AsFd,
point: u64,
) -> rustix::io::Result<()> {
let sync_file = sync_file.as_fd();
if self.render_node.direct_syncfile {
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjFdToHandle {
handle: self.handle,
flags: SyncobjFdToHandleFlags::IMPORT_SYNC_FILE
| SyncobjFdToHandleFlags::TIMELINE,
fd: sync_file.as_raw_fd(),
_padding: 0,
point,
},
)?
};
} else {
let tmp = TimelineSyncObj::new(&self.render_node)?;
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjFdToHandle {
handle: tmp.handle,
flags: SyncobjFdToHandleFlags::IMPORT_SYNC_FILE,
fd: sync_file.as_raw_fd(),
_padding: 0,
point: 0,
},
)?
};
tmp.transfer_point(self, 0, point)?;
}
Ok(())
}
fn query_point(&self, flags: SyncobjTimelineQueryFlags) -> rustix::io::Result<u64> {
let handles: &[_] = &[self.handle];
let points: &mut [_] = &mut [0u64];
let points = unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjTimelineQuery {
handles: handles.as_ptr() as u64,
points: points.as_ptr() as u64,
count_handles: handles.len() as u32,
flags,
},
)?
};
Ok(points[0])
}
pub fn query_signaled_point(&self) -> rustix::io::Result<u64> {
self.query_point(SyncobjTimelineQueryFlags::empty())
}
pub fn query_available_point(&self) -> rustix::io::Result<u64> {
self.query_point(SyncobjTimelineQueryFlags::LAST_SUBMITTED)
}
pub fn transfer_point(
&self,
to: &Self,
src_point: u64,
target_point: u64,
) -> rustix::io::Result<()> {
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjTransfer {
src_handle: self.get_raw_handle(),
dst_handle: to.get_raw_handle(),
src_point,
dst_point: target_point,
flags: SyncobjTransferFlags::empty(),
pad: 0,
},
)?
};
Ok(())
}
pub unsafe fn signal(&self, point: u64) -> rustix::io::Result<()> {
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjTimelineSignal {
handles: &raw const self.handle as u64,
points: [point].as_ptr() as u64,
count_handles: 1,
flags: SyncobjTimelineSignalFlags::empty(),
},
)?
};
Ok(())
}
fn blocking_wait_inner(
&self,
point: u64,
timeout: Option<Duration>,
flags: SyncobjWaitFlags,
) -> rustix::io::Result<()> {
let handles: &[_] = &[self.handle];
let points: &[_] = &[point];
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjTimelineWait {
handles: handles.as_ptr() as u64,
points: points.as_ptr() as u64,
timeout_nsec: timeout
.and_then(|v| v.as_nanos().try_into().ok())
.unwrap_or(i64::MAX),
count_handles: handles.len() as u32,
flags,
first_signaled: 0,
_padding: 0,
deadline_nsec: 0,
},
)?
};
Ok(())
}
pub fn blocking_wait(&self, point: u64, timeout: Option<Duration>) -> rustix::io::Result<()> {
self.blocking_wait_inner(point, timeout, SyncobjWaitFlags::FOR_SUBMIT)
}
pub fn blocking_wait_available(
&self,
point: u64,
timeout: Option<Duration>,
) -> rustix::io::Result<()> {
self.blocking_wait_inner(point, timeout, SyncobjWaitFlags::AVAILABLE)
}
}
impl TimelineSyncObj {
pub fn new(render_node: &DrmRenderNode) -> rustix::io::Result<Self> {
let (syncobj_cap, timeline_syncobj_cap) = unsafe {
(
rustix::ioctl::ioctl(
render_node,
DrmGetCap {
cap: DRM_CAP_SYNCOBJ,
value: 0,
},
)?,
rustix::ioctl::ioctl(
render_node,
DrmGetCap {
cap: DRM_CAP_SYNCOBJ_TIMELINE,
value: 0,
},
)?,
)
};
if syncobj_cap == 0 {
panic!("syncobj not supported by drm driver");
}
if timeline_syncobj_cap == 0 {
panic!("timeline_syncobj not supported by drm driver");
}
let handle = unsafe {
rustix::ioctl::ioctl(
render_node,
DrmSyncobjCreate {
handle: 0,
flags: SyncobjCreateFlags::empty(),
},
)?
};
Ok(TimelineSyncObj {
handle,
render_node: render_node.clone(),
})
}
pub fn import(render_node: &DrmRenderNode, fd: impl AsFd) -> rustix::io::Result<Self> {
let fd = fd.as_fd();
let handle = unsafe {
rustix::ioctl::ioctl(
render_node,
DrmSyncobjFdToHandle {
handle: RawDrmSyncobjHandle::NULL,
flags: SyncobjFdToHandleFlags::empty(),
fd: fd.as_raw_fd(),
_padding: 0,
point: 0,
},
)?
};
Ok(TimelineSyncObj {
handle,
render_node: render_node.clone(),
})
}
pub fn export(&self) -> rustix::io::Result<OwnedFd> {
unsafe {
rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjHandleToFd {
handle: self.handle,
flags: SyncobjHandleToFdFlags::empty(),
fd: -1,
_padding: 0,
point: 0,
},
)
}
}
pub unsafe fn get_raw_handle(&self) -> RawDrmSyncobjHandle {
self.handle
}
pub fn get_render_node(&self) -> &DrmRenderNode {
&self.render_node
}
}
impl Drop for TimelineSyncObj {
fn drop(&mut self) {
unsafe {
_ = rustix::ioctl::ioctl(
&self.render_node,
DrmSyncobjDestroy {
handle: self.handle,
_padding: 0,
},
);
}
}
}
#[test]
fn point_signaling() {
let node = crate::render_node::DrmRenderNode::new(128).expect("failed to open render node");
let obj = TimelineSyncObj::new(&node).expect("failed to create syncojb");
assert_eq!(obj.query_signaled_point(), Ok(0));
unsafe { obj.signal(32).unwrap() };
assert_eq!(obj.query_signaled_point(), Ok(32));
}
#[test]
fn point_transfer() {
let node = crate::render_node::DrmRenderNode::new(128).expect("failed to open render node");
let obj = TimelineSyncObj::new(&node).expect("failed to create syncojb");
let obj2 = TimelineSyncObj::new(&node).expect("failed to create second syncojb");
assert_eq!(obj2.query_signaled_point(), Ok(0));
unsafe { obj.signal(32).unwrap() };
obj.transfer_point(&obj2, 32, 1).unwrap();
assert_eq!(obj.query_signaled_point(), Ok(32));
assert_eq!(obj2.query_signaled_point(), Ok(1));
}
#[test]
fn sharing() {
let node = crate::render_node::DrmRenderNode::new(128).expect("failed to open render node");
let obj = TimelineSyncObj::new(&node).expect("failed to create syncojb");
let obj_fd = obj.export().expect("failed to export syncobj");
let obj2 = TimelineSyncObj::import(&node, obj_fd).expect("failed to import syncobj");
assert_eq!(obj.query_signaled_point(), Ok(0));
assert_eq!(obj2.query_signaled_point(), Ok(0));
unsafe { obj.signal(32).unwrap() };
assert_eq!(obj.query_signaled_point(), Ok(32));
assert_eq!(obj2.query_signaled_point(), Ok(32));
}
#[test]
fn sync_file_exporting() {
use std::io::ErrorKind;
let node = crate::render_node::DrmRenderNode::new(128).expect("failed to open render node");
let obj = TimelineSyncObj::new(&node).expect("failed to create syncojb");
let obj2 = TimelineSyncObj::new(&node).expect("failed to create second syncobj");
assert!(
obj.export_sync_file_point(32)
.is_err_and(|err| err.kind() == ErrorKind::InvalidInput)
);
unsafe { obj.signal(32).unwrap() };
let export = obj.export_sync_file_point(32).unwrap();
obj2.import_sync_file_point(export, 32).unwrap();
assert_eq!(obj2.query_signaled_point(), Ok(32));
}