Add a per-drm_file WAIT_EVENT manager for render-node events. The manager maintains a list of pending WAIT_EVENT records, supports blocking waits from userspace, copies the first matching event record to userspace, and enforces single-consumer semantics by removing records once they are consumed.
For queue-scoped events, queue_id is resolved to the corresponding usermode queue object at the ioctl boundary. Pending records are matched using queue pointer equality rather than queue_id values, avoiding internal routing through reusable userspace handles. Pending WAIT_EVENT records hold queue references while queued. Those references are released when records are consumed, explicitly removed, or destroyed during manager teardown. Embed the WAIT_EVENT manager in amdgpu_fpriv and tie its lifetime to drm_file. Initialize the manager during file open and destroy it during file close so that pending records are cleaned up and blocked waiters are released before file-private state is freed. Cc: Alex Deucher <[email protected]> Cc: Christian König <[email protected]> Signed-off-by: Srinivasan Shanmugam <[email protected]> --- drivers/gpu/drm/amd/amdgpu/Makefile | 3 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 5 +- .../gpu/drm/amd/amdgpu/amdgpu_wait_event.c | 286 ++++++++++++++++++ .../gpu/drm/amd/amdgpu/amdgpu_wait_event.h | 74 +++++ 5 files changed, 370 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.h diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index dd5a6fc5cbc2..0d3b08e4d9c9 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -72,7 +72,8 @@ amdgpu-y += amdgpu_device.o amdgpu_reg_access.o amdgpu_doorbell_mgr.o amdgpu_kms amdgpu_eeprom.o amdgpu_mca.o amdgpu_psp_ta.o amdgpu_lsdma.o \ amdgpu_ring_mux.o amdgpu_xcp.o amdgpu_seq64.o amdgpu_aca.o amdgpu_dev_coredump.o \ amdgpu_cper.o amdgpu_userq_fence.o amdgpu_eviction_fence.o amdgpu_ip.o \ - amdgpu_eventfd.o + amdgpu_eventfd.o \ + amdgpu_wait_event.o amdgpu-$(CONFIG_PROC_FS) += amdgpu_fdinfo.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 54bc31ee795a..6edd3ea5cbd2 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -104,6 +104,7 @@ #include "amdgpu_fdinfo.h" #include "amdgpu_mca.h" #include "amdgpu_aca.h" +#include "amdgpu_wait_event.h" #include "amdgpu_eventfd.h" #include "amdgpu_ras.h" #include "amdgpu_cper.h" @@ -458,13 +459,15 @@ struct amdgpu_fpriv { uint32_t xcp_id; struct amdgpu_eventfd_mgr eventfd_mgr; + struct amdgpu_wait_event_mgr wait_event_mgr; }; struct drm_device; struct drm_file; int amdgpu_eventfd_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); - +int amdgpu_wait_event_drm_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); int amdgpu_file_to_fpriv(struct file *filp, struct amdgpu_fpriv **fpriv); /* diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c index 3cb675a33945..c463308b02e8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c @@ -1555,6 +1555,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) } amdgpu_eventfd_mgr_init(&fpriv->eventfd_mgr); + amdgpu_wait_event_mgr_init(&fpriv->wait_event_mgr); pasid = amdgpu_pasid_alloc(16); if (pasid < 0) { @@ -1622,6 +1623,7 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv) if (pasid) amdgpu_pasid_free(pasid); + amdgpu_wait_event_mgr_fini(&fpriv->wait_event_mgr); kfree(fpriv); out_suspend: @@ -1652,8 +1654,9 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev, if (!fpriv) return; - /* Drop all subscriptions before fpriv goes away. */ + /* Drop eventfd subscriptions and pending wait-event records. */ amdgpu_eventfd_mgr_fini(&fpriv->eventfd_mgr); + amdgpu_wait_event_mgr_fini(&fpriv->wait_event_mgr); pm_runtime_get_sync(dev->dev); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.c new file mode 100644 index 000000000000..f98de1d94b56 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.c @@ -0,0 +1,286 @@ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include <linux/jiffies.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "amdgpu.h" +#include "amdgpu_userq_internal.h" +#include "amdgpu_wait_event.h" + +static long amdgpu_wait_event_to_jiffies(__s64 timeout_ns) +{ + unsigned long long t; + long timeout; + + if (timeout_ns < 0) + return MAX_SCHEDULE_TIMEOUT; + if (timeout_ns == 0) + return 0; + + t = nsecs_to_jiffies(timeout_ns); + if (t > MAX_SCHEDULE_TIMEOUT) + timeout = MAX_SCHEDULE_TIMEOUT - 1; + else + timeout = t; + + return timeout ?: 1; +} + +static bool amdgpu_wait_event_valid_type(u32 event_type) +{ + switch (event_type) { + case DRM_AMDGPU_EVENT_TYPE_USERQ_EOP: + case DRM_AMDGPU_EVENT_TYPE_QUEUE_RESET: + case DRM_AMDGPU_EVENT_TYPE_MEMORY_EXCEPTION: + case DRM_AMDGPU_EVENT_TYPE_SCRATCH: + case DRM_AMDGPU_EVENT_TYPE_GPU_RESET: + return true; + default: + return false; + } +} + +static void +amdgpu_wait_event_record_free(struct amdgpu_wait_event_record *rec) +{ + if (!rec) + return; + + if (rec->queue) + amdgpu_userq_put(rec->queue); + + kfree(rec); +} + +static bool +amdgpu_wait_event_match(const struct drm_amdgpu_wait_event *args, + struct amdgpu_usermode_queue *queue, + const struct amdgpu_wait_event_record *rec) +{ + if (rec->data.event_type != args->event_type) + return false; + + if (amdgpu_wait_event_type_is_queue_scoped(args->event_type)) + return rec->queue == queue; + + return !queue; +} + +static bool +amdgpu_wait_event_has_match(struct amdgpu_wait_event_mgr *mgr, + const struct drm_amdgpu_wait_event *args, + struct amdgpu_usermode_queue *queue) +{ + struct amdgpu_wait_event_record *rec; + bool found = false; + unsigned long flags; + + spin_lock_irqsave(&mgr->lock, flags); + list_for_each_entry(rec, &mgr->pending, node) { + if (amdgpu_wait_event_match(args, queue, rec)) { + found = true; + break; + } + } + spin_unlock_irqrestore(&mgr->lock, flags); + + return found; +} + +static struct amdgpu_wait_event_record * +amdgpu_wait_event_pop_match(struct amdgpu_wait_event_mgr *mgr, + const struct drm_amdgpu_wait_event *args, + struct amdgpu_usermode_queue *queue) +{ + struct amdgpu_wait_event_record *rec, *tmp, *found = NULL; + unsigned long flags; + + spin_lock_irqsave(&mgr->lock, flags); + list_for_each_entry_safe(rec, tmp, &mgr->pending, node) { + if (amdgpu_wait_event_match(args, queue, rec)) { + list_del(&rec->node); + found = rec; + break; + } + } + spin_unlock_irqrestore(&mgr->lock, flags); + + return found; +} + +static int +amdgpu_wait_event_get_queue(struct amdgpu_fpriv *fpriv, + const struct drm_amdgpu_wait_event *args, + struct amdgpu_usermode_queue **queue) +{ + *queue = NULL; + + if (!amdgpu_wait_event_valid_type(args->event_type)) + return -EINVAL; + + if (amdgpu_wait_event_type_is_queue_scoped(args->event_type)) { + if (!args->queue_id) + return -EINVAL; + + *queue = amdgpu_userq_get(&fpriv->userq_mgr, args->queue_id); + if (!*queue) + return -ENOENT; + + return 0; + } + + if (args->queue_id) + return -EINVAL; + + return 0; +} + +void amdgpu_wait_event_mgr_init(struct amdgpu_wait_event_mgr *mgr) +{ + spin_lock_init(&mgr->lock); + init_waitqueue_head(&mgr->wq); + INIT_LIST_HEAD(&mgr->pending); + atomic64_set(&mgr->seqno, 0); + mgr->dead = false; +} + +void amdgpu_wait_event_mgr_fini(struct amdgpu_wait_event_mgr *mgr) +{ + struct amdgpu_wait_event_record *rec, *tmp; + unsigned long flags; + LIST_HEAD(removed); + + spin_lock_irqsave(&mgr->lock, flags); + mgr->dead = true; + list_splice_init(&mgr->pending, &removed); + spin_unlock_irqrestore(&mgr->lock, flags); + + list_for_each_entry_safe(rec, tmp, &removed, node) { + list_del(&rec->node); + amdgpu_wait_event_record_free(rec); + } + + wake_up_interruptible_all(&mgr->wq); +} + +void amdgpu_wait_event_remove_queue(struct amdgpu_wait_event_mgr *mgr, + struct amdgpu_usermode_queue *queue) +{ + struct amdgpu_wait_event_record *rec, *tmp; + unsigned long flags; + LIST_HEAD(removed); + + if (!mgr || !queue) + return; + + spin_lock_irqsave(&mgr->lock, flags); + list_for_each_entry_safe(rec, tmp, &mgr->pending, node) { + if (rec->queue != queue) + continue; + + list_move_tail(&rec->node, &removed); + } + spin_unlock_irqrestore(&mgr->lock, flags); + + list_for_each_entry_safe(rec, tmp, &removed, node) { + list_del(&rec->node); + amdgpu_wait_event_record_free(rec); + } + + wake_up_interruptible_all(&mgr->wq); +} + +int amdgpu_wait_event_drm_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct amdgpu_fpriv *fpriv = file_priv->driver_priv; + struct drm_amdgpu_wait_event *args = data; + struct amdgpu_usermode_queue *queue = NULL; + struct amdgpu_wait_event_mgr *mgr; + struct amdgpu_wait_event_record *rec; + long timeout; + int ret; + + if (!fpriv) + return -EINVAL; + + if (args->flags || !args->event_type || !args->out_ptr) + return -EINVAL; + + if (args->out_size < sizeof(struct drm_amdgpu_wait_event_data)) + return -EINVAL; + + ret = amdgpu_wait_event_get_queue(fpriv, args, &queue); + if (ret) + return ret; + + mgr = &fpriv->wait_event_mgr; + timeout = amdgpu_wait_event_to_jiffies(args->timeout_ns); + + for (;;) { + rec = amdgpu_wait_event_pop_match(mgr, args, queue); + if (rec) + break; + + if (READ_ONCE(mgr->dead)) { + ret = -EIO; + goto out_put_queue; + } + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + goto out_put_queue; + } + + if (!timeout) { + ret = -ETIME; + goto out_put_queue; + } + + timeout = wait_event_interruptible_timeout(mgr->wq, + READ_ONCE(mgr->dead) || + amdgpu_wait_event_has_match(mgr, args, queue), + timeout); + if (timeout < 0) { + ret = timeout; + goto out_put_queue; + } + } + + if (copy_to_user(u64_to_user_ptr(args->out_ptr), &rec->data, + sizeof(rec->data))) + ret = -EFAULT; + else + ret = 0; + + amdgpu_wait_event_record_free(rec); + +out_put_queue: + if (queue) + amdgpu_userq_put(queue); + + return ret; +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.h new file mode 100644 index 000000000000..e887fffdc4ac --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_wait_event.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright 2026 Advanced Micro Devices, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __AMDGPU_WAIT_EVENT_H__ +#define __AMDGPU_WAIT_EVENT_H__ + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/atomic.h> +#include <drm/drm_device.h> +#include <drm/drm_file.h> +#include <uapi/drm/amdgpu_drm.h> + +struct amdgpu_usermode_queue; + +struct amdgpu_wait_event_record { + struct list_head node; + struct amdgpu_usermode_queue *queue; + struct drm_amdgpu_wait_event_data data; +}; + +struct amdgpu_wait_event_mgr { + /* Used when adding, removing, or checking pending events. */ + spinlock_t lock; + wait_queue_head_t wq; + struct list_head pending; + atomic64_t seqno; + bool dead; +}; + +void amdgpu_wait_event_mgr_init(struct amdgpu_wait_event_mgr *mgr); +void amdgpu_wait_event_mgr_fini(struct amdgpu_wait_event_mgr *mgr); + +void amdgpu_wait_event_remove_queue(struct amdgpu_wait_event_mgr *mgr, + struct amdgpu_usermode_queue *queue); + +int amdgpu_wait_event_drm_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +static inline bool amdgpu_wait_event_type_is_queue_scoped(u32 event_type) +{ + switch (event_type) { + case DRM_AMDGPU_EVENT_TYPE_USERQ_EOP: + case DRM_AMDGPU_EVENT_TYPE_QUEUE_RESET: + case DRM_AMDGPU_EVENT_TYPE_SCRATCH: + return true; + default: + return false; + } +} + +#endif /* __AMDGPU_WAIT_EVENT_H__ */ -- 2.34.1
