This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved. - llvmpipe currently cannot offer syncobj interop because it does not have access to a DRM device. This means that applications using llvmpipe cannot present images before they have finished rendering, despite llvmpipe using threaded rendering. - Clients that do not use the Vulkan WSI need to manually probe /dev/dri for devices that support the syncobj ioctls in order to use the wayland syncobj protocol. - Similarly, clients that want to use screen capture have no equivalent to the WSI and are therefore forced into that path. - Having to keep a DRM device open has potentially negative interactions with GPU hotplug. - Having to translate between syncobj FDs and handles is troublesome in the compositor usecase since syncobjs come and go frequently and need to be cleaned up when clients disconnect.
/dev/syncobj solves these issues by providing all syncobj ioctls under a consistent path that is not tied to any DRM device. It also operates directly on file descriptors instead of syncobj handles.
The series starts with a number of small refactorings in drm_syncobj.c to make its functionality available outside of the file and without the need for drm_file/handle pairs.
The last commit adds the /dev/syncobj module. I've added it as a misc device but maybe this should instead live somewhere under gpu/drm.
An application using the new interface can be found at [1].
[1]: https://github.com/mahkoh/jay/pull/947
--- Julian Orth (12): drm/syncobj: add drm_syncobj_from_fd drm/syncobj: add drm_syncobj_fence_lookup drm/syncobj: make drm_syncobj_array_wait_timeout public drm/syncobj: add drm_syncobj_register_eventfd drm/syncobj: have transfer functions accept drm_syncobj directly drm/syncobj: add drm_syncobj_transfer drm/syncobj: add drm_syncobj_timeline_signal drm/syncobj: add drm_syncobj_query drm/syncobj: fix resource leak in drm_syncobj_import_sync_file_fence drm/syncobj: add drm_syncobj_import_sync_file drm/syncobj: add drm_syncobj_export_sync_file misc/syncobj: add new device
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/gpu/drm/drm_syncobj.c | 374 ++++++++++++++----- drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/drm/drm_syncobj.h | 21 ++ include/uapi/linux/syncobj.h | 75 ++++ 7 files changed, 795 insertions(+), 91 deletions(-) --- base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6 change-id: 20260516-jorth-syncobj-d4d374c8c61b
Best regards, -- Julian Orth ju.orth@gmail.com
Given a syncobj FD, returns the underlying drm_syncobj.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 37 +++++++++++++++++++++++++++---------- include/drm/drm_syncobj.h | 1 + 2 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 8d9fd1917c6e..d992aa082ace 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -684,6 +684,31 @@ int drm_syncobj_get_fd(struct drm_syncobj *syncobj, int *p_fd) } EXPORT_SYMBOL(drm_syncobj_get_fd);
+/** + * drm_syncobj_from_fd - lookup and reference a syncobj. + * @fd: syncobj file descriptor + * + * Returns a reference to the syncobj pointed to by @fd or NULL. The + * reference must be released by calling drm_syncobj_put(). + */ +struct drm_syncobj *drm_syncobj_from_fd(int fd) +{ + struct drm_syncobj *syncobj; + + CLASS(fd, f)(fd); + + if (fd_empty(f)) + return NULL; + + if (fd_file(f)->f_op != &drm_syncobj_file_fops) + return NULL; + + syncobj = fd_file(f)->private_data; + drm_syncobj_get(syncobj); + return syncobj; +} +EXPORT_SYMBOL(drm_syncobj_from_fd); + static int drm_syncobj_handle_to_fd(struct drm_file *file_private, u32 handle, int *p_fd) { @@ -701,20 +726,12 @@ static int drm_syncobj_handle_to_fd(struct drm_file *file_private, static int drm_syncobj_fd_to_handle(struct drm_file *file_private, int fd, u32 *handle) { - struct drm_syncobj *syncobj; - CLASS(fd, f)(fd); + struct drm_syncobj *syncobj = drm_syncobj_from_fd(fd); int ret;
- if (fd_empty(f)) - return -EINVAL; - - if (fd_file(f)->f_op != &drm_syncobj_file_fops) + if (!syncobj) return -EINVAL;
- /* take a reference to put in the xarray */ - syncobj = fd_file(f)->private_data; - drm_syncobj_get(syncobj); - ret = xa_alloc(&file_private->syncobj_xa, handle, syncobj, xa_limit_32b, GFP_KERNEL); if (ret) diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index b40052132e52..5da9988834b5 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -117,6 +117,7 @@ drm_syncobj_fence_get(struct drm_syncobj *syncobj)
struct drm_syncobj *drm_syncobj_find(struct drm_file *file_private, u32 handle); +struct drm_syncobj *drm_syncobj_from_fd(int fd); void drm_syncobj_add_point(struct drm_syncobj *syncobj, struct dma_fence_chain *chain, struct dma_fence *fence,
This makes the logic from drm_syncobj_find_fence available to callers that have a drm_syncobj instead of a drm_file/handle pair.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 34 ++++++++++++++++++++++++++++------ include/drm/drm_syncobj.h | 2 ++ 2 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index d992aa082ace..8df438fe0807 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -427,7 +427,7 @@ static int drm_syncobj_assign_null_handle(struct drm_syncobj *syncobj) * @fence: out parameter for the fence * * This is just a convenience function that combines drm_syncobj_find() and - * drm_syncobj_fence_get(). + * drm_syncobj_fence_lookup(). * * Returns 0 on success or a negative error value on failure. On success @fence * contains a reference to the fence, which must be released by calling @@ -438,8 +438,6 @@ int drm_syncobj_find_fence(struct drm_file *file_private, struct dma_fence **fence) { struct drm_syncobj *syncobj = drm_syncobj_find(file_private, handle); - struct syncobj_wait_entry wait; - u64 timeout = nsecs_to_jiffies64(DRM_SYNCOBJ_WAIT_FOR_SUBMIT_TIMEOUT); int ret;
if (flags & ~DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) @@ -448,6 +446,32 @@ int drm_syncobj_find_fence(struct drm_file *file_private, if (!syncobj) return -ENOENT;
+ ret = drm_syncobj_fence_lookup(syncobj, point, flags, fence); + + drm_syncobj_put(syncobj); + + return ret; +} +EXPORT_SYMBOL(drm_syncobj_find_fence); + +/** + * drm_syncobj_fence_lookup - lookup and reference the fence in a sync object + * @syncobj: sync object to lookup. + * @point: timeline point + * @flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT or not + * @fence: out parameter for the fence + * + * Returns 0 on success or a negative error value on failure. On success @fence + * contains a reference to the fence, which must be released by calling + * dma_fence_put(). + */ +int drm_syncobj_fence_lookup(struct drm_syncobj *syncobj, u64 point, + u64 flags, struct dma_fence **fence) +{ + struct syncobj_wait_entry wait; + u64 timeout = nsecs_to_jiffies64(DRM_SYNCOBJ_WAIT_FOR_SUBMIT_TIMEOUT); + int ret; + /* Waiting for userspace with locks help is illegal cause that can * trivial deadlock with page faults for example. Make lockdep complain * about it early on. @@ -511,11 +535,9 @@ int drm_syncobj_find_fence(struct drm_file *file_private, drm_syncobj_remove_wait(syncobj, &wait);
out: - drm_syncobj_put(syncobj); - return ret; } -EXPORT_SYMBOL(drm_syncobj_find_fence); +EXPORT_SYMBOL(drm_syncobj_fence_lookup);
/** * drm_syncobj_free - free a sync object. diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index 5da9988834b5..580a967ae364 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -124,6 +124,8 @@ void drm_syncobj_add_point(struct drm_syncobj *syncobj, uint64_t point); void drm_syncobj_replace_fence(struct drm_syncobj *syncobj, struct dma_fence *fence); +int drm_syncobj_fence_lookup(struct drm_syncobj *syncobj, u64 point, + u64 flags, struct dma_fence **fence); int drm_syncobj_find_fence(struct drm_file *file_private, u32 handle, u64 point, u64 flags, struct dma_fence **fence);
For use by the upcoming misc/syncobj module.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 15 ++++++++------- include/drm/drm_syncobj.h | 5 +++++ 2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 8df438fe0807..648afd1f4fdd 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1069,13 +1069,13 @@ static void syncobj_wait_syncobj_func(struct drm_syncobj *syncobj, list_del_init(&wait->node); }
-static signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs, - void __user *user_points, - uint32_t count, - uint32_t flags, - signed long timeout, - uint32_t *idx, - ktime_t *deadline) +signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs, + void __user *user_points, + uint32_t count, + uint32_t flags, + signed long timeout, + uint32_t *idx, + ktime_t *deadline) { struct syncobj_wait_entry *entries; struct dma_fence *fence; @@ -1229,6 +1229,7 @@ static signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs,
return timeout; } +EXPORT_SYMBOL(drm_syncobj_array_wait_timeout);
/** * drm_timeout_abs_to_jiffies - calculate jiffies timeout from absolute value diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index 580a967ae364..7677fd995be0 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -129,6 +129,11 @@ int drm_syncobj_fence_lookup(struct drm_syncobj *syncobj, u64 point, int drm_syncobj_find_fence(struct drm_file *file_private, u32 handle, u64 point, u64 flags, struct dma_fence **fence); +signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs, + void __user *user_points, + uint32_t count, uint32_t flags, + signed long timeout, uint32_t *idx, + ktime_t *deadline); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This makes the logic from drm_syncobj_eventfd_ioctl available to callers that already have a drm_syncobj.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 37 ++++++++++++++++++++++++++++++------- include/drm/drm_syncobj.h | 2 ++ 2 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 648afd1f4fdd..3e8fb7e0cace 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1502,8 +1502,6 @@ drm_syncobj_eventfd_ioctl(struct drm_device *dev, void *data, { struct drm_syncobj_eventfd *args = data; struct drm_syncobj *syncobj; - struct eventfd_ctx *ev_fd_ctx; - struct syncobj_eventfd_entry *entry; int ret;
if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ_TIMELINE)) @@ -1519,7 +1517,33 @@ drm_syncobj_eventfd_ioctl(struct drm_device *dev, void *data, if (!syncobj) return -ENOENT;
- ev_fd_ctx = eventfd_ctx_fdget(args->fd); + ret = drm_syncobj_register_eventfd(syncobj, args->fd, args->point, args->flags); + + drm_syncobj_put(syncobj); + + return ret; +} + +/** + * drm_syncobj_register_eventfd - register an eventfd for a syncobj + * @syncobj: sync object to add the eventfd to + * @ev_fd: eventfd file descriptor to signal + * @point: timeline point to wait for + * @flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE or 0 + * + * Registers an eventfd that will be signaled when the point is + * signaled or available. + * + * Returns 0 on success or a negative error value on failure. + */ +int drm_syncobj_register_eventfd(struct drm_syncobj *syncobj, + int ev_fd, u64 point, u32 flags) +{ + struct eventfd_ctx *ev_fd_ctx; + struct syncobj_eventfd_entry *entry; + int ret; + + ev_fd_ctx = eventfd_ctx_fdget(ev_fd); if (IS_ERR(ev_fd_ctx)) { ret = PTR_ERR(ev_fd_ctx); goto err_fdget; @@ -1532,20 +1556,19 @@ drm_syncobj_eventfd_ioctl(struct drm_device *dev, void *data, } entry->syncobj = syncobj; entry->ev_fd_ctx = ev_fd_ctx; - entry->point = args->point; - entry->flags = args->flags; + entry->point = point; + entry->flags = flags;
drm_syncobj_add_eventfd(syncobj, entry); - drm_syncobj_put(syncobj);
return 0;
err_kzalloc: eventfd_ctx_put(ev_fd_ctx); err_fdget: - drm_syncobj_put(syncobj); return ret; } +EXPORT_SYMBOL(drm_syncobj_register_eventfd);
int drm_syncobj_reset_ioctl(struct drm_device *dev, void *data, diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index 7677fd995be0..85e7ca7f7896 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -134,6 +134,8 @@ signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs, uint32_t count, uint32_t flags, signed long timeout, uint32_t *idx, ktime_t *deadline); +int drm_syncobj_register_eventfd(struct drm_syncobj *syncobj, + int ev_fd, u64 point, u32 flags); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This removes the implicit flags check from drm_syncobj_find_fence. The check is moved to the only caller drm_syncobj_transfer_ioctl.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 62 ++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 27 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 3e8fb7e0cace..a746e787882d 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -955,29 +955,23 @@ drm_syncobj_fd_to_handle_ioctl(struct drm_device *dev, void *data, &args->handle); }
-static int drm_syncobj_transfer_to_timeline(struct drm_file *file_private, - struct drm_syncobj_transfer *args) +static int drm_syncobj_transfer_to_timeline(struct drm_syncobj *src, u64 src_point, + struct drm_syncobj *dst, u64 dst_point, + u32 flags) { - struct drm_syncobj *timeline_syncobj = NULL; struct dma_fence *fence, *tmp; struct dma_fence_chain *chain; int ret;
- timeline_syncobj = drm_syncobj_find(file_private, args->dst_handle); - if (!timeline_syncobj) { - return -ENOENT; - } - ret = drm_syncobj_find_fence(file_private, args->src_handle, - args->src_point, args->flags, - &tmp); + ret = drm_syncobj_fence_lookup(src, src_point, flags, &tmp); if (ret) - goto err_put_timeline; + goto out;
fence = dma_fence_unwrap_merge(tmp); dma_fence_put(tmp); if (!fence) { ret = -ENOMEM; - goto err_put_timeline; + goto out; }
chain = dma_fence_chain_alloc(); @@ -986,34 +980,27 @@ static int drm_syncobj_transfer_to_timeline(struct drm_file *file_private, goto err_free_fence; }
- drm_syncobj_add_point(timeline_syncobj, chain, fence, args->dst_point); + drm_syncobj_add_point(dst, chain, fence, dst_point); err_free_fence: dma_fence_put(fence); -err_put_timeline: - drm_syncobj_put(timeline_syncobj); +out:
return ret; }
static int -drm_syncobj_transfer_to_binary(struct drm_file *file_private, - struct drm_syncobj_transfer *args) +drm_syncobj_transfer_to_binary(struct drm_syncobj *src, u64 src_point, + struct drm_syncobj *dst, u32 flags) { - struct drm_syncobj *binary_syncobj = NULL; struct dma_fence *fence; int ret;
- binary_syncobj = drm_syncobj_find(file_private, args->dst_handle); - if (!binary_syncobj) - return -ENOENT; - ret = drm_syncobj_find_fence(file_private, args->src_handle, - args->src_point, args->flags, &fence); + ret = drm_syncobj_fence_lookup(src, src_point, flags, &fence); if (ret) goto err; - drm_syncobj_replace_fence(binary_syncobj, fence); + drm_syncobj_replace_fence(dst, fence); dma_fence_put(fence); err: - drm_syncobj_put(binary_syncobj);
return ret; } @@ -1022,18 +1009,39 @@ drm_syncobj_transfer_ioctl(struct drm_device *dev, void *data, struct drm_file *file_private) { struct drm_syncobj_transfer *args = data; + struct drm_syncobj *src, *dst; int ret;
if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ_TIMELINE)) return -EOPNOTSUPP;
+ if (args->flags & ~DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) + return -EINVAL; + if (args->pad) return -EINVAL;
+ src = drm_syncobj_find(file_private, args->src_handle); + if (!src) + return -ENOENT; + + dst = drm_syncobj_find(file_private, args->dst_handle); + if (!dst) { + ret = -ENOENT; + goto err_dst; + } + if (args->dst_point) - ret = drm_syncobj_transfer_to_timeline(file_private, args); + ret = drm_syncobj_transfer_to_timeline(src, args->src_point, + dst, args->dst_point, + args->flags); else - ret = drm_syncobj_transfer_to_binary(file_private, args); + ret = drm_syncobj_transfer_to_binary(src, args->src_point, + dst, args->flags); + + drm_syncobj_put(dst); +err_dst: + drm_syncobj_put(src);
return ret; }
This makes the logic from drm_syncobj_transfer_ioctl available to callers that already have two drm_syncobj.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 36 +++++++++++++++++++++++++++++------- include/drm/drm_syncobj.h | 3 +++ 2 files changed, 32 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index a746e787882d..8ccfbd972191 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1031,13 +1031,9 @@ drm_syncobj_transfer_ioctl(struct drm_device *dev, void *data, goto err_dst; }
- if (args->dst_point) - ret = drm_syncobj_transfer_to_timeline(src, args->src_point, - dst, args->dst_point, - args->flags); - else - ret = drm_syncobj_transfer_to_binary(src, args->src_point, - dst, args->flags); + ret = drm_syncobj_transfer(src, args->src_point, + dst, args->dst_point, + args->flags);
drm_syncobj_put(dst); err_dst: @@ -1046,6 +1042,32 @@ drm_syncobj_transfer_ioctl(struct drm_device *dev, void *data, return ret; }
+/** + * drm_syncobj_transfer - transfer a fence between syncobjs + * @src: source syncobj + * @src_point: source point + * @dst: destination syncobj + * @dst_point: destination point + * @flags: DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT or 0 + * + * Copies the fence at @src_point in @src to @dst_point in @dst. + * + * Returns 0 on success or a negative error value on failure. + */ +int drm_syncobj_transfer(struct drm_syncobj *src, u64 src_point, + struct drm_syncobj *dst, u64 dst_point, + u32 flags) +{ + if (dst_point) + return drm_syncobj_transfer_to_timeline(src, src_point, + dst, dst_point, + flags); + else + return drm_syncobj_transfer_to_binary(src, src_point, + dst, flags); +} +EXPORT_SYMBOL(drm_syncobj_transfer); + static void syncobj_wait_fence_func(struct dma_fence *fence, struct dma_fence_cb *cb) { diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index 85e7ca7f7896..ec8042d61466 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -136,6 +136,9 @@ signed long drm_syncobj_array_wait_timeout(struct drm_syncobj **syncobjs, ktime_t *deadline); int drm_syncobj_register_eventfd(struct drm_syncobj *syncobj, int ev_fd, u64 point, u32 flags); +int drm_syncobj_transfer(struct drm_syncobj *src, u64 src_point, + struct drm_syncobj *dst, u64 dst_point, + u32 flags); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This makes the logic from drm_syncobj_timeline_signal_ioctl available to callers that already have an array of drm_syncobj.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 50 ++++++++++++++++++++++++++++++++----------- include/drm/drm_syncobj.h | 2 ++ 2 files changed, 40 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 8ccfbd972191..948084f56c32 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1675,9 +1675,6 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, { struct drm_syncobj_timeline_array *args = data; struct drm_syncobj **syncobjs; - struct dma_fence_chain **chains; - uint64_t *points; - uint32_t i, j; int ret;
if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ_TIMELINE)) @@ -1696,26 +1693,55 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, if (ret < 0) return ret;
- points = kmalloc_array(args->count_handles, sizeof(*points), + ret = drm_syncobj_timeline_signal(syncobjs, args->points, args->count_handles); + + drm_syncobj_array_free(syncobjs, args->count_handles); + + return ret; +} + +/** + * drm_syncobj_timeline_signal - signal timeline points on syncobjs + * @syncobjs: array of syncobjs + * @user_points: user pointer to array of timeline points + * @count: number of syncobjs + * + * Signals each syncobj at the corresponding timeline point. + * + * Returns 0 on success or a negative error value on failure. + */ +int +drm_syncobj_timeline_signal(struct drm_syncobj **syncobjs, + u64 user_points, u32 count) +{ + struct dma_fence_chain **chains; + uint64_t *points; + uint32_t i, j; + int ret = 0; + + if (count == 0) + return -EINVAL; + + points = kmalloc_array(count, sizeof(*points), GFP_KERNEL); if (!points) { ret = -ENOMEM; goto out; } - if (!u64_to_user_ptr(args->points)) { - memset(points, 0, args->count_handles * sizeof(uint64_t)); - } else if (copy_from_user(points, u64_to_user_ptr(args->points), - sizeof(uint64_t) * args->count_handles)) { + if (!u64_to_user_ptr(user_points)) { + memset(points, 0, count * sizeof(uint64_t)); + } else if (copy_from_user(points, u64_to_user_ptr(user_points), + sizeof(uint64_t) * count)) { ret = -EFAULT; goto err_points; }
- chains = kmalloc_array(args->count_handles, sizeof(void *), GFP_KERNEL); + chains = kmalloc_array(count, sizeof(void *), GFP_KERNEL); if (!chains) { ret = -ENOMEM; goto err_points; } - for (i = 0; i < args->count_handles; i++) { + for (i = 0; i < count; i++) { chains[i] = dma_fence_chain_alloc(); if (!chains[i]) { for (j = 0; j < i; j++) @@ -1725,7 +1751,7 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, } }
- for (i = 0; i < args->count_handles; i++) { + for (i = 0; i < count; i++) { struct dma_fence *fence = dma_fence_get_stub();
drm_syncobj_add_point(syncobjs[i], chains[i], @@ -1737,10 +1763,10 @@ drm_syncobj_timeline_signal_ioctl(struct drm_device *dev, void *data, err_points: kfree(points); out: - drm_syncobj_array_free(syncobjs, args->count_handles);
return ret; } +EXPORT_SYMBOL(drm_syncobj_timeline_signal);
int drm_syncobj_query_ioctl(struct drm_device *dev, void *data, struct drm_file *file_private) diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index ec8042d61466..a9216ea07946 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -139,6 +139,8 @@ int drm_syncobj_register_eventfd(struct drm_syncobj *syncobj, int drm_syncobj_transfer(struct drm_syncobj *src, u64 src_point, struct drm_syncobj *dst, u64 dst_point, u32 flags); +int drm_syncobj_timeline_signal(struct drm_syncobj **syncobjs, + u64 user_points, u32 count); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This makes the logic from drm_syncobj_query_ioctl available to callers that already have an array of drm_syncobj.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 32 +++++++++++++++++++++++++++----- include/drm/drm_syncobj.h | 2 ++ 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 948084f56c32..9b7ecc2978f5 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -1773,8 +1773,6 @@ int drm_syncobj_query_ioctl(struct drm_device *dev, void *data, { struct drm_syncobj_timeline_array *args = data; struct drm_syncobj **syncobjs; - uint64_t __user *points = u64_to_user_ptr(args->points); - uint32_t i; int ret;
if (!drm_core_check_feature(dev, DRIVER_SYNCOBJ_TIMELINE)) @@ -1793,7 +1791,31 @@ int drm_syncobj_query_ioctl(struct drm_device *dev, void *data, if (ret < 0) return ret;
- for (i = 0; i < args->count_handles; i++) { + ret = drm_syncobj_query(syncobjs, args->points, + args->count_handles, args->flags); + + drm_syncobj_array_free(syncobjs, args->count_handles); + + return ret; +} + +/** + * drm_syncobj_query - query timeline points of syncobjs + * @syncobjs: array of syncobjs + * @user_points: user pointer to array of timeline points + * @count: number of syncobjs + * @flags: DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED or 0 + * + * Queries the timeline point of each syncobj and writes it to @points. + */ +int drm_syncobj_query(struct drm_syncobj **syncobjs, u64 user_points, + u32 count, u32 flags) +{ + uint64_t __user *points = u64_to_user_ptr(user_points); + uint32_t i; + int ret = 0; + + for (i = 0; i < count; i++) { struct dma_fence_chain *chain; struct dma_fence *fence; uint64_t point; @@ -1804,7 +1826,7 @@ int drm_syncobj_query_ioctl(struct drm_device *dev, void *data, struct dma_fence *iter, *last_signaled = dma_fence_get(fence);
- if (args->flags & + if (flags & DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED) { point = fence->seqno; } else { @@ -1832,7 +1854,7 @@ int drm_syncobj_query_ioctl(struct drm_device *dev, void *data, if (ret) break; } - drm_syncobj_array_free(syncobjs, args->count_handles);
return ret; } +EXPORT_SYMBOL(drm_syncobj_query); diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index a9216ea07946..da237ca3e61f 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -141,6 +141,8 @@ int drm_syncobj_transfer(struct drm_syncobj *src, u64 src_point, u32 flags); int drm_syncobj_timeline_signal(struct drm_syncobj **syncobjs, u64 user_points, u32 count); +int drm_syncobj_query(struct drm_syncobj **syncobjs, u64 user_points, + u32 count, u32 flags); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
Previously, if dma_fence_chain_alloc() failed, the syncobj and fence would be leaked.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 9b7ecc2978f5..1da96e23dfc0 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -767,30 +767,35 @@ static int drm_syncobj_import_sync_file_fence(struct drm_file *file_private, { struct dma_fence *fence = sync_file_get_fence(fd); struct drm_syncobj *syncobj; + int ret = 0;
if (!fence) return -EINVAL;
syncobj = drm_syncobj_find(file_private, handle); if (!syncobj) { - dma_fence_put(fence); - return -ENOENT; + ret = -ENOENT; + goto err_syncobj; }
if (point) { struct dma_fence_chain *chain = dma_fence_chain_alloc();
- if (!chain) - return -ENOMEM; + if (!chain) { + ret = -ENOMEM; + goto err; + }
drm_syncobj_add_point(syncobj, chain, fence, point); } else { drm_syncobj_replace_fence(syncobj, fence); }
- dma_fence_put(fence); +err: drm_syncobj_put(syncobj); - return 0; +err_syncobj: + dma_fence_put(fence); + return ret; }
static int drm_syncobj_export_sync_file(struct drm_file *file_private,
On 5/16/26 13:06, Julian Orth wrote:
Previously, if dma_fence_chain_alloc() failed, the syncobj and fence would be leaked.
Since it is a bug fix that patch should be send out separately from the patch set.
Signed-off-by: Julian Orth ju.orth@gmail.com
drivers/gpu/drm/drm_syncobj.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 9b7ecc2978f5..1da96e23dfc0 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -767,30 +767,35 @@ static int drm_syncobj_import_sync_file_fence(struct drm_file *file_private, { struct dma_fence *fence = sync_file_get_fence(fd); struct drm_syncobj *syncobj;
- int ret = 0;
Please don't initialize local return variables, initialize them when you know that the function is successful.
Regards, Christian.
if (!fence) return -EINVAL; syncobj = drm_syncobj_find(file_private, handle); if (!syncobj) {
dma_fence_put(fence);return -ENOENT;
ret = -ENOENT; }goto err_syncobj;if (point) { struct dma_fence_chain *chain = dma_fence_chain_alloc();
if (!chain)return -ENOMEM;
if (!chain) {ret = -ENOMEM;goto err;}drm_syncobj_add_point(syncobj, chain, fence, point); } else { drm_syncobj_replace_fence(syncobj, fence); }
- dma_fence_put(fence);
+err: drm_syncobj_put(syncobj);
- return 0;
+err_syncobj:
- dma_fence_put(fence);
- return ret;
} static int drm_syncobj_export_sync_file(struct drm_file *file_private,
This makes the logic from drm_syncobj_import_sync_file_fence available to callers that have a drm_syncobj instead of a drm_file/handle pair.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 35 ++++++++++++++++++++++++++--------- include/drm/drm_syncobj.h | 2 ++ 2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 1da96e23dfc0..4c1667c67cb7 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -765,19 +765,37 @@ static int drm_syncobj_fd_to_handle(struct drm_file *file_private, static int drm_syncobj_import_sync_file_fence(struct drm_file *file_private, int fd, int handle, u64 point) { - struct dma_fence *fence = sync_file_get_fence(fd); struct drm_syncobj *syncobj; int ret = 0;
+ syncobj = drm_syncobj_find(file_private, handle); + if (!syncobj) + return -ENOENT; + + ret = drm_syncobj_import_sync_file(syncobj, fd, point); + + drm_syncobj_put(syncobj); + + return ret; +} + +/** + * drm_syncobj_import_sync_file - import a sync_file fd into a syncobj + * @syncobj: syncobj to import into + * @fd: sync_file file descriptor + * @point: timeline point or 0 + * + * Returns 0 on success or a negative error value on failure. + */ +int drm_syncobj_import_sync_file(struct drm_syncobj *syncobj, + int fd, u64 point) +{ + struct dma_fence *fence = sync_file_get_fence(fd); + int ret = 0; + if (!fence) return -EINVAL;
- syncobj = drm_syncobj_find(file_private, handle); - if (!syncobj) { - ret = -ENOENT; - goto err_syncobj; - } - if (point) { struct dma_fence_chain *chain = dma_fence_chain_alloc();
@@ -792,11 +810,10 @@ static int drm_syncobj_import_sync_file_fence(struct drm_file *file_private, }
err: - drm_syncobj_put(syncobj); -err_syncobj: dma_fence_put(fence); return ret; } +EXPORT_SYMBOL(drm_syncobj_import_sync_file);
static int drm_syncobj_export_sync_file(struct drm_file *file_private, int handle, u64 point, int *p_fd) diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index da237ca3e61f..1571ffa12a5c 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -143,6 +143,8 @@ int drm_syncobj_timeline_signal(struct drm_syncobj **syncobjs, u64 user_points, u32 count); int drm_syncobj_query(struct drm_syncobj **syncobjs, u64 user_points, u32 count, u32 flags); +int drm_syncobj_import_sync_file(struct drm_syncobj *syncobj, + int sync_file_fd, u64 point); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This makes the logic from drm_syncobj_export_sync_file_by_handle available to callers that have a drm_syncobj instead of a drm_file/handle pair.
Signed-off-by: Julian Orth ju.orth@gmail.com --- drivers/gpu/drm/drm_syncobj.c | 39 ++++++++++++++++++++++++++++++++++----- include/drm/drm_syncobj.h | 2 ++ 2 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/drm_syncobj.c b/drivers/gpu/drm/drm_syncobj.c index 4c1667c67cb7..d5e633738730 100644 --- a/drivers/gpu/drm/drm_syncobj.c +++ b/drivers/gpu/drm/drm_syncobj.c @@ -815,8 +815,34 @@ int drm_syncobj_import_sync_file(struct drm_syncobj *syncobj, } EXPORT_SYMBOL(drm_syncobj_import_sync_file);
-static int drm_syncobj_export_sync_file(struct drm_file *file_private, - int handle, u64 point, int *p_fd) +static int drm_syncobj_export_sync_file_by_handle(struct drm_file *file_private, + int handle, u64 point, + int *p_fd) +{ + struct drm_syncobj *syncobj; + int ret; + + syncobj = drm_syncobj_find(file_private, handle); + if (!syncobj) + return -ENOENT; + + ret = drm_syncobj_export_sync_file(syncobj, point, p_fd); + + drm_syncobj_put(syncobj); + + return ret; +} + +/** + * drm_syncobj_export_sync_file - export a syncobj fence as a sync_file fd + * @syncobj: syncobj to export from + * @point: timeline point or 0 + * @p_fd: out parameter for the new file descriptor + * + * Returns 0 on success or a negative error value on failure. + */ +int drm_syncobj_export_sync_file(struct drm_syncobj *syncobj, + u64 point, int *p_fd) { int ret; struct dma_fence *fence; @@ -826,7 +852,7 @@ static int drm_syncobj_export_sync_file(struct drm_file *file_private, if (fd < 0) return fd;
- ret = drm_syncobj_find_fence(file_private, handle, point, 0, &fence); + ret = drm_syncobj_fence_lookup(syncobj, point, 0, &fence); if (ret) goto err_put_fd;
@@ -847,6 +873,8 @@ static int drm_syncobj_export_sync_file(struct drm_file *file_private, put_unused_fd(fd); return ret; } +EXPORT_SYMBOL(drm_syncobj_export_sync_file); + /** * drm_syncobj_open - initializes syncobj file-private structures at devnode open time * @file_private: drm file-private structure to set up @@ -933,8 +961,9 @@ drm_syncobj_handle_to_fd_ioctl(struct drm_device *dev, void *data, point = args->point;
if (args->flags & DRM_SYNCOBJ_HANDLE_TO_FD_FLAGS_EXPORT_SYNC_FILE) - return drm_syncobj_export_sync_file(file_private, args->handle, - point, &args->fd); + return drm_syncobj_export_sync_file_by_handle(file_private, + args->handle, + point, &args->fd);
if (args->point) return -EINVAL; diff --git a/include/drm/drm_syncobj.h b/include/drm/drm_syncobj.h index 1571ffa12a5c..48476c570595 100644 --- a/include/drm/drm_syncobj.h +++ b/include/drm/drm_syncobj.h @@ -145,6 +145,8 @@ int drm_syncobj_query(struct drm_syncobj **syncobjs, u64 user_points, u32 count, u32 flags); int drm_syncobj_import_sync_file(struct drm_syncobj *syncobj, int sync_file_fd, u64 point); +int drm_syncobj_export_sync_file(struct drm_syncobj *syncobj, + u64 point, int *p_fd); void drm_syncobj_free(struct kref *kref); int drm_syncobj_create(struct drm_syncobj **out_syncobj, uint32_t flags, struct dma_fence *fence);
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com --- Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 331223761fff..5e140ae5735e 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -395,6 +395,7 @@ Code Seq# Include File Comments mailto:michael.klein@puffin.lb.shuttle.de 0xCC 00-0F drivers/misc/ibmvmc.h pseries VMC driver 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 +0xCD 00-0F uapi/linux/syncobj.h 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 00683bf06258..c1e7749bd356 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI - lan966x-miim (MDIO_MSCC_MIIM) - lan966x-switch (LAN966X_SWITCH)
+config SYNCOBJ_DEV + tristate "DRM syncobj device (/dev/syncobj)" + depends on DRM + help + Creates a /dev/syncobj device node that provides DRM synchronization + objects (syncobjs) without requiring a DRM device. + + To compile this driver as a module, choose M here: the module + will be called syncobj. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b32a2597d246..9e5deb1d0d76 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ +obj-$(CONFIG_SYNCOBJ_DEV) += syncobj.o diff --git a/drivers/misc/syncobj.c b/drivers/misc/syncobj.c new file mode 100644 index 000000000000..11ef46ddfeef --- /dev/null +++ b/drivers/misc/syncobj.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * syncobj.c - Standalone device for syncobj manipulation. + * + * Copyright (C) 2026 Julian Orth ju.orth@gmail.com + */ + +#include <linux/fdtable.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <drm/drm_syncobj.h> +#include <drm/drm_utils.h> +#include <uapi/drm/drm.h> +#include <uapi/linux/syncobj.h> + +static int syncobj_array_find(void __user *user_fds, u32 count, + struct drm_syncobj ***syncobjs_out) +{ + u32 i; + s32 *fds; + struct drm_syncobj **syncobjs; + int ret; + + fds = kmalloc_array(count, sizeof(*fds), GFP_KERNEL); + if (!fds) + return -ENOMEM; + + if (copy_from_user(fds, user_fds, sizeof(s32) * count)) { + ret = -EFAULT; + goto err_free_fds; + } + + syncobjs = kmalloc_array(count, sizeof(*syncobjs), GFP_KERNEL); + if (!syncobjs) { + ret = -ENOMEM; + goto err_free_fds; + } + + for (i = 0; i < count; i++) { + syncobjs[i] = drm_syncobj_from_fd(fds[i]); + if (!syncobjs[i]) { + ret = -EBADF; + goto err_put_syncobjs; + } + } + + kfree(fds); + *syncobjs_out = syncobjs; + return 0; + +err_put_syncobjs: + while (i-- > 0) + drm_syncobj_put(syncobjs[i]); + kfree(syncobjs); +err_free_fds: + kfree(fds); + return ret; +} + +static void syncobj_array_free(struct drm_syncobj **syncobjs, u32 count) +{ + u32 i; + + for (i = 0; i < count; i++) + drm_syncobj_put(syncobjs[i]); + kfree(syncobjs); +} + +static int syncobj_ioctl_create(void __user *argp) +{ + struct syncobj_create_args args; + struct drm_syncobj *syncobj; + int fd, ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags & ~SYNCOBJ_CREATE_SIGNALED) + return -EINVAL; + + static_assert(SYNCOBJ_CREATE_SIGNALED == DRM_SYNCOBJ_CREATE_SIGNALED); + + ret = drm_syncobj_create(&syncobj, args.flags, NULL); + if (ret) + return ret; + + ret = drm_syncobj_get_fd(syncobj, &fd); + drm_syncobj_put(syncobj); + if (ret) + return ret; + + args.fd = fd; + if (copy_to_user(argp, &args, sizeof(args))) { + close_fd(fd); + return -EFAULT; + } + + return 0; +} + +static int syncobj_ioctl_wait(void __user *argp) +{ + struct syncobj_wait_args args; + struct drm_syncobj **syncobjs; + signed long timeout; + u32 first = ~0; + ktime_t t, *tp = NULL; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags & ~(SYNCOBJ_WAIT_FLAGS_WAIT_ALL | + SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT | + SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE | + SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE)) + return -EINVAL; + + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_ALL == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL); + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT); + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE); + + if (args.pad) + return -EINVAL; + + if (args.count == 0) + return 0; + + ret = syncobj_array_find(u64_to_user_ptr(args.fds), + args.count, &syncobjs); + if (ret < 0) + return ret; + + if (args.flags & SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE) { + t = ns_to_ktime(args.deadline_nsec); + tp = &t; + } + + timeout = drm_timeout_abs_to_jiffies(args.timeout_nsec); + timeout = drm_syncobj_array_wait_timeout(syncobjs, + u64_to_user_ptr(args.points), + args.count, + args.flags, + timeout, &first, tp); + + syncobj_array_free(syncobjs, args.count); + + if (timeout < 0) + return timeout; + + args.first_signaled = first; + if (copy_to_user(argp, &args, sizeof(args))) + return -EFAULT; + + return 0; +} + +static int syncobj_ioctl_reset(void __user *argp) +{ + struct syncobj_array_args args; + struct drm_syncobj **syncobjs; + u32 i; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags) + return -EINVAL; + + if (args.points) + return -EINVAL; + + if (args.count == 0) + return -EINVAL; + + ret = syncobj_array_find(u64_to_user_ptr(args.fds), + args.count, &syncobjs); + if (ret < 0) + return ret; + + for (i = 0; i < args.count; i++) + drm_syncobj_replace_fence(syncobjs[i], NULL); + + syncobj_array_free(syncobjs, args.count); + return 0; +} + +static int syncobj_ioctl_signal(void __user *argp) +{ + struct syncobj_array_args args; + struct drm_syncobj **syncobjs; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags) + return -EINVAL; + + if (args.count == 0) + return -EINVAL; + + ret = syncobj_array_find(u64_to_user_ptr(args.fds), + args.count, &syncobjs); + if (ret < 0) + return ret; + + ret = drm_syncobj_timeline_signal(syncobjs, args.points, args.count); + + syncobj_array_free(syncobjs, args.count); + return ret; +} + +static int syncobj_ioctl_query(void __user *argp) +{ + struct syncobj_array_args args; + struct drm_syncobj **syncobjs; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags & ~SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED) + return -EINVAL; + + static_assert(SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED == DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED); + + if (args.count == 0) + return -EINVAL; + + ret = syncobj_array_find(u64_to_user_ptr(args.fds), + args.count, &syncobjs); + if (ret < 0) + return ret; + + ret = drm_syncobj_query(syncobjs, args.points, args.count, args.flags); + + syncobj_array_free(syncobjs, args.count); + return ret; +} + +static int syncobj_ioctl_transfer(void __user *argp) +{ + struct syncobj_transfer_args args; + struct drm_syncobj *src, *dst; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.pad) + return -EINVAL; + + if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT) + return -EINVAL; + + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT); + + src = drm_syncobj_from_fd(args.src_fd); + if (!src) + return -EBADF; + + dst = drm_syncobj_from_fd(args.dst_fd); + if (!dst) { + drm_syncobj_put(src); + return -EBADF; + } + + ret = drm_syncobj_transfer(src, args.src_point, + dst, args.dst_point, args.flags); + + drm_syncobj_put(dst); + drm_syncobj_put(src); + + return ret; +} + +static int syncobj_ioctl_eventfd(void __user *argp) +{ + struct syncobj_eventfd_args args; + struct drm_syncobj *syncobj; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE) + return -EINVAL; + + static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + + if (args.pad) + return -EINVAL; + + syncobj = drm_syncobj_from_fd(args.syncobj_fd); + if (!syncobj) + return -EBADF; + + ret = drm_syncobj_register_eventfd(syncobj, args.eventfd, + args.point, args.flags); + + drm_syncobj_put(syncobj); + + return ret; +} + +static int syncobj_ioctl_export_sync_file(void __user *argp) +{ + struct syncobj_sync_file_args args; + struct drm_syncobj *syncobj; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + syncobj = drm_syncobj_from_fd(args.syncobj_fd); + if (!syncobj) + return -EBADF; + + ret = drm_syncobj_export_sync_file(syncobj, args.point, + &args.sync_file_fd); + drm_syncobj_put(syncobj); + if (ret) + return ret; + + if (copy_to_user(argp, &args, sizeof(args))) { + close_fd(args.sync_file_fd); + return -EFAULT; + } + + return 0; +} + +static int syncobj_ioctl_import_sync_file(void __user *argp) +{ + struct syncobj_sync_file_args args; + struct drm_syncobj *syncobj; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + + syncobj = drm_syncobj_from_fd(args.syncobj_fd); + if (!syncobj) + return -EBADF; + + ret = drm_syncobj_import_sync_file(syncobj, args.sync_file_fd, + args.point); + + drm_syncobj_put(syncobj); + + return ret; +} + +static long syncobj_dev_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case SYNCOBJ_IOC_CREATE: + return syncobj_ioctl_create(argp); + case SYNCOBJ_IOC_WAIT: + return syncobj_ioctl_wait(argp); + case SYNCOBJ_IOC_RESET: + return syncobj_ioctl_reset(argp); + case SYNCOBJ_IOC_SIGNAL: + return syncobj_ioctl_signal(argp); + case SYNCOBJ_IOC_QUERY: + return syncobj_ioctl_query(argp); + case SYNCOBJ_IOC_TRANSFER: + return syncobj_ioctl_transfer(argp); + case SYNCOBJ_IOC_EVENTFD: + return syncobj_ioctl_eventfd(argp); + case SYNCOBJ_IOC_EXPORT_SYNC_FILE: + return syncobj_ioctl_export_sync_file(argp); + case SYNCOBJ_IOC_IMPORT_SYNC_FILE: + return syncobj_ioctl_import_sync_file(argp); + default: + return -ENOIOCTLCMD; + } +} + +static const struct file_operations syncobj_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = syncobj_dev_ioctl, + .compat_ioctl = compat_ptr_ioctl, +}; + +static struct miscdevice syncobj_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = "syncobj", + .fops = &syncobj_dev_fops, + .mode = 0666, +}; + +module_misc_device(syncobj_misc); + +MODULE_AUTHOR("Julian Orth"); +MODULE_DESCRIPTION("DRM syncobj device"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/syncobj.h b/include/uapi/linux/syncobj.h new file mode 100644 index 000000000000..c4068fbd5773 --- /dev/null +++ b/include/uapi/linux/syncobj.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_SYNCOBJ_H_ +#define _UAPI_LINUX_SYNCOBJ_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +#define SYNCOBJ_CREATE_SIGNALED (1 << 0) + +#define SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0) +#define SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1) +#define SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE (1 << 2) +#define SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE (1 << 3) + +#define SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED (1 << 0) + +struct syncobj_create_args { + __s32 fd; + __u32 flags; +}; + +struct syncobj_wait_args { + __u64 fds; + __u64 points; + __s64 timeout_nsec; + __u32 count; + __u32 flags; + __u32 first_signaled; + __u32 pad; + __u64 deadline_nsec; +}; + +struct syncobj_array_args { + __u64 fds; + __u64 points; + __u32 count; + __u32 flags; +}; + +struct syncobj_transfer_args { + __s32 src_fd; + __s32 dst_fd; + __u64 src_point; + __u64 dst_point; + __u32 flags; + __u32 pad; +}; + +struct syncobj_eventfd_args { + __s32 syncobj_fd; + __s32 eventfd; + __u64 point; + __u32 flags; + __u32 pad; +}; + +struct syncobj_sync_file_args { + __s32 syncobj_fd; + __s32 sync_file_fd; + __u64 point; +}; + +#define SYNCOBJ_IOC_BASE 0xCD + +#define SYNCOBJ_IOC_CREATE _IOWR(SYNCOBJ_IOC_BASE, 0, struct syncobj_create_args) +#define SYNCOBJ_IOC_WAIT _IOWR(SYNCOBJ_IOC_BASE, 1, struct syncobj_wait_args) +#define SYNCOBJ_IOC_RESET _IOW(SYNCOBJ_IOC_BASE, 2, struct syncobj_array_args) +#define SYNCOBJ_IOC_SIGNAL _IOW(SYNCOBJ_IOC_BASE, 3, struct syncobj_array_args) +#define SYNCOBJ_IOC_QUERY _IOW(SYNCOBJ_IOC_BASE, 4, struct syncobj_array_args) +#define SYNCOBJ_IOC_TRANSFER _IOW(SYNCOBJ_IOC_BASE, 5, struct syncobj_transfer_args) +#define SYNCOBJ_IOC_EVENTFD _IOW(SYNCOBJ_IOC_BASE, 6, struct syncobj_eventfd_args) +#define SYNCOBJ_IOC_EXPORT_SYNC_FILE _IOWR(SYNCOBJ_IOC_BASE, 7, struct syncobj_sync_file_args) +#define SYNCOBJ_IOC_IMPORT_SYNC_FILE _IOW(SYNCOBJ_IOC_BASE, 8, struct syncobj_sync_file_args) + +#endif /* _UAPI_LINUX_SYNCOBJ_H_ */
On Sat, May 16, 2026 at 01:06:15PM +0200, Julian Orth wrote:
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 331223761fff..5e140ae5735e 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -395,6 +395,7 @@ Code Seq# Include File Comments mailto:michael.klein@puffin.lb.shuttle.de 0xCC 00-0F drivers/misc/ibmvmc.h pseries VMC driver 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 +0xCD 00-0F uapi/linux/syncobj.h 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 00683bf06258..c1e7749bd356 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI - lan966x-miim (MDIO_MSCC_MIIM) - lan966x-switch (LAN966X_SWITCH) +config SYNCOBJ_DEV
- tristate "DRM syncobj device (/dev/syncobj)"
- depends on DRM
If this is a drm-only thing, then please put it in the drm subdirs, not in drivers/misc/ as that would imply that I have to maintain this, not the DRM developers :)
thanks,
greg k-h
On Sat, May 16, 2026 at 01:06:15PM +0200, Julian Orth wrote:
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
As this is a bunch of user-facing code, why not do this in rust to at least get some semblance of proper parsing of user data sanity? Or is the api to the drm layer just to complex for that at the moment?
Just curious, not a criticism of this in C at all.
thanks,
greg k-h
On Sat, May 16, 2026 at 1:38 PM Greg Kroah-Hartman gregkh@linuxfoundation.org wrote:
On Sat, May 16, 2026 at 01:06:15PM +0200, Julian Orth wrote:
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
As this is a bunch of user-facing code, why not do this in rust to at least get some semblance of proper parsing of user data sanity? Or is the api to the drm layer just to complex for that at the moment?
I didn't consider using rust because I'm not familiar with rust in the kernel.
But even if I had considered it, I probably would not have done it because drm_syncobj currently has no rust bindings. The driver as-is is just a thin layer around drm_syncobj.c so if drm_syncobj gains rust bindings it should be easy to convert the driver.
Just curious, not a criticism of this in C at all.
thanks,
greg k-h
On 5/16/26 13:06, Julian Orth wrote:
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
That use case is invalid.
Usually drm_syncobj can only be filled with dma_fence objects and it is impossible to create one of those for software rendering.
What could be used is the drm_syncobj wait before signal functionality, but that usually requires special handling on the Wayland/Compositor side which as far as I can see doesn't make sense here either.
So the justification to use this for software rendering is very weak. Either I'm missing something or that is not going to fly at all.
Regards, Christian.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 331223761fff..5e140ae5735e 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -395,6 +395,7 @@ Code Seq# Include File Comments mailto:michael.klein@puffin.lb.shuttle.de 0xCC 00-0F drivers/misc/ibmvmc.h pseries VMC driver 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 +0xCD 00-0F uapi/linux/syncobj.h 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 00683bf06258..c1e7749bd356 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI - lan966x-miim (MDIO_MSCC_MIIM) - lan966x-switch (LAN966X_SWITCH) +config SYNCOBJ_DEV
- tristate "DRM syncobj device (/dev/syncobj)"
- depends on DRM
- help
Creates a /dev/syncobj device node that provides DRM synchronizationobjects (syncobjs) without requiring a DRM device.To compile this driver as a module, choose M here: the modulewill be called syncobj.source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b32a2597d246..9e5deb1d0d76 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ +obj-$(CONFIG_SYNCOBJ_DEV) += syncobj.o diff --git a/drivers/misc/syncobj.c b/drivers/misc/syncobj.c new file mode 100644 index 000000000000..11ef46ddfeef --- /dev/null +++ b/drivers/misc/syncobj.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- syncobj.c - Standalone device for syncobj manipulation.
- Copyright (C) 2026 Julian Orth ju.orth@gmail.com
- */
+#include <linux/fdtable.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <drm/drm_syncobj.h> +#include <drm/drm_utils.h> +#include <uapi/drm/drm.h> +#include <uapi/linux/syncobj.h>
+static int syncobj_array_find(void __user *user_fds, u32 count,
struct drm_syncobj ***syncobjs_out)+{
- u32 i;
- s32 *fds;
- struct drm_syncobj **syncobjs;
- int ret;
- fds = kmalloc_array(count, sizeof(*fds), GFP_KERNEL);
- if (!fds)
return -ENOMEM;- if (copy_from_user(fds, user_fds, sizeof(s32) * count)) {
ret = -EFAULT;goto err_free_fds;- }
- syncobjs = kmalloc_array(count, sizeof(*syncobjs), GFP_KERNEL);
- if (!syncobjs) {
ret = -ENOMEM;goto err_free_fds;- }
- for (i = 0; i < count; i++) {
syncobjs[i] = drm_syncobj_from_fd(fds[i]);if (!syncobjs[i]) {ret = -EBADF;goto err_put_syncobjs;}- }
- kfree(fds);
- *syncobjs_out = syncobjs;
- return 0;
+err_put_syncobjs:
- while (i-- > 0)
drm_syncobj_put(syncobjs[i]);- kfree(syncobjs);
+err_free_fds:
- kfree(fds);
- return ret;
+}
+static void syncobj_array_free(struct drm_syncobj **syncobjs, u32 count) +{
- u32 i;
- for (i = 0; i < count; i++)
drm_syncobj_put(syncobjs[i]);- kfree(syncobjs);
+}
+static int syncobj_ioctl_create(void __user *argp) +{
- struct syncobj_create_args args;
- struct drm_syncobj *syncobj;
- int fd, ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags & ~SYNCOBJ_CREATE_SIGNALED)
return -EINVAL;- static_assert(SYNCOBJ_CREATE_SIGNALED == DRM_SYNCOBJ_CREATE_SIGNALED);
- ret = drm_syncobj_create(&syncobj, args.flags, NULL);
- if (ret)
return ret;- ret = drm_syncobj_get_fd(syncobj, &fd);
- drm_syncobj_put(syncobj);
- if (ret)
return ret;- args.fd = fd;
- if (copy_to_user(argp, &args, sizeof(args))) {
close_fd(fd);return -EFAULT;- }
- return 0;
+}
+static int syncobj_ioctl_wait(void __user *argp) +{
- struct syncobj_wait_args args;
- struct drm_syncobj **syncobjs;
- signed long timeout;
- u32 first = ~0;
- ktime_t t, *tp = NULL;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags & ~(SYNCOBJ_WAIT_FLAGS_WAIT_ALL |
SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT |SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE |SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE))return -EINVAL;- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_ALL == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL);
- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT);
- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE);
- if (args.pad)
return -EINVAL;- if (args.count == 0)
return 0;- ret = syncobj_array_find(u64_to_user_ptr(args.fds),
args.count, &syncobjs);- if (ret < 0)
return ret;- if (args.flags & SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE) {
t = ns_to_ktime(args.deadline_nsec);tp = &t;- }
- timeout = drm_timeout_abs_to_jiffies(args.timeout_nsec);
- timeout = drm_syncobj_array_wait_timeout(syncobjs,
u64_to_user_ptr(args.points),args.count,args.flags,timeout, &first, tp);- syncobj_array_free(syncobjs, args.count);
- if (timeout < 0)
return timeout;- args.first_signaled = first;
- if (copy_to_user(argp, &args, sizeof(args)))
return -EFAULT;- return 0;
+}
+static int syncobj_ioctl_reset(void __user *argp) +{
- struct syncobj_array_args args;
- struct drm_syncobj **syncobjs;
- u32 i;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags)
return -EINVAL;- if (args.points)
return -EINVAL;- if (args.count == 0)
return -EINVAL;- ret = syncobj_array_find(u64_to_user_ptr(args.fds),
args.count, &syncobjs);- if (ret < 0)
return ret;- for (i = 0; i < args.count; i++)
drm_syncobj_replace_fence(syncobjs[i], NULL);- syncobj_array_free(syncobjs, args.count);
- return 0;
+}
+static int syncobj_ioctl_signal(void __user *argp) +{
- struct syncobj_array_args args;
- struct drm_syncobj **syncobjs;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags)
return -EINVAL;- if (args.count == 0)
return -EINVAL;- ret = syncobj_array_find(u64_to_user_ptr(args.fds),
args.count, &syncobjs);- if (ret < 0)
return ret;- ret = drm_syncobj_timeline_signal(syncobjs, args.points, args.count);
- syncobj_array_free(syncobjs, args.count);
- return ret;
+}
+static int syncobj_ioctl_query(void __user *argp) +{
- struct syncobj_array_args args;
- struct drm_syncobj **syncobjs;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags & ~SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED)
return -EINVAL;- static_assert(SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED == DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED);
- if (args.count == 0)
return -EINVAL;- ret = syncobj_array_find(u64_to_user_ptr(args.fds),
args.count, &syncobjs);- if (ret < 0)
return ret;- ret = drm_syncobj_query(syncobjs, args.points, args.count, args.flags);
- syncobj_array_free(syncobjs, args.count);
- return ret;
+}
+static int syncobj_ioctl_transfer(void __user *argp) +{
- struct syncobj_transfer_args args;
- struct drm_syncobj *src, *dst;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.pad)
return -EINVAL;- if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT)
return -EINVAL;- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT);
- src = drm_syncobj_from_fd(args.src_fd);
- if (!src)
return -EBADF;- dst = drm_syncobj_from_fd(args.dst_fd);
- if (!dst) {
drm_syncobj_put(src);return -EBADF;- }
- ret = drm_syncobj_transfer(src, args.src_point,
dst, args.dst_point, args.flags);- drm_syncobj_put(dst);
- drm_syncobj_put(src);
- return ret;
+}
+static int syncobj_ioctl_eventfd(void __user *argp) +{
- struct syncobj_eventfd_args args;
- struct drm_syncobj *syncobj;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE)
return -EINVAL;- static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);
- if (args.pad)
return -EINVAL;- syncobj = drm_syncobj_from_fd(args.syncobj_fd);
- if (!syncobj)
return -EBADF;- ret = drm_syncobj_register_eventfd(syncobj, args.eventfd,
args.point, args.flags);- drm_syncobj_put(syncobj);
- return ret;
+}
+static int syncobj_ioctl_export_sync_file(void __user *argp) +{
- struct syncobj_sync_file_args args;
- struct drm_syncobj *syncobj;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- syncobj = drm_syncobj_from_fd(args.syncobj_fd);
- if (!syncobj)
return -EBADF;- ret = drm_syncobj_export_sync_file(syncobj, args.point,
&args.sync_file_fd);- drm_syncobj_put(syncobj);
- if (ret)
return ret;- if (copy_to_user(argp, &args, sizeof(args))) {
close_fd(args.sync_file_fd);return -EFAULT;- }
- return 0;
+}
+static int syncobj_ioctl_import_sync_file(void __user *argp) +{
- struct syncobj_sync_file_args args;
- struct drm_syncobj *syncobj;
- int ret;
- if (copy_from_user(&args, argp, sizeof(args)))
return -EFAULT;- syncobj = drm_syncobj_from_fd(args.syncobj_fd);
- if (!syncobj)
return -EBADF;- ret = drm_syncobj_import_sync_file(syncobj, args.sync_file_fd,
args.point);- drm_syncobj_put(syncobj);
- return ret;
+}
+static long syncobj_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)+{
- void __user *argp = (void __user *)arg;
- switch (cmd) {
- case SYNCOBJ_IOC_CREATE:
return syncobj_ioctl_create(argp);- case SYNCOBJ_IOC_WAIT:
return syncobj_ioctl_wait(argp);- case SYNCOBJ_IOC_RESET:
return syncobj_ioctl_reset(argp);- case SYNCOBJ_IOC_SIGNAL:
return syncobj_ioctl_signal(argp);- case SYNCOBJ_IOC_QUERY:
return syncobj_ioctl_query(argp);- case SYNCOBJ_IOC_TRANSFER:
return syncobj_ioctl_transfer(argp);- case SYNCOBJ_IOC_EVENTFD:
return syncobj_ioctl_eventfd(argp);- case SYNCOBJ_IOC_EXPORT_SYNC_FILE:
return syncobj_ioctl_export_sync_file(argp);- case SYNCOBJ_IOC_IMPORT_SYNC_FILE:
return syncobj_ioctl_import_sync_file(argp);- default:
return -ENOIOCTLCMD;- }
+}
+static const struct file_operations syncobj_dev_fops = {
- .owner = THIS_MODULE,
- .unlocked_ioctl = syncobj_dev_ioctl,
- .compat_ioctl = compat_ptr_ioctl,
+};
+static struct miscdevice syncobj_misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "syncobj",
- .fops = &syncobj_dev_fops,
- .mode = 0666,
+};
+module_misc_device(syncobj_misc);
+MODULE_AUTHOR("Julian Orth"); +MODULE_DESCRIPTION("DRM syncobj device"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/syncobj.h b/include/uapi/linux/syncobj.h new file mode 100644 index 000000000000..c4068fbd5773 --- /dev/null +++ b/include/uapi/linux/syncobj.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_SYNCOBJ_H_ +#define _UAPI_LINUX_SYNCOBJ_H_
+#include <linux/ioctl.h> +#include <linux/types.h>
+#define SYNCOBJ_CREATE_SIGNALED (1 << 0)
+#define SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0) +#define SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1) +#define SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE (1 << 2) +#define SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE (1 << 3)
+#define SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED (1 << 0)
+struct syncobj_create_args {
- __s32 fd;
- __u32 flags;
+};
+struct syncobj_wait_args {
- __u64 fds;
- __u64 points;
- __s64 timeout_nsec;
- __u32 count;
- __u32 flags;
- __u32 first_signaled;
- __u32 pad;
- __u64 deadline_nsec;
+};
+struct syncobj_array_args {
- __u64 fds;
- __u64 points;
- __u32 count;
- __u32 flags;
+};
+struct syncobj_transfer_args {
- __s32 src_fd;
- __s32 dst_fd;
- __u64 src_point;
- __u64 dst_point;
- __u32 flags;
- __u32 pad;
+};
+struct syncobj_eventfd_args {
- __s32 syncobj_fd;
- __s32 eventfd;
- __u64 point;
- __u32 flags;
- __u32 pad;
+};
+struct syncobj_sync_file_args {
- __s32 syncobj_fd;
- __s32 sync_file_fd;
- __u64 point;
+};
+#define SYNCOBJ_IOC_BASE 0xCD
+#define SYNCOBJ_IOC_CREATE _IOWR(SYNCOBJ_IOC_BASE, 0, struct syncobj_create_args) +#define SYNCOBJ_IOC_WAIT _IOWR(SYNCOBJ_IOC_BASE, 1, struct syncobj_wait_args) +#define SYNCOBJ_IOC_RESET _IOW(SYNCOBJ_IOC_BASE, 2, struct syncobj_array_args) +#define SYNCOBJ_IOC_SIGNAL _IOW(SYNCOBJ_IOC_BASE, 3, struct syncobj_array_args) +#define SYNCOBJ_IOC_QUERY _IOW(SYNCOBJ_IOC_BASE, 4, struct syncobj_array_args) +#define SYNCOBJ_IOC_TRANSFER _IOW(SYNCOBJ_IOC_BASE, 5, struct syncobj_transfer_args) +#define SYNCOBJ_IOC_EVENTFD _IOW(SYNCOBJ_IOC_BASE, 6, struct syncobj_eventfd_args) +#define SYNCOBJ_IOC_EXPORT_SYNC_FILE _IOWR(SYNCOBJ_IOC_BASE, 7, struct syncobj_sync_file_args) +#define SYNCOBJ_IOC_IMPORT_SYNC_FILE _IOW(SYNCOBJ_IOC_BASE, 8, struct syncobj_sync_file_args)
+#endif /* _UAPI_LINUX_SYNCOBJ_H_ */
On Mon, May 18, 2026 at 2:06 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This device makes the DRM_IOCTL_SYNCOBJ_* ioctls available via a dedicated device. This allows applications to use syncobjs without having to open device nodes in /dev/dri, on systems that don't have any such nodes, or on systems whose devices don't support the DRIVER_SYNCOBJ_TIMELINE feature.
Wayland uses syncobjs as its buffer synchronization mechanism. Most compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a pure CPU wait for syncobj point. DRM devices are not involved in this process except insofar that a DRM device needs to be used to access the ioctl.
Similarly, a software-rendered client might perform rendering on a dedicated thread and use the wayland syncobj protocol to submit frames before they finish rendering. Again, this does not involve DRM devices except insofar ... as above.
That use case is invalid.
Usually drm_syncobj can only be filled with dma_fence objects and it is impossible to create one of those for software rendering.
That is simply not true. As I wrote above, DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL can be used with software rendering.
What could be used is the drm_syncobj wait before signal functionality, but that usually requires special handling on the Wayland/Compositor side which as far as I can see doesn't make sense here either.
Commit (to wayland) before submit (rendering work) is fully supported by the wayland syncobj protocol. No work needs to be done on the wayland side. In fact, everything that this series enables can already be done today by opening random /dev/dri nodes until you find one that supports the syncobj timeline ioctls. This series just makes it easier.
So the justification to use this for software rendering is very weak. Either I'm missing something or that is not going to fly at all.
Regards, Christian.
As an added benefit, this device removes the need to translate between file descriptors and handles.
Signed-off-by: Julian Orth ju.orth@gmail.com
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/uapi/linux/syncobj.h | 75 ++++ 5 files changed, 491 insertions(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 331223761fff..5e140ae5735e 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -395,6 +395,7 @@ Code Seq# Include File Comments mailto:michael.klein@puffin.lb.shuttle.de 0xCC 00-0F drivers/misc/ibmvmc.h pseries VMC driver 0xCD 01 linux/reiserfs_fs.h Dead since 6.13 +0xCD 00-0F uapi/linux/syncobj.h 0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices 0xCF 02 fs/smb/client/cifs_ioctl.h 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 00683bf06258..c1e7749bd356 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -644,6 +644,16 @@ config MCHP_LAN966X_PCI - lan966x-miim (MDIO_MSCC_MIIM) - lan966x-switch (LAN966X_SWITCH)
+config SYNCOBJ_DEV
tristate "DRM syncobj device (/dev/syncobj)"depends on DRMhelpCreates a /dev/syncobj device node that provides DRM synchronizationobjects (syncobjs) without requiring a DRM device.To compile this driver as a module, choose M here: the modulewill be called syncobj.source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b32a2597d246..9e5deb1d0d76 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -75,3 +75,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ +obj-$(CONFIG_SYNCOBJ_DEV) += syncobj.o diff --git a/drivers/misc/syncobj.c b/drivers/misc/syncobj.c new file mode 100644 index 000000000000..11ef46ddfeef --- /dev/null +++ b/drivers/misc/syncobj.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- syncobj.c - Standalone device for syncobj manipulation.
- Copyright (C) 2026 Julian Orth ju.orth@gmail.com
- */
+#include <linux/fdtable.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/uaccess.h> +#include <drm/drm_syncobj.h> +#include <drm/drm_utils.h> +#include <uapi/drm/drm.h> +#include <uapi/linux/syncobj.h>
+static int syncobj_array_find(void __user *user_fds, u32 count,
struct drm_syncobj ***syncobjs_out)+{
u32 i;s32 *fds;struct drm_syncobj **syncobjs;int ret;fds = kmalloc_array(count, sizeof(*fds), GFP_KERNEL);if (!fds)return -ENOMEM;if (copy_from_user(fds, user_fds, sizeof(s32) * count)) {ret = -EFAULT;goto err_free_fds;}syncobjs = kmalloc_array(count, sizeof(*syncobjs), GFP_KERNEL);if (!syncobjs) {ret = -ENOMEM;goto err_free_fds;}for (i = 0; i < count; i++) {syncobjs[i] = drm_syncobj_from_fd(fds[i]);if (!syncobjs[i]) {ret = -EBADF;goto err_put_syncobjs;}}kfree(fds);*syncobjs_out = syncobjs;return 0;+err_put_syncobjs:
while (i-- > 0)drm_syncobj_put(syncobjs[i]);kfree(syncobjs);+err_free_fds:
kfree(fds);return ret;+}
+static void syncobj_array_free(struct drm_syncobj **syncobjs, u32 count) +{
u32 i;for (i = 0; i < count; i++)drm_syncobj_put(syncobjs[i]);kfree(syncobjs);+}
+static int syncobj_ioctl_create(void __user *argp) +{
struct syncobj_create_args args;struct drm_syncobj *syncobj;int fd, ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags & ~SYNCOBJ_CREATE_SIGNALED)return -EINVAL;static_assert(SYNCOBJ_CREATE_SIGNALED == DRM_SYNCOBJ_CREATE_SIGNALED);ret = drm_syncobj_create(&syncobj, args.flags, NULL);if (ret)return ret;ret = drm_syncobj_get_fd(syncobj, &fd);drm_syncobj_put(syncobj);if (ret)return ret;args.fd = fd;if (copy_to_user(argp, &args, sizeof(args))) {close_fd(fd);return -EFAULT;}return 0;+}
+static int syncobj_ioctl_wait(void __user *argp) +{
struct syncobj_wait_args args;struct drm_syncobj **syncobjs;signed long timeout;u32 first = ~0;ktime_t t, *tp = NULL;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags & ~(SYNCOBJ_WAIT_FLAGS_WAIT_ALL |SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT |SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE |SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE))return -EINVAL;static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_ALL == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_ALL);static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT);static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE);if (args.pad)return -EINVAL;if (args.count == 0)return 0;ret = syncobj_array_find(u64_to_user_ptr(args.fds),args.count, &syncobjs);if (ret < 0)return ret;if (args.flags & SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE) {t = ns_to_ktime(args.deadline_nsec);tp = &t;}timeout = drm_timeout_abs_to_jiffies(args.timeout_nsec);timeout = drm_syncobj_array_wait_timeout(syncobjs,u64_to_user_ptr(args.points),args.count,args.flags,timeout, &first, tp);syncobj_array_free(syncobjs, args.count);if (timeout < 0)return timeout;args.first_signaled = first;if (copy_to_user(argp, &args, sizeof(args)))return -EFAULT;return 0;+}
+static int syncobj_ioctl_reset(void __user *argp) +{
struct syncobj_array_args args;struct drm_syncobj **syncobjs;u32 i;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags)return -EINVAL;if (args.points)return -EINVAL;if (args.count == 0)return -EINVAL;ret = syncobj_array_find(u64_to_user_ptr(args.fds),args.count, &syncobjs);if (ret < 0)return ret;for (i = 0; i < args.count; i++)drm_syncobj_replace_fence(syncobjs[i], NULL);syncobj_array_free(syncobjs, args.count);return 0;+}
+static int syncobj_ioctl_signal(void __user *argp) +{
struct syncobj_array_args args;struct drm_syncobj **syncobjs;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags)return -EINVAL;if (args.count == 0)return -EINVAL;ret = syncobj_array_find(u64_to_user_ptr(args.fds),args.count, &syncobjs);if (ret < 0)return ret;ret = drm_syncobj_timeline_signal(syncobjs, args.points, args.count);syncobj_array_free(syncobjs, args.count);return ret;+}
+static int syncobj_ioctl_query(void __user *argp) +{
struct syncobj_array_args args;struct drm_syncobj **syncobjs;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags & ~SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED)return -EINVAL;static_assert(SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED == DRM_SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED);if (args.count == 0)return -EINVAL;ret = syncobj_array_find(u64_to_user_ptr(args.fds),args.count, &syncobjs);if (ret < 0)return ret;ret = drm_syncobj_query(syncobjs, args.points, args.count, args.flags);syncobj_array_free(syncobjs, args.count);return ret;+}
+static int syncobj_ioctl_transfer(void __user *argp) +{
struct syncobj_transfer_args args;struct drm_syncobj *src, *dst;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.pad)return -EINVAL;if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT)return -EINVAL;static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT);src = drm_syncobj_from_fd(args.src_fd);if (!src)return -EBADF;dst = drm_syncobj_from_fd(args.dst_fd);if (!dst) {drm_syncobj_put(src);return -EBADF;}ret = drm_syncobj_transfer(src, args.src_point,dst, args.dst_point, args.flags);drm_syncobj_put(dst);drm_syncobj_put(src);return ret;+}
+static int syncobj_ioctl_eventfd(void __user *argp) +{
struct syncobj_eventfd_args args;struct drm_syncobj *syncobj;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;if (args.flags & ~SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE)return -EINVAL;static_assert(SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE == DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE);if (args.pad)return -EINVAL;syncobj = drm_syncobj_from_fd(args.syncobj_fd);if (!syncobj)return -EBADF;ret = drm_syncobj_register_eventfd(syncobj, args.eventfd,args.point, args.flags);drm_syncobj_put(syncobj);return ret;+}
+static int syncobj_ioctl_export_sync_file(void __user *argp) +{
struct syncobj_sync_file_args args;struct drm_syncobj *syncobj;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;syncobj = drm_syncobj_from_fd(args.syncobj_fd);if (!syncobj)return -EBADF;ret = drm_syncobj_export_sync_file(syncobj, args.point,&args.sync_file_fd);drm_syncobj_put(syncobj);if (ret)return ret;if (copy_to_user(argp, &args, sizeof(args))) {close_fd(args.sync_file_fd);return -EFAULT;}return 0;+}
+static int syncobj_ioctl_import_sync_file(void __user *argp) +{
struct syncobj_sync_file_args args;struct drm_syncobj *syncobj;int ret;if (copy_from_user(&args, argp, sizeof(args)))return -EFAULT;syncobj = drm_syncobj_from_fd(args.syncobj_fd);if (!syncobj)return -EBADF;ret = drm_syncobj_import_sync_file(syncobj, args.sync_file_fd,args.point);drm_syncobj_put(syncobj);return ret;+}
+static long syncobj_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)+{
void __user *argp = (void __user *)arg;switch (cmd) {case SYNCOBJ_IOC_CREATE:return syncobj_ioctl_create(argp);case SYNCOBJ_IOC_WAIT:return syncobj_ioctl_wait(argp);case SYNCOBJ_IOC_RESET:return syncobj_ioctl_reset(argp);case SYNCOBJ_IOC_SIGNAL:return syncobj_ioctl_signal(argp);case SYNCOBJ_IOC_QUERY:return syncobj_ioctl_query(argp);case SYNCOBJ_IOC_TRANSFER:return syncobj_ioctl_transfer(argp);case SYNCOBJ_IOC_EVENTFD:return syncobj_ioctl_eventfd(argp);case SYNCOBJ_IOC_EXPORT_SYNC_FILE:return syncobj_ioctl_export_sync_file(argp);case SYNCOBJ_IOC_IMPORT_SYNC_FILE:return syncobj_ioctl_import_sync_file(argp);default:return -ENOIOCTLCMD;}+}
+static const struct file_operations syncobj_dev_fops = {
.owner = THIS_MODULE,.unlocked_ioctl = syncobj_dev_ioctl,.compat_ioctl = compat_ptr_ioctl,+};
+static struct miscdevice syncobj_misc = {
.minor = MISC_DYNAMIC_MINOR,.name = "syncobj",.fops = &syncobj_dev_fops,.mode = 0666,+};
+module_misc_device(syncobj_misc);
+MODULE_AUTHOR("Julian Orth"); +MODULE_DESCRIPTION("DRM syncobj device"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/syncobj.h b/include/uapi/linux/syncobj.h new file mode 100644 index 000000000000..c4068fbd5773 --- /dev/null +++ b/include/uapi/linux/syncobj.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_SYNCOBJ_H_ +#define _UAPI_LINUX_SYNCOBJ_H_
+#include <linux/ioctl.h> +#include <linux/types.h>
+#define SYNCOBJ_CREATE_SIGNALED (1 << 0)
+#define SYNCOBJ_WAIT_FLAGS_WAIT_ALL (1 << 0) +#define SYNCOBJ_WAIT_FLAGS_WAIT_FOR_SUBMIT (1 << 1) +#define SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE (1 << 2) +#define SYNCOBJ_WAIT_FLAGS_WAIT_DEADLINE (1 << 3)
+#define SYNCOBJ_QUERY_FLAGS_LAST_SUBMITTED (1 << 0)
+struct syncobj_create_args {
__s32 fd;__u32 flags;+};
+struct syncobj_wait_args {
__u64 fds;__u64 points;__s64 timeout_nsec;__u32 count;__u32 flags;__u32 first_signaled;__u32 pad;__u64 deadline_nsec;+};
+struct syncobj_array_args {
__u64 fds;__u64 points;__u32 count;__u32 flags;+};
+struct syncobj_transfer_args {
__s32 src_fd;__s32 dst_fd;__u64 src_point;__u64 dst_point;__u32 flags;__u32 pad;+};
+struct syncobj_eventfd_args {
__s32 syncobj_fd;__s32 eventfd;__u64 point;__u32 flags;__u32 pad;+};
+struct syncobj_sync_file_args {
__s32 syncobj_fd;__s32 sync_file_fd;__u64 point;+};
+#define SYNCOBJ_IOC_BASE 0xCD
+#define SYNCOBJ_IOC_CREATE _IOWR(SYNCOBJ_IOC_BASE, 0, struct syncobj_create_args) +#define SYNCOBJ_IOC_WAIT _IOWR(SYNCOBJ_IOC_BASE, 1, struct syncobj_wait_args) +#define SYNCOBJ_IOC_RESET _IOW(SYNCOBJ_IOC_BASE, 2, struct syncobj_array_args) +#define SYNCOBJ_IOC_SIGNAL _IOW(SYNCOBJ_IOC_BASE, 3, struct syncobj_array_args) +#define SYNCOBJ_IOC_QUERY _IOW(SYNCOBJ_IOC_BASE, 4, struct syncobj_array_args) +#define SYNCOBJ_IOC_TRANSFER _IOW(SYNCOBJ_IOC_BASE, 5, struct syncobj_transfer_args) +#define SYNCOBJ_IOC_EVENTFD _IOW(SYNCOBJ_IOC_BASE, 6, struct syncobj_eventfd_args) +#define SYNCOBJ_IOC_EXPORT_SYNC_FILE _IOWR(SYNCOBJ_IOC_BASE, 7, struct syncobj_sync_file_args) +#define SYNCOBJ_IOC_IMPORT_SYNC_FILE _IOW(SYNCOBJ_IOC_BASE, 8, struct syncobj_sync_file_args)
+#endif /* _UAPI_LINUX_SYNCOBJ_H_ */
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
What could be possible instead is to pass an eventfd into Wayland, but that is something userspace needs to decide.
- llvmpipe currently cannot offer syncobj interop because it does not have access to a DRM device. This means that applications using llvmpipe cannot present images before they have finished rendering, despite llvmpipe using threaded rendering.
Yeah, but that is completely intentional. You *CAN'T* use a dma_fence as completion event for llvmpipe rendering. See the kernel documentation on that.
What could be possible is to use the drm_syncobjs functionality to wait before signal, but that has different semantics.
Regards, Christian.
- Clients that do not use the Vulkan WSI need to manually probe /dev/dri for devices that support the syncobj ioctls in order to use the wayland syncobj protocol.
- Similarly, clients that want to use screen capture have no equivalent to the WSI and are therefore forced into that path.
- Having to keep a DRM device open has potentially negative interactions with GPU hotplug.
- Having to translate between syncobj FDs and handles is troublesome in the compositor usecase since syncobjs come and go frequently and need to be cleaned up when clients disconnect.
/dev/syncobj solves these issues by providing all syncobj ioctls under a consistent path that is not tied to any DRM device. It also operates directly on file descriptors instead of syncobj handles.
The series starts with a number of small refactorings in drm_syncobj.c to make its functionality available outside of the file and without the need for drm_file/handle pairs.
The last commit adds the /dev/syncobj module. I've added it as a misc device but maybe this should instead live somewhere under gpu/drm.
An application using the new interface can be found at [1].
Julian Orth (12): drm/syncobj: add drm_syncobj_from_fd drm/syncobj: add drm_syncobj_fence_lookup drm/syncobj: make drm_syncobj_array_wait_timeout public drm/syncobj: add drm_syncobj_register_eventfd drm/syncobj: have transfer functions accept drm_syncobj directly drm/syncobj: add drm_syncobj_transfer drm/syncobj: add drm_syncobj_timeline_signal drm/syncobj: add drm_syncobj_query drm/syncobj: fix resource leak in drm_syncobj_import_sync_file_fence drm/syncobj: add drm_syncobj_import_sync_file drm/syncobj: add drm_syncobj_export_sync_file misc/syncobj: add new device
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/gpu/drm/drm_syncobj.c | 374 ++++++++++++++----- drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/drm/drm_syncobj.h | 21 ++ include/uapi/linux/syncobj.h | 75 ++++ 7 files changed, 795 insertions(+), 91 deletions(-)
base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6 change-id: 20260516-jorth-syncobj-d4d374c8c61b
Best regards,
Julian Orth ju.orth@gmail.com
On Mon, May 18, 2026 at 1:58 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
Not necessarily. The DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL ioctl attaches some kind of dummy fence that is already signaled. I don't believe this is device specific. That is also the path that llvmpipe would use.
What could be possible instead is to pass an eventfd into Wayland, but that is something userspace needs to decide.
- llvmpipe currently cannot offer syncobj interop because it does not have access to a DRM device. This means that applications using llvmpipe cannot present images before they have finished rendering, despite llvmpipe using threaded rendering.
Yeah, but that is completely intentional. You *CAN'T* use a dma_fence as completion event for llvmpipe rendering. See the kernel documentation on that.
What could be possible is to use the drm_syncobjs functionality to wait before signal, but that has different semantics.
Regards, Christian.
- Clients that do not use the Vulkan WSI need to manually probe /dev/dri for devices that support the syncobj ioctls in order to use the wayland syncobj protocol.
- Similarly, clients that want to use screen capture have no equivalent to the WSI and are therefore forced into that path.
- Having to keep a DRM device open has potentially negative interactions with GPU hotplug.
- Having to translate between syncobj FDs and handles is troublesome in the compositor usecase since syncobjs come and go frequently and need to be cleaned up when clients disconnect.
/dev/syncobj solves these issues by providing all syncobj ioctls under a consistent path that is not tied to any DRM device. It also operates directly on file descriptors instead of syncobj handles.
The series starts with a number of small refactorings in drm_syncobj.c to make its functionality available outside of the file and without the need for drm_file/handle pairs.
The last commit adds the /dev/syncobj module. I've added it as a misc device but maybe this should instead live somewhere under gpu/drm.
An application using the new interface can be found at [1].
Julian Orth (12): drm/syncobj: add drm_syncobj_from_fd drm/syncobj: add drm_syncobj_fence_lookup drm/syncobj: make drm_syncobj_array_wait_timeout public drm/syncobj: add drm_syncobj_register_eventfd drm/syncobj: have transfer functions accept drm_syncobj directly drm/syncobj: add drm_syncobj_transfer drm/syncobj: add drm_syncobj_timeline_signal drm/syncobj: add drm_syncobj_query drm/syncobj: fix resource leak in drm_syncobj_import_sync_file_fence drm/syncobj: add drm_syncobj_import_sync_file drm/syncobj: add drm_syncobj_export_sync_file misc/syncobj: add new device
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/gpu/drm/drm_syncobj.c | 374 ++++++++++++++----- drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/drm/drm_syncobj.h | 21 ++ include/uapi/linux/syncobj.h | 75 ++++ 7 files changed, 795 insertions(+), 91 deletions(-)
base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6 change-id: 20260516-jorth-syncobj-d4d374c8c61b
Best regards,
Julian Orth ju.orth@gmail.com
On 5/18/26 14:02, Julian Orth wrote:
On Mon, May 18, 2026 at 1:58 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
Not necessarily. The DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL ioctl attaches some kind of dummy fence that is already signaled. I don't believe this is device specific. That is also the path that llvmpipe would use.
Yeah I feared that.
This is the wait before signal path and if I'm not completely mistaken that one is not supported by a lot of compositors.
The last time I looked for GPU support the compositor needs to spawn a separate thread for each client to support this approach.
It could be that we have eventfd integration for that as well now, but in that case you could give the compositor an eventfd instead of a drm_syncobj fd in the first place.
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
Regards, Christian.
What could be possible instead is to pass an eventfd into Wayland, but that is something userspace needs to decide.
- llvmpipe currently cannot offer syncobj interop because it does not have access to a DRM device. This means that applications using llvmpipe cannot present images before they have finished rendering, despite llvmpipe using threaded rendering.
Yeah, but that is completely intentional. You *CAN'T* use a dma_fence as completion event for llvmpipe rendering. See the kernel documentation on that.
What could be possible is to use the drm_syncobjs functionality to wait before signal, but that has different semantics.
Regards, Christian.
- Clients that do not use the Vulkan WSI need to manually probe /dev/dri for devices that support the syncobj ioctls in order to use the wayland syncobj protocol.
- Similarly, clients that want to use screen capture have no equivalent to the WSI and are therefore forced into that path.
- Having to keep a DRM device open has potentially negative interactions with GPU hotplug.
- Having to translate between syncobj FDs and handles is troublesome in the compositor usecase since syncobjs come and go frequently and need to be cleaned up when clients disconnect.
/dev/syncobj solves these issues by providing all syncobj ioctls under a consistent path that is not tied to any DRM device. It also operates directly on file descriptors instead of syncobj handles.
The series starts with a number of small refactorings in drm_syncobj.c to make its functionality available outside of the file and without the need for drm_file/handle pairs.
The last commit adds the /dev/syncobj module. I've added it as a misc device but maybe this should instead live somewhere under gpu/drm.
An application using the new interface can be found at [1].
Julian Orth (12): drm/syncobj: add drm_syncobj_from_fd drm/syncobj: add drm_syncobj_fence_lookup drm/syncobj: make drm_syncobj_array_wait_timeout public drm/syncobj: add drm_syncobj_register_eventfd drm/syncobj: have transfer functions accept drm_syncobj directly drm/syncobj: add drm_syncobj_transfer drm/syncobj: add drm_syncobj_timeline_signal drm/syncobj: add drm_syncobj_query drm/syncobj: fix resource leak in drm_syncobj_import_sync_file_fence drm/syncobj: add drm_syncobj_import_sync_file drm/syncobj: add drm_syncobj_export_sync_file misc/syncobj: add new device
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/gpu/drm/drm_syncobj.c | 374 ++++++++++++++----- drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/drm/drm_syncobj.h | 21 ++ include/uapi/linux/syncobj.h | 75 ++++ 7 files changed, 795 insertions(+), 91 deletions(-)
base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6 change-id: 20260516-jorth-syncobj-d4d374c8c61b
Best regards,
Julian Orth ju.orth@gmail.com
On Mon, May 18, 2026 at 2:41 PM Christian König christian.koenig@amd.com wrote:
On 5/18/26 14:02, Julian Orth wrote:
On Mon, May 18, 2026 at 1:58 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
Not necessarily. The DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL ioctl attaches some kind of dummy fence that is already signaled. I don't believe this is device specific. That is also the path that llvmpipe would use.
Yeah I feared that.
This is the wait before signal path and if I'm not completely mistaken that one is not supported by a lot of compositors.
I believe this is supported by all compositors.
The last time I looked for GPU support the compositor needs to spawn a separate thread for each client to support this approach.
It could be that we have eventfd integration for that as well now, but in that case you could give the compositor an eventfd instead of a drm_syncobj fd in the first place.
Yes, all compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to wait async for the timeline point to materialize and/or be signaled. The wayland protocol was the motivation for that ioctl.
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
Using eventfd has some disadvantages:
- We've just added syncobj support to vulkan: https://github.com/KhronosGroup/Vulkan-Docs/issues/2473#issuecomment-4446117.... For eventfd we would not only have to add yet another extension, that would realistically only be exposed by llvmpipe, but also every compositor and every client would have to support both extensions. - Similarly, a new wayland protocol would need to be designed to support sync over eventfd. - Eventfd does not support timeline semantics. Meaning that you would have to send two eventfds over the wire for each commit, one for the acquire point and one for the release point. Whereas with syncobj you only need to send two integers per commit.
I don't see the advantage when drm_syncobj already does everything we need.
You seem to believe that compositors would not be ready for this and from that perspective I can understand your apprehension. But I can assure you that compositors are already fully set up to support all of the usecases I've described: The wayland protocol requires the compositor to support wait before signal.
Regards, Christian.
What could be possible instead is to pass an eventfd into Wayland, but that is something userspace needs to decide.
- llvmpipe currently cannot offer syncobj interop because it does not have access to a DRM device. This means that applications using llvmpipe cannot present images before they have finished rendering, despite llvmpipe using threaded rendering.
Yeah, but that is completely intentional. You *CAN'T* use a dma_fence as completion event for llvmpipe rendering. See the kernel documentation on that.
What could be possible is to use the drm_syncobjs functionality to wait before signal, but that has different semantics.
Regards, Christian.
- Clients that do not use the Vulkan WSI need to manually probe /dev/dri for devices that support the syncobj ioctls in order to use the wayland syncobj protocol.
- Similarly, clients that want to use screen capture have no equivalent to the WSI and are therefore forced into that path.
- Having to keep a DRM device open has potentially negative interactions with GPU hotplug.
- Having to translate between syncobj FDs and handles is troublesome in the compositor usecase since syncobjs come and go frequently and need to be cleaned up when clients disconnect.
/dev/syncobj solves these issues by providing all syncobj ioctls under a consistent path that is not tied to any DRM device. It also operates directly on file descriptors instead of syncobj handles.
The series starts with a number of small refactorings in drm_syncobj.c to make its functionality available outside of the file and without the need for drm_file/handle pairs.
The last commit adds the /dev/syncobj module. I've added it as a misc device but maybe this should instead live somewhere under gpu/drm.
An application using the new interface can be found at [1].
Julian Orth (12): drm/syncobj: add drm_syncobj_from_fd drm/syncobj: add drm_syncobj_fence_lookup drm/syncobj: make drm_syncobj_array_wait_timeout public drm/syncobj: add drm_syncobj_register_eventfd drm/syncobj: have transfer functions accept drm_syncobj directly drm/syncobj: add drm_syncobj_transfer drm/syncobj: add drm_syncobj_timeline_signal drm/syncobj: add drm_syncobj_query drm/syncobj: fix resource leak in drm_syncobj_import_sync_file_fence drm/syncobj: add drm_syncobj_import_sync_file drm/syncobj: add drm_syncobj_export_sync_file misc/syncobj: add new device
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 + drivers/gpu/drm/drm_syncobj.c | 374 ++++++++++++++----- drivers/misc/Kconfig | 10 + drivers/misc/Makefile | 1 + drivers/misc/syncobj.c | 404 +++++++++++++++++++++ include/drm/drm_syncobj.h | 21 ++ include/uapi/linux/syncobj.h | 75 ++++ 7 files changed, 795 insertions(+), 91 deletions(-)
base-commit: 6916d5703ddf9a38f1f6c2cc793381a24ee914c6 change-id: 20260516-jorth-syncobj-d4d374c8c61b
Best regards,
Julian Orth ju.orth@gmail.com
On 5/18/26 14:58, Julian Orth wrote:
On Mon, May 18, 2026 at 2:41 PM Christian König christian.koenig@amd.com wrote:
...
It could be that we have eventfd integration for that as well now, but in that case you could give the compositor an eventfd instead of a drm_syncobj fd in the first place.
Yes, all compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to wait async for the timeline point to materialize and/or be signaled. The wayland protocol was the motivation for that ioctl.
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
Using eventfd has some disadvantages:
- We've just added syncobj support to vulkan:
https://github.com/KhronosGroup/Vulkan-Docs/issues/2473#issuecomment-4446117.... For eventfd we would not only have to add yet another extension, that would realistically only be exposed by llvmpipe, but also every compositor and every client would have to support both extensions.
- Similarly, a new wayland protocol would need to be designed to
support sync over eventfd.
- Eventfd does not support timeline semantics. Meaning that you would
have to send two eventfds over the wire for each commit, one for the acquire point and one for the release point. Whereas with syncobj you only need to send two integers per commit.
I don't see the advantage when drm_syncobj already does everything we need.
You seem to believe that compositors would not be ready for this and from that perspective I can understand your apprehension. But I can assure you that compositors are already fully set up to support all of the usecases I've described: The wayland protocol requires the compositor to support wait before signal.
Yeah that's much better than I thought it would be.
And that eventfds don't support timeline points is indeed a pretty good argument.
But I still don't see much justification for creating a /dev/syncobj device, this is clearly something DRM specific.
What about using VGEM for this?
Regards, Christian.
Regards, Christian.
On Tue, May 19, 2026 at 10:18 AM Christian König christian.koenig@amd.com wrote:
On 5/18/26 14:58, Julian Orth wrote:
On Mon, May 18, 2026 at 2:41 PM Christian König christian.koenig@amd.com wrote:
...
It could be that we have eventfd integration for that as well now, but in that case you could give the compositor an eventfd instead of a drm_syncobj fd in the first place.
Yes, all compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to wait async for the timeline point to materialize and/or be signaled. The wayland protocol was the motivation for that ioctl.
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
Using eventfd has some disadvantages:
- We've just added syncobj support to vulkan:
https://github.com/KhronosGroup/Vulkan-Docs/issues/2473#issuecomment-4446117.... For eventfd we would not only have to add yet another extension, that would realistically only be exposed by llvmpipe, but also every compositor and every client would have to support both extensions.
- Similarly, a new wayland protocol would need to be designed to
support sync over eventfd.
- Eventfd does not support timeline semantics. Meaning that you would
have to send two eventfds over the wire for each commit, one for the acquire point and one for the release point. Whereas with syncobj you only need to send two integers per commit.
I don't see the advantage when drm_syncobj already does everything we need.
You seem to believe that compositors would not be ready for this and from that perspective I can understand your apprehension. But I can assure you that compositors are already fully set up to support all of the usecases I've described: The wayland protocol requires the compositor to support wait before signal.
Yeah that's much better than I thought it would be.
And that eventfds don't support timeline points is indeed a pretty good argument.
But I still don't see much justification for creating a /dev/syncobj device, this is clearly something DRM specific.
The justification is given in the cover letter. To repeat them briefly:
1. This series makes the ability to manipulate syncobjs available independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj. 3. It removes the need to translate between syncobjs fds and handles.
What about using VGEM for this?
If the vgem render node were made available unconditionally under, say, /dev/vgem and DRIVER_SYNCOBJ_TIMELINE were added to the driver, then maybe that could solve points 1 and 2 above.
But it would not solve point 3 and it sounds like a hack to me to have a render node available outside of /dev/dri.
Regards, Christian.
Regards, Christian.
On 5/19/26 15:19, Julian Orth wrote:
On Tue, May 19, 2026 at 10:18 AM Christian König christian.koenig@amd.com wrote:
On 5/18/26 14:58, Julian Orth wrote:
On Mon, May 18, 2026 at 2:41 PM Christian König christian.koenig@amd.com wrote:
...
It could be that we have eventfd integration for that as well now, but in that case you could give the compositor an eventfd instead of a drm_syncobj fd in the first place.
Yes, all compositors use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to wait async for the timeline point to materialize and/or be signaled. The wayland protocol was the motivation for that ioctl.
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
Using eventfd has some disadvantages:
- We've just added syncobj support to vulkan:
https://github.com/KhronosGroup/Vulkan-Docs/issues/2473#issuecomment-4446117.... For eventfd we would not only have to add yet another extension, that would realistically only be exposed by llvmpipe, but also every compositor and every client would have to support both extensions.
- Similarly, a new wayland protocol would need to be designed to
support sync over eventfd.
- Eventfd does not support timeline semantics. Meaning that you would
have to send two eventfds over the wire for each commit, one for the acquire point and one for the release point. Whereas with syncobj you only need to send two integers per commit.
I don't see the advantage when drm_syncobj already does everything we need.
You seem to believe that compositors would not be ready for this and from that perspective I can understand your apprehension. But I can assure you that compositors are already fully set up to support all of the usecases I've described: The wayland protocol requires the compositor to support wait before signal.
Yeah that's much better than I thought it would be.
And that eventfds don't support timeline points is indeed a pretty good argument.
But I still don't see much justification for creating a /dev/syncobj device, this is clearly something DRM specific.
The justification is given in the cover letter. To repeat them briefly:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
What about using VGEM for this?
If the vgem render node were made available unconditionally under,
Software rendering is a complete corner case, I don't think that this will be enabled by default.
Regards, Christian.
say, /dev/vgem and DRIVER_SYNCOBJ_TIMELINE were added to the driver, then maybe that could solve points 1 and 2 above.
But it would not solve point 3 and it sounds like a hack to me to have a render node available outside of /dev/dri.
Regards, Christian.
Regards, Christian.
Am Di., 19. Mai 2026 um 15:29 Uhr schrieb Christian König christian.koenig@amd.com:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
FWIW udmabuf is also under /dev directly, but I don't think any compositor developer would complain about a different path. What are the rules for that? Could this simply be put in /dev/dri/syncobj?
The part where we get this independent of attached hardware is quite important for us though, since we can't just ignore explicit sync once the device we previously imported the syncobj into is disconnected. Buffers can be from any device or allocated in system memory and access should be synchronized properly in all cases.
How exactly it's made available isn't all that critical.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
FTR for me at least, this part would be merely nice to have, since it slightly reduces the amount of ioctls a compositor needs to call, but it's not important.
What about using VGEM for this?
If the vgem render node were made available unconditionally under,
Software rendering is a complete corner case, I don't think that this will be enabled by default.
That simply makes vgem unsuitable for solving the problems we face in compositors.
- Xaver
On 5/19/26 17:31, Xaver Hugl wrote:
Am Di., 19. Mai 2026 um 15:29 Uhr schrieb Christian König christian.koenig@amd.com:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
FWIW udmabuf is also under /dev directly, but I don't think any compositor developer would complain about a different path. What are the rules for that? Could this simply be put in /dev/dri/syncobj?
The syncobj are actually the DRM specific way of doing things. The general kernel wide way is to use sync files (see drivers/dma-buf/sync_file.c).
But there has already been tons of problems with those sync files. E.g. they doesn't support your use case at all since they don't have wait before submit behavior.
So there are already ways to do this, but the Linux kernel so far told everybody that this is forbidden. The DRM syncobj wait before signal functionality is much better, but then basically the second try to do this.
The part where we get this independent of attached hardware is quite important for us though, since we can't just ignore explicit sync once the device we previously imported the syncobj into is disconnected.
Can you elaborate more on this?
Buffers can be from any device or allocated in system memory and access should be synchronized properly in all cases.
How exactly it's made available isn't all that critical.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
Well what we could do is to add an IOCTL to directly attach an syncobj file descriptor to an eventfd.
FTR for me at least, this part would be merely nice to have, since it slightly reduces the amount of ioctls a compositor needs to call, but it's not important.
What about using VGEM for this?
If the vgem render node were made available unconditionally under,
Software rendering is a complete corner case, I don't think that this will be enabled by default.
That simply makes vgem unsuitable for solving the problems we face in compositors.
Thinking more about it vgem also has the same issues as sync file mentioned above. So that is really also not doable.
Maybe Simona or David have another idea.
Regards, Christian.
- Xaver
The part where we get this independent of attached hardware is quite important for us though, since we can't just ignore explicit sync once the device we previously imported the syncobj into is disconnected.
Can you elaborate more on this?
In Wayland, the client is allowed to attach dmabuf and syncobj independently, they don't have to be from the same device (and the compositor wouldn't be able to verify the opposite anyways). The compositor will usually import both into the same drm device, but especially with compositors that render on multiple devices, that's not necessarily the case either.
If for example we had a system with one internal GPU and one external GPU, the client renders on the internal GPU and the compositor uses the external one. Now when the user yanks the USB C cable, afaiu - the buffers from the client stay valid - the syncobj stays valid on the client side - the syncobj becomes invalid on the compositor side
"invalid" there means either - the acquire point of the client is marked as signaled, before rendering on the client side is completed - the acquire point of the client is never signaled. Since the compositor waits for the acquire point, the Wayland surface is stuck forever
Afaik the latter is currently the case. The former wouldn't be much better though, not when it's preventable.
This is admittedly an edge case, but GPU hotunplug is something we try to support as well as possible in Plasma, and all the edge cases cause a lot of problems in combination and are a lot of headaches to handle (or really work around) in the compositor. Another edge case is when the client asks the compositor to import the syncobj, which can fail when a hotunplug is in process, and ends up disconnecting the client for no fault of either client or compositor.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
Well what we could do is to add an IOCTL to directly attach an syncobj file descriptor to an eventfd.
That would be nice.
- Xaver
On 5/19/26 19:08, Xaver Hugl wrote:
The part where we get this independent of attached hardware is quite important for us though, since we can't just ignore explicit sync once the device we previously imported the syncobj into is disconnected.
Can you elaborate more on this?
In Wayland, the client is allowed to attach dmabuf and syncobj independently, they don't have to be from the same device (and the compositor wouldn't be able to verify the opposite anyways). The compositor will usually import both into the same drm device, but especially with compositors that render on multiple devices, that's not necessarily the case either.
If for example we had a system with one internal GPU and one external GPU, the client renders on the internal GPU and the compositor uses the external one. Now when the user yanks the USB C cable, afaiu
Well I would say the other way around is a pretty common use case.
In other words the compositors uses the internal GPU for composing and displaying the picture. And the client uses the external GPU for fast rendering.
- the buffers from the client stay valid
Buffers from the hot plugged GPU don't stay valid. Accessing CPU mappings either result in a SIGBUS or are redirected to a dummy page.
DMA operations to hot plugged buffers from other GPUs (or rather more general other devices) are waited on before the underlying resource is removed (e.g. system memory or PCIe address space or whatever is backing that).
But no new DMA operations are usually permitted to start.
- the syncobj stays valid on the client side
- the syncobj becomes invalid on the compositor side
Nope that's not correct. The syncobj itself stays valid even if you completely hot plug the device.
It can just be that the fences inside the syncobj are terminated with an error.
"invalid" there means either
- the acquire point of the client is marked as signaled, before
rendering on the client side is completed
- the acquire point of the client is never signaled. Since the
compositor waits for the acquire point, the Wayland surface is stuck forever
Both of those would be a *massive* violation of documented kernel rules for hot-plugging which could lead to random data corruption and/or deadlocks.
If you see any HW driver showing behavior like that please open up a bug report and ping the relevant maintainers immediately.
When a hotplug happens all operations of the device should return an -ENODEV error, even when exposed to other devices/application through syncobj or syncfile.
One problem is that only syncfile allows for querying such error codes at the moment, we have patches pending to add that to syncobj as well but we lack a compositor with support for that as userspace client.
Afaik the latter is currently the case. The former wouldn't be much better though, not when it's preventable.
This is admittedly an edge case, but GPU hotunplug is something we try to support as well as possible in Plasma, and all the edge cases cause a lot of problems in combination and are a lot of headaches to handle (or really work around) in the compositor.
Well exactly that design is used in the Tesla 3 infotainment system for example.
So GPU hotplug is actually a pretty common use case.
Another edge case is when the client asks the compositor to import the syncobj, which can fail when a hotunplug is in process, and ends up disconnecting the client for no fault of either client or compositor.
Well the question here is if the device the compositor is using or the client is using is gone?
If the client device is hot removed the compositor should be perfectly capable to import the syncobj.
If the compositor device is gone then you don't have a device to display anything any more, so generating the next frame doesn't seem to make sense either.
What could be is that you want the compositor to be kept alive even when the display device is gone to switch over to vkms or whatever so that a VNC session or other remote desktop still works.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
Well what we could do is to add an IOCTL to directly attach an syncobj file descriptor to an eventfd.
That would be nice.
Take a look at drm_syncobj_file_fops and how drm_syncobj_add_eventfd() is used. Adding that functionality shouldn't be more than a typing exercise.
Do I see it right that this would already solve most problems in the compositor side?
Regards, Christian.
- Xaver
Am Mi., 20. Mai 2026 um 10:08 Uhr schrieb Christian König christian.koenig@amd.com:
Well I would say the other way around is a pretty common use case.
In other words the compositors uses the internal GPU for composing and displaying the picture. And the client uses the external GPU for fast rendering.
Sure, but that's not what I'm talking about.
- the buffers from the client stay valid
Buffers from the hot plugged GPU don't stay valid. Accessing CPU mappings either result in a SIGBUS or are redirected to a dummy page.
Again, not what I wrote about. The buffers are on the integrated GPU.
- the syncobj stays valid on the client side
- the syncobj becomes invalid on the compositor side
Nope that's not correct. The syncobj itself stays valid even if you completely hot plug the device.
It can just be that the fences inside the syncobj are terminated with an error.
What about eventfd created for a point on the syncobj?
Another (future) problem with hotplugs will be if the sync file hasn't materialized for the timeline point when the device is hotunplugged, since there can't be an error on the fence if there isn't one. Or could userspace somehow set an 'artificial' fence with an error in that case?
"invalid" there means either
- the acquire point of the client is marked as signaled, before
rendering on the client side is completed
- the acquire point of the client is never signaled. Since the
compositor waits for the acquire point, the Wayland surface is stuck forever
Both of those would be a *massive* violation of documented kernel rules for hot-plugging which could lead to random data corruption and/or deadlocks.
If you see any HW driver showing behavior like that please open up a bug report and ping the relevant maintainers immediately.
If there are no error codes with syncobj yet, then to userspace, the latter behavior is exactly what we get, isn't it?
When a hotplug happens all operations of the device should return an -ENODEV error, even when exposed to other devices/application through syncobj or syncfile.
Okay, that at least gives us a way to fail imports somewhat gracefully. Normally, failing to import a syncobj is a fatal error in the Wayland protocol.
One problem is that only syncfile allows for querying such error codes at the moment, we have patches pending to add that to syncobj as well but we lack a compositor with support for that as userspace client.
As long as the error case can be detected with an eventfd, implementing that in KWin shouldn't be a challenge.
Well the question here is if the device the compositor is using or the client is using is gone?
If the client device is hot removed the compositor should be perfectly capable to import the syncobj.
If the compositor device is gone then you don't have a device to display anything any more, so generating the next frame doesn't seem to make sense either.
What could be is that you want the compositor to be kept alive even when the display device is gone to switch over to vkms or whatever so that a VNC session or other remote desktop still works.
There are two GPUs in the example I gave. The compositor can use both for rendering (in cosmic-comp's case) or switch between them (what I'm trying to do with KWin), or use one device for rendering, and another for importing the syncobj.
- It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
Well what we could do is to add an IOCTL to directly attach an syncobj file descriptor to an eventfd.
That would be nice.
Take a look at drm_syncobj_file_fops and how drm_syncobj_add_eventfd() is used. Adding that functionality shouldn't be more than a typing exercise.
Yeah, this patchset already adds that functionality (on the new device).
Do I see it right that this would already solve most problems in the compositor side?
Skipping the syncobj handle step would only reduce the amounts of ioctls the compositor does, but afaict it wouldn't solve any compositor problems. At least not as long as it's still tied to a drm device. For device hotplugs, the only new thing we need for correctly handling syncobj is a way to receive errors on the eventfd.
A device-independent way to create and use syncobj would still be useful to us though, both to simplify the compositor and to improve the software rendering use cases.
- Xaver
On 5/20/26 14:33, Xaver Hugl wrote:
Am Mi., 20. Mai 2026 um 10:08 Uhr schrieb Christian König christian.koenig@amd.com:
Well I would say the other way around is a pretty common use case.
In other words the compositors uses the internal GPU for composing and displaying the picture. And the client uses the external GPU for fast rendering.
Sure, but that's not what I'm talking about.
Yeah sorry for that, I wasn't sure if I misunderstood your use case because it's usually the other way around.
- the buffers from the client stay valid
Buffers from the hot plugged GPU don't stay valid. Accessing CPU mappings either result in a SIGBUS or are redirected to a dummy page.
Again, not what I wrote about. The buffers are on the integrated GPU.
General rule of thumb is that as long as the exporter stays around the buffers stay around as well.
- the syncobj stays valid on the client side
- the syncobj becomes invalid on the compositor side
Nope that's not correct. The syncobj itself stays valid even if you completely hot plug the device.
It can just be that the fences inside the syncobj are terminated with an error.
What about eventfd created for a point on the syncobj?
The eventfd unfortunately doesn't has error handling as far as I know, so when a fence signals with an error condition then the eventfd you only sees that it is signaled.
Another (future) problem with hotplugs will be if the sync file hasn't materialized for the timeline point when the device is hotunplugged, since there can't be an error on the fence if there isn't one. Or could userspace somehow set an 'artificial' fence with an error in that case?
In general the answer is yes, userspace needs to take care of inserting fences when wait before signal is used and the work can not be submitted to the HW for some reason.
Currently we only have an IOCTL to insert the signaled dummy fence at some timeline sequence, but it should be trivial as well to insert a signaled fence with an error code.
But the compositor needs to be able to handle that case anyway, because it can be that a malicious or just buggy client just never inserts the fence.
So that a device is hot plugged is not different to just a client not inserting the fence in the first place.
"invalid" there means either
- the acquire point of the client is marked as signaled, before
rendering on the client side is completed
- the acquire point of the client is never signaled. Since the
compositor waits for the acquire point, the Wayland surface is stuck forever
Both of those would be a *massive* violation of documented kernel rules for hot-plugging which could lead to random data corruption and/or deadlocks.
If you see any HW driver showing behavior like that please open up a bug report and ping the relevant maintainers immediately.
If there are no error codes with syncobj yet, then to userspace, the latter behavior is exactly what we get, isn't it?
No, from userspace side you just see a signaled fence. It's just that you need to export the timeline point of the syncobj to a syncfile and then you can call the QUERY IOCTL on the syncfile to see the error code.
When a hotplug happens all operations of the device should return an -ENODEV error, even when exposed to other devices/application through syncobj or syncfile.
Okay, that at least gives us a way to fail imports somewhat gracefully. Normally, failing to import a syncobj is a fatal error in the Wayland protocol.
So the task at hand would be to avoid importing the syncobj into a driver. That should be relatively trivial.
The only real problem I see is if you want to create a syncobj without having any device whatsoever.
One problem is that only syncfile allows for querying such error codes at the moment, we have patches pending to add that to syncobj as well but we lack a compositor with support for that as userspace client.
As long as the error case can be detected with an eventfd,
Yeah that's the problem. The eventfd only tells you if the operation is completed (or at least has materialized).
To query the error you would need to ask the underlying syncobj or syncfile directly.
implementing that in KWin shouldn't be a challenge.
Well the question here is if the device the compositor is using or the client is using is gone?
If the client device is hot removed the compositor should be perfectly capable to import the syncobj.
If the compositor device is gone then you don't have a device to display anything any more, so generating the next frame doesn't seem to make sense either.
What could be is that you want the compositor to be kept alive even when the display device is gone to switch over to vkms or whatever so that a VNC session or other remote desktop still works.
There are two GPUs in the example I gave. The compositor can use both for rendering (in cosmic-comp's case) or switch between them (what I'm trying to do with KWin), or use one device for rendering, and another for importing the syncobj.
Ah! I think I got the problem now. You basically want to avoid importing the syncobj because when the wrong device goes away you are busted.
The reason we didn't considered having the IOCTLs on the FD is because if you don't import them and instead keep them around you can run out file descriptors quite quickly.
When you have an use case where you receive an FD from the client and do a one shot conversion to an eventfd that will probably work, but for keeping them in the long run you need some kind of container for the syncobjs, don't you?
> 3. It removes the need to translate between syncobjs fds and handles.
That's a pretty big no-go as well. The differentiation between FDs and handles is completely intentional.
Could you expand on why it's needed? For compositors, the handle is just an intermediary thing when translating between file descriptors.
Well what we could do is to add an IOCTL to directly attach an syncobj file descriptor to an eventfd.
That would be nice.
Take a look at drm_syncobj_file_fops and how drm_syncobj_add_eventfd() is used. Adding that functionality shouldn't be more than a typing exercise.
Yeah, this patchset already adds that functionality (on the new device).
Do I see it right that this would already solve most problems in the compositor side?
Skipping the syncobj handle step would only reduce the amounts of ioctls the compositor does, but afaict it wouldn't solve any compositor problems. At least not as long as it's still tied to a drm device.
Yeah, you need something like a syncobj container or dummy DRM device.
For device hotplugs, the only new thing we need for correctly handling syncobj is a way to receive errors on the eventfd.
I need to look into the eventfd code, could be that this is somehow possible but it's clearly not something I used before.
A device-independent way to create and use syncobj would still be useful to us though, both to simplify the compositor and to improve the software rendering use cases.
Yeah not sure how to cleanly do that. We could have a dummy /dev/dri/rendersync or something like that, but that would be quite a hack.
At least I understand the requirement now.
Thanks, Christian.
- Xaver
In general the answer is yes, userspace needs to take care of inserting fences when wait before signal is used and the work can not be submitted to the HW for some reason.
Currently we only have an IOCTL to insert the signaled dummy fence at some timeline sequence, but it should be trivial as well to insert a signaled fence with an error code.
But the compositor needs to be able to handle that case anyway, because it can be that a malicious or just buggy client just never inserts the fence.
So that a device is hot plugged is not different to just a client not inserting the fence in the first place.
A buggy client can always freeze its own surface, it doesn't need handling beyond cleaning up properly when the client disconnects. The hotplug case is different, since currently a well-behaved client can only attempt to signal the point in the syncobj... but the drm device is gone, so the ioctl will fail and the client's surface is frozen, even though it did everything right.
So afaict, whatever new ioctl is added for this will need to be independent of the drm device, or be special cased not to fail when the device is removed.
One problem is that only syncfile allows for querying such error codes at the moment, we have patches pending to add that to syncobj as well but we lack a compositor with support for that as userspace client.
As long as the error case can be detected with an eventfd,
Yeah that's the problem. The eventfd only tells you if the operation is completed (or at least has materialized).
To query the error you would need to ask the underlying syncobj or syncfile directly.
Issuing an additional ioctl after the eventfd fired for this rare case wouldn't be particularly nice, but also not difficult. If we'd get that with the eventfd directly, that would be much better though.
Ah! I think I got the problem now. You basically want to avoid importing the syncobj because when the wrong device goes away you are busted.
Exactly.
The reason we didn't considered having the IOCTLs on the FD is because if you don't import them and instead keep them around you can run out file descriptors quite quickly.
When you have an use case where you receive an FD from the client and do a one shot conversion to an eventfd that will probably work, but for keeping them in the long run you need some kind of container for the syncobjs, don't you?
Compositors always run with vastly increased fd limits since they have to handle a lot of fds for dmabufs alone, so keeping the fd around wouldn't be an issue for us.
A device-independent way to create and use syncobj would still be useful to us though, both to simplify the compositor and to improve the software rendering use cases.
Yeah not sure how to cleanly do that. We could have a dummy /dev/dri/rendersync or something like that, but that would be quite a hack.
I think for userspace it would be less of a hack than searching for a random drm node that can import it. I'd gladly take another solution as well though, if there is one.
- Xaver
On 5/19/26 18:00, Christian König wrote:
On 5/19/26 17:31, Xaver Hugl wrote:
Am Di., 19. Mai 2026 um 15:29 Uhr schrieb Christian König christian.koenig@amd.com:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
FWIW udmabuf is also under /dev directly, but I don't think any compositor developer would complain about a different path. What are the rules for that? Could this simply be put in /dev/dri/syncobj?
The syncobj are actually the DRM specific way of doing things. The general kernel wide way is to use sync files (see drivers/dma-buf/sync_file.c).
But there has already been tons of problems with those sync files. E.g. they doesn't support your use case at all since they don't have wait before submit behavior.
So there are already ways to do this, but the Linux kernel so far told everybody that this is forbidden. The DRM syncobj wait before signal functionality is much better, but then basically the second try to do this.
I'm not quite sure what you're getting at here, just to be clear though:
While the syncobj Wayland protocol extension supports wait-before-submit behaviour at the Wayland protocol level, it doesn't need or cause wait-before-submit behaviour for DMA fences in the kernel. The usual rules apply to fences attached to syncobj timeline points. The wait-before-submit behaviour at the Wayland protocol level comes from allowing submit before a fence is attached to the acquire timeline point.
(It took me a while to realize this distinction, before which I mistakenly thought the kernel's DMA fence rules would prohibit wait-before-submit behaviour at the Wayland protocol level as well)
On 5/20/26 10:13, Michel Dänzer wrote:
On 5/19/26 18:00, Christian König wrote:
On 5/19/26 17:31, Xaver Hugl wrote:
Am Di., 19. Mai 2026 um 15:29 Uhr schrieb Christian König christian.koenig@amd.com:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
FWIW udmabuf is also under /dev directly, but I don't think any compositor developer would complain about a different path. What are the rules for that? Could this simply be put in /dev/dri/syncobj?
The syncobj are actually the DRM specific way of doing things. The general kernel wide way is to use sync files (see drivers/dma-buf/sync_file.c).
But there has already been tons of problems with those sync files. E.g. they doesn't support your use case at all since they don't have wait before submit behavior.
So there are already ways to do this, but the Linux kernel so far told everybody that this is forbidden. The DRM syncobj wait before signal functionality is much better, but then basically the second try to do this.
I'm not quite sure what you're getting at here, just to be clear though:
While the syncobj Wayland protocol extension supports wait-before-submit behaviour at the Wayland protocol level, it doesn't need or cause wait-before-submit behaviour for DMA fences in the kernel. The usual rules apply to fences attached to syncobj timeline points. The wait-before-submit behaviour at the Wayland protocol level comes from allowing submit before a fence is attached to the acquire timeline point.
Yeah I know. I'm one of the people who came up with the idea of doing wait before signal this way in the drm_syncobj.
What I wanted to say is that a lot of people used the dma_fence to implement wait before signal before and got a bloody nose from that.
(It took me a while to realize this distinction, before which I mistakenly thought the kernel's DMA fence rules would prohibit wait-before-submit behaviour at the Wayland protocol level as well)
This is what surprised me.
The drm_syncobj implementation solved the wait before signal for the kernel, but my last feedback was that we basically just moved the issue to userspace and Wayland compositors would have quite some overhead to implement it correctly.
That compositors now use eventfd to simplify that was news to me but makes totally sense in hindsight.
But anyway, we need to somehow simplify the drm_syncobj -> eventfd usage in the compositor. That requirement is perfectly justified and avoiding importing the drm_syncobj fd into any DRM driver should actually be really easy to implement.
Regards, Christian.
On Wed, May 20, 2026 at 1:21 PM Christian König christian.koenig@amd.com wrote:
On 5/20/26 10:13, Michel Dänzer wrote:
On 5/19/26 18:00, Christian König wrote:
On 5/19/26 17:31, Xaver Hugl wrote:
Am Di., 19. Mai 2026 um 15:29 Uhr schrieb Christian König christian.koenig@amd.com:
- This series makes the ability to manipulate syncobjs available
independently of attached hardware. 2. It makes it available under a consistent path /dev/syncobj.
Exactly that is a big no-go. This has to be under /dev/dri.
FWIW udmabuf is also under /dev directly, but I don't think any compositor developer would complain about a different path. What are the rules for that? Could this simply be put in /dev/dri/syncobj?
The syncobj are actually the DRM specific way of doing things. The general kernel wide way is to use sync files (see drivers/dma-buf/sync_file.c).
But there has already been tons of problems with those sync files. E.g. they doesn't support your use case at all since they don't have wait before submit behavior.
So there are already ways to do this, but the Linux kernel so far told everybody that this is forbidden. The DRM syncobj wait before signal functionality is much better, but then basically the second try to do this.
I'm not quite sure what you're getting at here, just to be clear though:
While the syncobj Wayland protocol extension supports wait-before-submit behaviour at the Wayland protocol level, it doesn't need or cause wait-before-submit behaviour for DMA fences in the kernel. The usual rules apply to fences attached to syncobj timeline points. The wait-before-submit behaviour at the Wayland protocol level comes from allowing submit before a fence is attached to the acquire timeline point.
Yeah I know. I'm one of the people who came up with the idea of doing wait before signal this way in the drm_syncobj.
What I wanted to say is that a lot of people used the dma_fence to implement wait before signal before and got a bloody nose from that.
(It took me a while to realize this distinction, before which I mistakenly thought the kernel's DMA fence rules would prohibit wait-before-submit behaviour at the Wayland protocol level as well)
This is what surprised me.
The drm_syncobj implementation solved the wait before signal for the kernel, but my last feedback was that we basically just moved the issue to userspace and Wayland compositors would have quite some overhead to implement it correctly.
That compositors now use eventfd to simplify that was news to me but makes totally sense in hindsight.
But anyway, we need to somehow simplify the drm_syncobj -> eventfd usage in the compositor.
That is not the only usage in the compositor. Compositors use all of the following operations on syncobjs:
- creating syncobjs - waiting for points synchronously - signaling points without a sync file - querying points - transferring sync files between points - exporting sync files - importing sync files
Which you can see by looking at the userspace code linked in the cover letter. Bypassing the handle in one ioctl would gain compositors nothing since they would still have to convert to handles and manage their lifetime for all other ioctls.
That requirement is perfectly justified and avoiding importing the drm_syncobj fd into any DRM driver should actually be really easy to implement.
That is what this series does.
Regards, Christian.
On 5/18/26 14:41, Christian König wrote:
On 5/18/26 14:02, Julian Orth wrote:
On Mon, May 18, 2026 at 1:58 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
Not necessarily. The DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL ioctl attaches some kind of dummy fence that is already signaled. I don't believe this is device specific. That is also the path that llvmpipe would use.
Yeah I feared that.
This is the wait before signal path and if I'm not completely mistaken that one is not supported by a lot of compositors.
Where did you get that impression from?
It's arguably the main point of the syncobj Wayland protocol extension, which is supported by all major compositors (except Weston, where it's still a pending MR).
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
I agree with Julian's rebuttal to that.
On 5/18/26 16:59, Michel Dänzer wrote:
On 5/18/26 14:41, Christian König wrote:
On 5/18/26 14:02, Julian Orth wrote:
On Mon, May 18, 2026 at 1:58 PM Christian König christian.koenig@amd.com wrote:
On 5/16/26 13:06, Julian Orth wrote:
This series adds a new device /dev/syncobj that can be used to create and manipulate DRM syncobjs. Previously, these operations required the use of a DRM device and the device needed to support the DRIVER_SYNCOBJ and DRIVER_SYNCOBJ_TIMELINE features.
There are several issues with the existing API:
- Syncobjs are the only explicit sync mechanism available on wayland. Most compositors do not use GPU waits. Instead, they use the DRM_IOCTL_SYNCOBJ_EVENTFD ioctl to perform a CPU wait. Being tied to DRM devices means that compositors cannot consistently offer this feature even though no device-specific logic is involved.
Well the drm_syncobj is a container for device specific dma fences.
Not necessarily. The DRM_IOCTL_SYNCOBJ_TIMELINE_SIGNAL ioctl attaches some kind of dummy fence that is already signaled. I don't believe this is device specific. That is also the path that llvmpipe would use.
Yeah I feared that.
This is the wait before signal path and if I'm not completely mistaken that one is not supported by a lot of compositors.
Where did you get that impression from?
Kernel space seems to not handle that support very well. We added the flag at some point for drivers, but only a fraction actually implemented it.
I wasn't aware that the general eventfd implementation can handle it, but yeah when compositors use that one then that actually makes sense.
It's arguably the main point of the syncobj Wayland protocol extension, which is supported by all major compositors (except Weston, where it's still a pending MR).
So as far as I can see using drm_syncobj for software rendering really doesn't make sense, eventfd is a much better fit for that use case.
I agree with Julian's rebuttal to that.
That eventfd is missing the timeline functionality is a pretty good argument, but I'm still not sure if that justifies the extra kernel complexity.
Regards, Christian.
linaro-mm-sig@lists.linaro.org