From: "Kuehling, Felix" <felix.kuehling@amd.com>
To: Heng Zhou <Heng.Zhou@amd.com>, amd-gfx@lists.freedesktop.org
Cc: Lijo.Lazar@amd.com, Christian.Koenig@amd.com, Emily.Deng@amd.com,
Victor.Zhao@amd.com, phasta@kernel.org, Qing.Ma@amd.com,
HaiJun.Chang@amd.com, Jonathan.Kim@amd.com
Subject: Re: [PATCH v2] drm/amdkfd: fix lost wakeup in interrupt drain on debugged process exit
Date: Thu, 8 Oct 2026 17:59:12 -0400 [thread overview]
Message-ID: <cda6720c-0957-4345-9cbf-e78deaa2cc8a@amd.com> (raw)
In-Reply-To: <20261008140605.944513-1-Heng.Zhou@amd.com>
On 2026-10-08 10:06, Heng Zhou wrote:
> When a debugged process exits, kfd_process_notifier_release_internal()
> first removes it from kfd_processes_table and then calls
> kfd_dbg_trap_disable(), which drains the process interrupts. The drain
> sends a fence through the IH and waits, without a timeout, for
> kfd_process_close_interrupt_drain() to wake it up. That wakeup looks
> the process up by PASID in kfd_processes_table, which no longer holds
> it, so the wakeup is lost and the exiting task sleeps forever. The
> fatal signal has already been consumed in do_exit(), so the
> interruptible wait cannot be broken either.
>
> The wait happens inside the mmu_notifier release callback, i.e. within
> the global mmu_notifier SRCU read-side critical section, so every
> synchronize_srcu() on it stalls as well. Any other process releasing
> its mm then hangs in D state and the system needs a reboot. This is
> seen with rocgdb on MI300X.
>
> Fix this by:
>
> - Waiting on a completion in struct amdgpu_fpriv, looked up by PASID
> through amdgpu_pasid_xa, instead of looking the process up in
> kfd_processes_table. The DRM file owning the PASID is still
> referenced by KFD while it drains, so the drain fence always finds
> its waiter. The drain logic moves to amdgpu_ids.c and KFD only
> passes the PASID.
> - Bounding the wait with a timeout, since the fence can be lost when
> the IH ring or the KFD interrupt FIFO overflows, and warning when the
> drain does not complete.
> - Passing drain_irqs to kfd_dbg_trap_disable() and skipping the drain
> when the process itself exits. Its interrupts are no longer delivered
> once it is out of kfd_processes_table, and its PASID owner is cleared
> before the PASID is freed and later reallocated cyclically.
>
> v2: track drains with a completion in amdgpu_fpriv via amdgpu_pasid_xa
> and move the drain logic to amdgpu_ids.c; pass drain_irqs to
> kfd_dbg_trap_disable() and skip the drain on process exit instead
> of deferring it (Felix)
>
> Fixes: 12fb1ad70d65 ("drm/amdkfd: update process interrupt handling for debug events")
> Signed-off-by: Heng Zhou <Heng.Zhou@amd.com>
> ---
> drivers/gpu/drm/amd/amdgpu/amdgpu.h | 3 ++
> drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c | 62 ++++++++++++++++++++++++
> drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h | 3 ++
> drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c | 2 +
> drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 2 +-
> drivers/gpu/drm/amd/amdkfd/kfd_debug.c | 10 ++--
> drivers/gpu/drm/amd/amdkfd/kfd_debug.h | 2 +-
> drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 6 +--
> drivers/gpu/drm/amd/amdkfd/kfd_process.c | 46 ++++++------------
> 9 files changed, 93 insertions(+), 43 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> index ce671731b700..c32d3b0b4153 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h
> @@ -426,6 +426,9 @@ struct amdgpu_fpriv {
>
> /** GPU partition selection */
> uint32_t xcp_id;
> +
> + /** Signaled when a KFD interrupt drain fence for this PASID is seen */
> + struct completion irq_drain_done;
> };
>
> int amdgpu_file_to_fpriv(struct file *filp, struct amdgpu_fpriv **fpriv);
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c
> index 5e424e3b6e71..87cc297109a0 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.c
> @@ -171,6 +171,68 @@ struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid)
> return xa_load(&amdgpu_pasid_xa, pasid);
> }
>
> +/**
> + * amdgpu_pasid_drain_irq - wait for pending KFD interrupts of a PASID
> + * @adev: amdgpu device the drain fence is sent to
> + * @pasid: PASID whose interrupts should be drained
> + * @ih_fence: IH entry injected as the drain fence
> + * @timeout: maximum time to wait, in jiffies
> + *
> + * Injects @ih_fence behind the interrupts already queued for KFD and waits
> + * until amdgpu_pasid_drain_irq_done() is called for @pasid.
> + *
> + * The caller must hold a reference to the DRM file that owns @pasid, so
> + * that its amdgpu_fpriv stays valid after the PASID lock is dropped.
> + *
> + * Returns:
> + * 0 on success or when no fence could be sent, -ENOENT if @pasid has no
> + * owner, -ETIMEDOUT on timeout or -ERESTARTSYS if interrupted.
> + */
> +int amdgpu_pasid_drain_irq(struct amdgpu_device *adev, u32 pasid,
> + u32 *ih_fence, unsigned long timeout)
> +{
> + struct amdgpu_fpriv *fpriv;
> + unsigned long flags;
> + long r;
> +
> + amdgpu_pasid_lock(&flags);
> + fpriv = amdgpu_pasid_get_fpriv_locked(pasid);
> + if (fpriv)
> + reinit_completion(&fpriv->irq_drain_done);
> + amdgpu_pasid_unlock(flags);
> + if (!fpriv)
> + return -ENOENT;
> +
> + if (amdgpu_amdkfd_send_close_event_drain_irq(adev, ih_fence))
> + return 0;
> +
> + r = wait_for_completion_interruptible_timeout(&fpriv->irq_drain_done,
> + timeout);
> + if (!r)
> + return -ETIMEDOUT;
> +
> + return r < 0 ? r : 0;
> +}
> +
> +/**
> + * amdgpu_pasid_drain_irq_done - signal that a KFD interrupt drain completed
> + * @pasid: PASID carried by the drain fence
> + *
> + * Called by KFD when it processes the drain fence injected by
> + * amdgpu_pasid_drain_irq().
> + */
> +void amdgpu_pasid_drain_irq_done(u32 pasid)
> +{
> + struct amdgpu_fpriv *fpriv;
> + unsigned long flags;
> +
> + amdgpu_pasid_lock(&flags);
> + fpriv = amdgpu_pasid_get_fpriv_locked(pasid);
> + if (fpriv)
> + complete(&fpriv->irq_drain_done);
> + amdgpu_pasid_unlock(flags);
> +}
> +
> /**
> * amdgpu_pasid_free_delayed - free pasid when fences signal
> *
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h
> index 46b2c2160126..2af733d96d60 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ids.h
> @@ -98,6 +98,9 @@ int amdgpu_pasid_alloc(unsigned int bits, struct amdgpu_fpriv *fpriv);
> void amdgpu_pasid_lock(unsigned long *flags);
> void amdgpu_pasid_unlock(unsigned long flags);
> struct amdgpu_fpriv *amdgpu_pasid_get_fpriv_locked(u32 pasid);
> +int amdgpu_pasid_drain_irq(struct amdgpu_device *adev, u32 pasid,
> + u32 *ih_fence, unsigned long timeout);
> +void amdgpu_pasid_drain_irq_done(u32 pasid);
> void amdgpu_pasid_free(u32 pasid);
> void amdgpu_pasid_free_delayed(struct dma_resv *resv,
> u32 pasid);
> diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> index bac072d73e16..8b080e2e1496 100644
> --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_kms.c
> @@ -1507,6 +1507,8 @@ int amdgpu_driver_open_kms(struct drm_device *dev, struct drm_file *file_priv)
> if (r)
> goto error_pasid;
>
> + init_completion(&fpriv->irq_drain_done);
> +
> pasid = amdgpu_pasid_alloc(16, fpriv);
> if (pasid < 0) {
> dev_warn(adev->dev, "No more PASIDs available!");
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
> index 1d86ddfd6bde..b3ebbee76088 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c
> @@ -3223,7 +3223,7 @@ static int kfd_ioctl_set_debug_trap(struct file *filep, struct kfd_process *p, v
>
> break;
> case KFD_IOC_DBG_TRAP_DISABLE:
> - r = kfd_dbg_trap_disable(target);
> + r = kfd_dbg_trap_disable(target, true);
> break;
> case KFD_IOC_DBG_TRAP_SEND_RUNTIME_EVENT:
> r = kfd_dbg_send_exception_to_runtime(target,
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> index 0dd1fd448059..8bed0d0f9bd2 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> @@ -655,7 +655,8 @@ void kfd_dbg_trap_deactivate(struct kfd_process *target, bool unwind, int unwind
> kfd_dbg_set_workaround(target, false);
> }
>
> -static void kfd_dbg_clean_exception_status(struct kfd_process *target)
> +static void kfd_dbg_clean_exception_status(struct kfd_process *target,
> + bool drain_irqs)
> {
> struct process_queue_manager *pqm;
> struct process_queue_node *pqn;
> @@ -664,7 +665,8 @@ static void kfd_dbg_clean_exception_status(struct kfd_process *target)
> for (i = 0; i < target->n_pdds; i++) {
> struct kfd_process_device *pdd = target->pdds[i];
>
> - kfd_process_drain_interrupts(pdd);
> + if (drain_irqs)
> + kfd_process_drain_interrupts(pdd);
>
> pdd->exception_status = 0;
> }
> @@ -680,7 +682,7 @@ static void kfd_dbg_clean_exception_status(struct kfd_process *target)
> target->exception_status = 0;
> }
>
> -int kfd_dbg_trap_disable(struct kfd_process *target)
> +int kfd_dbg_trap_disable(struct kfd_process *target, bool drain_irqs)
> {
> if (!target->debug_trap_enabled)
> return 0;
> @@ -704,7 +706,7 @@ int kfd_dbg_trap_disable(struct kfd_process *target)
> }
>
> target->debug_trap_enabled = false;
> - kfd_dbg_clean_exception_status(target);
> + kfd_dbg_clean_exception_status(target, drain_irqs);
> kfd_unref_process(target);
>
> return 0;
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.h b/drivers/gpu/drm/amd/amdkfd/kfd_debug.h
> index fbb751821c69..5babf9b992cd 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.h
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.h
> @@ -43,7 +43,7 @@ bool kfd_dbg_ev_raise(uint64_t event_mask,
> unsigned int source_id, bool use_worker,
> void *exception_data,
> size_t exception_data_size);
> -int kfd_dbg_trap_disable(struct kfd_process *target);
> +int kfd_dbg_trap_disable(struct kfd_process *target, bool drain_irqs);
> int kfd_dbg_trap_enable(struct kfd_process *target, uint32_t fd,
> void __user *runtime_info,
> uint32_t *runtime_info_size);
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> index 2847a5ec5ede..ef9c698bd5cb 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> @@ -1030,10 +1030,6 @@ struct kfd_process {
> uint64_t exception_enable_mask;
> uint64_t exception_status;
>
> - /* Used to drain stale interrupts */
> - wait_queue_head_t wait_irq_drain;
> - bool irq_drain_is_open;
> -
> /* shared virtual memory registered by this process */
> struct svm_range_list svms;
>
> @@ -1245,7 +1241,7 @@ bool enqueue_ih_ring_entry(struct kfd_node *kfd, const void *ih_ring_entry);
> bool interrupt_is_wanted(struct kfd_node *dev,
> const uint32_t *ih_ring_entry,
> uint32_t *patched_ihre, bool *flag);
> -int kfd_process_drain_interrupts(struct kfd_process_device *pdd);
> +void kfd_process_drain_interrupts(struct kfd_process_device *pdd);
> void kfd_process_close_interrupt_drain(unsigned int pasid);
>
> /* amdkfd Apertures */
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> index 226f52626e84..c1a01260463e 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> @@ -55,6 +55,8 @@ DEFINE_MUTEX(kfd_processes_mutex);
>
> DEFINE_SRCU(kfd_processes_srcu);
>
> +#define KFD_IRQ_DRAIN_TIMEOUT msecs_to_jiffies(1000)
Could we use AMDGPU_FENCE_JIFFIES_TIMEOUT here instead of inventing a
new arbitrary timeout? If interrupts take longer than the fence-timeout
to drain, surely fences will timeout, too.
> +
> /* For process termination handling */
> static struct workqueue_struct *kfd_process_wq;
>
> @@ -1019,8 +1021,6 @@ struct kfd_process *kfd_create_process(struct task_struct *thread)
> if (ret)
> pr_warn("Failed to create debugfs entry for the kfd_process, ret = %d\n",
> ret);
> -
> - init_waitqueue_head(&process->wait_irq_drain);
> }
> out:
> mutex_unlock(&kfd_processes_mutex);
> @@ -1420,7 +1420,7 @@ void kfd_process_notifier_release_internal(struct kfd_process *p)
> * back on the MES list and, once proc_ctx_bo is freed, MES would fault
> * on the freed process context. Retiring the context last avoids this.
> */
> - kfd_dbg_trap_disable(p);
> + kfd_dbg_trap_disable(p, false);
>
> if (atomic_read(&p->debugged_process_count) > 0) {
> struct kfd_process *target;
> @@ -1430,7 +1430,7 @@ void kfd_process_notifier_release_internal(struct kfd_process *p)
> hash_for_each_rcu(kfd_processes_table, temp, target, kfd_processes) {
> if (target->debugger_process && target->debugger_process == p) {
> mutex_lock_nested(&target->mutex, 1);
> - kfd_dbg_trap_disable(target);
> + kfd_dbg_trap_disable(target, true);
> mutex_unlock(&target->mutex);
> if (atomic_read(&p->debugged_process_count) == 0)
> break;
> @@ -2313,17 +2313,14 @@ int kfd_resume_all_processes(void)
> return ret;
> }
>
> -/* assumes caller holds process lock. */
> -int kfd_process_drain_interrupts(struct kfd_process_device *pdd)
> +void kfd_process_drain_interrupts(struct kfd_process_device *pdd)
> {
> uint32_t irq_drain_fence[8];
> uint8_t node_id = 0;
> - int r = 0;
> -
> - if (!KFD_IS_SOC15(pdd->dev))
> - return 0;
> + int r;
>
> - pdd->process->irq_drain_is_open = true;
> + if (!KFD_IS_SOC15(pdd->dev) || !pdd->drm_priv)
> + return;
>
> memset(irq_drain_fence, 0, sizeof(irq_drain_fence));
> irq_drain_fence[0] = (KFD_IRQ_FENCE_SOURCEID << 8) |
> @@ -2342,32 +2339,17 @@ int kfd_process_drain_interrupts(struct kfd_process_device *pdd)
> }
>
> /* ensure stale irqs scheduled KFD interrupts and send drain fence. */
> - if (amdgpu_amdkfd_send_close_event_drain_irq(pdd->dev->adev,
> - irq_drain_fence)) {
> - pdd->process->irq_drain_is_open = false;
> - return 0;
> - }
> -
> - r = wait_event_interruptible(pdd->process->wait_irq_drain,
> - !READ_ONCE(pdd->process->irq_drain_is_open));
> + r = amdgpu_pasid_drain_irq(pdd->dev->adev, pdd->pasid, irq_drain_fence,
> + KFD_IRQ_DRAIN_TIMEOUT);
> if (r)
> - pdd->process->irq_drain_is_open = false;
> -
> - return r;
> + dev_warn_ratelimited(pdd->dev->adev->dev,
> + "irq drain on pasid 0x%x did not complete (%d)\n",
> + pdd->pasid, r);
> }
>
> void kfd_process_close_interrupt_drain(unsigned int pasid)
Please get rid of this wrapper and call amdgpu_pasid_drain_irq_done
directly from the interrupt handlers.
Thanks,
Felix
> {
> - struct kfd_process *p;
> -
> - p = kfd_lookup_process_by_pasid(pasid, NULL);
> -
> - if (!p)
> - return;
> -
> - WRITE_ONCE(p->irq_drain_is_open, false);
> - wake_up_all(&p->wait_irq_drain);
> - kfd_unref_process(p);
> + amdgpu_pasid_drain_irq_done(pasid);
> }
>
> struct send_exception_work_handler_workarea {
next prev parent reply other threads:[~2026-10-08 21:59 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 13:42 [PATCH] drm/amdkfd: fix lost wakeup in interrupt drain on debugged process exit Heng Zhou
2026-09-30 21:03 ` Kuehling, Felix
2026-10-08 14:06 ` [PATCH v2] " Heng Zhou
2026-10-08 21:59 ` Kuehling, Felix [this message]
2026-10-09 6:45 ` [PATCH v3] " Heng Zhou
2026-10-09 15:20 ` Kuehling, Felix
2026-10-08 14:10 ` 回复: [PATCH] " Zhou, Heng
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cda6720c-0957-4345-9cbf-e78deaa2cc8a@amd.com \
--to=felix.kuehling@amd.com \
--cc=Christian.Koenig@amd.com \
--cc=Emily.Deng@amd.com \
--cc=HaiJun.Chang@amd.com \
--cc=Heng.Zhou@amd.com \
--cc=Jonathan.Kim@amd.com \
--cc=Lijo.Lazar@amd.com \
--cc=Qing.Ma@amd.com \
--cc=Victor.Zhao@amd.com \
--cc=amd-gfx@lists.freedesktop.org \
--cc=phasta@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox