[PATCH v6 04/18] iommu: Implement IOMMU domain preservation
From: Samiullah Khawaja
Date: Sat Oct 24 2026 - 22:23:24 EST
Add IOMMU domain ops that can be implemented by the IOMMU drivers if
they support IOMMU domain preservation across liveupdate. The new IOMMU
domain preserve, unpreserve and restore APIs call these ops to perform
respective live update operations.
Reviewed-by: Pranjal Shrivastava <praan@xxxxxxxxxx>
Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
drivers/iommu/liveupdate.c | 119 +++++++++++++++++++++++++++++++
include/linux/iommu-liveupdate.h | 11 +++
include/linux/iommu.h | 5 ++
3 files changed, 135 insertions(+)
diff --git a/drivers/iommu/liveupdate.c b/drivers/iommu/liveupdate.c
index c8f51acd74b2..b7eda5615189 100644
--- a/drivers/iommu/liveupdate.c
+++ b/drivers/iommu/liveupdate.c
@@ -37,6 +37,7 @@
#define pr_fmt(fmt) "iommu: liveupdate: " fmt
#include <linux/errno.h>
+#include <linux/generic_pt/iommu.h>
#include <linux/iommu-liveupdate.h>
#include <linux/iommu.h>
#include <linux/kexec_handover.h>
@@ -67,6 +68,35 @@ struct iommu_flb_obj {
struct iommu_ser_set device_set;
};
+/*
+ * Reserve a new serialized object at the end of the set. The deleted objects
+ * are not reused, so the objects never move and can be referenced by their
+ * physical address.
+ */
+static void *iommu_ser_set_alloc(struct iommu_ser_set *set)
+{
+ struct iommu_hdr_ser *hdr;
+ int ret;
+
+ ret = kho_block_set_grow(&set->bs, set->count + 1);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (!set->count)
+ kho_block_set_it_init(&set->it, &set->bs);
+
+ hdr = kho_block_set_it_reserve_entry(&set->it);
+ if (WARN_ON_ONCE(!hdr))
+ return ERR_PTR(-ENOSPC);
+
+ set->count++;
+ *set->head_pa = kho_block_set_head_pa(&set->bs);
+
+ hdr->ref_count = 1;
+ hdr->size = set->bs.entry_size;
+ return hdr;
+}
+
/*
* Restore a set of serialized objects from the previous kernel. The objects
* are read using the size recorded by the previous kernel in their header.
@@ -239,3 +269,92 @@ void iommu_liveupdate_unregister_flb(struct liveupdate_file_handler *handler)
liveupdate_unregister_flb(handler, &iommu_flb);
}
EXPORT_SYMBOL(iommu_liveupdate_unregister_flb);
+
+/**
+ * iommu_preserve_domain() - Preserve an IOMMU domain across live update
+ * @domain: Domain to preserve
+ * @ser: Pointer to receive the virtual serialized domain state handle
+ *
+ * Return: 0 on success, or negative error code.
+ */
+int iommu_preserve_domain(struct iommu_domain *domain, struct iommu_domain_ser **ser)
+{
+ struct pt_iommu *pt = iommupt_from_domain(domain);
+ struct iommu_domain_ser *domain_ser;
+ struct iommu_flb_obj *flb_obj;
+ int ret;
+
+ if (!pt || !pt->ops->preserve || !pt->ops->unpreserve)
+ return -EOPNOTSUPP;
+
+ ret = liveupdate_flb_get_outgoing(&iommu_flb, (void **)&flb_obj);
+ if (ret)
+ return ret;
+
+ mutex_lock(&flb_obj->lock);
+ if (domain->domain_ser) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
+ domain_ser = iommu_ser_set_alloc(&flb_obj->domain_set);
+ if (IS_ERR(domain_ser)) {
+ ret = PTR_ERR(domain_ser);
+ goto out_unlock;
+ }
+
+ ret = pt->ops->preserve(pt, domain_ser);
+ if (ret) {
+ domain_ser->hdr.flags |= IOMMU_SER_FLAG_DELETED;
+ goto out_unlock;
+ }
+
+ domain->domain_ser = domain_ser;
+ *ser = domain_ser;
+ ret = 0;
+out_unlock:
+ mutex_unlock(&flb_obj->lock);
+ liveupdate_flb_put_outgoing(&iommu_flb);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_preserve_domain);
+
+/**
+ * iommu_unpreserve_domain() - Unpreserve a preserved IOMMU domain
+ * @domain: Domain to unpreserve
+ */
+void iommu_unpreserve_domain(struct iommu_domain *domain)
+{
+ struct pt_iommu *pt = iommupt_from_domain(domain);
+ struct iommu_domain_ser *domain_ser;
+ struct iommu_flb_obj *flb_obj;
+ int ret;
+
+ if (WARN_ON(!pt || !pt->ops->unpreserve))
+ return;
+
+ ret = liveupdate_flb_get_outgoing(&iommu_flb, (void **)&flb_obj);
+ if (WARN_ON(ret))
+ return;
+
+ mutex_lock(&flb_obj->lock);
+ if (!domain->domain_ser)
+ goto out_unlock;
+
+ /*
+ * There is no check for attached devices here. The correctness relies
+ * on the Live Update Orchestrator's session lifecycle. All resources
+ * (iommufd, vfio devices) are preserved within a single session. If the
+ * session is torn down, the .unpreserve callbacks for all files will be
+ * invoked, ensuring a consistent cleanup without needing explicit
+ * refcounting for the serialized objects here.
+ */
+ domain_ser = domain->domain_ser;
+ pt->ops->unpreserve(pt, domain_ser);
+ domain_ser->hdr.flags |= IOMMU_SER_FLAG_DELETED;
+ domain->domain_ser = NULL;
+out_unlock:
+ mutex_unlock(&flb_obj->lock);
+ liveupdate_flb_put_outgoing(&iommu_flb);
+}
+EXPORT_SYMBOL_GPL(iommu_unpreserve_domain);
diff --git a/include/linux/iommu-liveupdate.h b/include/linux/iommu-liveupdate.h
index 4755ab3cd67a..caa9778eee2d 100644
--- a/include/linux/iommu-liveupdate.h
+++ b/include/linux/iommu-liveupdate.h
@@ -15,6 +15,8 @@
#ifdef CONFIG_IOMMU_LIVEUPDATE
int iommu_liveupdate_register_flb(struct liveupdate_file_handler *handler);
void iommu_liveupdate_unregister_flb(struct liveupdate_file_handler *handler);
+int iommu_preserve_domain(struct iommu_domain *domain, struct iommu_domain_ser **ser);
+void iommu_unpreserve_domain(struct iommu_domain *domain);
#else
static inline int iommu_liveupdate_register_flb(struct liveupdate_file_handler *handler)
{
@@ -24,5 +26,14 @@ static inline int iommu_liveupdate_register_flb(struct liveupdate_file_handler *
static inline void iommu_liveupdate_unregister_flb(struct liveupdate_file_handler *handler)
{
}
+
+static inline int iommu_preserve_domain(struct iommu_domain *domain, struct iommu_domain_ser **ser)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void iommu_unpreserve_domain(struct iommu_domain *domain)
+{
+}
#endif
#endif /* _LINUX_IOMMU_LIVEUPDATE_H */
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 95e32c78dcb7..71e73c5181b5 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -14,6 +14,7 @@
#include <linux/err.h>
#include <linux/of.h>
#include <linux/iova_bitmap.h>
+#include <linux/kho/abi/iommu.h>
#include <uapi/linux/iommufd.h>
#define IOMMU_READ (1 << 0)
@@ -249,6 +250,10 @@ struct iommu_domain {
struct list_head next;
};
};
+
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+ struct iommu_domain_ser *domain_ser;
+#endif
};
static inline bool iommu_is_dma_domain(struct iommu_domain *domain)
--
2.56.0.257.gf7497c7d27-goog