[PATCH 4/7] iommu/vt-d: Reserve scalable-mode DIDs from PASID entries during copy
From: Lu Baolu
Date: Wed Sep 09 2026 - 04:17:04 EST
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@xxxxxxxxxxxxxxx>
---
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 ab46058d76c5..5553c57130f7 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