summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/drm++/helper.hpp35
-rw-r--r--include/drm++/ioctl.hpp65
-rw-r--r--include/drm++/syncobject.hpp248
-rw-r--r--src/ioctl.cpp55
-rw-r--r--src/syncobject.cpp432
-rw-r--r--src/syncobject.hpp225
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;
- };
-
-}