Re: [PATCH v2 11/16] iommu/vt-d: preserve PASID table of preserved device

From: Baolu Lu

Date: Fri May 08 2026 - 02:09:10 EST


On 4/28/26 01:56, Samiullah Khawaja wrote:
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/iommu.c | 5 +-
drivers/iommu/intel/iommu.h | 12 +++
drivers/iommu/intel/liveupdate.c | 141 +++++++++++++++++++++++++++++++
drivers/iommu/intel/pasid.c | 7 +-
drivers/iommu/intel/pasid.h | 9 ++
include/linux/kho/abi/iommu.h | 13 +++
6 files changed, 184 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index b90757164cd8..6d42051dcf7c 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2951,8 +2951,10 @@ static int clear_unpreserve_context_entry_fn(struct device *dev,
if (!info)
return 0;
- if (dev_is_pci(dev) && dev_iommu_preserved_state(dev))
+ if (dev_is_pci(dev) && dev_iommu_preserved_state(dev)) {
+ pasid_cleanup_preserved_table(dev);
return 0;
+ }
domain_context_clear(info);
return 0;
@@ -4013,6 +4015,7 @@ const struct iommu_ops intel_iommu_ops = {
.page_response = intel_iommu_page_response,
#ifdef CONFIG_IOMMU_LIVEUPDATE
.preserve_device = intel_iommu_preserve_device,
+ .unpreserve_device = intel_iommu_unpreserve_device,
.preserve = intel_iommu_preserve,
.unpreserve = intel_iommu_unpreserve,
#endif
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 8e37acf7de12..62076a1a0b4d 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -1290,12 +1290,15 @@ static inline int iopf_for_domain_replace(struct iommu_domain *new,
#ifdef CONFIG_IOMMU_LIVEUPDATE
int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser);
+void intel_iommu_unpreserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser);
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);
+void pasid_cleanup_preserved_table(struct device *dev);
#else
static inline int intel_iommu_preserve_device(struct device *dev,
struct iommu_device_ser *device_ser)
@@ -1303,6 +1306,11 @@ static inline int intel_iommu_preserve_device(struct device *dev,
return -EOPNOTSUPP;
}
+static inline void intel_iommu_unpreserve_device(struct device *dev,
+ struct iommu_device_ser *device_ser)
+{
+}
+
static inline int intel_iommu_preserve(struct iommu_device *iommu,
struct iommu_hw_ser *iommu_ser)
{
@@ -1318,6 +1326,10 @@ static inline void intel_iommu_liveupdate_restore_root_table(struct intel_iommu
struct iommu_hw_ser *iommu_ser)
{
}
+
+static inline void pasid_cleanup_preserved_table(struct device *dev)
+{
+}
#endif
#ifdef CONFIG_INTEL_IOMMU_SVM
diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c
index 50a63812533f..404b485e97b9 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"
static void unpreserve_iommu_context_table(struct intel_iommu *iommu, int end)
@@ -140,10 +141,96 @@ void intel_iommu_liveupdate_restore_root_table(struct intel_iommu *iommu,
iommu_for_each_preserved_device(_restore_used_domain_ids, iommu);
}
+enum pasid_lu_op {
+ PASID_LU_OP_PRESERVE = 1,
+ PASID_LU_OP_UNPRESERVE,
+ PASID_LU_OP_RESTORE,
+ PASID_LU_OP_FREE,
+};
+
+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_page(table);
+ break;
+ case PASID_LU_OP_UNPRESERVE:
+ iommu_unpreserve_page(table);
+ break;
+ case PASID_LU_OP_RESTORE:
+ iommu_restore_page(virt_to_phys(table));
+ break;
+ case PASID_LU_OP_FREE:
+ iommu_free_pages(table);
+ break;
+ }
+
+ return ret;
+}
+
+static int pasid_lu_handle_pd(struct pasid_dir_entry *dir, enum pasid_lu_op op)
+{
+ struct pasid_entry *table;
+ int ret;
+
+ /* Only preserve first table for NO_PASID. */
+ table = get_pasid_table_from_pde(&dir[0]);
+ if (!table)
+ return -EINVAL;
+
+ ret = pasid_lu_do_op(table, op);
+ if (ret)
+ return ret;
+
+ ret = pasid_lu_do_op(dir, op);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ if (op == PASID_LU_OP_PRESERVE)
+ pasid_lu_do_op(table, PASID_LU_OP_UNPRESERVE);
+
+ return ret;
+}
+
+void pasid_cleanup_preserved_table(struct device *dev)
+{
+ struct pasid_table *pasid_table;
+ struct pasid_dir_entry *dir;
+ struct pasid_entry *table;
+ size_t dir_size;
+
+ pasid_table = intel_pasid_get_table(dev);
+ if (!pasid_table)
+ return;
+
+ dir = pasid_table->table;
+ table = get_pasid_table_from_pde(&dir[0]);
+ if (!table)
+ return;
+
+ /* Clear everything except the first entry in table. */
+ memset(&table[1], 0, SZ_4K - sizeof(*table));
+
+ /* Use the folio order to calculate the size of Pasid Directory */
+ dir_size = (1 << (folio_order(virt_to_folio(dir)) + PAGE_SHIFT));
+
+ /* Clear everything except the first entry in directory */
+ memset(&dir[1], 0, dir_size - sizeof(struct pasid_dir_entry));
+
+ clflush_cache_range(&table[0], SZ_4K);
+ clflush_cache_range(&dir[0], dir_size);
+}

The PASID table is currently active and in use by the hardware. Clearing
the entries without the necessary hardware cache invalidation is buggy.

It seems this manual clearing is a workaround because PASID domain
preservation isn't supported yet. If so, rather than clearing the table
blindly, the code should verify if any PASIDs (other than
IOMMU_NO_PASID) are actually in use. If there are, the preserve callback
should return an error.

Or anything I overlooked here?

Thanks,
baolu