* [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4
@ 2026-09-28 3:27 Lu Baolu
2026-09-28 3:27 ` [PATCH 1/9] iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss() Lu Baolu
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
Hi Joerg,
The following changes are queued for v7.4-rc1. This update includes a
set of quick fixes and cleanups, including:
- Enhanced IQE fault handling for batched multiple descriptors,
- Optimization of page-table level calculation in compute_vasz_lg2_ss(),
- Fixes for several pre-existing issues reported by Sashiko,
- General cleanup and refinement.
These patches are based on v7.3-rc5. The full series is also available
on GitHub:
https://github.com/LuBaolu/intel-iommu/commits/iommu/vt-d/next
Please consider pulling this series into the iommu/vt-d branch.
Best regards,
baolu
Guanghui Feng (1):
iommu/vt-d: Fix IQE handling to cover all descriptors in submission
range
Lu Baolu (7):
iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid()
iommu/vt-d: Do not ignore context table copy failures
iommu/vt-d: Handle DID reservation errors when copying context tables
iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy
iommu/vt-d: Use old domain parameter when attaching the blocking
domain
iommu/vt-d: Fix iopf refcount leak in nested attach
iommu/vt-d: Drop old iopf ref only after attach succeeds
Zhenzhong Duan (1):
iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss()
drivers/iommu/intel/iommu.h | 17 +++-
drivers/iommu/intel/dmar.c | 70 +++++++++++++---
drivers/iommu/intel/iommu.c | 156 ++++++++++++++++++++++++++++++-----
drivers/iommu/intel/nested.c | 2 +
4 files changed, 211 insertions(+), 34 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/9] iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss()
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 2/9] iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid() Lu Baolu
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
From: Zhenzhong Duan <zhenzhong.duan@intel.com>
compute_vasz_lg2_ss() finds the optimal Second-Stage page table level by
intersecting the maximum guest address width (mgaw) with the hardware's
SAGAW capability register.
The VT-d spec maps the SAGAW bit field positions as:
- Bit 1: 39-bit AGAW (3-level page table, top_level = 2)
- Bit 2: 48-bit AGAW (4-level page table, top_level = 3)
- Bit 3: 57-bit AGAW (5-level page table, top_level = 4)
The fallback paths use bit shifts that are one position too large,
causing ffs() to select a deeper page table level than the mgaw window
requires:
- mgaw > 39: "3 + ffs(sagaw >> 3)" evaluates to top_level = 4 (5-level)
instead of top_level = 3 (4-level) when hardware supports both
48-bit (Bit 2) and 57-bit (Bit 3) AGAW.
- mgaw > 30: "2 + ffs(sagaw >> 2)" evaluates to top_level = 3 (4-level)
instead of top_level = 2 (3-level) when hardware supports both
39-bit (Bit 1) and 48-bit (Bit 2) AGAW.
In both cases the selected level is still one that the hardware advertises
in its SAGAW capability, so IOVA translation remains functionally correct.
However, an unnecessarily deep page table may be selected, adding an extra
level of page walk overhead and reducing TLB and cache efficiency without
providing any increase in addressable IOVA space beyond what the mgaw
window already caps.
Fix by decreasing the shift offset by one in each fallback case, ensuring
ffs() targets the correct SAGAW bit position and selects the smallest
page table level that fully covers the mgaw range:
- mgaw > 39: "2 + ffs(sagaw >> 2)" correctly yields top_level = 3
- mgaw > 30: "1 + ffs(sagaw >> 1)" correctly yields top_level = 2
Fixes: d856f9d27885 ("iommupt/vtd: Allow VT-d to have a larger table top than the vasz requires")
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 2e3b3ab216f8..05f351833d0b 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2911,10 +2911,10 @@ static unsigned int compute_vasz_lg2_ss(struct intel_iommu *iommu,
*top_level = 4;
return min(57, mgaw);
} else if (mgaw > 39 && sagaw >= BIT(2)) {
- *top_level = 3 + ffs(sagaw >> 3);
+ *top_level = 2 + ffs(sagaw >> 2);
return min(48, mgaw);
} else if (mgaw > 30 && sagaw >= BIT(1)) {
- *top_level = 2 + ffs(sagaw >> 2);
+ *top_level = 1 + ffs(sagaw >> 1);
return min(39, mgaw);
}
return 0;
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/9] iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid()
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
2026-09-28 3:27 ` [PATCH 1/9] iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss() Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 3/9] iommu/vt-d: Do not ignore context table copy failures Lu Baolu
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
Callers request a full Device-TLB flush by passing MAX_AGAW_PFN_WIDTH
(64 - VTD_PAGE_SHIFT == 52) as @size_order. Two expressions in
qi_desc_dev_iotlb_pasid() are not prepared for a value that large:
unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1);
...
if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order))
The second evaluates to 1UL << 64, which is undefined on all
architectures. On x86_64 the shift count masks to zero, IS_ALIGNED(addr,
1) is trivially true, and the alignment sanity check silently degrades
into a no-op.
The first evaluates to 1UL << 63. That is well defined on 64-bit builds,
where unsigned long is 64 bits wide, but is undefined on 32-bit ones. In
practice x86 masks the shift count to five bits, so the expression yields
1UL << 31 and ~mask becomes 0x7fffffff. That value is zero-extended when
applied to the 64-bit descriptor, so
desc->qw1 &= ~mask;
clears qw1[63:32] as well as bit 31, collapsing the ADDR field that had
just been filled with ones. Reaching that requires a scalable-mode PASID
configuration on 32-bit x86, which is not a realistic deployment, but the
construct is wrong regardless.
Compute the mask with BIT_ULL() so that it is 64-bit on every
architecture, and skip the alignment check for a full flush, where it is
both meaningless and the source of the out-of-range shift.
Note that @size_order must not be clamped below 64 - VTD_PAGE_SHIFT. With
S set, hardware derives the invalidation range from the least significant
zero bit N of ADDR and matches bits [63:N+1] of the incoming address. For
size_order 52 the descriptor sets ADDR[63:12] and clears bit 63, making
N = 63 and the comparison range empty, so everything is invalidated.
Reducing @size_order to 51 would instead leave N = 62 and cause hardware
to compare address bit 63; since callers pass an address of 0, only the
lower half of the address space would be invalidated. Bound @size_order
at 64 - VTD_PAGE_SHIFT so that a bogus caller cannot reintroduce an
out-of-range shift, without altering the full-flush encoding.
The non-PASID variant qi_desc_dev_iotlb() already uses 1ULL and is
unaffected.
Fixes: f701c9f36bcb7 ("iommu/vt-d: Factor out invalidation descriptor composition")
Cc: stable@vger.kernel.org
Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260623060122.3796325-1-guanghuifeng%40linux.alibaba.com
Assisted-by: Claude:claude-opus-5
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.h | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 23dbe6c24439..5b3d234ae265 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1105,12 +1105,20 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
unsigned int size_order,
struct qi_desc *desc)
{
- unsigned long mask = 1UL << (VTD_PAGE_SHIFT + size_order - 1);
-
desc->qw0 = QI_DEV_EIOTLB_PASID(pasid) | QI_DEV_EIOTLB_SID(sid) |
QI_DEV_EIOTLB_QDEP(qdep) | QI_DEIOTLB_TYPE |
QI_DEV_IOTLB_PFSID(pfsid);
+ /*
+ * The widest range the descriptor can express is a full flush, encoded
+ * by making bit 63 the least significant zero bit of ADDR, that is
+ * @size_order == 64 - VTD_PAGE_SHIFT. Bound @size_order there so that
+ * a caller passing something larger cannot produce an out-of-range
+ * shift below.
+ */
+ if (size_order > 64 - VTD_PAGE_SHIFT)
+ size_order = 64 - VTD_PAGE_SHIFT;
+
/*
* If S bit is 0, we only flush a single page. If S bit is set,
* The least significant zero bit indicates the invalidation address
@@ -1120,7 +1128,8 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
* Max Invs Pending (MIP) is set to 0 for now until we have DIT in
* ECAP.
*/
- if (!IS_ALIGNED(addr, VTD_PAGE_SIZE << size_order))
+ if (size_order < 64 - VTD_PAGE_SHIFT &&
+ !IS_ALIGNED(addr, BIT_ULL(VTD_PAGE_SHIFT + size_order)))
pr_warn_ratelimited("Invalidate non-aligned address %llx, order %d\n",
addr, size_order);
@@ -1136,7 +1145,7 @@ static inline void qi_desc_dev_iotlb_pasid(u16 sid, u16 pfsid, u32 pasid,
desc->qw1 |= GENMASK_ULL(size_order + VTD_PAGE_SHIFT - 1,
VTD_PAGE_SHIFT);
/* Clear size_order bit to indicate size */
- desc->qw1 &= ~mask;
+ desc->qw1 &= ~BIT_ULL(VTD_PAGE_SHIFT + size_order - 1);
/* Set the S bit to indicate flushing more than 1 page */
desc->qw1 |= QI_DEV_EIOTLB_SIZE;
}
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 3/9] iommu/vt-d: Do not ignore context table copy failures
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
2026-09-28 3:27 ` [PATCH 1/9] iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss() Lu Baolu
2026-09-28 3:27 ` [PATCH 2/9] iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid() Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 4/9] iommu/vt-d: Handle DID reservation errors when copying context tables Lu Baolu
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
copy_translation_tables() currently logs copy_context_table() failures
but still returns success, so partial copy failures are silently ignored.
That means Intel IOMMU may run with only part of the old tables copied.
Then some old domain IDs may not be reserved, and later may be reused by
new domains. With stale hardware cache entries still around, this can
cause bad DMA translations, DMA faults, or domain aliasing.
Fix by aborting on the first context-table copy failure, freeing temporary
context-table pages, and returning an error so caller falls back to a
clean root table path.
Fixes: f93b4ac5929a ("iommu/vt-d: Use ida to manage domain id")
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 05f351833d0b..bf8b3e3edf3b 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -1591,7 +1591,7 @@ static int copy_translation_tables(struct intel_iommu *iommu)
if (ret) {
pr_err("%s: Failed to copy context table for bus %d\n",
iommu->name, bus);
- continue;
+ goto err_free_ctxt_tbls;
}
}
@@ -1623,11 +1623,27 @@ static int copy_translation_tables(struct intel_iommu *iommu)
memunmap(old_rt);
return 0;
+err_free_ctxt_tbls:
+ /*
+ * None of these tables have been linked into iommu->root_entry yet,
+ * so they are unreachable and must be freed here.
+ */
+ for (bus = 0; bus < ctxt_table_entries; bus++)
+ iommu_free_pages(ctxt_tbls[bus]);
+ kfree(ctxt_tbls);
out_unmap:
memunmap(old_rt);
err_free_bitmap:
bitmap_free(iommu->copied_tables);
iommu->copied_tables = NULL;
+
+ /*
+ * Only reservations taken from the old context entries can be in the
+ * ida at this point; no domain has been allocated on this IOMMU yet.
+ * ida_destroy() empties it and leaves it ready for reuse.
+ */
+ ida_destroy(&iommu->domain_ida);
+
return ret;
}
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 4/9] iommu/vt-d: Handle DID reservation errors when copying context tables
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (2 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 3/9] iommu/vt-d: Do not ignore context table copy failures Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 5/9] iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy Lu Baolu
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
When kdump reuses old translation tables, all old domain IDs (DIDs) must
be reserved in the new kernel before new domains are created. Today
copy_context_table() ignores ida_alloc_range() return values, so a real
-ENOMEM can be missed and an ID may stay unreserved.
That can allow DID reuse while stale hardware cache entries still exist,
risking domain aliasing.
Fix this by moving DID reservation into a helper that:
- treats duplicate reservations (-ENOSPC) as expected success,
- skips out-of-range IDs as success,
- propagates real allocation failures (like -ENOMEM), and
- normalize successful return values.
On failure, unwind as in existing copy-allocation failure paths.
Fixes: f93b4ac5929a ("iommu/vt-d: Use ida to manage domain id")
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.c | 39 +++++++++++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index bf8b3e3edf3b..85400d0ab334 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -1452,12 +1452,39 @@ static void intel_iommu_init_qi(struct intel_iommu *iommu)
}
}
+/*
+ * Reserve a domain ID inherited from the previous kernel so that it is not
+ * handed out again while the copied translation structures are still live.
+ *
+ * Returns 0 when the ID is reserved, was already reserved, or cannot be
+ * re-assigned, and a negative errno for a genuine allocation failure.
+ */
+static int reserve_domain_id(struct intel_iommu *iommu, int did)
+{
+ int ret;
+
+ if (did < 0 || did >= iommu->max_domain_id)
+ return 0;
+
+ ret = ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
+ /*
+ * Devices sharing a domain share its ID, so the same ID is seen in
+ * more than one context entry; -ENOSPC merely reports that it is
+ * already reserved. On success the allocated ID is returned, which
+ * is not an error either.
+ */
+ if (ret == -ENOSPC || ret >= 0)
+ return 0;
+
+ return ret;
+}
+
static int copy_context_table(struct intel_iommu *iommu,
struct root_entry *old_re,
struct context_entry **tbl,
int bus, bool ext)
{
- int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0, did;
+ int tbl_idx, tbl_slot = 0, idx, devfn, ret = 0;
struct context_entry *new_ce = NULL, ce;
struct context_entry *old_ce = NULL;
struct root_entry re;
@@ -1520,9 +1547,13 @@ static int copy_context_table(struct intel_iommu *iommu,
if (!context_present(&ce))
continue;
- did = context_domain_id(&ce);
- if (did >= 0 && did < iommu->max_domain_id)
- ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
+ ret = reserve_domain_id(iommu, context_domain_id(&ce));
+ if (ret) {
+ /* Not yet published through @tbl, so free it here. */
+ iommu_free_pages(new_ce);
+ new_ce = NULL;
+ goto out_unmap;
+ }
set_context_copied(iommu, bus, devfn);
new_ce[idx] = ce;
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 5/9] iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (3 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 4/9] iommu/vt-d: Handle DID reservation errors when copying context tables Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 6/9] iommu/vt-d: Use old domain parameter when attaching the blocking domain Lu Baolu
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
copy_context_table() reserves old domain IDs (DIDs) from context entries.
That works for legacy mode, but not for scalable mode: scalable context
entries do not carry DID, so this path ends up reserving the wrong value
(often DID 0) repeatedly.
In scalable mode, real DIDs are stored in PASID table entries. If they
are not reserved during kdump table copy, new domains may reuse old DIDs
while stale PASID/IOTLB cache state still exists, causing translation
conflicts and DMA faults.
Fix this by walking PASID tables in scalable mode, and reserving DIDs
from present PASID entries. If PASID structures cannot be remapped, return
error so caller can fall back safely instead of continuing with an unsafe
DID space.
Fixes: 0c5f6c0d8201 ("iommu/vt-d: Fix kdump kernels boot failure with scalable mode")
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.c | 74 ++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 85400d0ab334..c9e246e8f8e2 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -1479,6 +1479,70 @@ static int reserve_domain_id(struct intel_iommu *iommu, int did)
return ret;
}
+/*
+ * Reserve the domain IDs used by a scalable mode context entry copied from
+ * the previous kernel.
+ */
+static int copy_pasid_table_dids(struct intel_iommu *iommu, struct context_entry *ce)
+{
+ struct pasid_dir_entry *dir;
+ unsigned long dir_size;
+ phys_addr_t dir_phys;
+ int ret = 0;
+ int i, j;
+
+ dir_phys = ce->lo & VTD_PAGE_MASK;
+ if (!dir_phys)
+ return 0;
+
+ dir_size = get_pasid_dir_size(ce);
+ dir = memremap(dir_phys, dir_size * sizeof(*dir), MEMREMAP_WB);
+ if (!dir)
+ return -ENOMEM;
+
+ for (i = 0; i < dir_size; i++) {
+ struct pasid_entry *table;
+ phys_addr_t table_phys;
+
+ if (!pasid_pde_is_present(&dir[i]))
+ continue;
+
+ /*
+ * Do not use get_pasid_table_from_pde(); that returns a
+ * phys_to_virt() pointer, which is not valid for memory
+ * owned by the previous kernel.
+ */
+ table_phys = READ_ONCE(dir[i].val) & PDE_PFN_MASK;
+ if (!table_phys)
+ continue;
+
+ /* A PASID table is one page: PASID_TBL_ENTRIES * 64 bytes. */
+ table = memremap(table_phys, PAGE_SIZE, MEMREMAP_WB);
+ if (!table) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (j = 0; j < PASID_TBL_ENTRIES; j++) {
+ if (!pasid_pte_is_present(&table[j]))
+ continue;
+
+ ret = reserve_domain_id(iommu, pasid_get_domain_id(&table[j]));
+ if (ret) {
+ memunmap(table);
+ goto out;
+ }
+ }
+
+ memunmap(table);
+ }
+
+out:
+ memunmap(dir);
+
+ return ret;
+}
+
static int copy_context_table(struct intel_iommu *iommu,
struct root_entry *old_re,
struct context_entry **tbl,
@@ -1546,8 +1610,14 @@ static int copy_context_table(struct intel_iommu *iommu,
if (!context_present(&ce))
continue;
-
- ret = reserve_domain_id(iommu, context_domain_id(&ce));
+ /*
+ * The context entry only holds a domain ID in legacy mode.
+ * In scalable mode the IDs are in the PASID table entries.
+ */
+ if (ext)
+ ret = copy_pasid_table_dids(iommu, &ce);
+ else
+ ret = reserve_domain_id(iommu, context_domain_id(&ce));
if (ret) {
/* Not yet published through @tbl, so free it here. */
iommu_free_pages(new_ce);
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 6/9] iommu/vt-d: Use old domain parameter when attaching the blocking domain
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (4 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 5/9] iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 7/9] iommu/vt-d: Fix iopf refcount leak in nested attach Lu Baolu
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
blocking_domain_attach_dev() drops the old domain’s iopf reference using
info->domain, but that value may already be cleared by
device_block_translation().
On attach failure fallback paths, this can cause the function to drop a
NULL-domain ref instead of @old, leaking the real old-domain reference.
Repeated leaks grow info->iopf_refcount, keep the device stuck on the
iopf queue, and can trigger WARN_ON(info->iopf_refcount) when PRI is
disabled.
Use the core-provided @old parameter directly. It always identifies the
correct domain to release and matches other attach paths.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index c9e246e8f8e2..1fb9b80f3b5e 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2899,9 +2899,7 @@ static int blocking_domain_attach_dev(struct iommu_domain *domain,
struct device *dev,
struct iommu_domain *old)
{
- struct device_domain_info *info = dev_iommu_priv_get(dev);
-
- iopf_for_domain_remove(info->domain ? &info->domain->domain : NULL, dev);
+ iopf_for_domain_remove(old, dev);
device_block_translation(dev);
return 0;
}
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 7/9] iommu/vt-d: Fix iopf refcount leak in nested attach
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (5 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 6/9] iommu/vt-d: Use old domain parameter when attaching the blocking domain Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 8/9] iommu/vt-d: Drop old iopf ref only after attach succeeds Lu Baolu
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
intel_nested_attach_dev() takes an iopf reference for the new domain but
does not drop the possible reference from the old domain. This leaks
info->iopf_refcount, can keep the device permanently on the iopf queue,
and later triggers WARN_ON(info->iopf_refcount) when PRI is disabled.
Fix this by dropping the possible reference from the old domain after
the nested translation setup completes.
Fixes: 17fce9d2336d9 ("iommu/vt-d: Put iopf enablement in domain attach path")
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/nested.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/iommu/intel/nested.c b/drivers/iommu/intel/nested.c
index 2b979bec56ce..f84fc8b41fde 100644
--- a/drivers/iommu/intel/nested.c
+++ b/drivers/iommu/intel/nested.c
@@ -59,6 +59,8 @@ static int intel_nested_attach_dev(struct iommu_domain *domain,
if (ret)
goto disable_iopf;
+ iopf_for_domain_remove(old, dev);
+
info->domain = dmar_domain;
info->domain_attached = true;
spin_lock_irqsave(&dmar_domain->lock, flags);
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 8/9] iommu/vt-d: Drop old iopf ref only after attach succeeds
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (6 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 7/9] iommu/vt-d: Fix iopf refcount leak in nested attach Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 3:27 ` [PATCH 9/9] iommu/vt-d: Fix IQE handling to cover all descriptors in submission range Lu Baolu
2026-09-28 8:18 ` [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Joerg Roedel
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
identity_domain_attach_dev() currently removes the old domain’s iopf
reference before programming pass-through. If pass-through setup fails,
attach fails but the old domain is still effectively attached — now
with its IOPF ref already dropped.
This can undercount info->iopf_refcount and may disable iopf queue
handling while the old domain can still issue page requests.
Fix it by removing the old domain’s iopf reference only after
pass-through setup succeeds, matching other attach paths.
Fixes: 236dd58fabd2 ("iommu/vt-d: Fix iopf_refcount leak on RID domain replacement")
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
---
drivers/iommu/intel/iommu.c | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 1fb9b80f3b5e..e3ec7f3b7826 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -3985,6 +3985,14 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
if (dev_is_real_dma_subdevice(dev))
return 0;
+ if (sm_supported(iommu))
+ ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
+ else
+ ret = device_setup_pass_through(dev);
+
+ if (ret)
+ return ret;
+
/*
* The identity domain has no iopf_handler, so no IOPF reference is
* taken for it. The reference held by the old domain must still be
@@ -3992,16 +4000,9 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
* not affect the IOPF reference count.
*/
iopf_for_domain_remove(old, dev);
+ info->domain_attached = true;
- if (sm_supported(iommu))
- ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
- else
- ret = device_setup_pass_through(dev);
-
- if (!ret)
- info->domain_attached = true;
-
- return ret;
+ return 0;
}
static int identity_domain_set_dev_pasid(struct iommu_domain *domain,
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 9/9] iommu/vt-d: Fix IQE handling to cover all descriptors in submission range
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (7 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 8/9] iommu/vt-d: Drop old iopf ref only after attach succeeds Lu Baolu
@ 2026-09-28 3:27 ` Lu Baolu
2026-09-28 8:18 ` [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Joerg Roedel
9 siblings, 0 replies; 11+ messages in thread
From: Lu Baolu @ 2026-09-28 3:27 UTC (permalink / raw)
To: Joerg Roedel; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
From: Guanghui Feng <guanghuifeng@linux.alibaba.com>
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 fenced no-op wait
descriptors, resubmit the wait descriptor at wait_index, clear IQE, and
spin until hardware signals QI_DONE (with DMAR_OPERATION_TIMEOUT). This
guarantees hardware has fully drained the batch before the caller
reclaims slots.
Fixes: 8a1d82462540 ("iommu/vt-d: Multiple descriptors per qi_submit_sync()")
Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/dmar.c | 70 ++++++++++++++++++++++++++++++++------
1 file changed, 60 insertions(+), 10 deletions(-)
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index ba675b08cd20..6377ba97a9ea 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -1344,6 +1344,49 @@ static void qi_dump_fault(struct intel_iommu *iommu, u32 fault)
(unsigned long long)desc->qw1);
}
+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_PRQ_DRAIN |
+ 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,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;
}
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
` (8 preceding siblings ...)
2026-09-28 3:27 ` [PATCH 9/9] iommu/vt-d: Fix IQE handling to cover all descriptors in submission range Lu Baolu
@ 2026-09-28 8:18 ` Joerg Roedel
9 siblings, 0 replies; 11+ messages in thread
From: Joerg Roedel @ 2026-09-28 8:18 UTC (permalink / raw)
To: Lu Baolu; +Cc: Guanghui Feng, Zhenzhong Duan, iommu, linux-kernel
On Mon, Sep 28, 2026 at 11:27:13AM +0800, Lu Baolu wrote:
> Guanghui Feng (1):
> iommu/vt-d: Fix IQE handling to cover all descriptors in submission
> range
>
> Lu Baolu (7):
> iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid()
> iommu/vt-d: Do not ignore context table copy failures
> iommu/vt-d: Handle DID reservation errors when copying context tables
> iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy
> iommu/vt-d: Use old domain parameter when attaching the blocking
> domain
> iommu/vt-d: Fix iopf refcount leak in nested attach
> iommu/vt-d: Drop old iopf ref only after attach succeeds
>
> Zhenzhong Duan (1):
> iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss()
Applied, thanks Baolu.
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-09-28 8:26 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-28 3:27 [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Lu Baolu
2026-09-28 3:27 ` [PATCH 1/9] iommu/vt-d: Fix page table level calculation in compute_vasz_lg2_ss() Lu Baolu
2026-09-28 3:27 ` [PATCH 2/9] iommu/vt-d: Avoid out-of-range shift in qi_desc_dev_iotlb_pasid() Lu Baolu
2026-09-28 3:27 ` [PATCH 3/9] iommu/vt-d: Do not ignore context table copy failures Lu Baolu
2026-09-28 3:27 ` [PATCH 4/9] iommu/vt-d: Handle DID reservation errors when copying context tables Lu Baolu
2026-09-28 3:27 ` [PATCH 5/9] iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy Lu Baolu
2026-09-28 3:27 ` [PATCH 6/9] iommu/vt-d: Use old domain parameter when attaching the blocking domain Lu Baolu
2026-09-28 3:27 ` [PATCH 7/9] iommu/vt-d: Fix iopf refcount leak in nested attach Lu Baolu
2026-09-28 3:27 ` [PATCH 8/9] iommu/vt-d: Drop old iopf ref only after attach succeeds Lu Baolu
2026-09-28 3:27 ` [PATCH 9/9] iommu/vt-d: Fix IQE handling to cover all descriptors in submission range Lu Baolu
2026-09-28 8:18 ` [PATCH 0/9][PULL REQUEST] Intel IOMMU updates for v7.4 Joerg Roedel
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®