[RFC PATCH v1 3/6] iommufd: Support MMIO provider attachment to vDEVICEs
From: Aneesh Kumar K.V (Arm)
Date: Sat Oct 10 2026 - 03:26:00 EST
Allow a device MMIO provider to attach to the existing vDEVICE after the
backend validates its association with the VM. Keep the vDEVICE and
IOMMUFD context alive until the provider detaches.
Add a per-vDEVICE MMIO mutex to serialize provider attachment and
detachment, and allow providers and backends to use the same lock for
memory conversion and device state changes. Provide callbacks to
invalidate MMIO mappings and query whether private ranges remain.
Cc: Jason Gunthorpe <jgg@xxxxxxxx>
Cc: Kevin Tian <kevin.tian@xxxxxxxxx>
Cc: "Joerg Roedel (AMD)" <joro@xxxxxxxxxx>
Cc: Will Deacon <will@xxxxxxxxxx>
Cc: Robin Murphy <robin.murphy@xxxxxxx>
Cc: iommu@xxxxxxxxxxxxxxx
Cc: linux-kernel@xxxxxxxxxxxxxxx
Assisted-by: Codex
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@xxxxxxxxxx>
---
drivers/iommu/iommufd/viommu.c | 90 ++++++++++++++++++++++++++++++++++
include/linux/iommufd.h | 33 +++++++++++++
2 files changed, 123 insertions(+)
diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index 9155d0dcb4b4..24a817fb2ae8 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -250,6 +250,7 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
goto out_unlock_igroup;
}
+ mutex_init(&vdev->mmio_lock);
vdev->virt_id = virt_id;
vdev->viommu = viommu;
refcount_inc(&viommu->obj.users);
@@ -487,3 +488,92 @@ int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd)
iommufd_put_object(ucmd->ictx, &viommu->obj);
return rc;
}
+
+struct iommufd_vdevice *
+iommufd_device_attach_mmio_provider(struct iommufd_device *idev,
+ struct kvm *kvm, void *data,
+ void (*invalidate_mmio)(void *),
+ bool (*has_private_mmio)(void *),
+ unsigned long *owner)
+{
+ struct iommufd_vdevice *vdev;
+ int ret;
+
+ if (!idev)
+ return ERR_PTR(-ENODEV);
+
+ guard(mutex)(&idev->igroup->lock);
+ if (idev->destroying)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ vdev = idev->vdev;
+ if (!vdev)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ if (!vdev->viommu->ops ||
+ !vdev->viommu->ops->vdevice_get_mmio_owner)
+ return ERR_PTR(-EOPNOTSUPP);
+
+ guard(mutex)(&vdev->mmio_lock);
+ if (vdev->mmio_provider_data)
+ return ERR_PTR(-EBUSY);
+
+ ret = vdev->viommu->ops->vdevice_get_mmio_owner(vdev, kvm, owner);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* IOMMU_DESTROY must wait for the attached gmem provider to detach. */
+ ret = iommufd_try_inc_users(idev->ictx, &vdev->obj);
+ if (ret)
+ return ERR_PTR(ret);
+
+ iommufd_ctx_get(idev->ictx);
+ vdev->mmio_provider_data = data;
+ vdev->invalidate_mmio = invalidate_mmio;
+ vdev->has_private_mmio = has_private_mmio;
+ invalidate_mmio(data);
+ return vdev;
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_attach_mmio_provider, "IOMMUFD");
+
+void iommufd_vdevice_detach_mmio_provider(struct iommufd_vdevice *vdev)
+{
+ struct iommufd_ctx *ictx = vdev->viommu->ictx;
+
+ scoped_guard(mutex, &vdev->mmio_lock) {
+ WARN_ON(iommufd_vdevice_has_private_mmio(vdev));
+ vdev->mmio_provider_data = NULL;
+ vdev->invalidate_mmio = NULL;
+ vdev->has_private_mmio = NULL;
+ }
+ refcount_dec(&vdev->obj.users);
+ iommufd_ctx_put(ictx);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_vdevice_detach_mmio_provider, "IOMMUFD");
+
+void iommufd_vdevice_mmio_lock(struct iommufd_vdevice *vdev)
+{
+ mutex_lock(&vdev->mmio_lock);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_vdevice_mmio_lock, "IOMMUFD");
+
+void iommufd_vdevice_mmio_unlock(struct iommufd_vdevice *vdev)
+{
+ mutex_unlock(&vdev->mmio_lock);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_vdevice_mmio_unlock, "IOMMUFD");
+
+/**
+ * iommufd_vdevice_has_private_mmio() - check for private MMIO ranges
+ * @vdev: vDEVICE whose private MMIO state is being checked
+ *
+ * Return: true if the attached provider has committed private ranges.
+ */
+bool iommufd_vdevice_has_private_mmio(struct iommufd_vdevice *vdev)
+{
+ lockdep_assert_held(&vdev->mmio_lock);
+ if (!vdev->has_private_mmio)
+ return false;
+ return vdev->has_private_mmio(vdev->mmio_provider_data);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_vdevice_has_private_mmio, "IOMMUFD");
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 2e67846d6f35..3f3b9d1dd22a 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -7,10 +7,12 @@
#define __LINUX_IOMMUFD_H
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/iommu.h>
#include <linux/refcount.h>
+#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/xarray.h>
#include <uapi/linux/iommufd.h>
@@ -26,6 +28,7 @@ struct iommufd_device;
struct iommufd_viommu_ops;
struct tsm_dev;
struct page;
+struct kvm;
struct tsm_guest_req_info;
enum iommufd_object_type {
@@ -73,6 +76,20 @@ int iommufd_device_replace(struct iommufd_device *idev, ioasid_t pasid,
u32 *pt_id);
void iommufd_device_detach(struct iommufd_device *idev, ioasid_t pasid);
+/* The returned opaque vDEVICE pins its IOMMUFD context and physical owner. */
+struct iommufd_vdevice *
+iommufd_device_attach_mmio_provider(struct iommufd_device *idev,
+ struct kvm *kvm, void *data,
+ void (*invalidate_mmio)(void *),
+ bool (*has_private_mmio)(void *),
+ unsigned long *owner);
+void iommufd_vdevice_detach_mmio_provider(struct iommufd_vdevice *vdev);
+void iommufd_vdevice_mmio_lock(struct iommufd_vdevice *vdev);
+void iommufd_vdevice_mmio_unlock(struct iommufd_vdevice *vdev);
+DEFINE_GUARD(iommufd_vdevice_mmio, struct iommufd_vdevice *,
+ iommufd_vdevice_mmio_lock(_T), iommufd_vdevice_mmio_unlock(_T))
+bool iommufd_vdevice_has_private_mmio(struct iommufd_vdevice *vdev);
+
struct iommufd_ctx *iommufd_device_to_ictx(struct iommufd_device *idev);
u32 iommufd_device_to_id(struct iommufd_device *idev);
@@ -130,6 +147,18 @@ struct iommufd_vdevice {
*/
u64 virt_id;
+ /* Serializes provider attachment and conversion. */
+ struct mutex mmio_lock;
+ /* Attached provider and callbacks, protected by mmio_lock until detach. */
+ void *mmio_provider_data;
+ /*
+ * Revoke VFIO userspace BAR mappings and shared guest stage-2 mappings
+ * before delegation. Private guest mappings remain intact; guest_memfd
+ * removes them through its architecture unmap operation.
+ */
+ void (*invalidate_mmio)(void *data);
+ bool (*has_private_mmio)(void *data);
+
/* Guest TSM requests accepted by this vdevice; set by vdevice_init(). */
u64 tsm_req_op_mask;
u32 tsm_tvm_arch;
@@ -187,6 +216,7 @@ struct iommufd_hw_queue {
* include/uapi/linux/iommufd.h)
* If driver has a deinit function to revert what vdevice_init op
* does, it should set it to the @vdev->destroy function pointer
+ * @vdevice_get_mmio_owner: Validate the VM and return its MMIO owner.
* @vdevice_tsm_req: Forward a guest TSM request to a driver-owned vDEVICE
* @get_hw_queue_size: Get the size of a driver-defined HW queue structure for a
* given @viommu corresponding to @queue_type. Driver should
@@ -218,6 +248,9 @@ struct iommufd_viommu_ops {
const struct iommu_user_data *user_data);
int (*cache_invalidate)(struct iommufd_viommu *viommu,
struct iommu_user_data_array *array);
+ /* Owner lookup runs with vdevice->mmio_lock held. */
+ int (*vdevice_get_mmio_owner)(struct iommufd_vdevice *vdev,
+ struct kvm *kvm, unsigned long *owner);
const size_t vdevice_size;
int (*vdevice_init)(struct iommufd_vdevice *vdev);
ssize_t (*vdevice_tsm_req)(struct iommufd_vdevice *vdev,
--
2.43.0