From: sashiko-bot@kernel.org
To: "Daniel Byrne" <daniel1.byrne@intel.com>
Cc: dmaengine@vger.kernel.org, vkoul@kernel.org, Frank.Li@kernel.org
Subject: Re: [PATCH 17/17] dmaengine: idxd: run the interrupt-handle revoke under the queue lock
Date: Mon, 19 Oct 2026 19:53:59 +0000 [thread overview]
Message-ID: <sashiko-outbox-178156@kernel.org> (raw)
In-Reply-To: <20261019150630.4026341-18-daniel1.byrne@intel.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] dmaengine: idxd: deadlock on wq_lock in idxd_abort_invalid_int_handle_descs()
- [High] dmaengine: idxd: drain and handle update on killed wq in idxd_int_handle_revoke()
- [Medium] dmaengine: idxd: handle leak and rebind race in idxd_int_handle_revoke()
--
commit 2920ff390e78f9271a288774e6a5796ea738663d
Author: Daniel Byrne <daniel1.byrne@intel.com>
dmaengine: idxd: run the interrupt-handle revoke under the queue lock
This commit acquires wq_lock in idxd_int_handle_revoke() after requesting a
new interrupt handle so that handle updates, drains, or failure quiesce and
descriptor aborts serialize against workqueue unbind and halt.
> diff --git a/drivers/dma/idxd/irq.c b/drivers/dma/idxd/irq.c
> index 4517a09fcf1b9..8217698056bb4 100644
> --- a/drivers/dma/idxd/irq.c
> +++ b/drivers/dma/idxd/irq.c
> @@ -115,6 +115,11 @@ static void idxd_abort_invalid_int_handle_descs(struct idxd_irq_entry *ie)
> struct idxd_desc *d, *t;
> struct llist_node *head;
>
> + /*
> + * An unbind frees the descriptors and the allocator under
> + * wq_lock, so they are completed under it as well.
> + */
> + lockdep_assert_held(&ie_to_wq(ie)->wq_lock);
> spin_lock(&ie->list_lock);
> head = llist_del_all(&ie->pending_llist);
> if (head) {
> @@ -142,6 +147,7 @@ static void idxd_int_handle_revoke(struct work_struct *work)
> struct pci_dev *pdev = idxd->pdev;
> struct device *dev = &pdev->dev;
> int i, new_handle, rc;
> + bool killed;
>
> if (!idxd->request_int_handles) {
> kfree(revoke);
> @@ -166,15 +172,31 @@ static void idxd_int_handle_revoke(struct work_struct *work)
> continue;
>
> rc = idxd_device_request_int_handle(idxd, i, &new_handle, IDXD_IRQ_MSIX);
> - if (rc < 0) {
> + if (rc < 0)
> dev_warn(dev, "get int handle %d failed: %d\n", i, rc);
> +
> + /*
> + * The request runs without wq_lock, so that a reset can end
> + * it, and the queue may have been unbound meanwhile. An
> + * unbind invalidates the handle in the same wq_lock hold
> + * that unmaps the portal and frees the descriptors, so a
> + * handle still valid under the lock means that the queue
> + * still has its portal, its descriptors and its completion
> + * handler. Everything below, the drain included, stays in
> + * this hold.
> + */
> + guard(mutex)(&wq->wq_lock);
> + if (ie->int_handle == INVALID_INT_HANDLE)
> + continue;
[Severity: Medium]
Can this leak new_handle or race with a concurrent unbind and rebind while
wq->wq_lock is not held during idxd_device_request_int_handle()?
First, if idxd_device_request_int_handle() succeeds (rc == 0) while a
concurrent unbind runs idxd_drv_disable_wq() -> idxd_wq_free_irq():
drivers/dma/idxd/device.c:idxd_wq_free_irq() {
if (idxd->request_int_handles)
idxd_device_release_int_handle(idxd, ie->int_handle, IDXD_IRQ_MSIX);
idxd_device_clear_perm_entry(idxd, ie);
ie->vector = -1;
ie->int_handle = INVALID_INT_HANDLE;
ie->pasid = IOMMU_PASID_INVALID;
}
idxd_wq_free_irq() only releases the old ie->int_handle and sets
ie->int_handle = INVALID_INT_HANDLE. When idxd_int_handle_revoke() then
acquires wq->wq_lock and sees ie->int_handle == INVALID_INT_HANDLE, it hits
continue without calling idxd_device_release_int_handle() on new_handle.
Second, if the queue is unbound and then rebound via idxd_drv_enable_wq() ->
idxd_wq_request_irq() before idxd_int_handle_revoke() acquires wq->wq_lock:
drivers/dma/idxd/device.c:idxd_wq_request_irq() {
if (idxd->request_int_handles) {
rc = idxd_device_request_int_handle(idxd, ie->id, &ie->int_handle,
IDXD_IRQ_MSIX);
if (rc < 0)
goto err_int_handle;
} else {
ie->int_handle = ie->id;
}
}
ie->int_handle is populated with a new valid handle for the rebound queue.
When idxd_int_handle_revoke() acquires wq->wq_lock, ie->int_handle is no
longer INVALID_INT_HANDLE, so it treats the newly rebound queue as the old
binding: if the earlier idxd_device_request_int_handle() failed (for example,
while the permission entry was cleared during the unbind), it overwrites the
new ie->int_handle with INVALID_INT_HANDLE and quiesces the rebound queue; if
it succeeded before the rebind, it overwrites the rebound queue's
ie->int_handle with the stale new_handle.
> +
> + if (rc < 0) {
> /*
> * Failed to acquire new interrupt handle. Kill the WQ
> * and release all the pending submitters. The submitters will
> * get error return code and handle appropriately.
> */
> ie->int_handle = INVALID_INT_HANDLE;
> - idxd_wq_quiesce(wq);
> + __idxd_wq_quiesce(wq);
> idxd_abort_invalid_int_handle_descs(ie);
> continue;
> }
[Severity: High]
Can calling idxd_abort_invalid_int_handle_descs() while holding wq->wq_lock
deadlock on wq->wq_lock?
idxd_abort_invalid_int_handle_descs() completes descriptors synchronously:
idxd_int_handle_revoke()
guard(mutex)(&wq->wq_lock)
idxd_abort_invalid_int_handle_descs(ie)
idxd_desc_complete(d, IDXD_COMPLETE_ABORT, true)
idxd_dma_complete_txd(d, IDXD_COMPLETE_ABORT, true, NULL, NULL)
dmaengine_desc_get_callback_invoke(tx, &res)
For a dmaengine workqueue, the client completion callback is invoked
synchronously under wq->wq_lock. As noted in idxd_flush_pending_descs() in
drivers/dma/idxd/device.c:
/*
* A halt flush that delivers the callbacks of a dmaengine queue
* leaves its descriptors to the interrupt thread, which runs them
* without wq_lock, as it runs every completion. A callback may
* terminate the channel, which takes that lock, and the halt flush
* holds it. The halted device writes no record any more, so one it
* never finished is marked aborted for the thread to complete.
*/
If the client callback terminates the channel on error via
dmaengine_terminate_async() or dmaengine_terminate_all(), it enters
idxd_dma_terminate_all():
drivers/dma/idxd/dma.c:idxd_dma_terminate_all() {
struct idxd_wq *wq = to_idxd_wq(c);
guard(mutex)(&wq->wq_lock);
...
}
Wouldn't that immediately self-deadlock on wq->wq_lock?
> @@ -193,21 +215,20 @@ static void idxd_int_handle_revoke(struct work_struct *work)
> continue;
> }
>
> - mutex_lock(&wq->wq_lock);
> - reinit_completion(&wq->wq_resurrect);
> -
> - /* Kill percpu_ref to pause additional descriptor submissions */
> - percpu_ref_kill(&wq->wq_active);
> -
> - /* Wait for all submitters quiesce before we change interrupt handle */
> - wait_for_completion(&wq->wq_dead);
> + /*
> + * Kill percpu_ref to pause additional descriptor submissions
> + * and wait for all submitters to quiesce before we change
> + * the interrupt handle. A queue a halt already killed and did
> + * not revive is left dead.
> + */
> + killed = idxd_wq_kill(wq);
>
> ie->int_handle = new_handle;
>
> /* Revive percpu ref and wake up all the waiting submitters */
> - percpu_ref_reinit(&wq->wq_active);
> + if (killed)
> + percpu_ref_reinit(&wq->wq_active);
> complete_all(&wq->wq_resurrect);
> - mutex_unlock(&wq->wq_lock);
>
> /*
> * The delay here is to wait for all possible MOVDIR64B that
[Severity: High]
When idxd_wq_kill(wq) returns false because a halt or teardown quiesce
already killed wq->wq_active, is it safe to still update ie->int_handle and
call idxd_int_handle_revoke_drain(ie)?
In idxd_halt() in drivers/dma/idxd/irq.c:
if (halt_flr(err)) {
idxd->state = IDXD_DEV_HALTED;
...
idxd_wqs_halt_flush(idxd, false);
for (i = 0; i < idxd->max_wqs; i++) {
struct idxd_wq *wq = idxd->wqs[i];
if (wq->type == IDXD_WQT_KERNEL)
WRITE_ONCE(wq->ie.int_handle, INVALID_INT_HANDLE);
}
...
} else {
idxd->state = IDXD_DEV_HALTED;
/* Same as above: nothing in flight will ever complete. */
idxd_wqs_halt_flush(idxd, false);
idxd_wqs_unmap_portal(idxd);
idxd_device_clear_state(idxd);
In the system-reset branch, idxd_wqs_halt_flush() kills wq->wq_active under
wq->wq_lock and unlocks, while wq->state is still IDXD_WQ_ENABLED and
ie->int_handle is not invalidated. Next, idxd_wqs_unmap_portal() unmaps
wq->portal and sets wq->portal = NULL without holding wq->wq_lock before
idxd_device_clear_state() clears wq->state under wq->wq_lock (and on
non-configurable devices without IDXD_FLAG_CONFIGURABLE,
idxd_device_clear_state() does not clear wq->state at all).
If idxd_int_handle_revoke() acquires wq->wq_lock after idxd_wqs_halt_flush(),
idxd_wq_kill(wq) returns false, but idxd_int_handle_revoke() proceeds to
idxd_int_handle_revoke_drain(ie):
drivers/dma/idxd/irq.c:idxd_int_handle_revoke_drain() {
...
portal = idxd_wq_portal_addr(wq);
...
wmb();
if (wq_dedicated(wq)) {
iosubmit_cmds512(portal, &desc, 1);
} else {
rc = idxd_enqcmds(wq, portal, &desc);
which dereferences wq->portal while it is NULL or being unmapped.
Similarly, in the FLR branch of idxd_halt(), wq->ie.int_handle is set to
INVALID_INT_HANDLE outside wq->wq_lock after idxd_wqs_halt_flush(). If
idxd_int_handle_revoke() acquires wq->wq_lock after idxd_wqs_halt_flush()
before that write, or if iaa_crypto_remove() has called idxd_wq_quiesce(wq)
prior to disabling the queue, idxd_wq_kill(wq) returns false, yet
idxd_int_handle_revoke() still writes ie->int_handle = new_handle and submits
a hardware drain descriptor to the halted or quiesced queue.
Should idxd_int_handle_revoke() avoid updating ie->int_handle and issuing the
drain when killed is false (or when the device is halted)?
титься
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019150630.4026341-1-daniel1.byrne@intel.com?part=17
next prev parent reply other threads:[~2026-10-19 19:54 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 15:06 [PATCH 00/17] [PATCH 00/17] dmaengine: idxd: fixes found by an in-kernel polling client Daniel Byrne
2026-10-19 15:06 ` [PATCH 01/17] dmaengine: idxd: don't queue EVL fault work for kernel WQs Daniel Byrne
2026-10-19 15:06 ` [PATCH 02/17] dmaengine: idxd: detach the user fault workqueue under the event log lock Daniel Byrne
2026-10-19 15:06 ` [PATCH 03/17] dmaengine: idxd: refuse a memcpy longer than the WQ max transfer size Daniel Byrne
2026-10-19 21:19 ` Frank Li
2026-10-19 15:06 ` [PATCH 04/17] dmaengine: idxd: list a new channel only once it is complete Daniel Byrne
2026-10-19 15:06 ` [PATCH 05/17] dmaengine: idxd: unbind the device driver before its work queues go away Daniel Byrne
2026-10-19 15:06 ` [PATCH 06/17] dmaengine: idxd: keep the interrupt handle across terminate_all Daniel Byrne
2026-10-19 15:06 ` [PATCH 07/17] dmaengine: idxd: bound device command waits and fail fast after a timeout Daniel Byrne
2026-10-19 17:05 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 08/17] dmaengine: idxd: compare the device disable status code whole Daniel Byrne
2026-10-19 15:06 ` [PATCH 09/17] dmaengine: idxd: quarantine terminate_all's descriptor slots until the device has drained Daniel Byrne
2026-10-19 17:26 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 10/17] dmaengine: idxd: return -ETIMEDOUT from drains and resets and leak what the device may still write Daniel Byrne
2026-10-19 17:50 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 11/17] dmaengine: idxd: disable the device before its work queues are unregistered Daniel Byrne
2026-10-19 18:09 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 12/17] dmaengine: idxd: hold device commands off while the reset re-creates its interrupts Daniel Byrne
2026-10-19 18:19 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 13/17] dmaengine: idxd: fix the double free of a descriptor whose submission failed Daniel Byrne
2026-10-19 15:06 ` [PATCH 14/17] dmaengine: idxd: complete an iaa_crypto descriptor through its own driver on abort and flush Daniel Byrne
2026-10-19 18:56 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 15/17] dmaengine: idxd: flush the work queues on every halt type Daniel Byrne
2026-10-19 19:17 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 16/17] dmaengine: idxd: keep client callbacks on a halt flush and cancel resubmissions after a terminate Daniel Byrne
2026-10-19 19:36 ` sashiko-bot
2026-10-19 15:06 ` [PATCH 17/17] dmaengine: idxd: run the interrupt-handle revoke under the queue lock Daniel Byrne
2026-10-19 19:53 ` sashiko-bot [this message]
2026-10-19 19:09 ` [PATCH 00/17] [PATCH 00/17] dmaengine: idxd: fixes found by an in-kernel polling client Frank Li
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=sashiko-outbox-178156@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=daniel1.byrne@intel.com \
--cc=dmaengine@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=vkoul@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