Introduce a per-drm_file eventfd manager to support render-node event subscriptions.
The manager is implemented in amdgpu_eventfd.[ch] and is owned by the drm_file (amdgpu_fpriv). It maps an event identifier to an eventfd_id object, where each eventfd_id can have multiple eventfds bound (fan-out). The design is IRQ-safe for signaling: IRQ path takes the xarray lock (irqsave) and signals eventfds while still holding the lock. This patch only adds the core manager. Duplicate binds are treated as success, allowing userspace to re-register subscriptions without errors. v10: (per Christian) - Avoid GFP_ATOMIC in the event identifier insertion path. - Use the recommended xarray insertion pattern with memory allocation outside the xarray lock. - Simplify the eventfd unbind loop for readability. v11: (per Christian) - Use goto-style cleanup in amdgpu_eventfd_bind() to avoid repeated cleanup paths. Cc: Alex Deucher <[email protected]> Suggested-by: 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_eventfd.c | 386 ++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h | 59 +++ 3 files changed, 447 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index ee3574797bc2..dd5a6fc5cbc2 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -71,7 +71,8 @@ amdgpu-y += amdgpu_device.o amdgpu_reg_access.o amdgpu_doorbell_mgr.o amdgpu_kms amdgpu_fw_attestation.o amdgpu_securedisplay.o \ 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_cper.o amdgpu_userq_fence.o amdgpu_eviction_fence.o amdgpu_ip.o \ + amdgpu_eventfd.o amdgpu-$(CONFIG_PROC_FS) += amdgpu_fdinfo.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c new file mode 100644 index 000000000000..4955ee942a1c --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c @@ -0,0 +1,386 @@ +/* + * 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. + * + */ + +/* + * Render-node eventfd subscription infrastructure. + * + * This module provides a simple event notification mechanism for render-node + * clients using Linux eventfd objects. + * + * Userspace can bind an eventfd to a userspace-defined event_id. When the + * driver signals that event_id, all eventfds bound to it are notified. + * + * This mechanism is intended to support lightweight GPU event notifications + * without polling from userspace. + */ + +#include <linux/slab.h> +#include <linux/err.h> + +#include "amdgpu_eventfd.h" + +#define AMDGPU_EVENTFD_MAX_BINDS 4096 + +/** + * amdgpu_eventfd_id_alloc - allocate an event id container + * @event_id: userspace-defined event identifier + * + * Each event_id represents a notification category. Multiple eventfds can + * be bound to the same event_id. + * + * This function allocates the container which stores the list of eventfds + * associated with that event_id. + * + * Return: + * Pointer to the newly allocated structure or NULL on failure. + */ +static struct amdgpu_eventfd_id *amdgpu_eventfd_id_alloc(u32 event_id) +{ + struct amdgpu_eventfd_id *id; + + id = kzalloc(sizeof(*id), GFP_KERNEL); + if (!id) + return NULL; + + id->event_id = event_id; + INIT_HLIST_HEAD(&id->entries); + id->n_entries = 0; + return id; +} + +/** + * amdgpu_eventfd_id_get_or_create - find or create an event_id entry + * @mgr: eventfd manager + * @event_id: event identifier + * + * This helper returns the container associated with the given event_id. + * If it does not exist, it will create one. + * + * The function is designed to be callable without holding any locks. + * Memory allocation is done outside the xarray lock to avoid blocking + * inside critical sections. + * + * Return: + * Pointer to the event_id structure or NULL on failure. + */ +static struct amdgpu_eventfd_id * +amdgpu_eventfd_id_get_or_create(struct amdgpu_eventfd_mgr *mgr, u32 event_id) +{ + struct amdgpu_eventfd_id *id; + struct amdgpu_eventfd_id *new_id; + XA_STATE(xas, &mgr->ids, event_id); + unsigned long flags; + int r; + + xa_lock_irqsave(&mgr->ids, flags); + id = xa_load(&mgr->ids, event_id); + xa_unlock_irqrestore(&mgr->ids, flags); + if (id) + return id; + + new_id = amdgpu_eventfd_id_alloc(event_id); + if (!new_id) + return NULL; + + /* + * Allocate xarray memory outside the lock if needed. + * This avoids using GFP_ATOMIC while holding the lock. + */ + do { + xas_lock_irqsave(&xas, flags); + + id = xas_load(&xas); + if (id) { + xas_unlock_irqrestore(&xas, flags); + kfree(new_id); + return id; + } + + xas_store(&xas, new_id); + r = xas_error(&xas); + + xas_unlock_irqrestore(&xas, flags); + } while (xas_nomem(&xas, GFP_KERNEL)); + + if (r) { + kfree(new_id); + return NULL; + } + + return new_id; +} + +/** + * amdgpu_eventfd_mgr_init - initialize eventfd manager + * @mgr: manager instance + * + * Each DRM file (amdgpu_fpriv) owns one eventfd manager. + * + * The manager stores mappings: + * + * event_id -> list of eventfds + * + * The xarray provides efficient lookup of event_id containers. + */ +void amdgpu_eventfd_mgr_init(struct amdgpu_eventfd_mgr *mgr) +{ + xa_init_flags(&mgr->ids, XA_FLAGS_LOCK_IRQ); + atomic_set(&mgr->bind_count, 0); +} + +/** + * amdgpu_eventfd_mgr_fini - destroy eventfd manager + * @mgr: manager instance + * + * Frees all registered eventfd bindings. + * + * The caller is expected to ensure that no IRQ-side signaling can occur + * after this function begins. This typically happens during drm_file + * teardown. + */ +void amdgpu_eventfd_mgr_fini(struct amdgpu_eventfd_mgr *mgr) +{ + unsigned long index; + struct amdgpu_eventfd_id *id; + + /* + * Expected teardown ordering: caller ensures no further IRQ-side + * signaling can race with this before fini() is called. + */ + xa_lock(&mgr->ids); + xa_for_each(&mgr->ids, index, id) { + struct amdgpu_eventfd_entry *e; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(e, tmp, &id->entries, hnode) { + hlist_del(&e->hnode); + eventfd_ctx_put(e->ctx); + kfree(e); + } + + kfree(id); + } + xa_unlock(&mgr->ids); + + xa_destroy(&mgr->ids); +} + +/** + * amdgpu_eventfd_bind - bind eventfd to an event_id + * @mgr: eventfd manager + * @event_id: userspace event identifier + * @eventfd: eventfd file descriptor + * + * This function allows userspace to subscribe to notifications for a + * specific event_id. + * + * Multiple eventfds can be bound to the same event_id. + * + * Duplicate bindings of the same eventfd are treated as success and do + * not create additional entries. + * + * Return: + * 0 on success, negative error code on failure. + */ +int amdgpu_eventfd_bind(struct amdgpu_eventfd_mgr *mgr, u32 event_id, int eventfd) +{ + struct amdgpu_eventfd_id *id; + struct amdgpu_eventfd_entry *e = NULL, *it; + struct eventfd_ctx *ctx; + unsigned long flags; + bool dup = false; + int r = 0; /* duplicate binds are treated as success */ + + if (!mgr || !event_id || eventfd < 0) + return -EINVAL; + + /* + * Enforce total bind limit without a separate manager lock. + * For duplicate binds, we decrement back before returning success. + */ + if (atomic_inc_return(&mgr->bind_count) > AMDGPU_EVENTFD_MAX_BINDS) { + atomic_dec(&mgr->bind_count); + return -ENOSPC; + } + + ctx = eventfd_ctx_fdget(eventfd); + if (IS_ERR(ctx)) { + atomic_dec(&mgr->bind_count); + return PTR_ERR(ctx); + } + + id = amdgpu_eventfd_id_get_or_create(mgr, event_id); + if (!id) { + r = -ENOMEM; + goto out_put_ctx; + } + + /* check for duplicate binding */ + xa_lock_irqsave(&mgr->ids, flags); + hlist_for_each_entry(it, &id->entries, hnode) { + if (it->ctx == ctx) { + dup = true; + break; + } + } + xa_unlock_irqrestore(&mgr->ids, flags); + + if (dup) + goto out_put_ctx; /* duplicate bind: success */ + + /* Allocate entry only after ctx/id prerequisites are satisfied. */ + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) { + r = -ENOMEM; + goto out_put_ctx; + } + + e->ctx = ctx; + + /* + * Re-check duplicate under lock to close the race with another bind() + * for the same (event_id, ctx). + */ + xa_lock_irqsave(&mgr->ids, flags); + hlist_for_each_entry(it, &id->entries, hnode) { + if (it->ctx == ctx) { + dup = true; + break; + } + } + + if (!dup) { + hlist_add_head(&e->hnode, &id->entries); + id->n_entries++; + e = NULL; /* consumed */ + } + xa_unlock_irqrestore(&mgr->ids, flags); + + if (dup) + goto out_free_entry; + + return 0; + +out_free_entry: + kfree(e); +out_put_ctx: + eventfd_ctx_put(ctx); + atomic_dec(&mgr->bind_count); + + return r; +} + +/** + * amdgpu_eventfd_unbind - remove eventfd binding + * @mgr: eventfd manager + * @event_id: event identifier + * @eventfd: eventfd file descriptor + * + * Removes an existing binding between an event_id and an eventfd. + * + * Return: + * 0 if removed, -ENOENT if binding does not exist. + */ +int amdgpu_eventfd_unbind(struct amdgpu_eventfd_mgr *mgr, u32 event_id, int eventfd) +{ + struct amdgpu_eventfd_id *id; + struct amdgpu_eventfd_entry *e; + struct hlist_node *tmp; + struct eventfd_ctx *ctx; + unsigned long flags; + bool removed = false; + + if (!mgr || !event_id || eventfd < 0) + return -EINVAL; + + ctx = eventfd_ctx_fdget(eventfd); + if (IS_ERR(ctx)) + return PTR_ERR(ctx); + + xa_lock_irqsave(&mgr->ids, flags); + + id = xa_load(&mgr->ids, event_id); + if (!id) + goto out_unlock; + + hlist_for_each_entry_safe(e, tmp, &id->entries, hnode) { + if (e->ctx != ctx) + continue; + + hlist_del(&e->hnode); + id->n_entries--; + removed = true; + + eventfd_ctx_put(e->ctx); + kfree(e); + + atomic_dec(&mgr->bind_count); + + if (!id->n_entries) { + __xa_erase(&mgr->ids, event_id); + kfree(id); + } + + break; + } + +out_unlock: + xa_unlock_irqrestore(&mgr->ids, flags); + eventfd_ctx_put(ctx); + + return removed ? 0 : -ENOENT; +} + +/** + * amdgpu_eventfd_signal - notify all eventfds bound to event_id + * @mgr: eventfd manager + * @event_id: event identifier + * + * This function is typically called from interrupt context. + * + * All eventfds registered for the given event_id will be signaled. + * Userspace processes waiting on those eventfds will wake up. + */ +void amdgpu_eventfd_signal(struct amdgpu_eventfd_mgr *mgr, u32 event_id) +{ + struct amdgpu_eventfd_id *id; + struct amdgpu_eventfd_entry *e; + unsigned long flags; + + if (!mgr || !event_id) + return; + + /* + * This can run from IRQ context. Keep the lock while walking the list + * so entries cannot disappear during signaling. + */ + xa_lock_irqsave(&mgr->ids, flags); + + id = xa_load(&mgr->ids, event_id); + if (id) { + hlist_for_each_entry(e, &id->entries, hnode) + eventfd_signal(e->ctx); + } + + xa_unlock_irqrestore(&mgr->ids, flags); +} diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h new file mode 100644 index 000000000000..248afb1f2f14 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h @@ -0,0 +1,59 @@ +/* + * 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. + * + */ + +/* + * Render-node eventfd subscription infrastructure. + */ + +#ifndef __AMDGPU_EVENTFD_H__ +#define __AMDGPU_EVENTFD_H__ + +#include <linux/eventfd.h> +#include <linux/xarray.h> +#include <linux/atomic.h> + +struct amdgpu_eventfd_entry { + struct eventfd_ctx *ctx; + struct hlist_node hnode; +}; + +struct amdgpu_eventfd_id { + u32 event_id; + struct hlist_head entries; + u32 n_entries; +}; + +struct amdgpu_eventfd_mgr { + struct xarray ids; /* event_id -> struct amdgpu_eventfd_id* */ + atomic_t bind_count; /* total binds across all event_ids */ +}; + +void amdgpu_eventfd_mgr_init(struct amdgpu_eventfd_mgr *mgr); +void amdgpu_eventfd_mgr_fini(struct amdgpu_eventfd_mgr *mgr); + +int amdgpu_eventfd_bind(struct amdgpu_eventfd_mgr *mgr, u32 event_id, int eventfd); +int amdgpu_eventfd_unbind(struct amdgpu_eventfd_mgr *mgr, u32 event_id, int eventfd); + +void amdgpu_eventfd_signal(struct amdgpu_eventfd_mgr *mgr, u32 event_id); + +#endif /* __AMDGPU_EVENTFD_H__ */ -- 2.34.1
