From: "Kuehling, Felix" <felix.kuehling@amd.com>
To: Heng Zhou <Heng.Zhou@amd.com>,
amd-gfx@lists.freedesktop.org, Alex Sierra <alex.sierra@amd.com>
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] drm/amdkfd: fix lost wakeup in interrupt drain on debugged process exit
Date: Wed, 30 Sep 2026 17:03:46 -0400 [thread overview]
Message-ID: <e546c00d-6686-4f33-9e17-fee0cf25eaed@amd.com> (raw)
In-Reply-To: <20260930134205.469578-1-Heng.Zhou@amd.com>
[-- Attachment #1: Type: text/plain, Size: 9529 bytes --]
On 2026-09-30 09:42, 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.
I suspect this may have been introduced by this patch by Alex:
commit f34034ce5d2116f397008c63901c9c1946e5d665
Author: Alex Sierra<alex.sierra@amd.com>
Date: Fri Jul 24 15:53:48 2026 -0500
drm/amdkfd: disable debug before retiring MES process context on teardown
...
If you can confirm that, please add an appropriate Fixes: tag.
>
> Fix this by:
>
> - Tracking pending drains in a list keyed by PASID instead of looking
> the process up in kfd_processes_table, so the drain fence always
> finds its waiter.
> - 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.
> - Deferring the drain of an exiting debugged process from the
> mmu_notifier release path to kfd_process_wq_release(), before the
> PDDs and their PASIDs are released, so that stale interrupts cannot
> hit a process that reuses the PASID.
>
> Signed-off-by: Heng Zhou<Heng.Zhou@amd.com>
> ---
> drivers/gpu/drm/amd/amdkfd/kfd_debug.c | 3 +-
> drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 7 +-
> drivers/gpu/drm/amd/amdkfd/kfd_process.c | 89 +++++++++++++++++-------
> 3 files changed, 68 insertions(+), 31 deletions(-)
>
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> index 0dd1fd448059..2b227d9de492 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c
> @@ -664,7 +664,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 (!target->irq_drain_deferred)
> + kfd_process_drain_interrupts(pdd);
The way you're setting and clearing p->irq_drain_deferred seems pretty
fragile. I'd prefer to just pass this as a parameter to
kfd_dbg_trap_disable and kfd_dbg_clean_exception_status. Then you also
don't need two different drain functions.
>
> pdd->exception_status = 0;
> }
> diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> index 2847a5ec5ede..c04c3a03f559 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h
> @@ -1030,9 +1030,7 @@ 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;
> + bool irq_drain_deferred;
>
> /* shared virtual memory registered by this process */
> struct svm_range_list svms;
> @@ -1245,7 +1243,8 @@ 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_drain_deferred_interrupts(struct kfd_process *p);
> 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..b2365c21db9e 100644
> --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c
> @@ -55,6 +55,22 @@ DEFINE_MUTEX(kfd_processes_mutex);
>
> DEFINE_SRCU(kfd_processes_srcu);
>
> +/*
> + * Pending interrupt drains, keyed by PASID. Deliberately not keyed off
> + * kfd_processes_table: a draining process may already have been removed from
> + * it, and the drain fence must still be able to find its waiter.
> + */
> +struct kfd_irq_drain_waiter {
> + struct list_head list;
> + u32 pasid;
> + struct completion done;
> +};
> +
> +static DEFINE_SPINLOCK(kfd_irq_drain_lock);
> +static LIST_HEAD(kfd_irq_drain_list);
If you use an XArray here, you have a more efficient lookup and you also
get the spin-lock for free. In fact, that xarray already exists in
amdgpu_ids.c: amdgpu_pasid_xa. It stores a pointer to the fpriv. You can
get it with amdgpu_pasid_get_fpriv_locked. So you could just add the
completion to struct amdgpu_fpriv. Maybe the whole IRQ draining logic
should move to amdgpu_ids.c in that case, and KFD would just invoke it
with a PASID.
Regards,
Felix
> +
> +#define KFD_IRQ_DRAIN_TIMEOUT msecs_to_jiffies(1000)
> +
> /* For process termination handling */
> static struct workqueue_struct *kfd_process_wq;
>
> @@ -1019,8 +1035,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);
> @@ -1344,6 +1358,8 @@ static void kfd_process_wq_release(struct work_struct *work)
> kfd_process_free_outstanding_kfd_bos(p);
> svm_range_list_fini(p);
>
> + kfd_process_drain_deferred_interrupts(p);
> +
> kfd_process_destroy_pdds(p);
> dma_fence_put(ef);
>
> @@ -1420,6 +1436,8 @@ 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.
> */
> + if (p->debug_trap_enabled)
> + p->irq_drain_deferred = true;
> kfd_dbg_trap_disable(p);
>
> if (atomic_read(&p->debugged_process_count) > 0) {
> @@ -2313,17 +2331,16 @@ 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)
> {
> + struct kfd_irq_drain_waiter waiter;
> uint32_t irq_drain_fence[8];
> + unsigned long flags;
> uint8_t node_id = 0;
> - int r = 0;
> + long r;
>
> if (!KFD_IS_SOC15(pdd->dev))
> - return 0;
> -
> - pdd->process->irq_drain_is_open = true;
> + return;
>
> memset(irq_drain_fence, 0, sizeof(irq_drain_fence));
> irq_drain_fence[0] = (KFD_IRQ_FENCE_SOURCEID << 8) |
> @@ -2341,33 +2358,53 @@ int kfd_process_drain_interrupts(struct kfd_process_device *pdd)
> irq_drain_fence[3] |= node_id << 16;
> }
>
> + waiter.pasid = pdd->pasid;
> + init_completion(&waiter.done);
> +
> + spin_lock_irqsave(&kfd_irq_drain_lock, flags);
> + list_add(&waiter.list, &kfd_irq_drain_list);
> + spin_unlock_irqrestore(&kfd_irq_drain_lock, flags);
> +
> /* 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;
> + if (!amdgpu_amdkfd_send_close_event_drain_irq(pdd->dev->adev,
> + irq_drain_fence)) {
> + r = wait_for_completion_interruptible_timeout(&waiter.done,
> + KFD_IRQ_DRAIN_TIMEOUT);
> + if (r <= 0)
> + dev_warn_ratelimited(pdd->dev->adev->dev,
> + "irq drain on pasid 0x%x did not complete (%ld)\n",
> + waiter.pasid, r);
> }
>
> - r = wait_event_interruptible(pdd->process->wait_irq_drain,
> - !READ_ONCE(pdd->process->irq_drain_is_open));
> - if (r)
> - pdd->process->irq_drain_is_open = false;
> -
> - return r;
> + spin_lock_irqsave(&kfd_irq_drain_lock, flags);
> + list_del(&waiter.list);
> + spin_unlock_irqrestore(&kfd_irq_drain_lock, flags);
> }
>
> -void kfd_process_close_interrupt_drain(unsigned int pasid)
> +void kfd_process_drain_deferred_interrupts(struct kfd_process *p)
> {
> - struct kfd_process *p;
> -
> - p = kfd_lookup_process_by_pasid(pasid, NULL);
> + int i;
>
> - if (!p)
> + if (!p->irq_drain_deferred)
> return;
>
> - WRITE_ONCE(p->irq_drain_is_open, false);
> - wake_up_all(&p->wait_irq_drain);
> - kfd_unref_process(p);
> + p->irq_drain_deferred = false;
> +
> + for (i = 0; i < p->n_pdds; i++)
> + kfd_process_drain_interrupts(p->pdds[i]);
> +}
> +
> +void kfd_process_close_interrupt_drain(unsigned int pasid)
> +{
> + struct kfd_irq_drain_waiter *waiter;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&kfd_irq_drain_lock, flags);
> + list_for_each_entry(waiter, &kfd_irq_drain_list, list) {
> + if (waiter->pasid == pasid)
> + complete(&waiter->done);
> + }
> + spin_unlock_irqrestore(&kfd_irq_drain_lock, flags);
> }
>
> struct send_exception_work_handler_workarea {
[-- Attachment #2: Type: text/html, Size: 10334 bytes --]
next prev parent reply other threads:[~2026-09-30 21:03 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 [this message]
2026-10-08 14:06 ` [PATCH v2] " Heng Zhou
2026-10-08 21:59 ` Kuehling, Felix
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=e546c00d-6686-4f33-9e17-fee0cf25eaed@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=alex.sierra@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.