diff options
| -rw-r--r-- | include/drm++/helper.hpp | 35 | ||||
| -rw-r--r-- | include/drm++/ioctl.hpp | 65 | ||||
| -rw-r--r-- | include/drm++/syncobject.hpp | 248 | ||||
| -rw-r--r-- | src/ioctl.cpp | 55 | ||||
| -rw-r--r-- | src/syncobject.cpp | 432 | ||||
| -rw-r--r-- | src/syncobject.hpp | 225 |
6 files changed, 730 insertions, 330 deletions
diff --git a/include/drm++/helper.hpp b/include/drm++/helper.hpp new file mode 100644 index 0000000..3b6e90d --- /dev/null +++ b/include/drm++/helper.hpp @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT */ + +#pragma once + +#include <cstdint> +#include <functional> + +namespace drm { + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; +using s8 = int8_t; +using s16 = int16_t; +using s32 = int32_t; +using s64 = int64_t; + +template<typename T> +using ref = std::reference_wrapper<T>; + +} + +/* Helpful macros */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" + +#define GETTER(name) [[nodiscard]] auto name() const { return m_##name; } +#define DEFAULT_OPERATORS(classname) \ + classname(const classname&) = default; \ + classname& operator=(const classname&) = default; \ + classname(classname&&) = default; \ + classname& operator=(classname&&) = default; + +#pragma clang diagnostic pop diff --git a/include/drm++/ioctl.hpp b/include/drm++/ioctl.hpp new file mode 100644 index 0000000..1851df6 --- /dev/null +++ b/include/drm++/ioctl.hpp @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: MIT */ + +#pragma once + +#include "drm++/helper.hpp" + +#include <exception> +#include <string> + +#include <sys/ioctl.h> + +namespace drm::ioctl { + +/// Type of ioctl() error +enum class Error : u8 { + BadFileDescriptor, + InvalidArgument, + NotSupported, + Other //!< check errno field +}; + +/// Exception class wrapping around ioctl() +class Exception : public std::exception { +public: + /// Construct an exception from errno + explicit Exception(int fd, unsigned long op); + + GETTER(fd) + GETTER(op) + GETTER(code) + GETTER(syserrno) + + /// Convert the exception into human-readable form. + std::string readable(); + + DEFAULT_OPERATORS(Exception) + ~Exception() override; +private: + int m_fd; + unsigned long m_op; + Error m_code; + int m_syserrno; +}; + +/// Perform an ioctl() call. +/// @throws drm::ioctl::Exception on failure +template<typename T> +T perform(int fd, unsigned long op) { + T data{}; + + if (::ioctl(fd, op, &data) < 0) + throw Exception(fd, op); + + return data; +} + +/// Perform an ioctl() call. +/// @throws drm::ioctl::Exception on failure +template<typename T> +void perform(int fd, unsigned long op, T& data) { + if (::ioctl(fd, op, &data) < 0) + throw Exception(fd, op); +} + +} diff --git a/include/drm++/syncobject.hpp b/include/drm++/syncobject.hpp new file mode 100644 index 0000000..9751559 --- /dev/null +++ b/include/drm++/syncobject.hpp @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: MIT */ + +#pragma once + +#include "drm++/helper.hpp" + +#include <optional> +#include <vector> + +namespace drm::syncobj { + +/// Wait modes for waiting on binary sync objects +enum class WaitMode : u8 { + NoWaitEmpty, //!< Return -EINVAL when waiting on an empty sync object + WaitEmpty, //!< Wait for empty sync objects to be filled and signaled + WaitAvailable //!< Wait for empty sync objects to be filled, but do not wait for signaling +}; + +class TimelineSyncObject; + +/// A (binary) synchronization object is a reference-counted container which can hold a DRM fence +/// @throws drm::ioctl::Exception on failure +class SyncObject { +public: + /// Create a new sync object + SyncObject(int fd, bool signaled = false); + + /// Import an existing sync object + /// @param close Close syncobj_fd after import (regardless of success) + SyncObject(int fd, int syncobj_fd, bool close = true); + + /// Export a new reference to sync object, increasing the reference counter + [[nodiscard]] int exportFd() const; + + /// Import a sync file (DRM fence) into the sync object + /// @param close Close syncfile_fd after import (regardless of success) + void importSyncFile(int syncfile_fd, bool close = true) const; + + /// Export a sync file from the DRM fence within the sync object + /// Any subsequent modifications to the sync object are not applied to the exported sync file + [[nodiscard]] int exportSyncFile() const; + + /// Transfer a DRM fence into another sync object + void transfer(const SyncObject& dest) const; + void transfer(const TimelineSyncObject& dest, u64 destPoint) const; + + /// Signal the sync object by emplacing a signaled fence into it + void signal() const; + + /// Reset the sync object by removing the fence from it + void reset() const; + + /// Wait for the sync object to be signaled + /// @param timeout Absolute timeout in nanoseconds, or zero for polling + /// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on the fence + void wait( + s64 timeout, + WaitMode waitMode = WaitMode::NoWaitEmpty, + std::optional<u64> deadlineHint = std::nullopt + ) const; + + /// Register an eventfd to the sync object + /// @param waitAvailable Trigger when a fence is emplaced, not when it is signaled + void registerEventFd(int eventfd_fd, bool waitAvailable = false) const; + + // Private access + GETTER(fd) + GETTER(handle) + + // Move constructor/operator + SyncObject(SyncObject&& other) noexcept : m_fd(other.m_fd), m_handle(other.m_handle) { + other.m_fd = -1; // invalidate other + } + + SyncObject& operator=(SyncObject&& other) noexcept { + if (this != &other) { + this->destruct(); + this->m_handle = other.m_handle; + this->m_fd = other.m_fd; + other.m_fd = -1; // invalidate other + } + + return *this; + } + + // Copy constructor/operator + SyncObject(const SyncObject& other) + : SyncObject(other.m_fd, other.exportFd(), true) {} + + SyncObject& operator=(const SyncObject& other) { + if (this != &other) { + const int fd{other.exportFd()}; + *this = SyncObject(other.m_fd, fd, true); + } + + return *this; + } + + // Destructor + ~SyncObject(); +private: + int m_fd; // indicates object validity (>= 0) + u32 m_handle; + + void destruct() noexcept; +}; + +/// A timeline synchronization object can hold multiple DRM fences, identified via a +/// monotonically increasing 64-bit unsigned integer "point" +/// @throws drm::ioctl::Exception on failure +class TimelineSyncObject { +public: + /// Create a new sync object + TimelineSyncObject(int fd); + + /// Import an existing sync object + /// @param close Close syncobj_fd after import (regardless of success) + TimelineSyncObject(int fd, int syncobj_fd, bool close = true); + + /// Export a new reference to sync object, increasing the reference counter + [[nodiscard]] int exportFd() const; + + /// Import a sync file (DRM fence) into the sync object + /// @param close Close syncfile_fd after import (regardless of success) + void importSyncFile(int syncfile_fd, u64 point, bool close = true) const; + + /// Export a sync file from the DRM fence within the sync object + /// Any subsequent modifications to the sync object are not applied to the exported sync file + [[nodiscard]] int exportSyncFile(u64 point) const; + + /// Transfer a DRM fence into another sync object + void transfer(const TimelineSyncObject& dest, u64 srcPoint, u64 destPoint) const; + void transfer(const SyncObject& dest, u64 srcPoint) const; + + /// Signal the sync object by emplacing a signaled fence into it + void signal(u64 point) const; + + /// Wait for the sync object to be signaled + /// @param timeout Absolute timeout in nanoseconds, or zero for polling + /// @param waitAvailable Only wait for a fence to become available, do not wait for signaling + /// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on the fence + void wait( + s64 timeout, + u64 point, + bool waitAvailable, + std::optional<u64> deadlineHint = std::nullopt + ) const; + + /// Query the current timeline point + /// @param lastSubmitted If true, query the last submitted point instead + [[nodiscard]] u64 query(bool lastSubmitted = false) const; + + /// Register an eventfd to the sync object + /// @param waitAvailable Trigger when a fence is emplaced, not when it is signaled + void registerEventFd(int eventfd_fd, u64 point, bool waitAvailable = false) const; + + // Private access + GETTER(fd) + GETTER(handle) + + // Move constructor/operator + TimelineSyncObject(TimelineSyncObject&& other) noexcept + : m_fd(other.m_fd), m_handle(other.m_handle) { + other.m_fd = -1; // invalidate other + } + + TimelineSyncObject& operator=(TimelineSyncObject&& other) noexcept { + if (this != &other) { + this->destruct(); + this->m_handle = other.m_handle; + this->m_fd = other.m_fd; + other.m_fd = -1; // invalidate other + } + + return *this; + } + + // Copy constructor/operator + TimelineSyncObject(const TimelineSyncObject& other) + : TimelineSyncObject(other.m_fd, other.exportFd(), true) {} + + TimelineSyncObject& operator=(const TimelineSyncObject& other) { + if (this != &other) { + const int fd{other.exportFd()}; + *this = TimelineSyncObject(other.m_fd, fd, true); + } + + return *this; + } + + // Destructor + ~TimelineSyncObject(); +private: + int m_fd; // indicates object validity (>= 0) + u32 m_handle; + + void destruct() noexcept; +}; + +/// Signal several binary sync objects at once +void signal(const std::vector<ref<SyncObject>>& objs); + +/// Reset several binary sync objects at once +void reset(const std::vector<ref<SyncObject>>& objs); + +// Wait on several binary sync objects at once +/// @param timeout Absolute timeout in nanoseconds, or zero for polling +/// @param waitAll Wait until all sync objects are signaled +/// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on the fence +/// @return The first signaled sync object when waitAll is false +/// @throws std::invalid_argument if objs is empty +SyncObject& wait( + const std::vector<ref<SyncObject>>& objs, + s64 timeout, + bool waitAll = false, + WaitMode waitMode = WaitMode::NoWaitEmpty, + std::optional<u64> deadlineHint = std::nullopt +); + +/// Signal several timeline sync objects at once +/// @throws std::invalid_argument if objs.size() != points.size() +void signal( + const std::vector<ref<TimelineSyncObject>>& objs, + const std::vector<u64>& points +); + +/// Wait on several timeline sync objects at once +/// @param timeout Absolute timeout in nanoseconds, or zero for polling +/// @param waitAvailable Only wait for a fence to become available, do not wait for signaling +/// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on the fence +/// @throws std::invalid_argument if objs.size() != points.size() +void wait( + const std::vector<ref<TimelineSyncObject>>& objs, + const std::vector<u64>& points, + s64 timeout, + bool waitAll = false, + bool waitAvailable = false, + std::optional<u64> deadlineHint = std::nullopt +); + +/// Query several timeline sync objects at once +/// @param lastSubmitted If true, query the last submitted point instead +std::vector<u64> query( + const std::vector<ref<TimelineSyncObject>>& objs, + bool lastSubmitted = false +); + +} diff --git a/src/ioctl.cpp b/src/ioctl.cpp new file mode 100644 index 0000000..982e1e9 --- /dev/null +++ b/src/ioctl.cpp @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: MIT */ + +#include "drm++/ioctl.hpp" + +#include <format> +#include <string> + +#include <errno.h> + +using namespace drm::ioctl; + +Exception::Exception(int fd, unsigned long op) + : m_fd(fd), m_op(op), m_syserrno(errno) { + switch (errno) { + case EBADF: + m_code = Error::BadFileDescriptor; + break; + case EINVAL: + case ENOTTY: + m_code = Error::InvalidArgument; + break; + case ENOTSUP: + m_code = Error::NotSupported; + break; + default: + m_code = Error::Other; + } +} + +std::string Exception::readable() { + switch (this->m_code) { + case Error::BadFileDescriptor: + return std::format( + "ioctl({}, {}) failed: Invalid file descriptor", + this->m_fd, this->m_op + ); + case Error::InvalidArgument: + return std::format( + "ioctl({}, {}) failed: Invalid argument (errno {})", + this->m_fd, this->m_op, this->m_syserrno + ); + case Error::NotSupported: + return std::format( + "ioctl({}, {}) failed: Operation not supported", + this->m_fd, this->m_op + ); + case Error::Other: + return std::format( + "ioctl({}, {}) failed: Unknown error (errno {})", + this->m_fd, this->m_op, this->m_syserrno + ); + } +} + +Exception::~Exception() = default; diff --git a/src/syncobject.cpp b/src/syncobject.cpp index 8d5e8b5..6614a9d 100644 --- a/src/syncobject.cpp +++ b/src/syncobject.cpp @@ -1,20 +1,26 @@ /* SPDX-License-Identifier: MIT */ -#include "syncobject.hpp" -#include "priv/ioctl.hpp" +#include "drm++/syncobject.hpp" +#include "drm++/helper.hpp" +#include "drm++/ioctl.hpp" -#include <cstdint> +#include <cstddef> +#include <functional> #include <optional> #include <stdexcept> #include <vector> #include <unistd.h> +#include <drm.h> using namespace drm; +using namespace drm::syncobj; -SyncObject::SyncObject(int fd, bool signal) : m_fd(fd) { +/* Binary Sync Objects */ + +SyncObject::SyncObject(int fd, bool signaled) : m_fd(fd) { drm_syncobj_create args{ - .flags = signal ? DRM_SYNCOBJ_CREATE_SIGNALED : 0U + .flags = signaled ? DRM_SYNCOBJ_CREATE_SIGNALED : 0U }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_CREATE, args); @@ -51,13 +57,12 @@ int SyncObject::exportFd() const { return args.fd; } -void SyncObject::importSyncFile(int syncfile_fd, uint64_t point, bool close) const { +void SyncObject::importSyncFile(int syncfile_fd, bool close) const { try { drm_syncobj_handle args{ .handle = this->m_handle, .flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, - .fd = syncfile_fd, - .point = point + .fd = syncfile_fd }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); } catch (...) { @@ -73,169 +78,386 @@ void SyncObject::importSyncFile(int syncfile_fd, uint64_t point, bool close) con } } -int SyncObject::exportSyncFile(uint64_t point) const { +int SyncObject::exportSyncFile() const { drm_syncobj_handle args{ .handle = this->m_handle, - .flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE, - .point = point + .flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); return args.fd; } -void SyncObject::registerEventFd(int eventfd_fd, uint64_t point, bool waitAvailable) const { +void SyncObject::transfer(const SyncObject& dest) const { + drm_syncobj_transfer args{ + .src_handle = this->m_handle, + .dst_handle = dest.m_handle + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_TRANSFER, args); +} + +void SyncObject::transfer(const TimelineSyncObject& dest, u64 destPoint) const { + drm_syncobj_transfer args{ + .src_handle = this->m_handle, + .dst_handle = dest.handle(), + .dst_point = destPoint + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_TRANSFER, args); +} + +void SyncObject::signal() const { + drm_syncobj_array args{ + .handles = reinterpret_cast<u64>(&this->m_handle), + .count_handles = 1 + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_SIGNAL, args); +} + +void SyncObject::reset() const { + drm_syncobj_array args{ + .handles = reinterpret_cast<u64>(&this->m_handle), + .count_handles = 1 + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_RESET, args); +} + +void SyncObject::wait( + s64 timeout, + WaitMode waitMode, + std::optional<u64> deadlineHint +) const { + drm_syncobj_wait args{ + .handles = reinterpret_cast<u64>(&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); +} + +void SyncObject::registerEventFd(int eventfd_fd, bool waitAvailable) const { drm_syncobj_eventfd args{ .handle = this->m_handle, .flags = waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U, - .point = point, .fd = eventfd_fd }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, args); } -void SyncObject::transfer(uint32_t dstObject, uint64_t srcPoint, uint64_t dstPoint) const { +void SyncObject::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; +} + +/* Timeline Sync Objects */ + +TimelineSyncObject::TimelineSyncObject(int fd) : m_fd(fd) { + drm_syncobj_create args{ + .flags = 0U + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_CREATE, args); + + this->m_handle = args.handle; +} + +TimelineSyncObject::TimelineSyncObject(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 TimelineSyncObject::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 TimelineSyncObject::importSyncFile(int syncfile_fd, u64 point, bool close) const { + try { + drm_syncobj_handle args{ + .handle = this->m_handle, + .flags = DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE + | DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_TIMELINE, + .fd = syncfile_fd, + .point = point + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE, args); + } catch (...) { + if (close) { + ::close(syncfile_fd); + } + + throw; + } + + if (close) { + ::close(syncfile_fd); + } +} + +int TimelineSyncObject::exportSyncFile(u64 point) const { + drm_syncobj_handle args{ + .handle = this->m_handle, + .flags = DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE + | DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_TIMELINE, + .point = point + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_HANDLE_TO_FD, args); + + return args.fd; +} + +void TimelineSyncObject::transfer( + const TimelineSyncObject& dest, + u64 srcPoint, + u64 destPoint +) const { drm_syncobj_transfer args{ .src_handle = this->m_handle, - .dst_handle = dstObject, + .dst_handle = dest.m_handle, .src_point = srcPoint, - .dst_point = dstPoint + .dst_point = destPoint }; ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_TRANSFER, args); } -void SyncObject::signal( - int fd, - const std::vector<uint32_t>& objects, - const std::vector<uint64_t>& points -) { - if (objects.empty()) { - return; - } +void TimelineSyncObject::transfer(const SyncObject& dest, u64 srcPoint) const { + drm_syncobj_transfer args{ + .src_handle = this->m_handle, + .dst_handle = dest.handle(), + .src_point = srcPoint + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_TRANSFER, args); +} - if (points.empty()) { - drm_syncobj_array args{ - .handles = reinterpret_cast<uint64_t>(objects.data()), - .count_handles = static_cast<uint32_t>(objects.size()) - }; - ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_SIGNAL, args); +void TimelineSyncObject::signal(u64 point) const { + drm_syncobj_timeline_array args{ + .handles = reinterpret_cast<u64>(&this->m_handle), + .points = reinterpret_cast<u64>(&point), + .count_handles = 1 + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, args); +} + +void TimelineSyncObject::wait( + s64 timeout, + u64 point, + bool waitAvailable, + std::optional<u64> deadlineHint +) const { + drm_syncobj_timeline_wait args{ + .handles = reinterpret_cast<u64>(&this->m_handle), + .points = reinterpret_cast<u64>(&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); +} + +u64 TimelineSyncObject::query(bool lastSubmitted) const { + u64 point{}; + + drm_syncobj_timeline_array args{ + .handles = reinterpret_cast<u64>(&this->m_handle), + .points = reinterpret_cast<u64>(&point), + .count_handles = 1, + .flags = lastSubmitted ? DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED : 0U + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_QUERY, args); + + return point; +} + +void TimelineSyncObject::registerEventFd( + int eventfd_fd, + u64 point, + bool waitAvailable +) const { + drm_syncobj_eventfd args{ + .handle = this->m_handle, + .flags = waitAvailable ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE : 0U, + .point = point, + .fd = eventfd_fd + }; + ioctl::perform(this->m_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, args); +} + +/* Bulk Operations */ + +void syncobj::signal(const std::vector<std::reference_wrapper<SyncObject>>& objs) { + if (objs.empty()) { + return; } - if (points.size() != objects.size()) { - throw std::invalid_argument("points size must match objects size"); + std::vector<u32> handles(objs.size()); + for (size_t i = 0; i < objs.size(); ++i) { + handles.at(i) = objs.at(i).get().handle(); } - drm_syncobj_timeline_array args{ - .handles = reinterpret_cast<uint64_t>(objects.data()), - .points = reinterpret_cast<uint64_t>(points.data()), - .count_handles = static_cast<uint32_t>(objects.size()) + drm_syncobj_array args{ + .handles = reinterpret_cast<u64>(handles.data()), + .count_handles = static_cast<u32>(handles.size()) }; - ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, args); + ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_SIGNAL, args); } -void SyncObject::reset(int fd, const std::vector<uint32_t>& objects) { - if (objects.empty()) { +void syncobj::reset(const std::vector<std::reference_wrapper<SyncObject>>& objs) { + if (objs.empty()) { return; } + std::vector<u32> handles(objs.size()); + for (size_t i = 0; i < objs.size(); ++i) { + handles.at(i) = objs.at(i).get().handle(); + } + drm_syncobj_array args{ - .handles = reinterpret_cast<uint64_t>(objects.data()), - .count_handles = static_cast<uint32_t>(objects.size()) + .handles = reinterpret_cast<u64>(handles.data()), + .count_handles = static_cast<u32>(handles.size()) }; - ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_RESET, args); + ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_RESET, args); } -uint32_t SyncObject::wait( - int fd, - const std::vector<uint32_t>& objects, - const std::vector<uint64_t>& points, - int64_t timeout, +SyncObject& syncobj::wait( + const std::vector<std::reference_wrapper<SyncObject>>& objs, + s64 timeout, bool waitAll, - bool waitEmpty, - bool waitAvailable, - std::optional<uint64_t> deadlineHint + WaitMode waitMode, + std::optional<u64> deadlineHint ) { - if (objects.empty()) { - return 0; + if (objs.empty()) { + throw std::invalid_argument("must wait on at least one object"); } - if (waitAvailable) { - waitEmpty = false; + std::vector<u32> handles(objs.size()); + for (size_t i = 0; i < objs.size(); ++i) { + handles.at(i) = objs.at(i).get().handle(); } - if (points.empty()) { - drm_syncobj_wait args{ - .handles = reinterpret_cast<uint64_t>(objects.data()), - .timeout_nsec = timeout, - .count_handles = static_cast<uint32_t>(objects.size()), - .flags = - (waitAll ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL : 0U) | - (waitEmpty ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT : 0U) | - (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(fd, DRM_IOCTL_SYNCOBJ_WAIT, args); + drm_syncobj_wait args{ + .handles = reinterpret_cast<u64>(handles.data()), + .timeout_nsec = timeout, + .count_handles = static_cast<u32>(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 args.first_signaled; + return objs.at(args.first_signaled).get(); +} + +void syncobj::signal( + const std::vector<std::reference_wrapper<TimelineSyncObject>>& objs, + const std::vector<u64>& points +) { + if (objs.empty()) { + return; } - if (points.size() != objects.size()) { - throw std::invalid_argument("points size must match objects size"); + std::vector<u32> handles(objs.size()); + for (size_t i = 0; i < objs.size(); ++i) { + handles.at(i) = objs.at(i).get().handle(); + } + + drm_syncobj_timeline_array args{ + .handles = reinterpret_cast<u64>(handles.data()), + .points = reinterpret_cast<u64>(points.data()), + .count_handles = static_cast<u32>(handles.size()) + }; + ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL, args); +} + +void syncobj::wait( + const std::vector<std::reference_wrapper<TimelineSyncObject>>& objs, + const std::vector<u64>& points, + s64 timeout, + bool waitAll, + bool waitAvailable, + std::optional<u64> deadlineHint +) { + if (objs.empty()) { + return; + } + + std::vector<u32> 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<uint64_t>(objects.data()), - .points = reinterpret_cast<uint64_t>(points.data()), + .handles = reinterpret_cast<u64>(handles.data()), + .points = reinterpret_cast<u64>(points.data()), .timeout_nsec = timeout, - .count_handles = static_cast<uint32_t>(objects.size()), - .flags = - (waitAll ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL : 0U) | - (waitEmpty ? DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT : 0U) | + .count_handles = static_cast<u32>(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(fd, DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, args); - - return args.first_signaled; + ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_TIMELINE_WAIT, args); } -std::vector<uint64_t> SyncObject::query( - int fd, - const std::vector<uint32_t>& objects, +std::vector<u64> syncobj::query( + const std::vector<std::reference_wrapper<TimelineSyncObject>>& objs, bool lastSubmitted ) { - if (objects.empty()) { + if (objs.empty()) { return {}; } - std::vector<uint64_t> points(objects.size()); + std::vector<u32> handles(objs.size()); + for (size_t i = 0; i < objs.size(); ++i) { + handles.at(i) = objs.at(i).get().handle(); + } + + std::vector<u64> points(objs.size()); drm_syncobj_timeline_array args{ - .handles = reinterpret_cast<uint64_t>(objects.data()), - .points = reinterpret_cast<uint64_t>(points.data()), - .count_handles = static_cast<uint32_t>(objects.size()), + .handles = reinterpret_cast<u64>(handles.data()), + .points = reinterpret_cast<u64>(points.data()), + .count_handles = static_cast<u32>(handles.size()), .flags = lastSubmitted ? DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED : 0U }; - ioctl::perform(fd, DRM_IOCTL_SYNCOBJ_QUERY, args); + ioctl::perform(objs.front().get().fd(), DRM_IOCTL_SYNCOBJ_QUERY, args); return points; } - -void SyncObject::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; -} diff --git a/src/syncobject.hpp b/src/syncobject.hpp deleted file mode 100644 index 6221753..0000000 --- a/src/syncobject.hpp +++ /dev/null @@ -1,225 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -#pragma once - -#include <cstdint> -#include <optional> -#include <vector> - -namespace drm { - - /// - /// A DRM synchronization object is a container which can hold one DRM fence. - /// - /// Their primary use-case is to implement Vulkan fences & semaphores, however they can - /// be used across ordinary processes as well. - /// - /// A sync object is reference counted, therefore destroying a sync object does not - /// invalidate all exported opaque file descriptors. - /// - /// Ordinary use cases for sync objects include: - /// - Creating two sync objects in separate processes, exporting a sync object fd into - /// the other process. Then signaling/polling the shared sync object. - /// - Exporting a Vulkan fence/semaphore as a sync file and importing it into a sync object. - /// It is then possible to wait for a signal from Vulkan inside of the DRM subsystem. - /// - /// Timeline synchronization objects extend the single DRM fence to a list of fences, where - /// fences are identified via 64-bit unsigned integer. - /// - /// https://www.kernel.org/doc/html/v6.19/gpu/drm-mm.html#drm-sync-objects - /// - class SyncObject { - public: - /// - /// Create a new DRM sync object - /// - /// By default, the sync object will not have a DRM fence emplaced. - /// - /// @param fd DRM node file descriptor - /// @param signal Emplace a signaled fence on creation - /// @throws drm::ioctl::Exception on failure - /// - SyncObject(int fd, bool signal = false); - - /// - /// Import a DRM sync object from a file descriptor - /// - /// @param fd DRM node file descriptor - /// @param syncobj_fd Sync object file descriptor - /// @param close Close the file descriptor after import (regardless of success) - /// @throws drm::ioctl::Exception on failure - /// - SyncObject(int fd, int syncobj_fd, bool close = true); - - /// - /// Export a reference to the sync object - /// - /// @throws drm::ioctl::Exception on failure - /// @returns Exported file descriptor. - /// - [[nodiscard]] int exportFd() const; - - /// - /// Import a sync file (DRM fence) into the sync object - /// - /// @param syncfile_fd File descriptor to import - /// @param point Timeline point to export for timeline sync objects - /// @param close Close the file descriptor after import (regardless of success) - /// @throws drm::ioctl::Exception on failure - /// - void importSyncFile(int syncfile_fd, uint64_t point = 0, bool close = true) const; - - /// - /// Export a sync file from the DRM fence within the sync object - /// - /// Any subsequent modifications to the sync object are not applied to - /// the exported sync file. - /// - /// @param point Timeline point to export for timeline sync objects - /// @throws drm::ioctl::Exception on failure - /// @returns Exported file descriptor - /// - [[nodiscard]] int exportSyncFile(uint64_t point = 0) const; - - /// - /// Register an eventfd to be signaled by a sync object - /// - /// @param eventfd_fd File descriptor to signal - /// @param point Timeline point to signal for timeline sync objects - /// @param waitAvailable Only wait for a fence to be available, as opposed to signaled. - /// @throws drm::ioctl::Exception on failure - /// - void registerEventFd(int eventfd_fd, uint64_t point = 0, bool waitAvailable = false) const; - - /// - /// Copy a DRM fence into another sync object - /// - /// @param dstObject Destination sync object handle - /// @param srcPoint Timeline point to copy from - /// @param dstPoint Timeline point to copy into - /// @throws drm::ioctl::Exception on failure - /// - void transfer(uint32_t dstObject, uint64_t srcPoint = 0, uint64_t dstPoint = 0) const; - - /// - /// Emplace signaled fences into a list of sync objects - /// - /// When not using timeline sync objects, pass an empty list of timeline points. - /// - /// @param fd DRM node file descriptor - /// @param objects List of sync object handles - /// @param points List of timeline points to signal for each sync object - /// @throws drm::ioctl::Exception on failure - /// @throws std::invalid_argument invalid points size - /// - static void signal( - int fd, - const std::vector<uint32_t>& objects, - const std::vector<uint64_t>& points = {} - ); - - /// - /// Remove the DRM fence from a list of sync objects - /// - /// @param fd DRM node file descriptor - /// @param objects List of sync object handles - /// @throws drm::ioctl::Exception on failure - /// - static void reset(int fd, const std::vector<uint32_t>& objects); - - /// - /// Wait for a list of sync objects to be signaled - /// - /// When not using timeline sync objects, pass an empty list of timeline points. - /// - /// If waitEmpty or waitAvailable is not set, any empty sync object will result in - /// an error. - /// - /// If waitAvailable is set, the function will return as soon as a fence is emplaced into - /// all sync objects, as opposed to waiting for the fence to be signaled. This option - /// should not be used together with waitEmpty. - /// - /// @param fd DRM node file descriptor - /// @param objects List of sync object handles - /// @param points List of timeline points to wait on for each sync object - /// @param timeout Absolute timeout in nanoseconds, or zero for polling - /// @param waitAll Wait for all sync objects, as opposed to a single one - /// @param waitEmpty Wait for sync objects, which do not yet have a fence emplaced - /// @param waitAvailable Only wait for a fence to be available, as opposed to signaled. - /// @param deadlineHint Set a CLOCK_MONOTONIC deadline hint in nanoseconds on all fences - /// @throws drm::ioctl::Exception on failure - /// @throws std::invalid_argument invalid points size - /// @return Handle which was signaled first, when waitAll is false. - /// - [[nodiscard]] - static uint32_t wait( - int fd, - const std::vector<uint32_t>& objects, - const std::vector<uint64_t>& points = {}, - int64_t timeout = 0, - bool waitAll = false, - bool waitEmpty = false, - bool waitAvailable = false, - std::optional<uint64_t> deadlineHint = std::nullopt - ); - - /// - /// Query the timeline points of a list of sync objects - /// - /// This should only be used with timeline sync objects. - /// - /// @param fd DRM node file descriptor - /// @param objects List of sync object handles - /// @param lastSubmitted Query the last submitted instead of signaled point. - /// @throws drm::ioctl::Exception on failure - /// @returns List of timeline points - /// - [[nodiscard]] - static std::vector<uint64_t> query( - int fd, - const std::vector<uint32_t>& objects, - bool lastSubmitted = false - ); - - // Into handle - operator uint32_t() const { return this->m_handle; } - - // Move constructor/operator - SyncObject(SyncObject&& other) noexcept : m_fd(other.m_fd), m_handle(other.m_handle) { - other.m_fd = -1; // invalidate other - } - - SyncObject& operator=(SyncObject&& other) noexcept { - if (this != &other) { - this->destruct(); - m_handle = other.m_handle; - m_fd = other.m_fd; - other.m_fd = -1; // invalidate other - } - - return *this; - } - - // Copy constructor/operator - SyncObject(const SyncObject& other) - : SyncObject(other.m_fd, other.exportFd(), true) {} - - SyncObject& operator=(const SyncObject& other) { - if (this != &other) { - const int fd = other.exportFd(); - *this = SyncObject(other.m_fd, fd, true); - } - - return *this; - } - - // Destructor - ~SyncObject(); - private: - int m_fd; // indicates object validity (>= 0) - uint32_t m_handle; - - void destruct() noexcept; - }; - -} |
