[PATCH v4 13/18] iommu/vt-d: Preserve PASID table of preserved device
From: Samiullah Khawaja
Date: Fri Aug 07 2026 - 22:29:27 EST
In scalable mode the PASID table is used to fetch the io page tables.
Preserve and restore the PASID table of the preserved devices.
Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
drivers/iommu/intel/liveupdate.c | 130 +++++++++++++++++++++++++++++++
drivers/iommu/intel/pasid.c | 10 ++-
drivers/iommu/intel/pasid.h | 8 ++
include/linux/kho/abi/iommu.h | 13 ++++
4 files changed, 159 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c
index 05dea3893399..b324697aac92 100644
--- a/drivers/iommu/intel/liveupdate.c
+++ b/drivers/iommu/intel/liveupdate.c
@@ -15,6 +15,7 @@
#include <linux/pci-ats.h>
#include "iommu.h"
+#include "pasid.h"
#include "../iommu-pages.h"
/* 2 tables per bus in scalable mode with upper table at odd bit */
@@ -462,6 +463,69 @@ int intel_iommu_restore_device(struct iommu_domain *domain,
return ret;
}
+enum pasid_lu_op {
+ PASID_LU_OP_PRESERVE = 1,
+ PASID_LU_OP_UNPRESERVE,
+ PASID_LU_OP_RESTORE,
+};
+
+static int pasid_lu_do_op(void *table, enum pasid_lu_op op)
+{
+ int ret = 0;
+
+ switch (op) {
+ case PASID_LU_OP_PRESERVE:
+ ret = iommu_preserve_pages(table);
+ break;
+ case PASID_LU_OP_UNPRESERVE:
+ iommu_unpreserve_pages(table);
+ break;
+ case PASID_LU_OP_RESTORE:
+ iommu_restore_pages(virt_to_phys(table));
+ break;
+ }
+
+ return ret;
+}
+
+static int pasid_lu_handle_pd(struct pasid_dir_entry *dir,
+ u32 max_pasid, enum pasid_lu_op op)
+{
+ int max_pde = max_pasid >> PASID_PDE_SHIFT;
+ struct pasid_entry *table;
+ int i, ret;
+
+ for (i = 0; i < max_pde; i++) {
+ table = get_pasid_table_from_pde(&dir[i]);
+ if (!table)
+ continue;
+
+ ret = pasid_lu_do_op(table, op);
+ if (ret)
+ goto err;
+ }
+
+ ret = pasid_lu_do_op(dir, op);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ if (op != PASID_LU_OP_PRESERVE)
+ return ret;
+
+ while (i > 0) {
+ table = get_pasid_table_from_pde(&dir[--i]);
+ if (!table)
+ continue;
+
+ pasid_lu_do_op(table, PASID_LU_OP_UNPRESERVE);
+ }
+
+ return ret;
+}
+
/**
* intel_iommu_preserve_device() - Intel IOMMU callback to preserve device state
* @dev: Target device
@@ -473,6 +537,7 @@ int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
{
struct device_domain_info *info = dev_iommu_priv_get(dev);
+ struct pasid_table *pasid_table;
int ret;
if (!dev_is_pci(dev)) {
@@ -492,6 +557,22 @@ int intel_iommu_preserve_device(struct device *dev,
device_ser->domain_iommu_ser.attachment_id = domain_id_iommu(info->domain,
info->iommu);
+
+ if (!sm_supported(info->iommu))
+ return 0;
+
+ pasid_table = intel_pasid_get_table(dev);
+ if (!pasid_table)
+ return -EINVAL;
+
+ ret = pasid_lu_handle_pd(pasid_table->table,
+ pasid_table->max_pasid,
+ PASID_LU_OP_PRESERVE);
+ if (ret)
+ return ret;
+
+ device_ser->intel.pasid_table = virt_to_phys(pasid_table->table);
+ device_ser->intel.max_pasid = pasid_table->max_pasid;
return 0;
}
@@ -503,6 +584,28 @@ int intel_iommu_preserve_device(struct device *dev,
void intel_iommu_unpreserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
{
+ struct device_domain_info *info = dev_iommu_priv_get(dev);
+ struct pasid_table *pasid_table;
+
+ if (!dev_is_pci(dev))
+ return;
+
+ if (!info)
+ return;
+
+ if (!sm_supported(info->iommu))
+ return;
+
+ if (!device_ser->intel.pasid_table)
+ return;
+
+ pasid_table = intel_pasid_get_table(dev);
+ if (!pasid_table)
+ return;
+
+ pasid_lu_handle_pd(pasid_table->table,
+ pasid_table->max_pasid,
+ PASID_LU_OP_UNPRESERVE);
}
/**
@@ -547,3 +650,30 @@ void intel_iommu_unpreserve(struct iommu_device *iommu_dev,
unpreserve_iommu_context_tables(iommu, ser);
iommu_unpreserve_pages(iommu->root_entry);
}
+
+/**
+ * intel_pasid_restore_table() - Restore preserved PASID table for a device
+ * @dev: Restored device
+ * @max_pasid: Maximum supported PASID
+ *
+ * Return: Pointer to restored PASID table directory, or NULL if not preserved.
+ */
+void *intel_pasid_restore_table(struct device *dev, u64 max_pasid)
+{
+ struct iommu_device_ser *ser = dev_iommu_restored_state(dev);
+
+ if (!ser || !ser->intel.pasid_table)
+ return NULL;
+
+ /*
+ * MAX PASID of a device should not change as it is read from
+ * capabilities.
+ */
+ BUG_ON(ser->intel.max_pasid != max_pasid);
+
+ BUG_ON(pasid_lu_handle_pd(phys_to_virt(ser->intel.pasid_table),
+ ser->intel.max_pasid,
+ PASID_LU_OP_RESTORE));
+
+ return phys_to_virt(ser->intel.pasid_table);
+}
diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c
index 81353fd46b37..ae09d47ca8a6 100644
--- a/drivers/iommu/intel/pasid.c
+++ b/drivers/iommu/intel/pasid.c
@@ -13,6 +13,7 @@
#include <linux/cpufeature.h>
#include <linux/dmar.h>
#include <linux/iommu.h>
+#include <linux/iommu-liveupdate.h>
#include <linux/memory.h>
#include <linux/pci.h>
#include <linux/pci-ats.h>
@@ -60,8 +61,13 @@ int intel_pasid_alloc_table(struct device *dev)
size = max_pasid >> (PASID_PDE_SHIFT - 3);
order = size ? get_order(size) : 0;
- dir = iommu_alloc_pages_node_sz(info->iommu->node, GFP_KERNEL,
- 1 << (order + PAGE_SHIFT));
+
+ max_pasid = 1 << (order + PAGE_SHIFT + 3);
+ if (dev_iommu_restored_state(dev))
+ dir = intel_pasid_restore_table(dev, max_pasid);
+ else
+ dir = iommu_alloc_pages_node_sz(info->iommu->node, GFP_KERNEL,
+ 1 << (order + PAGE_SHIFT));
if (!dir) {
kfree(pasid_table);
return -ENOMEM;
diff --git a/drivers/iommu/intel/pasid.h b/drivers/iommu/intel/pasid.h
index 48d3bb6b68de..801768cdea16 100644
--- a/drivers/iommu/intel/pasid.h
+++ b/drivers/iommu/intel/pasid.h
@@ -301,6 +301,14 @@ static inline void pasid_set_eafe(struct pasid_entry *pe)
extern unsigned int intel_pasid_max_id;
int intel_pasid_alloc_table(struct device *dev);
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+void *intel_pasid_restore_table(struct device *dev, u64 max_pasid);
+#else
+static inline void *intel_pasid_restore_table(struct device *dev, u64 max_pasid)
+{
+ return NULL;
+}
+#endif
void intel_pasid_free_table(struct device *dev);
struct pasid_table *intel_pasid_get_table(struct device *dev);
int intel_pasid_setup_first_level(struct intel_iommu *iommu, struct device *dev,
diff --git a/include/linux/kho/abi/iommu.h b/include/linux/kho/abi/iommu.h
index 430c42bf9561..f87a92ff6a38 100644
--- a/include/linux/kho/abi/iommu.h
+++ b/include/linux/kho/abi/iommu.h
@@ -129,6 +129,16 @@ struct iommu_dev_map_ser {
u64 iommu_phys;
} __packed;
+/**
+ * struct iommu_device_intel_ser - Intel specific state of serialized device
+ * @pasid_table: Physical address of pasid table
+ * @max_pasid: Maximum supported pasid
+ */
+struct iommu_device_intel_ser {
+ u64 pasid_table;
+ u64 max_pasid;
+} __packed;
+
/**
* struct iommu_device_ser - Serialized state of a device
* @hdr: Common object header
@@ -141,6 +151,9 @@ struct iommu_device_ser {
u32 devid;
u32 pci_domain_nr;
struct iommu_dev_map_ser domain_iommu_ser;
+ union {
+ struct iommu_device_intel_ser intel;
+ };
} __packed;
--
2.55.0.679.g6767b8d81c-goog