On 3/2/26 04:02, Srinivasan Shanmugam wrote:
> 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 event_id -> 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 and wires its lifetime into
> open/postclose.
>
> Cc: Harish Kasiviswanathan <[email protected]>
> Cc: Felix Kuehling <[email protected]>
> Cc: Alex Deucher <[email protected]>
> Suggested-by: Christian König <[email protected]>
> Signed-off-by: Srinivasan Shanmugam <[email protected]>
> Change-Id: Iac025dcb7c1b4f9ed74ac4190085e1925e2c8e68
> ---
> drivers/gpu/drm/amd/amdgpu/Makefile | 3 +-
> drivers/gpu/drm/amd/amdgpu/amdgpu.h | 5 +
> drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c | 321 ++++++++++++++++++++
> drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h | 76 +++++
> drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 28 +-
> 5 files changed, 430 insertions(+), 3 deletions(-)
> 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 006d49d6b4af..30b1cf3c6cdf 100644
> --- a/drivers/gpu/drm/amd/amdgpu/Makefile
> +++ b/drivers/gpu/drm/amd/amdgpu/Makefile
> @@ -67,7 +67,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.h
> b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> index 1e71a03c8bba..9e650b3707e3 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> @@ -44,6 +44,7 @@
> #include <linux/hashtable.h>
> #include <linux/dma-fence.h>
> #include <linux/pci.h>
> +#include <linux/xarray.h>
>
> #include <drm/ttm/ttm_bo.h>
> #include <drm/ttm/ttm_placement.h>
> @@ -434,6 +435,8 @@ struct amdgpu_flip_work {
> bool async;
> };
>
> +struct amdgpu_eventfd_mgr;
> +
> /*
> * file private structure
> */
> @@ -453,6 +456,8 @@ struct amdgpu_fpriv {
>
> /** GPU partition selection */
> uint32_t xcp_id;
> +
> + struct amdgpu_eventfd_mgr *eventfd_mgr;
If possible we should embed that structure here instead of having a pointer.
> };
>
> int amdgpu_file_to_fpriv(struct file *filp, struct amdgpu_fpriv **fpriv);
> 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..15ffb74c1de3
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.c
> @@ -0,0 +1,321 @@
> +// 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.
> + *
> + */
> +
> +/*
> + * Render-node eventfd subscription infrastructure.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +
> +#include "amdgpu_eventfd.h"
> +
> +static void amdgpu_eventfd_mgr_release(struct kref *ref)
> +{
> + struct amdgpu_eventfd_mgr *mgr =
> + container_of(ref, struct amdgpu_eventfd_mgr, refcount);
> + unsigned long index;
> + struct amdgpu_eventfd_id *id;
> +
> + /* Final teardown: remove all ids and drop all eventfd references. */
> + spin_lock(&mgr->lock);
> + mgr->fd_closing = true;
> + spin_unlock(&mgr->lock);
Of hand I can't see a necessity for that. Please explain further.
> +
> + xa_lock(&mgr->ids);
> + xa_for_each(&mgr->ids, index, id) {
> + struct amdgpu_eventfd_entry *e;
> + struct hlist_node *tmp;
> +
> + __xa_erase(&mgr->ids, index);
> +
> + spin_lock(&id->lock);
> + hlist_for_each_entry_safe(e, tmp, &id->entries, hnode) {
> + hlist_del(&e->hnode);
> + id->n_entries--;
> + if (e->ctx)
> + eventfd_ctx_put(e->ctx);
> + kfree(e);
> + }
> + spin_unlock(&id->lock);
> + kfree(id);
> + }
> + xa_unlock(&mgr->ids);
> +
> + xa_destroy(&mgr->ids);
> + kfree(mgr);
> +}
> +
> +struct amdgpu_eventfd_mgr *amdgpu_eventfd_mgr_alloc(void)
> +{
> + struct amdgpu_eventfd_mgr *mgr;
> +
> + mgr = kzalloc(sizeof(*mgr), GFP_KERNEL);
> + if (!mgr)
> + return NULL;
> +
> + kref_init(&mgr->refcount);
> + xa_init(&mgr->ids);
> + spin_lock_init(&mgr->lock);
> + mgr->bind_count = 0;
> + mgr->fd_closing = false;
> +
> + return mgr;
> +}
> +
> +void amdgpu_eventfd_mgr_get(struct amdgpu_eventfd_mgr *mgr)
> +{
> + if (mgr)
> + kref_get(&mgr->refcount);
> +}
> +
> +void amdgpu_eventfd_mgr_put(struct amdgpu_eventfd_mgr *mgr)
> +{
> + if (mgr)
> + kref_put(&mgr->refcount, amdgpu_eventfd_mgr_release);
> +}
> +
> +void amdgpu_eventfd_mgr_mark_closing(struct amdgpu_eventfd_mgr *mgr)
> +{
> + if (!mgr)
> + return;
> +
> + spin_lock(&mgr->lock);
> + mgr->fd_closing = true;
> + spin_unlock(&mgr->lock);
> +}
> +
> +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;
> + spin_lock_init(&id->lock);
> + INIT_HLIST_HEAD(&id->entries);
> + id->n_entries = 0;
> + return id;
> +}
> +
> +int amdgpu_eventfd_bind(struct amdgpu_eventfd_mgr *mgr, u32 event_id, int
> eventfd)
> +{
> + struct amdgpu_eventfd_id *id, *new_id = NULL;
> + struct amdgpu_eventfd_entry *e;
> + struct eventfd_ctx *ctx;
> + unsigned long flags;
> + bool found = false;
> +
> + if (!mgr || !event_id || eventfd < 0)
> + return -EINVAL;
> +
> + /* If file is closing, refuse new binds. */
> + spin_lock(&mgr->lock);
> + if (mgr->fd_closing) {
> + spin_unlock(&mgr->lock);
> + return -EBADF;
> + }
> + spin_unlock(&mgr->lock);
Such checks are unecessary. bind/unbind can only come through the DRM render
node file descriptor and when we set fd_closing to true that one is already
gone.
> +
> + ctx = eventfd_ctx_fdget(eventfd);
> + if (IS_ERR(ctx))
> + return PTR_ERR(ctx);
> +
> + e = kzalloc(sizeof(*e), GFP_KERNEL);
> + if (!e) {
> + eventfd_ctx_put(ctx);
> + return -ENOMEM;
> + }
> + e->ctx = ctx;
Do that after allocating new_id. It is not a problem if we allocate new_id and
then never use it.
> + e->eventfd = eventfd;
Stuff like that is a big no-go. The file descriptor number is only valid to
call eventfd_ctx_fdget() once!
Even a second call to eventfd_ctx_fdget() can return a different value.
> +
> + /*
> + * Prepare id object outside xa_lock_irqsave(): kzalloc(GFP_KERNEL)
> + * must not happen while holding spinlock/irqs-disabled.
> + */
> + new_id = amdgpu_eventfd_id_alloc(event_id);
> +
> + /*
> + * IRQ-safe design:
> + * - protect id lookup + insertion under xarray lock (irqsave)
> + * - protect entry list under id->lock
That is a bit overkill, just protecting everything under the xa lock should be
sufficient as far as I can see.
> + */
> + xa_lock_irqsave(&mgr->ids, flags);
> +
> + id = xa_load(&mgr->ids, event_id);
> + if (!id) {
> + if (!new_id) {
> + xa_unlock_irqrestore(&mgr->ids, flags);
> + eventfd_ctx_put(ctx);
> + kfree(e);
> + return -ENOMEM;
> + }
> + if (xa_err(xa_store(&mgr->ids, event_id, new_id, GFP_NOWAIT))) {
> + xa_unlock_irqrestore(&mgr->ids, flags);
> + kfree(new_id);
> + eventfd_ctx_put(ctx);
> + kfree(e);
> + return -ENOMEM;
> + }
> + id = new_id;
> + new_id = NULL;
> + }
I strongly suggest to move that whole dance into amdgpu_eventfd_id_alloc().
> +
> + /* Enforce total bind limit. */
> + spin_lock(&mgr->lock);
> + if (mgr->bind_count >= AMDGPU_EVENTFD_MAX_BINDS) {
> + spin_unlock(&mgr->lock);
> + xa_unlock_irqrestore(&mgr->ids, flags);
> + kfree(new_id);
> + eventfd_ctx_put(ctx);
> + kfree(e);
> + return -ENOSPC;
> + }
> + spin_unlock(&mgr->lock);
That could be an atomic operation instead. Would save the mgr lock as far as I
can see.
> +
> + spin_lock(&id->lock);
> + {
> + struct amdgpu_eventfd_entry *it;
> +
> + hlist_for_each_entry(it, &id->entries, hnode) {
> + if (it->eventfd == eventfd) {
Absolutely clear NAK to stuff like that! You must compare the ctx instead.
Apart from that is it problematic to bind the same fd to and event multiple
times? I mean it doesn't make to much sense but it also doesn't hurt us in any
way.
I'm running out of time to further review the patches, but I think that already
gives you quite a bit of todo.
Regards,
Christian.
> + found = true;
> + break;
> + }
> + }
> + if (!found) {
> + hlist_add_head(&e->hnode, &id->entries);
> + id->n_entries++;
> + }
> + }
> + spin_unlock(&id->lock);
> +
> + if (!found) {
> + spin_lock(&mgr->lock);
> + mgr->bind_count++;
> + spin_unlock(&mgr->lock);
> + }
> +
> + xa_unlock_irqrestore(&mgr->ids, flags);
> +
> + /* If event_id already existed, drop unused allocation. */
> + kfree(new_id);
> +
> + /* If already bound, keep existing and drop the new reference. */
> + if (found) {
> + eventfd_ctx_put(ctx);
> + kfree(e);
> + }
> + return 0;
> +}
> +
> +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;
> + unsigned long flags;
> + bool removed = false;
> +
> + if (!mgr || !event_id || eventfd < 0)
> + return -EINVAL;
> +
> + xa_lock_irqsave(&mgr->ids, flags);
> + id = xa_load(&mgr->ids, event_id);
> + if (!id) {
> + xa_unlock_irqrestore(&mgr->ids, flags);
> + return -ENOENT;
> + }
> +
> + spin_lock(&id->lock);
> + hlist_for_each_entry_safe(e, tmp, &id->entries, hnode) {
> + if (e->eventfd == eventfd) {
> + hlist_del(&e->hnode);
> + id->n_entries--;
> + removed = true;
> + if (e->ctx)
> + eventfd_ctx_put(e->ctx);
> + kfree(e);
> + break;
> + }
> + }
> + spin_unlock(&id->lock);
> +
> + if (removed) {
> + spin_lock(&mgr->lock);
> + if (mgr->bind_count)
> + mgr->bind_count--;
> + spin_unlock(&mgr->lock);
> + }
> +
> + /* If no more eventfds bound to this event_id, remove id object. */
> + if (removed && id->n_entries == 0) {
> + (void)xa_erase(&mgr->ids, event_id);
> + kfree(id);
> + }
> +
> + xa_unlock_irqrestore(&mgr->ids, flags);
> +
> + return removed ? 0 : -ENOENT;
> +}
> +
> +void amdgpu_eventfd_signal(struct amdgpu_eventfd_mgr *mgr, u32 event_id, u64
> count)
> +{
> + struct amdgpu_eventfd_id *id;
> + struct amdgpu_eventfd_entry *e;
> + unsigned long flags;
> + bool closing;
> +
> + if (!mgr || !event_id || !count)
> + return;
> +
> + /* Fast closing check (best-effort). */
> + spin_lock(&mgr->lock);
> + closing = mgr->fd_closing;
> + spin_unlock(&mgr->lock);
> + if (closing)
> + return;
> +
> + /*
> + * IRQ path: keep xarray locked while signaling, as suggested.
> + * eventfd_signal() is IRQ-safe.
> + */
> + xa_lock_irqsave(&mgr->ids, flags);
> + id = xa_load(&mgr->ids, event_id);
> + if (!id) {
> + xa_unlock_irqrestore(&mgr->ids, flags);
> + return;
> + }
> +
> + spin_lock(&id->lock);
> + hlist_for_each_entry(e, &id->entries, hnode) {
> + if (e->ctx)
> + eventfd_signal(e->ctx);
> + }
> + spin_unlock(&id->lock);
> +
> + 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..542a1d65e13b
> --- /dev/null
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_eventfd.h
> @@ -0,0 +1,76 @@
> +/* 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.
> + *
> + */
> +
> +/*
> + * Render-node eventfd subscription infrastructure.
> + */
> +
> +#ifndef __AMDGPU_EVENTFD_H__
> +#define __AMDGPU_EVENTFD_H__
> +
> +#include <linux/kref.h>
> +#include <linux/spinlock.h>
> +#include <linux/xarray.h>
> +#include <linux/eventfd.h>
> +
> +/*
> + * Manager lifetime:
> + * - owned by drm_file (amdgpu_fpriv)
> + * - can also be referenced by longer-lived producer objects (e.g. USERQ
> fence
> + * driver) via kref.
> + */
> +struct amdgpu_eventfd_mgr {
> + struct kref refcount;
> + struct xarray ids; /* key: event_id(u32) -> struct
> amdgpu_eventfd_id* */
> + spinlock_t lock; /* protects ids + counts */
> + u32 bind_count;
> + bool fd_closing;
> +};
> +
> +struct amdgpu_eventfd_entry {
> + struct hlist_node hnode;
> + struct eventfd_ctx *ctx;
> + int eventfd;
> +};
> +
> +struct amdgpu_eventfd_id {
> + u32 event_id;
> + spinlock_t lock; /* protects entries */
> + struct hlist_head entries; /* struct amdgpu_eventfd_entry */
> + u32 n_entries;
> +};
> +
> +/* Per-file cap to align with common KFD-style event-id space and avoid
> abuse */
> +#define AMDGPU_EVENTFD_MAX_BINDS 4096U
> +
> +struct amdgpu_eventfd_mgr *amdgpu_eventfd_mgr_alloc(void);
> +void amdgpu_eventfd_mgr_get(struct amdgpu_eventfd_mgr *mgr);
> +void amdgpu_eventfd_mgr_put(struct amdgpu_eventfd_mgr *mgr);
> +void amdgpu_eventfd_mgr_mark_closing(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, u64
> count);
> +
> +#endif /* __AMDGPU_EVENTFD_H__ */
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> index f69332eed051..8ab8f9dc4cfa 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> @@ -42,6 +42,7 @@
> #include "amdgpu_amdkfd.h"
> #include "amdgpu_gem.h"
> #include "amdgpu_display.h"
> +#include "amdgpu_eventfd.h"
> #include "amdgpu_ras.h"
> #include "amdgpu_reset.h"
> #include "amd_pcie.h"
> @@ -1469,10 +1470,17 @@ int amdgpu_driver_open_kms(struct drm_device *dev,
> struct drm_file *file_priv)
> goto out_suspend;
> }
>
> + fpriv->eventfd_mgr = amdgpu_eventfd_mgr_alloc();
> + if (!fpriv->eventfd_mgr) {
> + r = -ENOMEM;
> + goto free_fpriv;
> + }
> +
> pasid = amdgpu_pasid_alloc(16);
> if (pasid < 0) {
> dev_warn(adev->dev, "No more PASIDs available!");
> pasid = 0;
> + goto error_pasid;
> }
>
> r = amdgpu_xcp_open_device(adev, fpriv, file_priv);
> @@ -1538,6 +1546,12 @@ int amdgpu_driver_open_kms(struct drm_device *dev,
> struct drm_file *file_priv)
> if (pasid)
> amdgpu_pasid_free(pasid);
>
> +free_fpriv:
> + if (fpriv && fpriv->eventfd_mgr) {
> + amdgpu_eventfd_mgr_put(fpriv->eventfd_mgr);
> + fpriv->eventfd_mgr = NULL;
> + }
> +
> kfree(fpriv);
>
> out_suspend:
> @@ -1568,6 +1582,7 @@ void amdgpu_driver_postclose_kms(struct drm_device *dev,
> if (!fpriv)
> return;
>
> + amdgpu_eventfd_mgr_mark_closing(fpriv->eventfd_mgr);
> pm_runtime_get_sync(dev->dev);
>
> if (amdgpu_device_ip_get_ip_block(adev, AMD_IP_BLOCK_TYPE_UVD) != NULL)
> @@ -1605,10 +1620,19 @@ void amdgpu_driver_postclose_kms(struct drm_device
> *dev,
> idr_destroy(&fpriv->bo_list_handles);
> mutex_destroy(&fpriv->bo_list_lock);
>
> + pm_runtime_put_autosuspend(dev->dev);
> +
> + /*
> + * Drop the eventfd manager BEFORE freeing fpriv.
> + * (mgr can be refcounted by longer-lived producers)
> + */
> + if (fpriv->eventfd_mgr) {
> + amdgpu_eventfd_mgr_put(fpriv->eventfd_mgr);
> + fpriv->eventfd_mgr = NULL;
> + }
> +
> kfree(fpriv);
> file_priv->driver_priv = NULL;
> -
> - pm_runtime_put_autosuspend(dev->dev);
> }
>
>