[PATCH v3 08/18] iommu/vt-d: clear unpreserved context entries during shutdown

From: Samiullah Khawaja

Date: Sun Jun 14 2026 - 19:40:48 EST


During normal shutdown the iommu translation is disabled. Since the root
table is preserved during live update, it needs to be cleaned up and the
context entries of the unpreserved devices and root entries for the
unpreserved context tables need to be cleared.

Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
drivers/iommu/intel/iommu.c | 9 ++--
drivers/iommu/intel/iommu.h | 6 +++
drivers/iommu/intel/liveupdate.c | 74 ++++++++++++++++++++++++++++++++
3 files changed, 86 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 715b538e7efe..26258861e3bf 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2374,8 +2374,11 @@ void intel_iommu_shutdown(void)
/* Disable PMRs explicitly here. */
iommu_disable_protect_mem_regions(iommu);

- /* Make sure the IOMMUs are switched off */
- iommu_disable_translation(iommu);
+ /* Make sure the IOMMUs are switched off if not preserved. */
+ if (iommu_preserved_state(&iommu->iommu))
+ clear_unpreserved_context_entries(iommu);
+ else
+ iommu_disable_translation(iommu);
}
}

@@ -2699,7 +2702,7 @@ static int domain_context_clear_one_cb(struct pci_dev *pdev, u16 alias, void *op
* devices, unbinding the driver from any one of them will possibly leave
* the others unable to operate.
*/
-static void domain_context_clear(struct device_domain_info *info)
+void domain_context_clear(struct device_domain_info *info)
{
if (!dev_is_pci(info->dev)) {
domain_context_clear_one(info, info->bus, info->devfn);
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 5e0bc17e76bf..561cc4d14912 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1241,6 +1241,7 @@ void cache_tag_flush_all(struct dmar_domain *domain);
void cache_tag_flush_range_np(struct dmar_domain *domain, unsigned long start,
unsigned long end);

+void domain_context_clear(struct device_domain_info *info);
void intel_context_flush_no_pasid(struct device_domain_info *info,
struct context_entry *context, u16 did);

@@ -1293,6 +1294,7 @@ int intel_iommu_preserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser);
void intel_iommu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser);
+void clear_unpreserved_context_entries(struct intel_iommu *iommu);
#else
static inline int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
@@ -1310,6 +1312,10 @@ static inline void intel_iommu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser)
{
}
+
+static inline void clear_unpreserved_context_entries(struct intel_iommu *iommu)
+{
+}
#endif

#ifdef CONFIG_INTEL_IOMMU_SVM
diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c
index b5613e4ad43a..aac7aa89a182 100644
--- a/drivers/iommu/intel/liveupdate.c
+++ b/drivers/iommu/intel/liveupdate.c
@@ -64,6 +64,80 @@ static int preserve_context_table(struct intel_iommu *iommu,
return 0;
}

+static void clear_unpreserved_context_root_entries(struct intel_iommu *iommu,
+ struct iommu_hw_ser *ser)
+{
+ struct root_entry *root;
+ int i;
+
+ for (i = 0; i < ROOT_ENTRY_NR; i++) {
+ root = &iommu->root_entry[i];
+
+ if (!is_context_table_preserved(iommu, ser, i, 0) && (root->lo & 1)) {
+ root->lo = 0;
+ __iommu_flush_cache(iommu,
+ &root->lo,
+ sizeof(root->lo));
+ }
+
+ if (!sm_supported(iommu))
+ continue;
+
+ if (!is_context_table_preserved(iommu, ser, i, 0x80) && (root->hi & 1)) {
+ root->hi = 0;
+ __iommu_flush_cache(iommu,
+ &root->hi,
+ sizeof(root->hi));
+ }
+ }
+}
+
+static int clear_unpreserve_context_entry_fn(struct device *dev,
+ struct iommu_device *iommu,
+ void *arg)
+{
+ struct device_domain_info *info;
+
+ info = dev_iommu_priv_get(dev);
+ if (!info)
+ return 0;
+
+ if (dev_is_pci(dev) && dev_iommu_preserved_state(dev))
+ return 0;
+
+ domain_context_clear(info);
+ return 0;
+}
+
+void clear_unpreserved_context_entries(struct intel_iommu *iommu)
+{
+ struct iommu_dev_iter iter = {
+ .fn = clear_unpreserve_context_entry_fn,
+ .iommu = &iommu->iommu,
+ .arg = NULL,
+
+ };
+
+ /* Clear context entries for unpreserved devices */
+ iommu_for_each_dev(&iter);
+
+ /* Clear reference to unpreserved context tables */
+ clear_unpreserved_context_root_entries(iommu,
+ iommu_preserved_state(&iommu->iommu));
+
+ /*
+ * Some devices might not have teardown/detached properly depending on
+ * whether a proper device remove is done before kexec is triggered.
+ * Also unpreserved context tables are removed during shutdown. So issue
+ * global invalidations to remove references to unpreserved tables and
+ * entries.
+ */
+ iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
+ if (sm_supported(iommu))
+ qi_flush_pasid_cache(iommu, 0, QI_PC_GLOBAL, 0);
+ iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
+}
+
static void unpreserve_iommu_context_tables(struct intel_iommu *iommu,
struct iommu_hw_ser *ser)
{
--
2.54.0.1136.gdb2ca164c4-goog