Re: [PATCH 05/14] iommupt: Implement preserve/unpreserve/restore callbacks
From: Samiullah Khawaja
Date: Mon Mar 23 2026 - 13:12:42 EST
On Fri, Mar 20, 2026 at 09:57:08PM +0000, Pranjal Shrivastava wrote:
On Tue, Feb 03, 2026 at 10:09:39PM +0000, Samiullah Khawaja wrote:
Implement the iommu domain ops for presevation, unpresevation and
restoration of iommu domains for liveupdate. Use the existing page
walker to preserve the ioptdesc of the top_table and the lower tables.
Preserve the top_level also so it can be restored during boot.
Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
drivers/iommu/generic_pt/iommu_pt.h | 96 +++++++++++++++++++++++++++++
include/linux/generic_pt/iommu.h | 10 +++
2 files changed, 106 insertions(+)
diff --git a/drivers/iommu/generic_pt/iommu_pt.h b/drivers/iommu/generic_pt/iommu_pt.h
index 3327116a441c..0a1adb6312dd 100644
--- a/drivers/iommu/generic_pt/iommu_pt.h
+++ b/drivers/iommu/generic_pt/iommu_pt.h
@@ -921,6 +921,102 @@ int DOMAIN_NS(map_pages)(struct iommu_domain *domain, unsigned long iova,
}
EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(map_pages), "GENERIC_PT_IOMMU");
+/**
+ * unpreserve() - Unpreserve page tables and other state of a domain.
+ * @domain: Domain to unpreserve
+ */
+void DOMAIN_NS(unpreserve)(struct iommu_domain *domain, struct iommu_domain_ser *ser)
I don't see us using the `ser` arg in this function? Shall we remove it?
Hmm.. this is tricky. Its not needed for GPT, but to keep the
unpreserve() callback complete, "ser" should be there as some IOMMU that
preserves/unpreserves more state with each domain might need this. I am
ok either way, WDYT?
+{
+ struct pt_iommu *iommu_table =
+ container_of(domain, struct pt_iommu, domain);
+ struct pt_common *common = common_from_iommu(iommu_table);
+ struct pt_range range = pt_all_range(common);
+ struct pt_iommu_collect_args collect = {
+ .free_list = IOMMU_PAGES_LIST_INIT(collect.free_list),
+ };
+
+ iommu_pages_list_add(&collect.free_list, range.top_table);
+ pt_walk_range(&range, __collect_tables, &collect);
+
+ iommu_unpreserve_pages(&collect.free_list, -1);
+}
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(unpreserve), "GENERIC_PT_IOMMU");
+
+/**
+ * preserve() - Preserve page tables and other state of a domain.
+ * @domain: Domain to preserve
+ *
+ * Returns: -ERRNO on failure, on success.
Nit: 0 on success?
Agreed. Will update.
+ */
+int DOMAIN_NS(preserve)(struct iommu_domain *domain, struct iommu_domain_ser *ser)
+{
+ struct pt_iommu *iommu_table =
+ container_of(domain, struct pt_iommu, domain);
+ struct pt_common *common = common_from_iommu(iommu_table);
+ struct pt_range range = pt_all_range(common);
+ struct pt_iommu_collect_args collect = {
+ .free_list = IOMMU_PAGES_LIST_INIT(collect.free_list),
+ };
+ int ret;
+
+ iommu_pages_list_add(&collect.free_list, range.top_table);
+ pt_walk_range(&range, __collect_tables, &collect);
+
+ ret = iommu_preserve_pages(&collect.free_list);
+ if (ret)
+ return ret;
+
+ ser->top_table = virt_to_phys(range.top_table);
+ ser->top_level = range.top_level;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(preserve), "GENERIC_PT_IOMMU");
+
+static int __restore_tables(struct pt_range *range, void *arg,
+ unsigned int level, struct pt_table_p *table)
+{
+ struct pt_state pts = pt_init(range, level, table);
+ int ret;
+
+ for_each_pt_level_entry(&pts) {
+ if (pts.type == PT_ENTRY_TABLE) {
+ iommu_restore_page(virt_to_phys(pts.table_lower));
+ ret = pt_descend(&pts, arg, __restore_tables);
+ if (ret)
+ return ret;
If pt_descend() returns an error, we immediately return ret. However, we
have already successfully called iommu_restore_page() on pts.table_lower
and potentially many other tables earlier in the loop or higher up in
the tree..
If the domain restore fails halfway through the tree walk, how are these
partially restored pages cleaned up? Should we clean them up / free them
in an err label or does the caller perform a BUG_ON(ret) ?
Yes, a failed restore here should be fatal. Looking at pt_descend
source, it should not fail here anyways as table_lower is init. I will
add a BUG_ON here.
+ }
+ }
+ return 0;
+}
+
+/**
+ * restore() - Restore page tables and other state of a domain.
+ * @domain: Domain to preserve
+ *
+ * Returns: -ERRNO on failure, on success.
Nit: 0 on success?
Agreed.
+ */
+int DOMAIN_NS(restore)(struct iommu_domain *domain, struct iommu_domain_ser *ser)
+{
+ struct pt_iommu *iommu_table =
+ container_of(domain, struct pt_iommu, domain);
+ struct pt_common *common = common_from_iommu(iommu_table);
+ struct pt_range range = pt_all_range(common);
+
+ iommu_restore_page(ser->top_table);
+
+ /* Free new table */
+ iommu_free_pages(range.top_table);
+
+ /* Set the restored top table */
+ pt_top_set(common, phys_to_virt(ser->top_table), ser->top_level);
+
+ /* Restore all pages*/
+ range = pt_all_range(common);
+ return pt_walk_range(&range, __restore_tables, NULL);
+}
+EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(restore), "GENERIC_PT_IOMMU");
+
[ ------ >8 Snip >8------]
Thanks,
Praan