From: Baolu Lu <baolu.lu@linux.intel.com>
To: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Cc: baolu.lu@linux.intel.com, bikuan.zbk@alibaba-inc.com,
dwmw2@infradead.org, iommu@lists.linux.dev, joro@8bytes.org,
linux-kernel@vger.kernel.org, robin.murphy@arm.com,
will@kernel.org
Subject: Re: [PATCH v3] iommu/vt-d: Fix IQE handling to cover all descriptors in submission range
Date: Wed, 16 Sep 2026 14:23:27 +0800 [thread overview]
Message-ID: <e56a23f2-216c-41ea-991d-495962e6b7af@linux.intel.com> (raw)
In-Reply-To: <20260914141958.584919-1-guanghuifeng@linux.alibaba.com>
On 9/14/2026 10:19 PM, Guanghui Feng wrote:
> When an Invalidation Queue Error (IQE) occurs, hardware halts fetching
> and IQH points at the faulting descriptor. The previous code only
> checked whether IQH matched the first descriptor index of the current
> submission, missing faults on any other descriptor within the batch.
>
> Expand the check to cover the entire submission range [index, wait_index],
> accounting for circular wrap-around.
>
> Furthermore, after detecting IQE, the old recovery only replaced the
> single faulting slot with a copy of the wait descriptor and immediately
> returned -EINVAL. This left two problems:
>
> a) Hardware resumed fetching and could hit another invalid descriptor
> in the same abandoned batch, raising a second IQE that no submitter
> would claim — permanently deadlocking the queue.
>
> b) The caller reclaimed all batch slots (QI_FREE) while hardware might
> still be asynchronously processing descriptors from that batch,
> allowing concurrent overwrite and descriptor corruption.
>
> Fix both by introducing qi_drain_remaining_descs(): upon IQE detection,
> overwrite the stranded slots in [IQH, wait_index) with no-op wait
> descriptors, resubmit the wait descriptor at wait_index, then clear IQE.
> The caller's existing poll loop naturally waits for QI_DONE — which per
> VT-d spec §6.5.2.12 is only written after ALL preceding descriptors
> complete — guaranteeing hardware has fully drained the batch before
> slots are reclaimed.
>
> Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
> Signed-off-by: bikuan.zbk <bikuan.zbk@alibaba-inc.com>
> ---
> drivers/iommu/intel/dmar.c | 78 ++++++++++++++++++++++++++++++--------
> 1 file changed, 63 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
> index ba675b08cd20..db92fb247131 100644
> --- a/drivers/iommu/intel/dmar.c
> +++ b/drivers/iommu/intel/dmar.c
> @@ -1344,6 +1344,43 @@ static void qi_dump_fault(struct intel_iommu *iommu, u32 fault)
> (unsigned long long)desc->qw1);
> }
>
> +/**
> + * qi_drain_remaining_descs - drain descriptors stranded by an IQE
> + * @iommu: the affected IOMMU
> + * @wait_index: slot index of the wait descriptor of the current submission
> + * @shift: qi_shift(iommu), converts a slot index into a byte offset
> + * @options: QI_OPT_* flags of the current submission
> + *
> + * Overwrite the slots in [IQH, @wait_index) with no-op descriptors, resubmit
> + * the wait descriptor at @wait_index, and clear IQE so hardware resumes and
> + * eventually signals QI_DONE. Must be called with qi->q_lock held.
> + */
> +static void qi_drain_remaining_descs(struct intel_iommu *iommu,
> + int wait_index, int shift,
> + unsigned long options)
This is only an internal helper, so there is no need to add a kernel-doc
comment like this.
> +{
> + struct q_inval *qi = iommu->qi;
> + struct qi_desc desc;
> + int head_idx;
> + int cur;
> +
> + head_idx = (readl(iommu->reg + DMAR_IQH_REG)) >> shift;
> +
> + memset(&desc, 0, sizeof(desc));
> + desc.qw0 = QI_IWD_TYPE;
> + for (cur = head_idx; cur != wait_index; cur = (cur + 1) % QI_LENGTH)
> + memcpy(qi->desc + (cur << shift), &desc, 1 << shift);
> +
> + desc.qw0 = QI_IWD_STATUS_DATA(QI_DONE) |
> + QI_IWD_STATUS_WRITE | QI_IWD_TYPE;
> + if (options & QI_OPT_WAIT_DRAIN)
> + desc.qw0 |= QI_IWD_PRQ_DRAIN;
> + desc.qw1 = virt_to_phys(&qi->desc_status[wait_index]);
> + memcpy(qi->desc + (wait_index << shift), &desc, 1 << shift);
> +
> + writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
> +}
I would like this helper to be self-contained; that is, it should
replace all abandoned descriptors with wait descriptors and then wait
until hardware fetch passes those descriptors. Something like this (not
tested yet):
+static int qi_drain_remaining_descs(struct intel_iommu *iommu, int head,
+ int wait_index)
+{
+ struct q_inval *qi = iommu->qi;
+ int shift = qi_shift(iommu);
+ struct qi_desc wait_desc = {};
+ struct qi_desc nop_desc = {};
+ cycles_t start;
+
+ nop_desc.qw0 = QI_IWD_FENCE | QI_IWD_TYPE;
+
+ wait_desc.qw0 = QI_IWD_STATUS_DATA(QI_DONE) |
+ QI_IWD_STATUS_WRITE | QI_IWD_FENCE | QI_IWD_TYPE;
+ wait_desc.qw1 = virt_to_phys(&qi->desc_status[wait_index]);
+
+ while (head != wait_index) {
+ memcpy(qi->desc + (head << shift), &nop_desc, 1 << shift);
+ head = (head + 1) % QI_LENGTH;
+ }
+
+ WRITE_ONCE(qi->desc_status[wait_index], QI_IN_USE);
+ memcpy(qi->desc + (wait_index << shift), &wait_desc, 1 << shift);
+
+ /*
+ * Order the descriptor rewrites before the writel() that restarts
+ * the fetch engine.
+ */
+ wmb();
+
+ writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
+
+ start = get_cycles();
+ while (READ_ONCE(qi->desc_status[wait_index]) != QI_DONE) {
+ if (DMAR_OPERATION_TIMEOUT < (get_cycles() - start)) {
+ pr_err("Timeout draining invalidation queue after IQE\n");
+ return -ETIMEDOUT;
+ }
+ cpu_relax();
+ }
+
+ return 0;
+}
> +
> static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
> {
> u32 fault;
> @@ -1366,21 +1403,22 @@ static int qi_check_fault(struct intel_iommu *iommu, int index, int wait_index)
> * is cleared.
> */
> if (fault & DMA_FSTS_IQE) {
> + int head_idx;
> +
> head = readl(iommu->reg + DMAR_IQH_REG);
> - if ((head >> shift) == index) {
> - struct qi_desc *desc = qi->desc + head;
> + head_idx = head >> shift;
>
> - /*
> - * desc->qw2 and desc->qw3 are either reserved or
> - * used by software as private data. We won't print
> - * out these two qw's for security consideration.
> - */
> - memcpy(desc, qi->desc + (wait_index << shift),
> - 1 << shift);
> - writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
> - pr_info("Invalidation Queue Error (IQE) cleared\n");
> + /*
> + * The faulting descriptor can be anywhere within the current
> + * submission's range [index, wait_index]. Since the queue is
> + * circular, this submission may wrap around QI_LENGTH
> + * (index > wait_index in that case), so check both the
> + * non-wrapped and wrapped cases of the range.
> + */
> + if (index <= wait_index ?
> + (head_idx >= index && head_idx <= wait_index) :
> + (head_idx >= index || head_idx <= wait_index))
> return -EINVAL;
> - }
> }
Then call the above helper directly here.
@@ -1366,18 +1409,25 @@ static int qi_check_fault(struct intel_iommu
*iommu, int index, int wait_index)
* is cleared.
*/
if (fault & DMA_FSTS_IQE) {
+ int head_idx, ret;
+
head = readl(iommu->reg + DMAR_IQH_REG);
- if ((head >> shift) == index) {
- struct qi_desc *desc = qi->desc + head;
+ head_idx = (head >> shift) % QI_LENGTH;
+
+ /*
+ * The faulting descriptor can be anywhere within the current
+ * submission's range [index, wait_index]. Since the queue is
+ * circular, this submission may wrap around QI_LENGTH
+ * (index > wait_index in that case), so check both the
+ * non-wrapped and wrapped cases of the range.
+ */
+ if (index <= wait_index ?
+ (head_idx >= index && head_idx <= wait_index) :
+ (head_idx >= index || head_idx <= wait_index)) {
+ ret = qi_drain_remaining_descs(iommu, head_idx, wait_index);
+ if (ret)
+ return ret;
- /*
- * desc->qw2 and desc->qw3 are either reserved or
- * used by software as private data. We won't print
- * out these two qw's for security consideration.
- */
- memcpy(desc, qi->desc + (wait_index << shift),
- 1 << shift);
- writel(DMA_FSTS_IQE, iommu->reg + DMAR_FSTS_REG);
pr_info("Invalidation Queue Error (IQE) cleared\n");
return -EINVAL;
}
>
> /*
> @@ -1452,7 +1490,7 @@ int qi_submit_sync(struct intel_iommu *iommu, struct qi_desc *desc,
> int wait_index, index;
> unsigned long flags;
> int offset, shift;
> - int rc, i;
> + int rc = 0, fault, i;
> u64 type;
>
> if (!qi)
> @@ -1528,9 +1566,19 @@ int qi_submit_sync(struct intel_iommu *iommu, struct qi_desc *desc,
> * a deadlock where the interrupt context can wait indefinitely
> * for free slots in the queue.
> */
> - rc = qi_check_fault(iommu, index, wait_index);
> - if (rc)
> + fault = qi_check_fault(iommu, index, wait_index);
> + if (fault == -EINVAL) {
> + /*
> + * IQE in our batch: drain the remaining descriptors
> + * and keep polling until hardware completes.
> + */
> + qi_drain_remaining_descs(iommu, wait_index, shift,
> + options);
> + rc = -EINVAL;
> + } else if (fault) {
> + rc = fault;
> break;
> + }
As a result, there is no need to change the caller of qi_check_fault().
>
> raw_spin_unlock(&qi->q_lock);
> cpu_relax();
Thanks,
baolu
next prev parent reply other threads:[~2026-09-16 6:23 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 4:20 [PATCH] " Guanghui Feng
2026-08-20 3:09 ` Baolu Lu
2026-08-20 14:47 ` Guanghui Feng
2026-08-20 19:37 ` Samiullah Khawaja
2026-08-21 2:56 ` Baolu Lu
2026-09-09 7:13 ` Baolu Lu
2026-09-14 14:19 ` [PATCH v3] " Guanghui Feng
2026-09-16 6:23 ` Baolu Lu [this message]
2026-09-16 8:11 ` [PATCH v4] " Guanghui Feng
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=e56a23f2-216c-41ea-991d-495962e6b7af@linux.intel.com \
--to=baolu.lu@linux.intel.com \
--cc=bikuan.zbk@alibaba-inc.com \
--cc=dwmw2@infradead.org \
--cc=guanghuifeng@linux.alibaba.com \
--cc=iommu@lists.linux.dev \
--cc=joro@8bytes.org \
--cc=linux-kernel@vger.kernel.org \
--cc=robin.murphy@arm.com \
--cc=will@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
all inboxes | Powered by JetHome®