[PATCH v2 14/16] iommufd: Add APIs to preserve/unpreserve a vfio cdev

From: Samiullah Khawaja

Date: Mon Apr 27 2026 - 14:01:10 EST


Add APIs that can be used to preserve and unpreserve a vfio cdev. Use
the APIs exported by the IOMMU core to preserve/unpreserve device.

The LUO token of the preserved iommufd is fetched and returned back to
the caller as that can be used during restore to get the restored
iommufd. Handle to the preserved state of the device is also returned to
reassociate with the restored state after live update kexec.

Signed-off-by: Samiullah Khawaja <skhawaja@xxxxxxxxxx>
---
drivers/iommu/iommufd/device.c | 102 ++++++++++++++++++++++++
drivers/iommu/iommufd/iommufd_private.h | 3 +
include/linux/iommufd.h | 29 +++++++
3 files changed, 134 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 170a7005f0bc..d19fece00da3 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -2,6 +2,7 @@
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
*/
#include <linux/iommu.h>
+#include <linux/iommu-liveupdate.h>
#include <linux/iommufd.h>
#include <linux/pci-ats.h>
#include <linux/slab.h>
@@ -610,6 +611,10 @@ int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt,
int rc;

mutex_lock(&igroup->lock);
+ if (iommufd_device_is_preserved(idev)) {
+ rc = -EBUSY;
+ goto err_unlock;
+ }

attach = xa_cmpxchg(&igroup->pasid_attach, pasid, NULL,
XA_ZERO_ENTRY, GFP_KERNEL);
@@ -1665,3 +1670,100 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd)
iommufd_put_object(ucmd->ictx, &idev->obj);
return rc;
}
+
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+static bool _iommufd_device_has_pasid_attachments(struct iommufd_device *idev)
+{
+ struct iommufd_group *igroup = idev->igroup;
+ unsigned long start = IOMMU_NO_PASID;
+
+ if (xa_find_after(&igroup->pasid_attach,
+ &start, UINT_MAX, XA_PRESENT))
+ return true;
+
+ return false;
+}
+
+int iommufd_device_preserve(struct liveupdate_session *s,
+ struct iommufd_device *idev,
+ u64 *iommufd_tokenp,
+ u64 *preserved_state)
+{
+ struct iommufd_group *igroup = idev->igroup;
+ struct iommufd_hwpt_paging *hwpt_paging;
+ struct iommufd_hw_pagetable *hwpt;
+ struct iommufd_attach *attach;
+ int ret;
+
+ mutex_lock(&igroup->lock);
+ if (_iommufd_device_has_pasid_attachments(idev)) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ attach = xa_load(&igroup->pasid_attach, IOMMU_NO_PASID);
+ if (!attach) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ hwpt = attach->hwpt;
+ hwpt_paging = find_hwpt_paging(hwpt);
+ if (!hwpt_paging || !hwpt_paging->liveupdate_preserved) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = liveupdate_get_token_outgoing(s, idev->ictx->file, iommufd_tokenp);
+ if (ret)
+ goto out;
+
+ ret = iommu_preserve_device(hwpt_paging->common.domain,
+ idev->dev,
+ preserved_state);
+
+ if (!ret)
+ igroup->liveupdate_preserved = true;
+out:
+ mutex_unlock(&igroup->lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_preserve, "IOMMUFD");
+
+void iommufd_device_unpreserve(struct liveupdate_session *s,
+ struct iommufd_device *idev)
+{
+ struct iommufd_group *igroup = idev->igroup;
+ struct iommufd_hwpt_paging *hwpt_paging;
+ struct iommufd_hw_pagetable *hwpt;
+ struct iommufd_attach *attach;
+
+ mutex_lock(&igroup->lock);
+ attach = xa_load(&igroup->pasid_attach, IOMMU_NO_PASID);
+ if (!attach) {
+ WARN(1, "IOMMU_NO_PASID attachment not found");
+ igroup->liveupdate_preserved = false;
+ goto out;
+ }
+
+ hwpt = attach->hwpt;
+ hwpt_paging = find_hwpt_paging(hwpt);
+ if (!hwpt_paging || !hwpt_paging->liveupdate_preserved) {
+ WARN(1, "Attached domain is not preserved");
+ igroup->liveupdate_preserved = false;
+ goto out;
+ }
+
+ iommu_unpreserve_device(hwpt_paging->common.domain, idev->dev);
+ igroup->liveupdate_preserved = false;
+out:
+ mutex_unlock(&igroup->lock);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_unpreserve, "IOMMUFD");
+
+bool iommufd_device_is_preserved(struct iommufd_device *idev)
+{
+ return idev && idev->igroup && idev->igroup->liveupdate_preserved;
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_is_preserved, "IOMMUFD");
+#endif
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 3c88aa115d08..9b47eaf92d42 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -486,6 +486,9 @@ struct iommufd_group {
struct xarray pasid_attach;
struct iommufd_sw_msi_maps required_sw_msi;
phys_addr_t sw_msi_start;
+#ifdef CONFIG_IOMMU_LIVEUPDATE
+ bool liveupdate_preserved;
+#endif
};

/*
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 6e7efe83bc5d..d1fd5d71e0fd 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -9,6 +9,7 @@
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/iommu.h>
+#include <linux/liveupdate.h>
#include <linux/refcount.h>
#include <linux/types.h>
#include <linux/xarray.h>
@@ -71,6 +72,34 @@ void iommufd_device_detach(struct iommufd_device *idev, ioasid_t pasid);
struct iommufd_ctx *iommufd_device_to_ictx(struct iommufd_device *idev);
u32 iommufd_device_to_id(struct iommufd_device *idev);

+#ifdef CONFIG_IOMMU_LIVEUPDATE
+int iommufd_device_preserve(struct liveupdate_session *s,
+ struct iommufd_device *idev,
+ u64 *iommufd_tokenp,
+ u64 *preserved_state);
+void iommufd_device_unpreserve(struct liveupdate_session *s,
+ struct iommufd_device *idev);
+bool iommufd_device_is_preserved(struct iommufd_device *idev);
+#else
+static inline int iommufd_device_preserve(struct liveupdate_session *s,
+ struct iommufd_device *idev,
+ u64 *iommufd_tokenp,
+ u64 *preserved_state)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void iommufd_device_unpreserve(struct liveupdate_session *s,
+ struct iommufd_device *idev)
+{
+}
+
+static inline bool iommufd_device_is_preserved(struct iommufd_device *idev)
+{
+ return false;
+}
+#endif
+
struct iommufd_access_ops {
u8 needs_pin_pages : 1;
void (*unmap)(void *data, unsigned long iova, unsigned long length);
--
2.54.0.545.g6539524ca2-goog