/* SPDX-License-Identifier: MIT */ #include "drm++/syncobject.hpp" #include "drm++/helper.hpp" #include "drm++/ioctl.hpp" #include #include #include #include #include #include #include using namespace drm; using namespace drm::syncobj; /* Base sync object class */ SyncObjectBase::SyncObjectBase(int fd, bool signaled) : m_fd(fd) { drm_syncobj_create args{ .flags = signaled ? DRM_SYNCOBJ_CREATE_SIGNALED : 0U }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_CREATE, args); this->m_handle = args.handle; } SyncObjectBase::SyncObjectBase(int fd, int syncobj_fd, bool close) : m_fd(fd) { try { drm_syncobj_handle args{ .fd = syncobj_fd }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); this->m_handle = args.handle; } catch (...) { if (close) { ::close(syncobj_fd); } throw; } if (close) { ::close(syncobj_fd); } } int SyncObjectBase::exportFd() const { drm_syncobj_handle args{ .handle = this->m_handle, }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); return args.fd; } void SyncObjectBase::destruct() noexcept { if (this->m_fd < 0) { return; } drm_syncobj_destroy args{ .handle = this->m_handle }; try { ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_DESTROY, args); } catch(...) { (void) 0; // not much we can do about the leak } this->m_fd = -1; } /* Importing/exporting sync fiels */ namespace { void importSyncFile(int fd, u32 syncobj, int syncfile_fd, u64 point, bool close) { try { drm_syncobj_handle args{ .handle = syncobj, .flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE | (point != 0 ? DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_TIMELINE : 0U), .fd = syncfile_fd, .point = point }; ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); } catch (...) { if (close) { ::close(syncfile_fd); } throw; } if (close) { ::close(syncfile_fd); } } int exportSyncFile(int fd, u32 syncobj, u64 point) { drm_syncobj_handle args{ .handle = syncobj, .flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE | (point != 0 ? DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_TIMELINE : 0U), .point = point }; ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); return args.fd; } } void SyncObject::importSyncFile(int syncfile_fd, bool close) const { ::importSyncFile(this->m_fd, this->m_handle, syncfile_fd, 0, close); } int SyncObject::exportSyncFile() const { return ::exportSyncFile(this->m_fd, this->m_handle, 0); } void TimelineSyncObject::importSyncFile(int syncfile_fd, u64 point, bool close) const { ::importSyncFile(this->m_fd, this->m_handle, syncfile_fd, point, close); } int TimelineSyncObject::exportSyncFile(u64 point) const { return ::exportSyncFile(this->m_fd, this->m_handle, point); } /* Transfer operations */ namespace { void transfer(int fd, u32 src, u32 dst, u64 srcPoint, u64 dstPoint) { drm_syncobj_transfer args{ .src_handle = src, .dst_handle = dst, .src_point = srcPoint, .dst_point = dstPoint }; ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_TRANSFER, args); } } void SyncObject::transfer(const SyncObject& dest) const { ::transfer(this->m_fd, this->m_handle, dest.m_handle, 0, 0); } void SyncObject::transfer(const TimelineSyncObject& dest, u64 destPoint) const { ::transfer(this->m_fd, this->m_handle, dest.m_handle, 0, destPoint); } void TimelineSyncObject::transfer(const TimelineSyncObject& dest, u64 srcPoint, u64 destPoint) const { ::transfer(this->m_fd, this->m_handle, dest.m_handle, srcPoint, destPoint); } void TimelineSyncObject::transfer(const SyncObject& dest, u64 srcPoint) const { ::transfer(this->m_fd, this->m_handle, dest.m_handle, srcPoint, 0); } /* Registering eventfds */ namespace { void registerEventFd(int fd, u32 syncobj, int eventfd_fd, u64 point, bool waitAvailable) { drm_syncobj_eventfd args{ .handle = syncobj, .flags = waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U, .point = point, .fd = eventfd_fd }; ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_EVENTFD, args); } } void SyncObject::registerEventFd(int eventfd_fd, bool waitAvailable) const { ::registerEventFd(this->m_fd, this->m_handle, eventfd_fd, 0, waitAvailable); } void TimelineSyncObject::registerEventFd(int eventfd_fd, u64 point, bool waitAvailable) const { ::registerEventFd(this->m_fd, this->m_handle, eventfd_fd, point, waitAvailable); } /* Binary signal & reset operations */ namespace { template void signal(int fd, std::span syncobjs) { drm_syncobj_array args{ .handles = reinterpret_cast(syncobjs.data()), .count_handles = static_cast(syncobjs.size()) }; ioctl::perform(fd, OP, args); } template void signalBulk(const std::vector>& syncobjs) { if (syncobjs.empty()) { return; } std::vector handles(syncobjs.size()); for (size_t i = 0; i < syncobjs.size(); ++i) { handles.at(i) = syncobjs.at(i).get().handle(); } ::signal(syncobjs.front().get().fd(), handles); } } void SyncObject::signal() const { ::signal(this->m_fd, std::span(&this->m_handle, 1)); } void SyncObject::reset() const { ::signal(this->m_fd, std::span(&this->m_handle, 1)); } void syncobj::signal(const std::vector>& objs) { ::signalBulk(objs); } void syncobj::reset(const std::vector>& objs) { ::signalBulk(objs); } /* Timeline signal & query operations */ namespace { template void signal(int fd, std::span syncobjs, std::span points, bool lastSubmitted) { drm_syncobj_timeline_array args{ .handles = reinterpret_cast(syncobjs.data()), .points = reinterpret_cast(points.data()), .count_handles = static_cast(syncobjs.size()), .flags = lastSubmitted ? DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED : 0U }; ioctl::perform(fd, OP, args); } template void signalBulk(const std::vector>& syncobjs, std::span points, bool lastSubmitted) { if (syncobjs.empty()) { return; } if (syncobjs.size() != points.size()) { throw std::invalid_argument("syncobjs and points must have the same size"); } std::vector handles(syncobjs.size()); for (size_t i = 0; i < syncobjs.size(); ++i) { handles.at(i) = syncobjs.at(i).get().handle(); } ::signal(syncobjs.front().get().fd(), handles, points, lastSubmitted); } } void TimelineSyncObject::signal(u64 point) const { ::signal(this->m_fd, std::span(&this->m_handle, 1), std::span(&point, 1), false); } u64 TimelineSyncObject::query(bool lastSubmitted) const { u64 point{}; ::signal(this->m_fd, std::span(&this->m_handle, 1), std::span(&point, 1), lastSubmitted); return point; } void syncobj::signal( const std::vector>& objs, std::vector points ) { ::signalBulk(objs, points, false); } std::vector syncobj::query( const std::vector>& objs, bool lastSubmitted ) { std::vector points(objs.size()); ::signalBulk(objs, points, lastSubmitted); return points; } /* Wait operations */ void SyncObject::wait( s64 timeout, WaitMode waitMode, std::optional deadlineHint ) const { drm_syncobj_wait args{ .handles = reinterpret_cast(&this->m_handle), .timeout_nsec = timeout, .count_handles = 1, .flags = (waitMode == WaitMode::WaitEmpty ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT : 0U) | (waitMode == WaitMode::WaitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U) | (deadlineHint.has_value() ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE : 0U), .deadline_nsec = deadlineHint.value_or(0) }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_WAIT, args); } const SyncObject& syncobj::wait( const std::vector>& objs, s64 timeout, bool waitAll, WaitMode waitMode, std::optional deadlineHint ) { if (objs.empty()) { throw std::invalid_argument("must wait on at least one object"); } std::vector handles(objs.size()); for (size_t i = 0; i < objs.size(); ++i) { handles.at(i) = objs.at(i).get().handle(); } drm_syncobj_wait args{ .handles = reinterpret_cast(handles.data()), .timeout_nsec = timeout, .count_handles = static_cast(handles.size()), .flags = (waitAll ? 0U : DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL) | (waitMode == WaitMode::WaitEmpty ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT : 0U) | (waitMode == WaitMode::WaitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U) | (deadlineHint.has_value() ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE : 0U), .deadline_nsec = deadlineHint.value_or(0) }; ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_WAIT, args); return objs.at(args.first_signaled).get(); } void TimelineSyncObject::wait( s64 timeout, u64 point, bool waitAvailable, std::optional deadlineHint ) const { drm_syncobj_timeline_wait args{ .handles = reinterpret_cast(&this->m_handle), .points = reinterpret_cast(&point), .timeout_nsec = timeout, .count_handles = 1, .flags = (waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U) | (deadlineHint.has_value() ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE : 0U), .deadline_nsec = deadlineHint.value_or(0) }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_WAIT, args); } void syncobj::wait( const std::vector>& objs, const std::vector& points, s64 timeout, bool waitAll, bool waitAvailable, std::optional deadlineHint ) { if (objs.empty()) { return; } std::vector handles(objs.size()); for (size_t i = 0; i < objs.size(); ++i) { handles.at(i) = objs.at(i).get().handle(); } drm_syncobj_timeline_wait args{ .handles = reinterpret_cast(handles.data()), .points = reinterpret_cast(points.data()), .timeout_nsec = timeout, .count_handles = static_cast(handles.size()), .flags = (waitAll ? 0U : DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL) | (waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U) | (deadlineHint.has_value() ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE : 0U), .deadline_nsec = deadlineHint.value_or(0) }; ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, args); }