[PATCH v3 13/18] iommu/vt-d: preserve PASID table of preserved device
From: Samiullah Khawaja
Date: Sun Jun 14 2026 - 19:42:56 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 | 57 ++++++++++++++++++++++++++++++++
drivers/iommu/intel/pasid.c | 7 ++--
drivers/iommu/intel/pasid.h | 9 +++++
include/linux/kho/abi/iommu.h | 13 ++++++++
4 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c
index 61d864a2e27c..25c940f12955 100644
--- a/drivers/iommu/intel/liveupdate.c
+++ b/drivers/iommu/intel/liveupdate.c
@@ -14,6 +14,7 @@
#include <linux/pci.h>
#include "iommu.h"
+#include "pasid.h"
#include "../iommu-pages.h"
/* 2 tables per bus in scalable mode with upper table at odd bit */
@@ -369,6 +370,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)) {
@@ -385,9 +387,49 @@ 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;
}
+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;
+
+ 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);
+}
+
int intel_iommu_preserve(struct iommu_device *iommu_dev,
struct iommu_hw_ser *ser)
{
@@ -418,3 +460,18 @@ void intel_iommu_unpreserve(struct iommu_device *iommu_dev,
unpreserve_iommu_context_tables(iommu, ser);
iommu_unpreserve_pages(iommu->root_entry);
}
+
+void *intel_pasid_try_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;
+
+ BUG_ON(pasid_lu_handle_pd(phys_to_virt(ser->intel.pasid_table),
+ ser->intel.max_pasid,
+ PASID_LU_OP_RESTORE));
+ BUG_ON(ser->intel.max_pasid < max_pasid);
+
+ return phys_to_virt(ser->intel.pasid_table);
+}
diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c
index 89541b74ab8c..5cac8e95f73b 100644
--- a/drivers/iommu/intel/pasid.c
+++ b/drivers/iommu/intel/pasid.c
@@ -60,8 +60,11 @@ 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));
+
+ dir = intel_pasid_try_restore_table(dev, 1 << (order + PAGE_SHIFT + 3));
+ if (!dir)
+ 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..44e673a4ad8f 100644
--- a/drivers/iommu/intel/pasid.h
+++ b/drivers/iommu/intel/pasid.h
@@ -301,6 +301,15 @@ 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_try_restore_table(struct device *dev, u64 max_pasid);
+#else
+static inline void *intel_pasid_try_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 ad760c497e13..27d1fcd7f7e2 100644
--- a/include/linux/kho/abi/iommu.h
+++ b/include/linux/kho/abi/iommu.h
@@ -127,6 +127,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
@@ -139,6 +149,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.54.0.1136.gdb2ca164c4-goog