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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox