[PATCH v2 09/16] iommu/vt-d: Restore IOMMU state and reclaimed domain ids

Samiullah Khawaja
2026-04-27T17:56:51Z

During boot fetch the preserved state of IOMMU unit and if found then
restore the state.

- Reuse the root_table that was preserved in the previous kernel.
- Reclaim the domain ids of the preserved domains for each preserved
devices so these are not acquired by another domain.

Signed-off-by: Samiullah Khawaja <skhawaja@google.com>
---
drivers/iommu/intel/iommu.c | 55 ++++++++++++++++++++++--------
drivers/iommu/intel/iommu.h | 7 ++++
drivers/iommu/intel/liveupdate.c | 57 ++++++++++++++++++++++++++++++++
3 files changed, 105 insertions(+), 14 deletions(-)

diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 68fecd4e57fa..4118a0861f38 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -670,10 +670,17 @@ void dmar_fault_dump_ptes(struct intel_iommu *iommu, u16 source_id,
#endif

/* iommu handling */
-static int iommu_alloc_root_entry(struct intel_iommu *iommu)
+static int iommu_alloc_root_entry(struct intel_iommu *iommu,
+ struct iommu_hw_ser *iommu_ser)
{
struct root_entry *root;

+ if (iommu_ser) {
+ intel_iommu_liveupdate_restore_root_table(iommu, iommu_ser);
+ __iommu_flush_cache(iommu, iommu->root_entry, ROOT_SIZE);
+ return 0;
+ }
+
root = iommu_alloc_pages_node_sz(iommu->node, GFP_ATOMIC, SZ_4K);
if (!root) {
pr_err("Allocating root entry for %s failed\n",
@@ -992,15 +999,16 @@ static void disable_dmar_iommu(struct intel_iommu *iommu)
iommu_disable_translation(iommu);
}

-static void free_dmar_iommu(struct intel_iommu *iommu)
+static void free_dmar_iommu(struct intel_iommu *iommu, struct iommu_hw_ser *iommu_ser)
{
if (iommu->copied_tables) {
bitmap_free(iommu->copied_tables);
iommu->copied_tables = NULL;
}

- /* free context mapping */
- free_context_table(iommu);
+ /* free context mapping if there is no serialized state. */
+ if (!iommu_ser)
+ free_context_table(iommu);

if (ecap_prs(iommu->ecap))
intel_iommu_finish_prq(iommu);
@@ -1611,6 +1619,7 @@ static int copy_translation_tables(struct intel_iommu *iommu)

static int __init init_dmars(void)
{
+ struct iommu_hw_ser *iommu_ser = NULL;
struct dmar_drhd_unit *drhd;
struct intel_iommu *iommu;
int ret;
@@ -1633,8 +1642,12 @@ static int __init init_dmars(void)
intel_pasid_max_id);
}

+ iommu_ser = iommu_get_preserved_data(iommu->reg_phys, IOMMU_INTEL);
+
intel_iommu_init_qi(iommu);
- init_translation_status(iommu);
+
+ if (!iommu_ser)
+ init_translation_status(iommu);

if (translation_pre_enabled(iommu) && !is_kdump_kernel()) {
iommu_disable_translation(iommu);
@@ -1648,7 +1661,7 @@ static int __init init_dmars(void)
* we could share the same root & context tables
* among all IOMMU's. Need to Split it later.
*/
- ret = iommu_alloc_root_entry(iommu);
+ ret = iommu_alloc_root_entry(iommu, iommu_ser);
if (ret)
goto free_iommu;

@@ -1732,8 +1745,12 @@ static int __init init_dmars(void)

free_iommu:
for_each_active_iommu(iommu, drhd) {
- disable_dmar_iommu(iommu);
- free_dmar_iommu(iommu);
+ iommu_ser = iommu_get_preserved_data(iommu->reg_phys, IOMMU_INTEL);
+
+ if (!iommu_ser)
+ disable_dmar_iommu(iommu);
+
+ free_dmar_iommu(iommu, iommu_ser);
}

return ret;
@@ -2107,15 +2124,19 @@ int dmar_parse_one_satc(struct acpi_dmar_header *hdr, void *arg)
static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
{
struct intel_iommu *iommu = dmaru->iommu;
+ struct iommu_hw_ser *iommu_ser = NULL;
int ret;

+ /* Use IOMMU HW unit MMIO base to identify the preserved state. */
+ iommu_ser = iommu_get_preserved_data(iommu->reg_phys, IOMMU_INTEL);
+
/*
* Disable translation if already enabled prior to OS handover.
*/
- if (iommu->gcmd & DMA_GCMD_TE)
+ if (!iommu_ser && iommu->gcmd & DMA_GCMD_TE)
iommu_disable_translation(iommu);

- ret = iommu_alloc_root_entry(iommu);
+ ret = iommu_alloc_root_entry(iommu, iommu_ser);
if (ret)
goto out;

@@ -2150,9 +2171,10 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
return 0;

disable_iommu:
- disable_dmar_iommu(iommu);
+ if (!iommu_ser)
+ disable_dmar_iommu(iommu);
out:
- free_dmar_iommu(iommu);
+ free_dmar_iommu(iommu, iommu_ser);
return ret;
}

@@ -2160,6 +2182,7 @@ int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
{
int ret = 0;
struct intel_iommu *iommu = dmaru->iommu;
+ struct iommu_hw_ser *iommu_ser;

if (!intel_iommu_enabled)
return 0;
@@ -2169,8 +2192,12 @@ int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
if (insert) {
ret = intel_iommu_add(dmaru);
} else {
- disable_dmar_iommu(iommu);
- free_dmar_iommu(iommu);
+ iommu_ser = iommu_get_preserved_data(iommu->reg_phys, IOMMU_INTEL);
+
+ if (!iommu_ser)
+ disable_dmar_iommu(iommu);
+
+ free_dmar_iommu(iommu, iommu_ser);
}

return ret;
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 5e0bc17e76bf..b0ec0b471a43 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1293,6 +1293,8 @@ 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 intel_iommu_liveupdate_restore_root_table(struct intel_iommu *iommu,
+ struct iommu_hw_ser *iommu_ser);
#else
static inline int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
@@ -1310,6 +1312,11 @@ static inline void intel_iommu_unpreserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser)
{
}
+
+static inline void intel_iommu_liveupdate_restore_root_table(struct intel_iommu *iommu,
+ struct iommu_hw_ser *iommu_ser)
+{
+}
#endif

#ifdef CONFIG_INTEL_IOMMU_SVM
diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c
index 75fa68b701bf..50a63812533f 100644
--- a/drivers/iommu/intel/liveupdate.c
+++ b/drivers/iommu/intel/liveupdate.c
@@ -83,6 +83,63 @@ static int preserve_iommu_context_table(struct intel_iommu *iommu)
return ret;
}

+static void restore_iommu_context(struct intel_iommu *iommu)
+{
+ struct context_entry *context;
+ int i;
+
+ for (i = 0; i < ROOT_ENTRY_NR; i++) {
+ context = iommu_context_addr(iommu, i, 0, 0);
+ if (context)
+ BUG_ON(!kho_restore_folio(virt_to_phys(context)));
+
+ if (!sm_supported(iommu))
+ continue;
+
+ context = iommu_context_addr(iommu, i, 0x80, 0);
+ if (context)
+ BUG_ON(!kho_restore_folio(virt_to_phys(context)));
+ }
+}
+
+static int _restore_used_domain_ids(struct iommu_device_ser *ser, void *arg)
+{
+ int id = ser->domain_iommu_ser.attachment_id;
+ struct iommu_hw_ser *iommu_hw_ser;
+ struct intel_iommu *iommu = arg;
+
+ iommu_hw_ser = phys_to_virt(ser->domain_iommu_ser.iommu_phys);
+ if (iommu_hw_ser->type != IOMMU_INTEL)
+ return 0;
+
+ /* Only allocate domain ID from associated IOMMU HW unit */
+ if (iommu_hw_ser->intel.phys_addr != iommu->reg_phys)
+ return 0;
+
+ /*
+ * This can fail as multiple preserved devices can share the same domain
+ * ID. Since this is done during DMAR init so these failures can be
+ * ignored.
+ */
+ ida_alloc_range(&iommu->domain_ida, id, id, GFP_ATOMIC);
+ return 0;
+}
+
+void intel_iommu_liveupdate_restore_root_table(struct intel_iommu *iommu,
+ struct iommu_hw_ser *iommu_ser)
+{
+ if (!iommu_ser->intel.restored)
+ BUG_ON(!kho_restore_folio(iommu_ser->intel.root_table));
+
+ iommu->root_entry = __va(iommu_ser->intel.root_table);
+
+ if (!iommu_ser->intel.restored)
+ restore_iommu_context(iommu);
+
+ iommu_ser->intel.restored = 1;
+ iommu_for_each_preserved_device(_restore_used_domain_ids, iommu);
+}
+
int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
{
--
2.54.0.545.g6539524ca2-goog