* [RFC PATCH v6 01/11] vfio: cache KVM VM file references instead of raw struct kvm pointers
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 02/11] vfio: cdev: Reject duplicate bind before updating KVM file Aneesh Kumar K.V (Arm)
` (10 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini, Anthony Krowiak
VFIO currently records struct kvm pointers on vfio_group, vfio_device_file
and the opened vfio_device. Switch VFIO to track the VM's struct file
instead, so VFIO and iommufd can use normal file references for VM lifetime
instead of depending on KVM's internal struct kvm refcounting.
KVM_CREATE_DEVICE binds the KVM VM lifetime to the KVM device fd lifetime.
For KVM_DEV_TYPE_VFIO, the KVM VFIO device fd also takes references to each
VFIO file added through KVM_DEV_VFIO_FILE_ADD. The KVM VFIO device fd
therefore owns both the internal KVM reference and the VFIO file references
in kvf->file.
KVM_DEV_VFIO_FILE_ADD further installs the VM file association into the
VFIO file. VFIO converts the struct kvm pointer to a VM file reference with
get_file_active(&kvm->_file), because the KVM device fd can keep struct kvm
alive after the original VM fd is already in final release.
The association intentionally pins the VM file until KVM_DEV_VFIO_FILE_DEL
or until the KVM VFIO device fd is released. This gives VFIO/iommufd a
stable VM file reference source without taking a dependency on KVM's struct
kvm lifetime. The KVM VFIO device release path clears the VFIO-side
association before dropping its VFIO file references.
When a VFIO device is opened or bound, VFIO takes an additional reference
from the associated VM file and stores it in vfio_device::kvm_file for
driver and iommufd use. That open-time reference is released from
vfio_device_put_kvm() when the VFIO device is closed or unbound.
This gives the ownership model:
- KVM device fd pins struct kvm through kvm->users_count
- KVM VFIO device fd pins VFIO files through kvf->file
- VFIO group/device-file state pins the VM file while associated with KVM
- vfio_device::kvm_file pins the VM file during active VFIO device use
Acked-by: Anthony Krowiak <akrowiak@linxux.ibm.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/s390/crypto/vfio_ap_ops.c | 5 +-
drivers/vfio/device_cdev.c | 10 ++--
drivers/vfio/group.c | 14 +++---
drivers/vfio/pci/vfio_pci_zdev.c | 7 +--
drivers/vfio/vfio.h | 16 ++++--
drivers/vfio/vfio_main.c | 81 ++++++++++++++++---------------
include/linux/kvm_host.h | 3 ++
include/linux/vfio.h | 17 ++++++-
virt/kvm/kvm_main.c | 2 +
9 files changed, 91 insertions(+), 64 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 44b3a1dcc1b3..05996a8fd860 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -2054,11 +2054,12 @@ static int vfio_ap_mdev_open_device(struct vfio_device *vdev)
{
struct ap_matrix_mdev *matrix_mdev =
container_of(vdev, struct ap_matrix_mdev, vdev);
+ struct kvm *kvm = vfio_device_get_kvm(vdev);
- if (!vdev->kvm)
+ if (!kvm)
return -EINVAL;
- return vfio_ap_mdev_set_kvm(matrix_mdev, vdev->kvm);
+ return vfio_ap_mdev_set_kvm(matrix_mdev, kvm);
}
static void vfio_ap_mdev_close_device(struct vfio_device *vdev)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index 54abf312cf04..ca75ab8eb7bd 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -56,7 +56,7 @@ int vfio_device_fops_cdev_open(struct inode *inode, struct file *filep)
static void vfio_df_get_kvm_safe(struct vfio_device_file *df)
{
spin_lock(&df->kvm_ref_lock);
- vfio_device_get_kvm_safe(df->device, df->kvm);
+ vfio_device_get_kvm_safe(df->device, df->kvm_file);
spin_unlock(&df->kvm_ref_lock);
}
@@ -133,10 +133,10 @@ long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df,
}
/*
- * Before the device open, get the KVM pointer currently
- * associated with the device file (if there is) and obtain
- * a reference. This reference is held until device closed.
- * Save the pointer in the device for use by drivers.
+ * Before the device open, get the VM struct file currently
+ * associated with the device file (if there is one) and obtain a
+ * reference. This reference is held until the device is closed.
+ * Save the file in the device for use by drivers.
*/
vfio_df_get_kvm_safe(df);
diff --git a/drivers/vfio/group.c b/drivers/vfio/group.c
index b2299e5bc6df..8950cfb9405d 100644
--- a/drivers/vfio/group.c
+++ b/drivers/vfio/group.c
@@ -163,7 +163,7 @@ static int vfio_group_ioctl_set_container(struct vfio_group *group,
static void vfio_device_group_get_kvm_safe(struct vfio_device *device)
{
spin_lock(&device->group->kvm_ref_lock);
- vfio_device_get_kvm_safe(device, device->group->kvm);
+ vfio_device_get_kvm_safe(device, device->group->kvm_file);
spin_unlock(&device->group->kvm_ref_lock);
}
@@ -181,10 +181,10 @@ static int vfio_df_group_open(struct vfio_device_file *df)
mutex_lock(&device->dev_set->lock);
/*
- * Before the first device open, get the KVM pointer currently
- * associated with the group (if there is one) and obtain a reference
- * now that will be held until the open_count reaches 0 again. Save
- * the pointer in the device for use by drivers.
+ * Before the first device open, get the VM struct file currently
+ * associated with the group (if there is one) and obtain a
+ * reference now that will be held until the open_count reaches 0
+ * again. Save the file in the device for use by drivers.
*/
if (device->open_count == 0)
vfio_device_group_get_kvm_safe(device);
@@ -862,9 +862,7 @@ bool vfio_group_enforced_coherent(struct vfio_group *group)
void vfio_group_set_kvm(struct vfio_group *group, struct kvm *kvm)
{
- spin_lock(&group->kvm_ref_lock);
- group->kvm = kvm;
- spin_unlock(&group->kvm_ref_lock);
+ vfio_kvm_file_replace(&group->kvm_file, &group->kvm_ref_lock, kvm);
}
/**
diff --git a/drivers/vfio/pci/vfio_pci_zdev.c b/drivers/vfio/pci/vfio_pci_zdev.c
index 0990fdb146b7..a9d8e6aa3839 100644
--- a/drivers/vfio/pci/vfio_pci_zdev.c
+++ b/drivers/vfio/pci/vfio_pci_zdev.c
@@ -144,15 +144,16 @@ int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev,
int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
+ struct kvm *kvm = vfio_device_get_kvm(&vdev->vdev);
if (!zdev)
return -ENODEV;
- if (!vdev->vdev.kvm)
+ if (!kvm)
return 0;
if (zpci_kvm_hook.kvm_register)
- return zpci_kvm_hook.kvm_register(zdev, vdev->vdev.kvm);
+ return zpci_kvm_hook.kvm_register(zdev, kvm);
return -ENOENT;
}
@@ -161,7 +162,7 @@ void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev)
{
struct zpci_dev *zdev = to_zpci(vdev->pdev);
- if (!zdev || !vdev->vdev.kvm)
+ if (!zdev || !vfio_device_get_kvm(&vdev->vdev))
return;
if (zpci_kvm_hook.kvm_unregister)
diff --git a/drivers/vfio/vfio.h b/drivers/vfio/vfio.h
index e4b72e79b7e3..41032104eb36 100644
--- a/drivers/vfio/vfio.h
+++ b/drivers/vfio/vfio.h
@@ -22,8 +22,8 @@ struct vfio_device_file {
u8 access_granted;
u32 devid; /* only valid when iommufd is valid */
- spinlock_t kvm_ref_lock; /* protect kvm field */
- struct kvm *kvm;
+ spinlock_t kvm_ref_lock; /* protect kvm_file */
+ struct file *kvm_file;
struct iommufd_ctx *iommufd; /* protected by struct vfio_device_set::lock */
};
@@ -88,7 +88,7 @@ struct vfio_group {
#endif
enum vfio_group_type type;
struct mutex group_lock;
- struct kvm *kvm;
+ struct file *kvm_file;
struct file *opened_file;
struct iommufd_ctx *iommufd;
spinlock_t kvm_ref_lock;
@@ -434,11 +434,17 @@ static inline void vfio_virqfd_exit(void)
#endif
#if IS_ENABLED(CONFIG_KVM)
-void vfio_device_get_kvm_safe(struct vfio_device *device, struct kvm *kvm);
+void vfio_kvm_file_replace(struct file **dst, spinlock_t *lock, struct kvm *kvm);
+void vfio_device_get_kvm_safe(struct vfio_device *device, struct file *kvm_file);
void vfio_device_put_kvm(struct vfio_device *device);
#else
+static inline void vfio_kvm_file_replace(struct file **dst,
+ spinlock_t *lock, struct kvm *kvm)
+{
+}
+
static inline void vfio_device_get_kvm_safe(struct vfio_device *device,
- struct kvm *kvm)
+ struct file *kvm_file)
{
}
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index ed538aebb0b8..fabff4aace1c 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -448,55 +448,61 @@ void vfio_unregister_group_dev(struct vfio_device *device)
EXPORT_SYMBOL_GPL(vfio_unregister_group_dev);
#if IS_ENABLED(CONFIG_KVM)
-void vfio_device_get_kvm_safe(struct vfio_device *device, struct kvm *kvm)
+void vfio_kvm_file_replace(struct file **dst, spinlock_t *lock, struct kvm *kvm)
{
- void (*pfn)(struct kvm *kvm);
- bool (*fn)(struct kvm *kvm);
- bool ret;
+ struct file *old_kvm_file, *new_kvm_file = NULL;
- lockdep_assert_held(&device->dev_set->lock);
+ /*
+ * @kvm can outlive the VM fd and its final __fput(). Only take a
+ * new reference if the VM file is still active.
+ */
+ if (kvm)
+ new_kvm_file = get_file_active(&kvm->_file);
- if (!kvm)
- return;
+ spin_lock(lock);
+ old_kvm_file = *dst;
+ *dst = new_kvm_file;
+ spin_unlock(lock);
- pfn = symbol_get(kvm_put_kvm);
- if (WARN_ON(!pfn))
- return;
+ if (old_kvm_file)
+ fput(old_kvm_file);
+}
- fn = symbol_get(kvm_get_kvm_safe);
- if (WARN_ON(!fn)) {
- symbol_put(kvm_put_kvm);
- return;
- }
+void vfio_device_get_kvm_safe(struct vfio_device *device, struct file *kvm_file)
+{
+ lockdep_assert_held(&device->dev_set->lock);
- ret = fn(kvm);
- symbol_put(kvm_get_kvm_safe);
- if (!ret) {
- symbol_put(kvm_put_kvm);
- return;
- }
+ /*
+ * Take a VM file reference if the KVM fd is still active.
+ */
+ if (kvm_file)
+ kvm_file = get_file(kvm_file);
- device->put_kvm = pfn;
- device->kvm = kvm;
+ device->kvm_file = kvm_file;
}
void vfio_device_put_kvm(struct vfio_device *device)
{
+ struct file *kvm_file;
+
lockdep_assert_held(&device->dev_set->lock);
- if (!device->kvm)
+ kvm_file = device->kvm_file;
+ if (!kvm_file)
return;
- if (WARN_ON(!device->put_kvm))
- goto clear;
+ device->kvm_file = NULL;
+ fput(kvm_file);
+}
- device->put_kvm(device->kvm);
- device->put_kvm = NULL;
- symbol_put(kvm_put_kvm);
+struct kvm *vfio_device_get_kvm(struct vfio_device *device)
+{
+ if (!device->kvm_file)
+ return NULL;
-clear:
- device->kvm = NULL;
+ return device->kvm_file->private_data;
}
+EXPORT_SYMBOL_GPL(vfio_device_get_kvm);
#endif
/* true if the vfio_device has open_device() called but not close_device() */
@@ -1525,13 +1531,10 @@ static void vfio_device_file_set_kvm(struct file *file, struct kvm *kvm)
struct vfio_device_file *df = file->private_data;
/*
- * The kvm is first recorded in the vfio_device_file, and will
- * be propagated to vfio_device::kvm when the file is bound to
- * iommufd successfully in the vfio device cdev path.
+ * Cache the VM file reference associated with this VFIO file so it
+ * can be pinned into vfio_device while the device is open.
*/
- spin_lock(&df->kvm_ref_lock);
- df->kvm = kvm;
- spin_unlock(&df->kvm_ref_lock);
+ vfio_kvm_file_replace(&df->kvm_file, &df->kvm_ref_lock, kvm);
}
/**
@@ -1539,8 +1542,8 @@ static void vfio_device_file_set_kvm(struct file *file, struct kvm *kvm)
* @file: VFIO group file or VFIO device file
* @kvm: KVM to link
*
- * When a VFIO device is first opened the KVM will be available in
- * device->kvm if one was associated with the file.
+ * When a VFIO device is first opened, VFIO caches a VM file reference if
+ * one was associated with the file.
*/
void vfio_file_set_kvm(struct file *file, struct kvm *kvm)
{
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 9c1cf1a6559e..b3b0fa7d53a2 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -45,6 +45,8 @@
#include <asm/kvm_host.h>
#include <linux/kvm_dirty_ring.h>
+struct file;
+
#ifndef KVM_MAX_VCPU_IDS
#define KVM_MAX_VCPU_IDS KVM_MAX_VCPUS
#endif
@@ -840,6 +842,7 @@ struct kvm {
struct srcu_struct srcu;
struct srcu_struct irq_srcu;
pid_t userspace_pid;
+ struct file __rcu *_file;
bool override_halt_poll_ns;
unsigned int max_halt_poll_ns;
u32 dirty_ring_size;
diff --git a/include/linux/vfio.h b/include/linux/vfio.h
index 31b826efba00..bca1d00f7845 100644
--- a/include/linux/vfio.h
+++ b/include/linux/vfio.h
@@ -22,8 +22,22 @@ struct kvm;
struct iommufd_ctx;
struct iommufd_device;
struct iommufd_access;
+struct vfio_device;
struct vfio_info_cap;
+#if IS_ENABLED(CONFIG_KVM)
+/*
+ * Return the KVM associated with @vdev's kvm_file. The returned pointer
+ * is valid only while VFIO device open holds the kvm_file reference.
+ */
+struct kvm *vfio_device_get_kvm(struct vfio_device *vdev);
+#else
+static inline struct kvm *vfio_device_get_kvm(struct vfio_device *vdev)
+{
+ return NULL;
+}
+#endif
+
/*
* VFIO devices can be placed in a set, this allows all devices to share this
* structure and the VFIO core will provide a lock that is held around
@@ -54,7 +68,7 @@ struct vfio_device {
struct list_head dev_set_list;
unsigned int migration_flags;
u8 precopy_info_v2;
- struct kvm *kvm;
+ struct file *kvm_file;
/* Members below here are private, not for driver use */
unsigned int index;
@@ -66,7 +80,6 @@ struct vfio_device {
unsigned int open_count;
struct completion comp;
struct iommufd_access *iommufd_access;
- void (*put_kvm)(struct kvm *kvm);
struct inode *inode;
#if IS_ENABLED(CONFIG_IOMMUFD)
struct iommufd_device *iommufd_device;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 46e92b5dc380..ba39e3857e7d 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1369,6 +1369,7 @@ static int kvm_vm_release(struct inode *inode, struct file *filp)
kvm_irqfd_release(kvm);
+ RCU_INIT_POINTER(kvm->_file, NULL);
kvm_put_kvm(kvm);
return 0;
}
@@ -5550,6 +5551,7 @@ static int kvm_dev_ioctl_create_vm(unsigned long type)
r = PTR_ERR(file);
goto put_kvm;
}
+ rcu_assign_pointer(kvm->_file, file);
/*
* Don't call kvm_put_kvm anymore at this point; file->f_op is
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 02/11] vfio: cdev: Reject duplicate bind before updating KVM file
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 01/11] vfio: cache KVM VM file references instead of raw struct kvm pointers Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 03/11] iommufd/device: Associate KVM file pointer with iommufd_device Aneesh Kumar K.V (Arm)
` (9 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
The cdev path only supports one bound/open device fd, but
VFIO_DEVICE_BIND_IOMMUFD only checked the per-file access_granted flag
before capturing the KVM file reference. A second fd for the same device
could therefore replace device->kvm_file, fail later in vfio_df_open()
because open_count is already nonzero, and then clear the active KVM
association during error cleanup.
Reject the bind while holding dev_set->lock if the device is already
open, matching the existing cdev single-open rule before touching the
device-wide KVM state.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/vfio/device_cdev.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index ca75ab8eb7bd..67e48f7ebfc3 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -115,8 +115,8 @@ long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df,
return ret;
mutex_lock(&device->dev_set->lock);
- /* one device cannot be bound twice */
- if (df->access_granted) {
+ /* The cdev path only supports one bound/open device fd. */
+ if (df->access_granted || device->open_count) {
ret = -EINVAL;
goto out_unlock;
}
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 03/11] iommufd/device: Associate KVM file pointer with iommufd_device
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 01/11] vfio: cache KVM VM file references instead of raw struct kvm pointers Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 02/11] vfio: cdev: Reject duplicate bind before updating KVM file Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 04/11] iommufd/viommu: Keep a reference to the KVM file Aneesh Kumar K.V (Arm)
` (8 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini, Jason Gunthorpe
From: Shameer Kolothum <shameerali.kolothum.thodi@huawei.com>
TSM vDevice support needs access to the KVM associated with a VFIO device
after the device has been bound to iommufd.
Extend iommufd_device_bind() to accept the device's KVM file and store it
in the iommufd_device. The KVM file reference is owned by VFIO and is
already held for the duration of the device open path.
Signed-off-by: Shameer Kolothum <shameerali.kolothum.thodi@huawei.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
[nicolinc: fix build error in iommufd_test_mock_domain()]
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
[aneesh.kumar: Switch to use kvm_file]
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommufd/device.c | 7 ++++++-
drivers/iommu/iommufd/iommufd_private.h | 2 ++
drivers/iommu/iommufd/selftest.c | 2 +-
drivers/vfio/iommufd.c | 3 ++-
include/linux/iommufd.h | 4 +++-
5 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 170a7005f0bc..718abdc0e627 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -203,6 +203,7 @@ void iommufd_device_destroy(struct iommufd_object *obj)
* iommufd_device_bind - Bind a physical device to an iommu fd
* @ictx: iommufd file descriptor
* @dev: Pointer to a physical device struct
+ * @kvm_file: VM file if device belongs to a KVM VM
* @id: Output ID number to return to userspace for this device
*
* A successful bind establishes an ownership over the device and returns
@@ -216,7 +217,9 @@ void iommufd_device_destroy(struct iommufd_object *obj)
* The caller must undo this with iommufd_device_unbind()
*/
struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
- struct device *dev, u32 *id)
+ struct device *dev,
+ struct file *kvm_file,
+ u32 *id)
{
struct iommufd_device *idev;
struct iommufd_group *igroup;
@@ -266,6 +269,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
if (!iommufd_selftest_is_mock_dev(dev))
iommufd_ctx_get(ictx);
idev->dev = dev;
+ /* reference is already taken in vfio_df_ioctl_bind_iommufd() */
+ idev->kvm_file = kvm_file;
idev->enforce_cache_coherency =
device_iommu_capable(dev, IOMMU_CAP_ENFORCE_CACHE_COHERENCY);
/* The calling driver is a user until iommufd_device_unbind() */
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 43fbc5bed8de..6cf76f7ca379 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -488,6 +488,8 @@ struct iommufd_device {
struct list_head group_item;
/* always the physical device */
struct device *dev;
+ /* ..and the VM file if available */
+ struct file *kvm_file;
bool enforce_cache_coherency;
struct iommufd_vdevice *vdev;
bool destroying;
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..a193390f9d07 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -1069,7 +1069,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
goto out_sobj;
}
- idev = iommufd_device_bind(ucmd->ictx, &sobj->idev.mock_dev->dev,
+ idev = iommufd_device_bind(ucmd->ictx, &sobj->idev.mock_dev->dev, NULL,
&idev_id);
if (IS_ERR(idev)) {
rc = PTR_ERR(idev);
diff --git a/drivers/vfio/iommufd.c b/drivers/vfio/iommufd.c
index a38d262c6028..d2d0bd9382a1 100644
--- a/drivers/vfio/iommufd.c
+++ b/drivers/vfio/iommufd.c
@@ -119,7 +119,8 @@ int vfio_iommufd_physical_bind(struct vfio_device *vdev,
{
struct iommufd_device *idev;
- idev = iommufd_device_bind(ictx, vdev->dev, out_device_id);
+ idev = iommufd_device_bind(ictx, vdev->dev, vdev->kvm_file,
+ out_device_id);
if (IS_ERR(idev))
return PTR_ERR(idev);
vdev->iommufd_device = idev;
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 6e7efe83bc5d..0a0bb4abfbd2 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -59,7 +59,9 @@ struct iommufd_object {
};
struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
- struct device *dev, u32 *id);
+ struct device *dev,
+ struct file *kvm_file,
+ u32 *id);
void iommufd_device_unbind(struct iommufd_device *idev);
int iommufd_device_attach(struct iommufd_device *idev, ioasid_t pasid,
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 04/11] iommufd/viommu: Keep a reference to the KVM file
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (2 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 03/11] iommufd/device: Associate KVM file pointer with iommufd_device Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 05/11] iommu: Add a helper to validate a vIOMMU parent Aneesh Kumar K.V (Arm)
` (7 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
From: Nicolin Chen <nicolinc@nvidia.com>
The TSM vDevice operations need access to the KVM associated with the
device's vIOMMU. Save the device's KVM file in the iommufd_viommu when the
vIOMMU is allocated, and take a file reference so it remains valid for the
lifetime of the vIOMMU.
Release the reference when the vIOMMU is destroyed.
Based on an original patch by Shameer Kolothum <shameerali.kolothum.thodi@huawei.com>
[nicolinc: hold kvm's users_count]
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
[aneesh.kumar: Switch to use kvm_file]
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommufd/viommu.c | 5 +++++
include/linux/iommufd.h | 1 +
2 files changed, 6 insertions(+)
diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index 4081deda9b33..bf5d58d55939 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES
*/
+#include <linux/file.h>
#include "iommufd_private.h"
void iommufd_viommu_destroy(struct iommufd_object *obj)
@@ -11,6 +12,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj)
if (viommu->ops && viommu->ops->destroy)
viommu->ops->destroy(viommu);
refcount_dec(&viommu->hwpt->common.obj.users);
+ if (viommu->kvm_file)
+ fput(viommu->kvm_file);
xa_destroy(&viommu->vdevs);
}
@@ -76,6 +79,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
}
xa_init(&viommu->vdevs);
+ if (idev->kvm_file)
+ viommu->kvm_file = get_file(idev->kvm_file);
viommu->type = cmd->type;
viommu->ictx = ucmd->ictx;
viommu->hwpt = hwpt_paging;
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 0a0bb4abfbd2..3267717f676d 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -103,6 +103,7 @@ struct iommufd_viommu {
struct iommufd_ctx *ictx;
struct iommu_device *iommu_dev;
struct iommufd_hwpt_paging *hwpt;
+ struct file *kvm_file;
const struct iommufd_viommu_ops *ops;
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 05/11] iommu: Add a helper to validate a vIOMMU parent
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (3 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 04/11] iommufd/viommu: Keep a reference to the KVM file Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 06/11] iommu: Add a helper to query vIOMMU hardware parameters Aneesh Kumar K.V (Arm)
` (6 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
Let a vIOMMU implementation ask the physical IOMMU driver whether a
device and nesting parent are compatible. Return EOPNOTSUPP when the
driver does not supply the validation callback.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommu.c | 11 +++++++++++
include/linux/iommu.h | 8 ++++++++
2 files changed, 19 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index e8f13dcebbde..04cd5a686e18 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -47,6 +47,17 @@ static unsigned int iommu_def_domain_type __read_mostly;
static bool iommu_dma_strict __read_mostly = IS_ENABLED(CONFIG_IOMMU_DEFAULT_DMA_STRICT);
static u32 iommu_cmd_line __read_mostly;
+int iommu_viommu_validate_parent(struct device *dev,
+ enum iommu_viommu_type type, struct iommu_domain *parent_domain)
+{
+ const struct iommu_ops *ops = dev_iommu_ops(dev);
+
+ if (!ops->viommu_validate_parent)
+ return -EOPNOTSUPP;
+ return ops->viommu_validate_parent(dev, type, parent_domain);
+}
+EXPORT_SYMBOL_GPL(iommu_viommu_validate_parent);
+
/* Tags used with xa_tag_pointer() in group->pasid_array */
enum { IOMMU_PASID_ARRAY_DOMAIN = 0, IOMMU_PASID_ARRAY_HANDLE = 1 };
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863a..ee016a7c7a41 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -435,6 +435,9 @@ struct iommu_user_data {
size_t len;
};
+int iommu_viommu_validate_parent(struct device *dev,
+ enum iommu_viommu_type type, struct iommu_domain *parent_domain);
+
/**
* struct iommu_user_data_array - iommu driver specific user space data array
* @type: The data type of all the entries in the user buffer array
@@ -679,6 +682,8 @@ __iommu_copy_struct_to_user(const struct iommu_user_data *dst_data,
* resources shared/passed to user space IOMMU instance. Associate
* it with a nesting @parent_domain. It is required for driver to
* set @viommu->ops pointing to its own viommu_ops
+ * @viommu_validate_parent: Validate that @parent_domain is compatible with
+ * @dev for @viommu_type.
* @owner: Driver module providing these ops
* @identity_domain: An always available, always attachable identity
* translation.
@@ -734,6 +739,9 @@ struct iommu_ops {
int (*viommu_init)(struct iommufd_viommu *viommu,
struct iommu_domain *parent_domain,
const struct iommu_user_data *user_data);
+ int (*viommu_validate_parent)(struct device *dev,
+ enum iommu_viommu_type viommu_type,
+ struct iommu_domain *parent_domain);
const struct iommu_domain_ops *default_domain_ops;
struct module *owner;
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 06/11] iommu: Add a helper to query vIOMMU hardware parameters
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (4 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 05/11] iommu: Add a helper to validate a vIOMMU parent Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 07/11] coco: tsm: Expose active-user lifetime references Aneesh Kumar K.V (Arm)
` (5 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
Expose a type-specific kernel parameter query through the physical IOMMU
driver. This lets an external vIOMMU provider obtain hardware parameters
without depending on private driver structures.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommu.c | 11 +++++++++++
include/linux/iommu.h | 7 +++++++
2 files changed, 18 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 04cd5a686e18..70ca10380742 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -58,6 +58,17 @@ int iommu_viommu_validate_parent(struct device *dev,
}
EXPORT_SYMBOL_GPL(iommu_viommu_validate_parent);
+int iommu_viommu_get_params(struct device *dev,
+ enum iommu_viommu_type type, void *params, size_t params_size)
+{
+ const struct iommu_ops *ops = dev_iommu_ops(dev);
+
+ if (!ops->viommu_get_params)
+ return -EOPNOTSUPP;
+ return ops->viommu_get_params(dev, type, params, params_size);
+}
+EXPORT_SYMBOL_GPL(iommu_viommu_get_params);
+
/* Tags used with xa_tag_pointer() in group->pasid_array */
enum { IOMMU_PASID_ARRAY_DOMAIN = 0, IOMMU_PASID_ARRAY_HANDLE = 1 };
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ee016a7c7a41..c006478c4b04 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -437,6 +437,8 @@ struct iommu_user_data {
int iommu_viommu_validate_parent(struct device *dev,
enum iommu_viommu_type type, struct iommu_domain *parent_domain);
+int iommu_viommu_get_params(struct device *dev,
+ enum iommu_viommu_type type, void *params, size_t params_size);
/**
* struct iommu_user_data_array - iommu driver specific user space data array
@@ -684,6 +686,8 @@ __iommu_copy_struct_to_user(const struct iommu_user_data *dst_data,
* set @viommu->ops pointing to its own viommu_ops
* @viommu_validate_parent: Validate that @parent_domain is compatible with
* @dev for @viommu_type.
+ * @viommu_get_params: Fill a kernel buffer with parameters for @viommu_type.
+ * The buffer format is defined by that vIOMMU type.
* @owner: Driver module providing these ops
* @identity_domain: An always available, always attachable identity
* translation.
@@ -742,6 +746,9 @@ struct iommu_ops {
int (*viommu_validate_parent)(struct device *dev,
enum iommu_viommu_type viommu_type,
struct iommu_domain *parent_domain);
+ int (*viommu_get_params)(struct device *dev,
+ enum iommu_viommu_type viommu_type,
+ void *params, size_t params_size);
const struct iommu_domain_ops *default_domain_ops;
struct module *owner;
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 07/11] coco: tsm: Expose active-user lifetime references
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (5 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 06/11] iommu: Add a helper to query vIOMMU hardware parameters Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 08/11] iommufd: Add vIOMMU provider support Aneesh Kumar K.V (Arm)
` (4 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
Add separate active-user references for consumers that require both the
TSM device and its PCI/TSM resources.
Initialize the users count to one when allocating the TSM device. This
initial reference represents the registration and keeps the PCI/TSM
resources registered until tsm_unregister() drops it.
tsm_get() takes both an active-user reference and a device reference.
tsm_put() drops the active-user reference first, allowing the last
active user to tear down PCI/TSM resources while struct tsm_dev and the
driver operations remain valid, and then drops the device reference.
tsm_unregister() drops the initial registration reference and
unregisters the device. Existing active users retain the PCI/TSM
resources, while their device references keep struct tsm_dev alive. The
final active-user reference tears down the PCI/TSM resources,
independently of the final device reference releasing struct tsm_dev.
Restructure PCI/TSM registration error handling so tsm_register()
removes the device and lets its scoped device reference perform the
final release.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/tsm-core.c | 55 ++++++++++++++++++++++++++++++------
include/linux/tsm.h | 13 +++++++++
2 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/drivers/virt/coco/tsm-core.c b/drivers/virt/coco/tsm-core.c
index 0843b77c6549..90d304d55b59 100644
--- a/drivers/virt/coco/tsm-core.c
+++ b/drivers/virt/coco/tsm-core.c
@@ -57,6 +57,39 @@ static const struct class tsm_class = {
.dev_groups = tsm_pci_groups,
};
static DEFINE_IDA(tsm_ida);
+static void tsm_put_active(struct tsm_dev *tsm_dev)
+{
+ if (!refcount_dec_and_test(&tsm_dev->users))
+ return;
+ if (tsm_dev->pci_ops)
+ pci_tsm_unregister(tsm_dev);
+}
+
+/**
+ * tsm_get() - Take an active reference to a TSM
+ * @tsm_dev: registered TSM or TSM already held by an active reference
+ *
+ * Keeps both the device and its PCI/TSM resources alive.
+ */
+void tsm_get(struct tsm_dev *tsm_dev)
+{
+ get_device(&tsm_dev->dev);
+ refcount_inc(&tsm_dev->users);
+}
+EXPORT_SYMBOL_GPL(tsm_get);
+
+/**
+ * tsm_put() - Release an active TSM reference
+ * @tsm_dev: TSM acquired with tsm_get()
+ *
+ * The last active reference tears down PCI/TSM resources after unregister.
+ */
+void tsm_put(struct tsm_dev *tsm_dev)
+{
+ tsm_put_active(tsm_dev);
+ put_device(&tsm_dev->dev);
+}
+EXPORT_SYMBOL_GPL(tsm_put);
static int match_id(struct device *dev, const void *data)
{
@@ -90,6 +123,7 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)
return ERR_PTR(id);
tsm_dev->id = id;
+ refcount_set(&tsm_dev->users, 1);
dev = &tsm_dev->dev;
dev->parent = parent;
dev->class = &tsm_class;
@@ -98,27 +132,26 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)
return no_free_ptr(tsm_dev);
}
-static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev,
- struct pci_tsm_ops *pci_ops)
+static int tsm_register_pci(struct tsm_dev *tsm_dev, struct pci_tsm_ops *pci_ops)
{
int rc;
if (!pci_ops)
- return tsm_dev;
+ return 0;
tsm_dev->pci_ops = pci_ops;
rc = pci_tsm_register(tsm_dev);
if (rc) {
+ tsm_dev->pci_ops = NULL;
dev_err(tsm_dev->dev.parent,
"PCI/TSM registration failure: %d\n", rc);
- device_unregister(&tsm_dev->dev);
- return ERR_PTR(rc);
+ return rc;
}
sysfs_update_group(&tsm_dev->dev.kobj, &tsm_pci_group);
/* Notify TSM userspace that PCI/TSM operations are now possible */
kobject_uevent(&tsm_dev->dev.kobj, KOBJ_CHANGE);
- return tsm_dev;
+ return 0;
}
struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)
@@ -139,14 +172,18 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)
if (rc)
return ERR_PTR(rc);
- return tsm_register_pci_or_reset(no_free_ptr(tsm_dev), pci_ops);
+ rc = tsm_register_pci(tsm_dev, pci_ops);
+ if (rc) {
+ device_del(dev);
+ return ERR_PTR(rc);
+ }
+ return no_free_ptr(tsm_dev);
}
EXPORT_SYMBOL_GPL(tsm_register);
void tsm_unregister(struct tsm_dev *tsm_dev)
{
- if (tsm_dev->pci_ops)
- pci_tsm_unregister(tsm_dev);
+ tsm_put_active(tsm_dev);
device_unregister(&tsm_dev->dev);
}
EXPORT_SYMBOL_GPL(tsm_unregister);
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
index 7f72a154b6b2..f38d6fcf9cc9 100644
--- a/include/linux/tsm.h
+++ b/include/linux/tsm.h
@@ -2,6 +2,7 @@
#ifndef __TSM_H
#define __TSM_H
+#include <linux/refcount.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include <linux/uuid.h>
@@ -109,8 +110,18 @@ struct tsm_report_ops {
};
struct pci_tsm_ops;
+
+/**
+ * struct tsm_dev - TEE Security Manager device
+ * @dev: device-model representation of the TSM
+ * @users: registration reference plus active references that retain the
+ * PCI/TSM resources; reaching zero tears down those resources
+ * @id: instance identifier
+ * @pci_ops: PCI/TSM operations, or %NULL when PCI/TSM is unsupported
+ */
struct tsm_dev {
struct device dev;
+ refcount_t users;
int id;
const struct pci_tsm_ops *pci_ops;
};
@@ -122,6 +133,8 @@ int tsm_report_register(const struct tsm_report_ops *ops, void *priv);
int tsm_report_unregister(const struct tsm_report_ops *ops);
struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *ops);
void tsm_unregister(struct tsm_dev *tsm_dev);
+void tsm_get(struct tsm_dev *tsm_dev);
+void tsm_put(struct tsm_dev *tsm_dev);
struct tsm_dev *find_tsm_dev(int id);
struct pci_ide;
int tsm_ide_stream_register(struct pci_ide *ide);
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 08/11] iommufd: Add vIOMMU provider support
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (6 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 07/11] coco: tsm: Expose active-user lifetime references Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 09/11] iommufd: Add the vdevice TSM request ioctl Aneesh Kumar K.V (Arm)
` (3 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
vIOMMU creation currently dispatches directly through the physical IOMMU
driver. Some vIOMMU types need an implementation supplied by another
subsystem, together with private data whose lifetime extends across all
vIOMMUs using that implementation.
Introduce a vIOMMU provider that associates implementation operations,
their module owner and private data. Allow one external provider to
register for each non-default vIOMMU type.
Look up an external provider first when allocating a vIOMMU. If none
matches the requested type, represent the physical IOMMU driver as a
fallback provider and dispatch through its get_viommu_size() and
viommu_init() operations.
A registered provider starts with one registration reference.
Registration does not pin the provider module. Each successful lookup
takes both a provider reference and a module reference. Unregistration
removes the provider from lookup before dropping the registration
reference and does not wait for existing vIOMMUs.
A fallback provider instance is created for each vIOMMU allocation. Its
initial users count of one represents the reference returned to that
allocation. It has no registration reference and is not shared with
other allocations, but otherwise follows the same lifetime rules as a
registered provider.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommufd/Makefile | 3 +-
drivers/iommu/iommufd/iommufd_private.h | 22 ++++
drivers/iommu/iommufd/viommu.c | 29 +++--
drivers/iommu/iommufd/viommu_provider.c | 160 ++++++++++++++++++++++++
include/linux/iommufd.h | 32 +++++
5 files changed, 234 insertions(+), 12 deletions(-)
create mode 100644 drivers/iommu/iommufd/viommu_provider.c
diff --git a/drivers/iommu/iommufd/Makefile b/drivers/iommu/iommufd/Makefile
index 71d692c9a8f4..7ed46c286c42 100644
--- a/drivers/iommu/iommufd/Makefile
+++ b/drivers/iommu/iommufd/Makefile
@@ -8,7 +8,8 @@ iommufd-y := \
main.o \
pages.o \
vfio_compat.o \
- viommu.o
+ viommu.o \
+ viommu_provider.o
iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 6cf76f7ca379..eae607eb5d76 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -21,6 +21,28 @@ struct iommu_option;
struct iommufd_device;
struct dma_buf_attachment;
+/**
+ * struct iommufd_viommu_provider - vIOMMU implementation provider
+ * @node: entry in the provider registry
+ * @users: references retaining @ops, @owner, and @data; registered providers
+ * have one registry reference plus one per live vIOMMU, while a fallback
+ * provider is private to one vIOMMU
+ * @ops: provider operations
+ * @owner: module implementing @ops
+ * @data: provider-private data retained until the final reference is released
+ */
+struct iommufd_viommu_provider {
+ struct list_head node;
+ const struct iommufd_viommu_provider_ops *ops;
+ refcount_t users;
+ struct module *owner;
+ void *data;
+};
+
+void iommufd_put_viommu_provider(struct iommufd_viommu_provider *provider);
+struct iommufd_viommu_provider *
+iommufd_get_viommu_provider(struct device *dev, enum iommu_viommu_type type);
+
struct iommufd_sw_msi_map {
struct list_head sw_msi_item;
phys_addr_t sw_msi_start;
diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index bf5d58d55939..66bbd6e4571d 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -11,6 +11,7 @@ void iommufd_viommu_destroy(struct iommufd_object *obj)
if (viommu->ops && viommu->ops->destroy)
viommu->ops->destroy(viommu);
+ iommufd_put_viommu_provider(viommu->provider);
refcount_dec(&viommu->hwpt->common.obj.users);
if (viommu->kvm_file)
fput(viommu->kvm_file);
@@ -28,7 +29,7 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
struct iommufd_hwpt_paging *hwpt_paging;
struct iommufd_viommu *viommu;
struct iommufd_device *idev;
- const struct iommu_ops *ops;
+ struct iommufd_viommu_provider *provider = NULL;
size_t viommu_size;
int rc;
@@ -39,16 +40,16 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
if (IS_ERR(idev))
return PTR_ERR(idev);
- ops = dev_iommu_ops(idev->dev);
- if (!ops->get_viommu_size || !ops->viommu_init) {
- rc = -EOPNOTSUPP;
+ provider = iommufd_get_viommu_provider(idev->dev, cmd->type);
+ if (IS_ERR(provider)) {
+ rc = PTR_ERR(provider);
+ provider = NULL;
goto out_put_idev;
}
-
- viommu_size = ops->get_viommu_size(idev->dev, cmd->type);
+ viommu_size = provider->ops->get_size(idev->dev, cmd->type);
if (!viommu_size) {
rc = -EOPNOTSUPP;
- goto out_put_idev;
+ goto out_put_provider;
}
/*
@@ -57,13 +58,13 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
*/
if (WARN_ON_ONCE(viommu_size < sizeof(*viommu))) {
rc = -EOPNOTSUPP;
- goto out_put_idev;
+ goto out_put_provider;
}
hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
if (IS_ERR(hwpt_paging)) {
rc = PTR_ERR(hwpt_paging);
- goto out_put_idev;
+ goto out_put_provider;
}
if (!hwpt_paging->nest_parent) {
@@ -84,6 +85,9 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
viommu->type = cmd->type;
viommu->ictx = ucmd->ictx;
viommu->hwpt = hwpt_paging;
+ viommu->provider = provider;
+ viommu->provider_data = provider->data;
+ provider = NULL;
refcount_inc(&viommu->hwpt->common.obj.users);
INIT_LIST_HEAD(&viommu->veventqs);
init_rwsem(&viommu->veventqs_rwsem);
@@ -94,8 +98,9 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
*/
viommu->iommu_dev = __iommu_get_iommu_dev(idev->dev);
- rc = ops->viommu_init(viommu, hwpt_paging->common.domain,
- user_data.len ? &user_data : NULL);
+ rc = viommu->provider->ops->init(viommu, idev->dev,
+ hwpt_paging->common.domain,
+ user_data.len ? &user_data : NULL);
if (rc)
goto out_put_hwpt;
@@ -110,6 +115,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
out_put_hwpt:
iommufd_put_object(ucmd->ictx, &hwpt_paging->common.obj);
+out_put_provider:
+ iommufd_put_viommu_provider(provider);
out_put_idev:
iommufd_put_object(ucmd->ictx, &idev->obj);
return rc;
diff --git a/drivers/iommu/iommufd/viommu_provider.c b/drivers/iommu/iommufd/viommu_provider.c
new file mode 100644
index 000000000000..5dda907d2abb
--- /dev/null
+++ b/drivers/iommu/iommufd/viommu_provider.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "iommufd_private.h"
+
+static LIST_HEAD(viommu_providers);
+static DEFINE_MUTEX(viommu_providers_lock);
+
+static struct iommufd_viommu_provider *
+viommu_find_provider(enum iommu_viommu_type type)
+{
+ struct iommufd_viommu_provider *provider;
+
+ lockdep_assert_held(&viommu_providers_lock);
+ list_for_each_entry(provider, &viommu_providers, node)
+ if (provider->ops->type == type)
+ return provider;
+ return NULL;
+}
+
+static void viommu_provider_put(struct iommufd_viommu_provider *provider)
+{
+ if (!refcount_dec_and_test(&provider->users))
+ return;
+ if (provider->ops->release)
+ provider->ops->release(provider->data);
+ kfree(provider);
+}
+
+void iommufd_put_viommu_provider(struct iommufd_viommu_provider *provider)
+{
+ struct module *owner;
+
+ if (!provider)
+ return;
+
+ owner = provider->owner;
+ viommu_provider_put(provider);
+ module_put(owner);
+}
+
+/**
+ * iommufd_register_viommu_provider() - Register an external vIOMMU provider
+ * @ops: Provider operations, including its module owner and exact type
+ * @data: Private data to retain until the final provider release
+ *
+ * Only one provider may be registered per type. ops->release() is called
+ * after unregister and the last vIOMMU release. Registration itself does
+ * not pin the module; each allocation does.
+ *
+ * Return: Registration handle or ERR_PTR() on failure.
+ */
+struct iommufd_viommu_provider *
+iommufd_register_viommu_provider(const struct iommufd_viommu_provider_ops *ops,
+ void *data)
+{
+ struct iommufd_viommu_provider *provider;
+
+ if (ops->type == IOMMU_VIOMMU_TYPE_DEFAULT ||
+ !ops->get_size || !ops->init)
+ return ERR_PTR(-EINVAL);
+
+ provider = kzalloc_obj(*provider);
+ if (!provider)
+ return ERR_PTR(-ENOMEM);
+ provider->ops = ops;
+ provider->owner = ops->owner;
+ provider->data = data;
+ refcount_set(&provider->users, 1);
+
+ mutex_lock(&viommu_providers_lock);
+ if (viommu_find_provider(ops->type)) {
+ mutex_unlock(&viommu_providers_lock);
+ kfree(provider);
+ return ERR_PTR(-EBUSY);
+ }
+ list_add_tail(&provider->node, &viommu_providers);
+ mutex_unlock(&viommu_providers_lock);
+ return provider;
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_register_viommu_provider, "IOMMUFD");
+
+/**
+ * iommufd_unregister_viommu_provider() - Stop new lookups of a provider
+ * @provider: Handle returned by iommufd_register_viommu_provider()
+ *
+ * Call once per registration. Existing vIOMMUs retain the operations,
+ * private data and module until destruction. This does not wait for them
+ * to be released.
+ */
+void iommufd_unregister_viommu_provider(struct iommufd_viommu_provider *provider)
+{
+ mutex_lock(&viommu_providers_lock);
+ list_del(&provider->node);
+ mutex_unlock(&viommu_providers_lock);
+ viommu_provider_put(provider);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_unregister_viommu_provider, "IOMMUFD");
+
+static size_t fallback_viommu_get_size(struct device *dev,
+ enum iommu_viommu_type type)
+{
+ return dev_iommu_ops(dev)->get_viommu_size(dev, type);
+}
+
+static int fallback_viommu_init(struct iommufd_viommu *viommu,
+ struct device *dev, struct iommu_domain *parent,
+ const struct iommu_user_data *user_data)
+{
+ const struct iommu_ops *ops = viommu->provider_data;
+
+ return ops->viommu_init(viommu, parent, user_data);
+}
+
+static const struct iommufd_viommu_provider_ops fallback_viommu_provider_ops = {
+ .get_size = fallback_viommu_get_size,
+ .init = fallback_viommu_init,
+};
+
+struct iommufd_viommu_provider *
+iommufd_get_viommu_provider(struct device *dev, enum iommu_viommu_type type)
+{
+ const struct iommu_ops *iommu_ops;
+ struct iommufd_viommu_provider *provider;
+
+ mutex_lock(&viommu_providers_lock);
+ provider = viommu_find_provider(type);
+ if (provider) {
+ if (!try_module_get(provider->owner)) {
+ mutex_unlock(&viommu_providers_lock);
+ return ERR_PTR(-ENODEV);
+ }
+ refcount_inc(&provider->users);
+ mutex_unlock(&viommu_providers_lock);
+ return provider;
+ }
+ mutex_unlock(&viommu_providers_lock);
+
+ iommu_ops = dev_iommu_ops(dev);
+ if (!iommu_ops->get_viommu_size || !iommu_ops->viommu_init)
+ return ERR_PTR(-EOPNOTSUPP);
+ if (!try_module_get(iommu_ops->owner))
+ return ERR_PTR(-ENODEV);
+
+ provider = kzalloc_obj(*provider);
+ if (!provider) {
+ module_put(iommu_ops->owner);
+ return ERR_PTR(-ENOMEM);
+ }
+ provider->ops = &fallback_viommu_provider_ops;
+ provider->owner = iommu_ops->owner;
+ provider->data = (void *)iommu_ops;
+ refcount_set(&provider->users, 1);
+ return provider;
+}
+
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 3267717f676d..7b906e0d6400 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -22,9 +22,39 @@ struct iommu_user_data_array;
struct iommufd_access;
struct iommufd_ctx;
struct iommufd_device;
+struct iommufd_viommu;
struct iommufd_viommu_ops;
+struct iommufd_viommu_provider;
+struct module;
struct page;
+/**
+ * struct iommufd_viommu_provider_ops - External vIOMMU implementation
+ * @owner: Module implementing the callbacks
+ * @type: Exact vIOMMU type implemented by this provider
+ * @get_size: Allocation size for a device, or zero if unsupported
+ * @init: Initialize the vIOMMU; private data is in viommu->provider_data
+ * @release: Optional final release of the registration's private data
+ *
+ * Callbacks run without the registry lock held. The operations must remain
+ * valid until @release completes. An allocation failure is authoritative:
+ * it must not cause allocation through the physical IOMMU driver instead.
+ */
+struct iommufd_viommu_provider_ops {
+ struct module *owner;
+ enum iommu_viommu_type type;
+ size_t (*get_size)(struct device *dev, enum iommu_viommu_type type);
+ int (*init)(struct iommufd_viommu *viommu, struct device *dev,
+ struct iommu_domain *parent,
+ const struct iommu_user_data *user_data);
+ void (*release)(void *data);
+};
+
+struct iommufd_viommu_provider *
+iommufd_register_viommu_provider(const struct iommufd_viommu_provider_ops *ops,
+ void *data);
+void iommufd_unregister_viommu_provider(struct iommufd_viommu_provider *provider);
+
enum iommufd_object_type {
IOMMUFD_OBJ_NONE,
IOMMUFD_OBJ_ANY = IOMMUFD_OBJ_NONE,
@@ -104,6 +134,8 @@ struct iommufd_viommu {
struct iommu_device *iommu_dev;
struct iommufd_hwpt_paging *hwpt;
struct file *kvm_file;
+ struct iommufd_viommu_provider *provider;
+ void *provider_data;
const struct iommufd_viommu_ops *ops;
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 09/11] iommufd: Add the vdevice TSM request ioctl
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (7 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 08/11] iommufd: Add vIOMMU provider support Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 10/11] PCI/TSM: Remove the legacy guest request interface Aneesh Kumar K.V (Arm)
` (2 subsequent siblings)
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
A VMM needs to forward guest-originated TSM commands to the TSM
implementation managing an assigned device. The IOMMUFD vdevice
identifies that device within its vIOMMU and provides the appropriate
dispatch point.
Add IOMMU_VDEVICE_TSM_REQ to send an opaque request to a vdevice and
optionally return a response. Define common operation and guest
architecture identifiers for CCA, SEV and TDX requests.
The ioctl return value reports dispatch or data transfer errors, while
tsm_code carries the TSM-specific result.
Use sockptr_t for the internal request and response buffers so TSM
implementations are not tied to userspace pointers.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/iommufd/Makefile | 2 +
drivers/iommu/iommufd/iommufd_private.h | 8 ++
drivers/iommu/iommufd/main.c | 3 +
drivers/iommu/iommufd/tsm.c | 98 +++++++++++++++++++++++++
drivers/iommu/iommufd/viommu.c | 2 +
include/linux/iommufd.h | 5 ++
include/linux/tsm.h | 25 +++++++
include/uapi/linux/iommufd.h | 73 ++++++++++++++++++
8 files changed, 216 insertions(+)
create mode 100644 drivers/iommu/iommufd/tsm.c
diff --git a/drivers/iommu/iommufd/Makefile b/drivers/iommu/iommufd/Makefile
index 7ed46c286c42..105a54ff89f5 100644
--- a/drivers/iommu/iommufd/Makefile
+++ b/drivers/iommu/iommufd/Makefile
@@ -11,6 +11,8 @@ iommufd-y := \
viommu.o \
viommu_provider.o
+iommufd-$(CONFIG_TSM) += tsm.o
+
iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o
obj-$(CONFIG_IOMMUFD) += iommufd.o
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index eae607eb5d76..0c67f2e1f137 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -721,6 +721,14 @@ void iommufd_vdevice_destroy(struct iommufd_object *obj);
void iommufd_vdevice_abort(struct iommufd_object *obj);
int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd);
void iommufd_hw_queue_destroy(struct iommufd_object *obj);
+#ifdef CONFIG_TSM
+int iommufd_vdevice_tsm_req_ioctl(struct iommufd_ucmd *ucmd);
+#else
+static inline int iommufd_vdevice_tsm_req_ioctl(struct iommufd_ucmd *ucmd)
+{
+ return -EOPNOTSUPP;
+}
+#endif
static inline struct iommufd_vdevice *
iommufd_get_vdevice(struct iommufd_ctx *ictx, u32 id)
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 8c6d43601afb..4a42cda0e8f8 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -432,6 +432,7 @@ union ucmd_buffer {
struct iommu_veventq_alloc veventq;
struct iommu_vfio_ioas vfio_ioas;
struct iommu_viommu_alloc viommu;
+ struct iommu_vdevice_tsm_req tsm_req;
#ifdef CONFIG_IOMMUFD_TEST
struct iommu_test_cmd test;
#endif
@@ -493,6 +494,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
__reserved),
IOCTL_OP(IOMMU_VIOMMU_ALLOC, iommufd_viommu_alloc_ioctl,
struct iommu_viommu_alloc, out_viommu_id),
+ IOCTL_OP(IOMMU_VDEVICE_TSM_REQ, iommufd_vdevice_tsm_req_ioctl,
+ struct iommu_vdevice_tsm_req, tsm_code),
#ifdef CONFIG_IOMMUFD_TEST
IOCTL_OP(IOMMU_TEST_CMD, iommufd_test, struct iommu_test_cmd, last),
#endif
diff --git a/drivers/iommu/iommufd/tsm.c b/drivers/iommu/iommufd/tsm.c
new file mode 100644
index 000000000000..2fb51a86c5ec
--- /dev/null
+++ b/drivers/iommu/iommufd/tsm.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/tsm.h>
+#include "iommufd_private.h"
+
+static bool iommufd_vdevice_tsm_req_arch_valid(u32 tvm_arch)
+{
+ switch (tvm_arch) {
+ case IOMMU_VDEVICE_TSM_TVM_ARCH_CCA:
+ case IOMMU_VDEVICE_TSM_TVM_ARCH_SEV:
+ case IOMMU_VDEVICE_TSM_TVM_ARCH_TDX:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool iommufd_vdevice_tsm_req_op_valid(u32 op, u32 tvm_arch)
+{
+ switch (op) {
+ case TSM_REQ_VALIDATE_MMIO:
+ case TSM_REQ_SET_TDI_STATE:
+ return true;
+ case TSM_REQ_SEV_ENABLE_DMA:
+ case TSM_REQ_SEV_DISABLE_DMA:
+ return tvm_arch == IOMMU_VDEVICE_TSM_TVM_ARCH_SEV;
+ case TSM_REQ_READ_OBJECT:
+ case TSM_REQ_REGEN_OBJECT:
+ case TSM_REQ_OBJECT_INFO:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * iommufd_vdevice_tsm_req_ioctl - Forward TSM requests
+ * @ucmd: user command data for IOMMU_VDEVICE_TSM_REQ
+ *
+ * Resolve @iommu_vdevice_tsm_req::vdevice_id to a vdevice and pass the
+ * request/response buffers to its vIOMMU provider.
+ *
+ * Return:
+ * -errno on error.
+ * positive residue if response/request bytes were left unconsumed.
+ * if response buffer is provided, residue indicates the number of bytes
+ * not used in response buffer
+ * if there is no response buffer, residue indicates the number of bytes
+ * not consumed in req buffer
+ * 0 otherwise.
+ */
+int iommufd_vdevice_tsm_req_ioctl(struct iommufd_ucmd *ucmd)
+{
+ int ret;
+ struct iommufd_vdevice *vdev;
+ struct iommu_vdevice_tsm_req *cmd = ucmd->cmd;
+ struct tsm_guest_req_info info = {
+ .op = cmd->op,
+ .tvm_arch = cmd->tvm_arch,
+ .req = {
+ .user = u64_to_user_ptr(cmd->req_uptr),
+ .is_kernel = false,
+ },
+ .req_len = cmd->req_len,
+ .resp = {
+ .user = u64_to_user_ptr(cmd->resp_uptr),
+ .is_kernel = false,
+ },
+ .resp_len = cmd->resp_len,
+ };
+
+ if (!iommufd_vdevice_tsm_req_arch_valid(cmd->tvm_arch))
+ return -EINVAL;
+
+ if (!iommufd_vdevice_tsm_req_op_valid(cmd->op, cmd->tvm_arch))
+ return -EINVAL;
+
+ vdev = iommufd_get_vdevice(ucmd->ictx, cmd->vdevice_id);
+ if (IS_ERR(vdev))
+ return PTR_ERR(vdev);
+
+ cmd->tsm_code = 0;
+ if (!vdev->viommu->ops || !vdev->viommu->ops->vdevice_tsm_req)
+ ret = -EOPNOTSUPP;
+ else
+ ret = vdev->viommu->ops->vdevice_tsm_req(vdev, &info,
+ &cmd->tsm_code);
+
+ /* Always copy the tsm_code as response */
+ if (iommufd_ucmd_respond(ucmd, sizeof(*cmd)))
+ ret = -EFAULT;
+
+ iommufd_put_object(ucmd->ictx, &vdev->obj);
+ return ret;
+}
diff --git a/drivers/iommu/iommufd/viommu.c b/drivers/iommu/iommufd/viommu.c
index 66bbd6e4571d..c462086ff5df 100644
--- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c
@@ -3,6 +3,8 @@
*/
#include <linux/file.h>
#include "iommufd_private.h"
+#include <linux/cleanup.h>
+#include <linux/tsm.h>
void iommufd_viommu_destroy(struct iommufd_object *obj)
{
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 7b906e0d6400..f16b82034be9 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -27,6 +27,7 @@ struct iommufd_viommu_ops;
struct iommufd_viommu_provider;
struct module;
struct page;
+struct tsm_guest_req_info;
/**
* struct iommufd_viommu_provider_ops - External vIOMMU implementation
@@ -199,6 +200,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_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
* return 0 if HW queue aren't supported accordingly. It is
@@ -225,6 +227,9 @@ struct iommufd_viommu_ops {
struct iommu_user_data_array *array);
const size_t vdevice_size;
int (*vdevice_init)(struct iommufd_vdevice *vdev);
+ ssize_t (*vdevice_tsm_req)(struct iommufd_vdevice *vdev,
+ struct tsm_guest_req_info *info,
+ u64 *tsm_code);
size_t (*get_hw_queue_size)(struct iommufd_viommu *viommu,
enum iommu_hw_queue_type queue_type);
/* AMD's HW will add hw_queue_init simply using @hw_queue->base_addr */
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
index f38d6fcf9cc9..39bad60b4815 100644
--- a/include/linux/tsm.h
+++ b/include/linux/tsm.h
@@ -7,6 +7,8 @@
#include <linux/types.h>
#include <linux/uuid.h>
#include <linux/device.h>
+#include <linux/sockptr.h>
+#include <uapi/linux/iommufd.h>
#define TSM_REPORT_INBLOB_MAX 64
#define TSM_REPORT_OUTBLOB_MAX SZ_16M
@@ -139,4 +141,27 @@ struct tsm_dev *find_tsm_dev(int id);
struct pci_ide;
int tsm_ide_stream_register(struct pci_ide *ide);
void tsm_ide_stream_unregister(struct pci_ide *ide);
+
+#ifdef CONFIG_TSM
+/**
+ * struct tsm_guest_req_info - parameters for a guest-initiated TSM request
+ * @op: operation for the guest-initiated request
+ * @tvm_arch: guest TVM architecture
+ * @req: request data buffer filled by guest
+ * @req_len: the size of @req filled by guest
+ * @resp: response data buffer filled by host
+ * @resp_len: the size of @resp buffer filled by guest
+ */
+struct tsm_guest_req_info {
+ enum iommu_vdevice_tsm_guest_req_op op;
+ enum iommu_vdevice_tsm_guest_tvm_arch tvm_arch;
+ sockptr_t req;
+ size_t req_len;
+ sockptr_t resp;
+ size_t resp_len;
+};
+#else
+struct tsm_guest_req_info;
+#endif
+
#endif /* __TSM_H */
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 0425d452d41e..43ed082a9421 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -57,6 +57,7 @@ enum {
IOMMUFD_CMD_IOAS_CHANGE_PROCESS = 0x92,
IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
+ IOMMUFD_CMD_VDEVICE_TSM_REQ = 0x96,
};
/**
@@ -1351,4 +1352,76 @@ struct iommu_hw_queue_alloc {
__aligned_u64 length;
};
#define IOMMU_HW_QUEUE_ALLOC _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HW_QUEUE_ALLOC)
+
+/**
+ * enum iommu_vdevice_tsm_guest_tvm_arch - guest TVM architecture
+ * @IOMMU_VDEVICE_TSM_TVM_ARCH_CCA: Arm CCA TVM
+ * @IOMMU_VDEVICE_TSM_TVM_ARCH_SEV: AMD SEV TVM
+ * @IOMMU_VDEVICE_TSM_TVM_ARCH_TDX: Intel TDX TVM
+ */
+enum iommu_vdevice_tsm_guest_tvm_arch {
+ IOMMU_VDEVICE_TSM_TVM_ARCH_CCA = 1,
+ IOMMU_VDEVICE_TSM_TVM_ARCH_SEV,
+ IOMMU_VDEVICE_TSM_TVM_ARCH_TDX,
+};
+
+/**
+ * enum iommu_vdevice_tsm_guest_req_op - operation for guest TSM requests
+ * @TSM_REQ_VALIDATE_MMIO: Validate MMIO for the TDI
+ * @TSM_REQ_SET_TDI_STATE: Set TDI state
+ * @TSM_REQ_SEV_ENABLE_DMA: Enable SEV DMA
+ * @TSM_REQ_SEV_DISABLE_DMA: Disable SEV DMA
+ * @TSM_REQ_READ_OBJECT: Read a TSM object
+ * @TSM_REQ_REGEN_OBJECT: Regenerate a TSM object
+ * @TSM_REQ_OBJECT_INFO: Read TSM object information
+ */
+enum iommu_vdevice_tsm_guest_req_op {
+ TSM_REQ_VALIDATE_MMIO = 1,
+ TSM_REQ_SET_TDI_STATE,
+ TSM_REQ_SEV_ENABLE_DMA,
+ TSM_REQ_SEV_DISABLE_DMA,
+ TSM_REQ_READ_OBJECT,
+ TSM_REQ_REGEN_OBJECT,
+ TSM_REQ_OBJECT_INFO,
+};
+
+/**
+ * struct iommu_vdevice_tsm_req - ioctl(IOMMU_VDEVICE_TSM_REQ)
+ * @size: sizeof(struct iommu_vdevice_tsm_req)
+ * @vdevice_id: vDevice ID the guest request is for
+ * @op: One of enum iommu_vdevice_tsm_guest_req_op
+ * @tvm_arch: One of enum iommu_vdevice_tsm_guest_tvm_arch
+ * @req_len: Size in bytes of the input payload at @req_uptr
+ * @resp_len: Size in bytes of the output buffer at @resp_uptr
+ * @req_uptr: Userspace pointer to the guest-provided request payload
+ * @resp_uptr: Userspace pointer to the guest response buffer
+ * @tsm_code: TSM-specific result code returned by the TSM implementation
+ *
+ * Forward a TSM request to the TSM bound vDevice. This is intended for
+ * guest TSM/TDISP message transport where the host kernel only marshals
+ * bytes between userspace and the TSM implementation.
+ *
+ * The request operation is guest initiated. The TSM backend validates
+ * @tvm_arch against its bound TVM architecture assumptions.
+ *
+ * The request payload is read from @req_uptr/@req_len. If a response is
+ * expected, userspace provides @resp_uptr/@resp_len as writable storage for
+ * response bytes returned by the TSM path.
+ *
+ * The ioctl is only suitable for commands and results that the host kernel
+ * has no use, the host is only facilitating guest to TSM communication.
+ */
+struct iommu_vdevice_tsm_req {
+ __u32 size;
+ __u32 vdevice_id;
+ __u32 op;
+ __u32 tvm_arch;
+ __u32 req_len;
+ __u32 resp_len;
+ __aligned_u64 req_uptr;
+ __aligned_u64 resp_uptr;
+ __aligned_u64 tsm_code;
+};
+
+#define IOMMU_VDEVICE_TSM_REQ _IO(IOMMUFD_TYPE, IOMMUFD_CMD_VDEVICE_TSM_REQ)
#endif
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 10/11] PCI/TSM: Remove the legacy guest request interface
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (8 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 09/11] iommufd: Add the vdevice TSM request ioctl Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:01 ` [RFC PATCH v6 11/11] PCI/TSM: Add reference-counted contexts for vdevice providers Aneesh Kumar K.V (Arm)
2026-09-17 14:17 ` [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
Guest TSM requests can now be dispatched through the IOMMUFD vdevice
operation. Remove the PCI-device-based guest request entry point, its
scope enum and the corresponding PCI/TSM driver callback.
Remove the sample guest request callback and tsm_request attribute along
with the API.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/pci/tsm/core.c | 60 --------------------------------------
include/linux/pci-tsm.h | 61 +--------------------------------------
samples/devsec/link_tsm.c | 52 +--------------------------------
3 files changed, 2 insertions(+), 171 deletions(-)
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 7082fdbe8c7e..c7e0d241e55d 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -398,66 +398,6 @@ int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
}
EXPORT_SYMBOL_GPL(pci_tsm_bind);
-/**
- * pci_tsm_guest_req() - helper to marshal guest requests to the TSM driver
- * @pdev: @pdev representing a bound tdi
- * @scope: caller asserts this passthrough request is limited to TDISP operations
- * @req_in: Input payload forwarded from the guest
- * @in_len: Length of @req_in
- * @req_out: Output payload buffer response to the guest
- * @out_len: Length of @req_out on input, bytes filled in @req_out on output
- * @tsm_code: Optional TSM arch specific result code for the guest TSM
- *
- * This is a common entry point for requests triggered by userspace KVM-exit
- * service handlers responding to TDI information or state change requests. The
- * scope parameter limits requests to TDISP state management, or limited debug.
- * This path is only suitable for commands and results that are the host kernel
- * has no use, the host is only facilitating guest to TSM communication.
- *
- * Returns 0 on success and -error on failure and positive "residue" on success
- * but @req_out is filled with less then @out_len, or @req_out is NULL and a
- * residue number of bytes were not consumed from @req_in. On success or
- * failure @tsm_code may be populated with a TSM implementation specific result
- * code for the guest to consume.
- *
- * Context: Caller is responsible for calling this within the pci_tsm_bind()
- * state of the TDI.
- */
-ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
- sockptr_t req_in, size_t in_len, sockptr_t req_out,
- size_t out_len, u64 *tsm_code)
-{
- struct pci_tsm_pf0 *tsm_pf0;
- struct pci_tdi *tdi;
- int rc;
-
- /* Forbid requests that are not directly related to TDISP operations */
- if (scope > PCI_TSM_REQ_STATE_CHANGE)
- return -EINVAL;
-
- ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
- if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
- return rc;
-
- if (!pdev->tsm)
- return -ENXIO;
-
- if (!is_link_tsm(pdev->tsm->tsm_dev))
- return -ENXIO;
-
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
- if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
- return rc;
-
- tdi = pdev->tsm->tdi;
- if (!tdi)
- return -ENXIO;
- return to_pci_tsm_ops(pdev->tsm)->guest_req(tdi, scope, req_in, in_len,
- req_out, out_len, tsm_code);
-}
-EXPORT_SYMBOL_GPL(pci_tsm_guest_req);
-
static void pci_tsm_unbind_all(struct pci_dev *pdev)
{
pci_tsm_walk_fns_reverse(pdev, __pci_tsm_unbind, NULL);
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 8869585230a3..15907bad47b7 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -4,14 +4,12 @@
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/rwsem.h>
-#include <linux/sockptr.h>
#include <uapi/linux/hash_info.h>
#include <uapi/linux/pci-tsm-netlink.h>
struct pci_tsm;
struct tsm_dev;
struct kvm;
-enum pci_tsm_req_scope;
/*
* struct pci_tsm_ops - manage confidential links and security state
@@ -37,14 +35,13 @@ struct pci_tsm_ops {
* @disconnect: teardown the secure link
* @bind: bind a TDI in preparation for it to be accepted by a TVM
* @unbind: remove a TDI from secure operation with a TVM
- * @guest_req: marshal TVM information and state change requests
*
* Context: @probe, @remove, @connect, and @disconnect run under
* pci_tsm_rwsem held for write to sync with TSM unregistration and
* mutual exclusion of @connect and @disconnect. @connect and
* @disconnect additionally run under the DSM lock (struct
* pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions.
- * @bind, @unbind, and @guest_req run under pci_tsm_rwsem held for read
+ * @bind and @unbind run under pci_tsm_rwsem held for read
* and the DSM lock.
*/
struct_group_tagged(pci_tsm_link_ops, link_ops,
@@ -56,11 +53,6 @@ struct pci_tsm_ops {
struct pci_tdi *(*bind)(struct pci_dev *pdev,
struct kvm *kvm, u32 tdi_id);
void (*unbind)(struct pci_tdi *tdi);
- ssize_t (*guest_req)(struct pci_tdi *tdi,
- enum pci_tsm_req_scope scope,
- sockptr_t req_in, size_t in_len,
- sockptr_t req_out, size_t out_len,
- u64 *tsm_code);
);
/*
@@ -265,46 +257,6 @@ static inline bool is_pci_tsm_pf0(struct pci_dev *pdev)
return PCI_FUNC(pdev->devfn) == 0;
}
-/**
- * enum pci_tsm_req_scope - Scope of guest requests to be validated by TSM
- *
- * Guest requests are a transport for a TVM to communicate with a TSM + DSM for
- * a given TDI. A TSM driver is responsible for maintaining the kernel security
- * model and limit commands that may affect the host, or are otherwise outside
- * the typical TDISP operational model.
- */
-enum pci_tsm_req_scope {
- /**
- * @PCI_TSM_REQ_INFO: Read-only, without side effects, request for
- * typical TDISP collateral information like Device Interface Reports.
- * No device secrets are permitted, and no device state is changed.
- */
- PCI_TSM_REQ_INFO = 0,
- /**
- * @PCI_TSM_REQ_STATE_CHANGE: Request to change the TDISP state from
- * UNLOCKED->LOCKED, LOCKED->RUN, or other architecture specific state
- * changes to support those transitions for a TDI. No other (unrelated
- * to TDISP) device / host state, configuration, or data change is
- * permitted.
- */
- PCI_TSM_REQ_STATE_CHANGE = 1,
- /**
- * @PCI_TSM_REQ_DEBUG_READ: Read-only request for debug information
- *
- * A method to facilitate TVM information retrieval outside of typical
- * TDISP operational requirements. No device secrets are permitted.
- */
- PCI_TSM_REQ_DEBUG_READ = 2,
- /**
- * @PCI_TSM_REQ_DEBUG_WRITE: Device state changes for debug purposes
- *
- * The request may affect the operational state of the device outside of
- * the TDISP operational model. If allowed, requires CAP_SYS_RAW_IO, and
- * will taint the kernel.
- */
- PCI_TSM_REQ_DEBUG_WRITE = 3,
-};
-
#ifdef CONFIG_PCI_TSM
int pci_tsm_register(struct tsm_dev *tsm_dev);
void pci_tsm_unregister(struct tsm_dev *tsm_dev);
@@ -321,9 +273,6 @@ int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id);
void pci_tsm_unbind(struct pci_dev *pdev);
void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
struct kvm *kvm, u32 tdi_id);
-ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
- sockptr_t req_in, size_t in_len, sockptr_t req_out,
- size_t out_len, u64 *tsm_code);
struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm);
void pci_tsm_init_evidence(struct pci_tsm_evidence *evidence, int slot,
enum hash_algo digest_algo);
@@ -346,14 +295,6 @@ static inline int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u64 tdi_id
static inline void pci_tsm_unbind(struct pci_dev *pdev)
{
}
-static inline ssize_t pci_tsm_guest_req(struct pci_dev *pdev,
- enum pci_tsm_req_scope scope,
- sockptr_t req_in, size_t in_len,
- sockptr_t req_out, size_t out_len,
- u64 *tsm_code)
-{
- return -ENXIO;
-}
#endif
/* private: */
diff --git a/samples/devsec/link_tsm.c b/samples/devsec/link_tsm.c
index 21b6c3c7ea52..1d102dd9590a 100644
--- a/samples/devsec/link_tsm.c
+++ b/samples/devsec/link_tsm.c
@@ -260,22 +260,6 @@ static void devsec_link_tsm_unbind(struct pci_tdi *tdi)
kfree(devsec_tdi);
}
-static ssize_t devsec_link_tsm_guest_req(struct pci_tdi *tdi,
- enum pci_tsm_req_scope scope,
- sockptr_t req_in, size_t in_len,
- sockptr_t req_out, size_t out_len,
- u64 *tsm_code)
-{
- if (!sockptr_is_kernel(req_in))
- return -ENXIO;
-
- dev_dbg(pci_tsm_host(tdi->pdev), "%s\n", pci_name(tdi->pdev));
- print_hex_dump_debug("devsec req_in ", DUMP_PREFIX_OFFSET, 16, 4,
- req_in.kernel, min(in_len, 256u), true);
-
- return 0;
-}
-
static struct pci_tsm_ops devsec_link_pci_ops = {
.probe = devsec_link_tsm_pci_probe,
.remove = devsec_link_tsm_pci_remove,
@@ -283,7 +267,6 @@ static struct pci_tsm_ops devsec_link_pci_ops = {
.disconnect = devsec_link_tsm_disconnect,
.bind = devsec_link_tsm_bind,
.unbind = devsec_link_tsm_unbind,
- .guest_req = devsec_link_tsm_guest_req,
};
static void devsec_link_tsm_remove(void *tsm_dev)
@@ -358,46 +341,13 @@ static ssize_t tsm_unbind_store(struct device *dev,
}
static DEVICE_ATTR_WO(tsm_unbind);
-static ssize_t tsm_request_store(struct device *dev,
- struct device_attribute *attr,
- const char *__buf, size_t count)
-{
- ssize_t rc;
- u64 tsm_code = 0;
- struct device *host;
- char req_out[16] = {0};
- size_t out_len = sizeof(req_out);
-
- struct pci_dev *pdev __free(pci_dev_put) = pci_find_device(__buf);
- if (!pdev)
- return -ENODEV;
-
- char *buf __free(kvfree) = kvmemdup(__buf, count, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- host = pci_tsm_host(pdev);
- if (!host || host != &devsec_link_tsm->dev)
- return -ENXIO;
-
- rc = pci_tsm_guest_req(pdev, PCI_TSM_REQ_INFO, KERNEL_SOCKPTR(buf),
- count, KERNEL_SOCKPTR(req_out), out_len,
- &tsm_code);
- if (rc)
- return rc;
-
- return count;
-}
-static DEVICE_ATTR_WO(tsm_request);
-
/*
- * Facilitate testing of the bind and request flows in lieu of VFIO/IOMMUFD
+ * Facilitate testing of the bind flows in lieu of VFIO/IOMMUFD
* support to exercise these paths.
*/
static struct attribute *devsec_link_attrs[] = {
&dev_attr_tsm_bind.attr,
&dev_attr_tsm_unbind.attr,
- &dev_attr_tsm_request.attr,
NULL,
};
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v6 11/11] PCI/TSM: Add reference-counted contexts for vdevice providers
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (9 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 10/11] PCI/TSM: Remove the legacy guest request interface Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:01 ` Aneesh Kumar K.V (Arm)
2026-09-17 14:17 ` [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-09-17 14:01 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Aneesh Kumar K.V (Arm),
Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
Replace the PCI/TSM bind/unbind interface with reference-counted
contexts that retain the function, DSM device and Link TSM for a vdevice
provider. Reject disconnect while a context is live.
Keep the existing bound sysfs attribute and derive its status from
successful provider binding, with balanced per-function accounting on
context release. Update the ABI documentation and retire the obsolete
sample bind/unbind operations in the same patch.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
Documentation/ABI/testing/sysfs-bus-pci | 29 ++-
drivers/pci/tsm/core.c | 256 +++++++++++++-----------
include/linux/pci-tsm.h | 85 +++++---
samples/devsec/link_tsm.c | 96 ---------
tools/testing/devsec/devsec.sh | 27 +--
5 files changed, 211 insertions(+), 282 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index c2a5c4fe9373..5288ea4ed343 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -657,6 +657,9 @@ Description:
to 'connect' to teardown the connection. This is a
"link" TSM attribute, see
Documentation/ABI/testing/sysfs-class-tsm.
+ The write fails with EBUSY while any vdevice depends on the
+ connection. Userspace must destroy those vdevices before
+ disconnecting the link.
What: /sys/bus/pci/devices/.../tsm/dsm
Contact: linux-coco@lists.linux.dev
@@ -676,17 +679,21 @@ Description: (RO) Return PCI device name of this device's DSM (Device
What: /sys/bus/pci/devices/.../tsm/bound
Contact: linux-coco@lists.linux.dev
-Description: (RO) Return the device name of the TSM when the device is in a
- TDISP (TEE Device Interface Security Protocol) operational state
- (LOCKED, RUN, or ERROR, not UNLOCKED). Bound devices consume
- platform TSM resources and depend on the device's configuration
- (e.g. BME (Bus Master Enable) and MSE (Memory Space Enable)
- among other settings) to remain stable for the duration of the
- bound state. This attribute is only visible for devices that
- support TDISP operation, and it is only populated after
- successful connect and TSM bind. The TSM bind operation is
- initiated by VFIO/IOMMUFD. This is a "link" TSM attribute, see
- Documentation/ABI/testing/sysfs-class-tsm.
+Description: (RO) Return the device name of the TSM when this PCI function
+ has a successfully initialized TSM-backed vdevice binding, or
+ an empty line when no such binding exists. The binding is
+ established through VFIO/IOMMUFD and remains visible until
+ the provider releases its context during vdevice teardown.
+ Merely connecting the device to a TSM or acquiring a context
+ does not establish a binding. Bindings of other functions
+ managed by the same DSM do not affect this attribute.
+
+ This reports the binding lifetime, not the current TDISP
+ (TEE Device Interface Security Protocol) state. A bound vdevice
+ may be UNLOCKED, and TDISP lock/unlock transitions do not
+ change this attribute. This attribute is only visible for
+ devices that support TDISP operation. This is a "link" TSM
+ attribute, see Documentation/ABI/testing/sysfs-class-tsm.
What: /sys/bus/pci/devices/.../authenticated
Contact: linux-pci@vger.kernel.org
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index c7e0d241e55d..667328d1c686 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -67,11 +67,10 @@ static struct pci_tsm_pf0 *to_pci_tsm_pf0(struct pci_tsm *tsm)
static inline bool is_devsec(struct pci_dev *pdev)
{
- return pdev->tsm && pdev->tsm->dsm_dev == NULL &&
- pdev->tsm->tdi == NULL;
+ return pdev->tsm && !pdev->tsm->dsm_dev;
}
-/* 'struct pci_tsm_devsec' wraps 'struct pci_tsm' when ->tdi == ->dsm == NULL */
+/* 'struct pci_tsm_devsec' wraps 'struct pci_tsm' when ->dsm_dev == NULL */
struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm)
{
struct pci_dev *pdev = tsm->pdev;
@@ -315,96 +314,118 @@ static int remove_fn(struct pci_dev *pdev, void *data)
return 0;
}
-/*
- * Note, this helper only returns an error code and takes an argument for
- * compatibility with the pci_walk_bus() callback prototype. pci_tsm_unbind()
- * always succeeds.
- */
-static int __pci_tsm_unbind(struct pci_dev *pdev, void *data)
+bool pci_tsm_is_configured(struct pci_dev *pdev)
{
- struct pci_tdi *tdi;
- struct pci_tsm_pf0 *tsm_pf0;
-
- lockdep_assert_held(&pci_tsm_rwsem);
+ guard(rwsem_read)(&pci_tsm_rwsem);
- if (!pdev->tsm)
- return 0;
+ return !!pdev->tsm;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_is_configured);
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- guard(mutex)(&tsm_pf0->lock);
+struct pci_tsm_context {
+ struct pci_tsm_pf0 *pf0;
+ struct pci_dev *pdev;
+ struct pci_dev *dsm_dev;
+ struct tsm_dev *tsm_dev;
+ struct list_head bound_node;
+};
- tdi = pdev->tsm->tdi;
- if (!tdi)
- return 0;
+struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev)
+{
+ struct pci_tsm_context *context;
+ struct pci_tsm_pf0 *pf0;
+ struct device *tsm_device;
- to_pci_tsm_ops(pdev->tsm)->unbind(tdi);
- pdev->tsm->tdi = NULL;
+ guard(rwsem_read)(&pci_tsm_rwsem);
+ if (!pdev->tsm || !is_link_tsm(pdev->tsm->tsm_dev))
+ return ERR_PTR(-EOPNOTSUPP);
- return 0;
+ pf0 = to_pci_tsm_pf0(pdev->tsm);
+ if (!pf0)
+ return ERR_PTR(-ENXIO);
+
+ context = kzalloc_obj(*context);
+ if (!context)
+ return ERR_PTR(-ENOMEM);
+
+ guard(mutex)(&pf0->lock);
+ pf0->context_users++;
+ context->pf0 = pf0;
+ context->pdev = pci_dev_get(pdev);
+ INIT_LIST_HEAD(&context->bound_node);
+ context->dsm_dev = pci_dev_get(pf0->base_tsm.pdev);
+ tsm_device = get_device(&pdev->tsm->tsm_dev->dev);
+ context->tsm_dev = container_of(tsm_device, struct tsm_dev, dev);
+ return context;
}
+EXPORT_SYMBOL_GPL(pci_tsm_context_get);
-void pci_tsm_unbind(struct pci_dev *pdev)
+void pci_tsm_context_put(struct pci_tsm_context *context)
{
- guard(rwsem_read)(&pci_tsm_rwsem);
- __pci_tsm_unbind(pdev, NULL);
+ struct pci_tsm_pf0 *pf0 = context->pf0;
+
+ down_read(&pci_tsm_rwsem);
+ mutex_lock(&pf0->lock);
+ list_del(&context->bound_node);
+ if (!WARN_ON(!pf0->context_users))
+ pf0->context_users--;
+ mutex_unlock(&pf0->lock);
+ up_read(&pci_tsm_rwsem);
+
+ put_device(&context->tsm_dev->dev);
+ pci_dev_put(context->pdev);
+ pci_dev_put(context->dsm_dev);
+ kfree(context);
}
-EXPORT_SYMBOL_GPL(pci_tsm_unbind);
+EXPORT_SYMBOL_GPL(pci_tsm_context_put);
/**
- * pci_tsm_bind() - Bind @pdev as a TDI for @kvm
- * @pdev: PCI device function to bind
- * @kvm: Private memory attach context
- * @tdi_id: Identifier (virtual BDF) for the TDI as referenced by the TSM and DSM
+ * pci_tsm_context_mark_bound() - Publish a successful per-function binding
+ * @context: context acquired for the bound PCI function
*
- * Returns 0 on success, or a negative error code on failure.
+ * Call once after the provider has successfully initialized the vdevice.
+ * The binding remains visible in tsm/bound until pci_tsm_context_put(),
+ * independently of the device's TDISP state. Acquiring a context alone
+ * does not establish a binding.
*
- * Context: Caller is responsible for constraining the bind lifetime to the
- * registered state of the device. For example, pci_tsm_bind() /
- * pci_tsm_unbind() limited to the VFIO driver bound state of the device.
+ * Context: Caller holds the context's pci_tsm_pf0::lock.
*/
-int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
+void pci_tsm_context_mark_bound(struct pci_tsm_context *context)
{
- struct pci_tsm_pf0 *tsm_pf0;
- struct pci_tdi *tdi;
-
- if (!kvm)
- return -EINVAL;
-
- guard(rwsem_read)(&pci_tsm_rwsem);
-
- if (!pdev->tsm)
- return -EINVAL;
-
- if (!is_link_tsm(pdev->tsm->tsm_dev))
- return -ENXIO;
-
- tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
- guard(mutex)(&tsm_pf0->lock);
-
- /* Resolve races to bind a TDI */
- if (pdev->tsm->tdi) {
- if (pdev->tsm->tdi->kvm != kvm)
- return -EBUSY;
- return 0;
- }
+ lockdep_assert_held(&context->pf0->lock);
+ list_add_tail(&context->bound_node, &context->pf0->bound_contexts);
+}
+EXPORT_SYMBOL_GPL(pci_tsm_context_mark_bound);
- tdi = to_pci_tsm_ops(pdev->tsm)->bind(pdev, kvm, tdi_id);
- if (IS_ERR(tdi))
- return PTR_ERR(tdi);
+struct tsm_dev *pci_tsm_context_tsm_dev(struct pci_tsm_context *context)
+{
+ return context->tsm_dev;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_context_tsm_dev);
- pdev->tsm->tdi = tdi;
+struct pci_tsm_pf0 *pci_tsm_context_pf0(struct pci_tsm_context *context)
+{
+ return context->pf0;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_context_pf0);
- return 0;
+struct pci_dev *pci_tsm_context_dsm_dev(struct pci_tsm_context *context)
+{
+ return context->dsm_dev;
}
-EXPORT_SYMBOL_GPL(pci_tsm_bind);
+EXPORT_SYMBOL_GPL(pci_tsm_context_dsm_dev);
-static void pci_tsm_unbind_all(struct pci_dev *pdev)
+bool pci_tsm_context_match_device(struct pci_tsm_context *context,
+ struct pci_dev *pdev)
{
- pci_tsm_walk_fns_reverse(pdev, __pci_tsm_unbind, NULL);
- __pci_tsm_unbind(pdev, NULL);
+ guard(rwsem_read)(&pci_tsm_rwsem);
+
+ return pdev->tsm && is_link_tsm(pdev->tsm->tsm_dev) &&
+ to_pci_tsm_pf0(pdev->tsm) == context->pf0;
}
+EXPORT_SYMBOL_GPL(pci_tsm_context_match_device);
-static void __pci_tsm_disconnect(struct pci_dev *pdev)
+static int __pci_tsm_disconnect(struct pci_dev *pdev)
{
struct pci_tsm_pf0 *tsm_pf0 = to_pci_tsm_pf0(pdev->tsm);
const struct pci_tsm_ops *ops = to_pci_tsm_ops(pdev->tsm);
@@ -412,21 +433,29 @@ static void __pci_tsm_disconnect(struct pci_dev *pdev)
/* disconnect() mutually exclusive with subfunction pci_tsm_init() */
lockdep_assert_held_write(&pci_tsm_rwsem);
- pci_tsm_unbind_all(pdev);
-
/*
- * disconnect() is uninterruptible as it may be called for device
- * teardown
+ * A vdevice holds a context for its lifetime. Refuse to tear down the
+ * link until userspace destroys all dependent vdevices.
+ *
+ * disconnect() is uninterruptible as it may also be called for device
+ * teardown.
*/
- guard(mutex)(&tsm_pf0->lock);
+ scoped_guard(mutex, &tsm_pf0->lock)
+ if (tsm_pf0->context_users)
+ return -EBUSY;
pci_tsm_walk_fns_reverse(pdev, remove_fn, NULL);
ops->disconnect(pdev);
+ return 0;
}
-static void pci_tsm_disconnect(struct pci_dev *pdev)
+static int pci_tsm_disconnect(struct pci_dev *pdev)
{
- __pci_tsm_disconnect(pdev);
+ int ret = __pci_tsm_disconnect(pdev);
+
+ if (ret)
+ return ret;
tsm_remove(pdev->tsm);
+ return 0;
}
static ssize_t disconnect_store(struct device *dev,
@@ -448,35 +477,43 @@ static ssize_t disconnect_store(struct device *dev,
if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
return -EINVAL;
- pci_tsm_disconnect(pdev);
+ rc = pci_tsm_disconnect(pdev);
+ if (rc)
+ return rc;
return len;
}
static DEVICE_ATTR_WO(disconnect);
-static ssize_t bound_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t bound_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
- struct pci_tsm_pf0 *tsm_pf0;
- struct pci_tsm *tsm;
+ struct pci_tsm_context *context;
+ struct pci_tsm_pf0 *pf0;
int rc;
ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
- if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
+ rc = ACQUIRE_ERR(rwsem_read_intr, &lock);
+ if (rc)
return rc;
- tsm = pdev->tsm;
- if (!tsm)
+ if (!pdev->tsm || !is_link_tsm(pdev->tsm->tsm_dev))
return sysfs_emit(buf, "\n");
- tsm_pf0 = to_pci_tsm_pf0(tsm);
+ pf0 = to_pci_tsm_pf0(pdev->tsm);
+ if (!pf0)
+ return -ENXIO;
- ACQUIRE(mutex_intr, ops_lock)(&tsm_pf0->lock);
- if ((rc = ACQUIRE_ERR(mutex_intr, &ops_lock)))
+ ACQUIRE(mutex_intr, ops_lock)(&pf0->lock);
+ rc = ACQUIRE_ERR(mutex_intr, &ops_lock);
+ if (rc)
return rc;
- if (!tsm->tdi)
- return sysfs_emit(buf, "\n");
- return sysfs_emit(buf, "%s\n", dev_name(&tsm->tsm_dev->dev));
+ list_for_each_entry(context, &pf0->bound_contexts, bound_node)
+ if (context->pdev == pdev)
+ return sysfs_emit(buf, "%s\n",
+ dev_name(&context->tsm_dev->dev));
+
+ return sysfs_emit(buf, "\n");
}
static DEVICE_ATTR_RO(bound);
@@ -955,7 +992,8 @@ static umode_t pci_tsm_attr_visible(struct kobject *kobj,
if (attr == &dev_attr_bound.attr) {
if (is_pci_tsm_pf0(pdev) && has_tee(pdev))
return attr->mode;
- if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
+ if (pdev->tsm && pdev->tsm->dsm_dev &&
+ has_tee(pdev->tsm->dsm_dev))
return attr->mode;
}
@@ -1086,22 +1124,6 @@ static struct pci_dev *find_dsm_dev(struct pci_dev *pdev)
return NULL;
}
-/**
- * pci_tsm_tdi_constructor() - base 'struct pci_tdi' initialization for link TSMs
- * @pdev: PCI device function representing the TDI
- * @tdi: context to initialize
- * @kvm: Private memory attach context
- * @tdi_id: Identifier (virtual BDF) for the TDI as referenced by the TSM and DSM
- */
-void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
- struct kvm *kvm, u32 tdi_id)
-{
- tdi->pdev = pdev;
- tdi->kvm = kvm;
- tdi->tdi_id = tdi_id;
-}
-EXPORT_SYMBOL_GPL(pci_tsm_tdi_constructor);
-
void pci_tsm_init_evidence(struct pci_tsm_evidence *evidence, int slot,
enum hash_algo digest_algo)
{
@@ -1151,7 +1173,6 @@ int pci_tsm_devsec_constructor(struct pci_dev *pdev, struct pci_tsm_devsec *tsm,
return -EINVAL;
pci_tsm->dsm_dev = NULL;
- pci_tsm->tdi = NULL;
pci_tsm->pdev = pdev;
pci_tsm->tsm_dev = tsm_dev;
@@ -1169,6 +1190,7 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
struct tsm_dev *tsm_dev)
{
mutex_init(&tsm->lock);
+ INIT_LIST_HEAD(&tsm->bound_contexts);
/*
* Note, low-level TSM driver responsible for determining if it wants to
* proceed with a device that has no DOE mailbox. TSM may have an
@@ -1224,12 +1246,6 @@ int pci_tsm_register(struct tsm_dev *tsm_dev)
return 0;
}
-static void pci_tsm_fn_exit(struct pci_dev *pdev)
-{
- __pci_tsm_unbind(pdev, NULL);
- tsm_remove(pdev->tsm);
-}
-
/**
* __pci_tsm_destroy() - destroy the TSM context for @pdev
* @pdev: device to cleanup
@@ -1266,12 +1282,14 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
else if (tsm_dev != tsm->tsm_dev)
return;
- /* Disconnect DSMs, unlock assigned TDIs, or cleanup DSM subfunctions */
+ /* Disconnect DSMs, unlock assigned TDIs, or clean up DSM subfunctions. */
if (is_link_tsm(tsm_dev)) {
- if (is_pci_tsm_pf0(pdev))
- pci_tsm_disconnect(pdev);
- else
- pci_tsm_fn_exit(pdev);
+ if (is_pci_tsm_pf0(pdev)) {
+ if (pci_tsm_disconnect(pdev))
+ pci_warn(pdev, "TSM connection is still in use\n");
+ } else {
+ tsm_remove(pdev->tsm);
+ }
}
if (is_devsec_tsm(tsm_dev) && has_tee(pdev)) {
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 15907bad47b7..e351e1490fd0 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PCI_TSM_H
#define __PCI_TSM_H
+#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/rwsem.h>
@@ -8,8 +9,8 @@
#include <uapi/linux/pci-tsm-netlink.h>
struct pci_tsm;
+struct pci_tsm_context;
struct tsm_dev;
-struct kvm;
/*
* struct pci_tsm_ops - manage confidential links and security state
@@ -33,16 +34,11 @@ struct pci_tsm_ops {
* @connect: establish / validate a secure connection (e.g. IDE)
* with the device
* @disconnect: teardown the secure link
- * @bind: bind a TDI in preparation for it to be accepted by a TVM
- * @unbind: remove a TDI from secure operation with a TVM
- *
* Context: @probe, @remove, @connect, and @disconnect run under
* pci_tsm_rwsem held for write to sync with TSM unregistration and
* mutual exclusion of @connect and @disconnect. @connect and
* @disconnect additionally run under the DSM lock (struct
* pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions.
- * @bind and @unbind run under pci_tsm_rwsem held for read
- * and the DSM lock.
*/
struct_group_tagged(pci_tsm_link_ops, link_ops,
struct pci_tsm *(*probe)(struct tsm_dev *tsm_dev,
@@ -50,9 +46,6 @@ struct pci_tsm_ops {
void (*remove)(struct pci_tsm *tsm);
int (*connect)(struct pci_dev *pdev);
void (*disconnect)(struct pci_dev *pdev);
- struct pci_tdi *(*bind)(struct pci_dev *pdev,
- struct kvm *kvm, u32 tdi_id);
- void (*unbind)(struct pci_tdi *tdi);
);
/*
@@ -81,18 +74,6 @@ struct pci_tsm_ops {
size_t nonce_len);
};
-/**
- * struct pci_tdi - Core TEE I/O Device Interface (TDI) context
- * @pdev: host side representation of guest-side TDI
- * @kvm: TEE VM context of bound TDI
- * @tdi_id: Identifier (virtual BDF) for the TDI as referenced by the TSM and DSM
- */
-struct pci_tdi {
- struct pci_dev *pdev;
- struct kvm *kvm;
- u32 tdi_id;
-};
-
/**
* struct pci_tsm_evidence_object - General PCI/TSM blob descriptor
* @data: pointer to the evidence data blob
@@ -145,7 +126,6 @@ struct pci_tsm_evidence {
* @dsm_dev: PCI Device Security Manager for link operations on @pdev
* @tsm_dev: PCI TEE Security Manager device for Link Confidentiality or Device
* Function Security operations
- * @tdi: TDI context established by the @bind link operation
* @evidence: cached evidence from SPDM session establishment (connect), or
* TDISP bind (lock)
*
@@ -170,7 +150,6 @@ struct pci_tsm {
struct pci_dev *pdev;
struct pci_dev *dsm_dev;
struct tsm_dev *tsm_dev;
- struct pci_tdi *tdi;
struct pci_tsm_evidence evidence;
};
@@ -178,11 +157,17 @@ struct pci_tsm {
* struct pci_tsm_pf0 - Physical Function 0 TDISP link context
* @base_tsm: generic core "tsm" context
* @lock: mutual exclustion for pci_tsm_ops invocation
+ * @context_users: live per-function contexts on this PF0, including contexts
+ * being initialized and not yet in @bound_contexts; a nonzero count blocks
+ * link disconnect and is independent of TSM and vIOMMU provider lifetimes
+ * @bound_contexts: bound per-function contexts, protected by @lock
* @doe_mb: PCIe Data Object Exchange mailbox
*/
struct pci_tsm_pf0 {
struct pci_tsm base_tsm;
struct mutex lock;
+ unsigned int context_users;
+ struct list_head bound_contexts;
struct pci_doe_mb *doe_mb;
};
@@ -269,10 +254,15 @@ int pci_tsm_devsec_constructor(struct pci_dev *pdev, struct pci_tsm_devsec *tsm,
void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *tsm);
int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req,
size_t req_sz, void *resp, size_t resp_sz);
-int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id);
-void pci_tsm_unbind(struct pci_dev *pdev);
-void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
- struct kvm *kvm, u32 tdi_id);
+bool pci_tsm_is_configured(struct pci_dev *pdev);
+struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev);
+void pci_tsm_context_put(struct pci_tsm_context *context);
+void pci_tsm_context_mark_bound(struct pci_tsm_context *context);
+struct tsm_dev *pci_tsm_context_tsm_dev(struct pci_tsm_context *context);
+struct pci_tsm_pf0 *pci_tsm_context_pf0(struct pci_tsm_context *context);
+struct pci_dev *pci_tsm_context_dsm_dev(struct pci_tsm_context *context);
+bool pci_tsm_context_match_device(struct pci_tsm_context *context,
+ struct pci_dev *pdev);
struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm);
void pci_tsm_init_evidence(struct pci_tsm_evidence *evidence, int slot,
enum hash_algo digest_algo);
@@ -288,12 +278,47 @@ static inline int pci_tsm_register(struct tsm_dev *tsm_dev)
static inline void pci_tsm_unregister(struct tsm_dev *tsm_dev)
{
}
-static inline int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u64 tdi_id)
+static inline bool pci_tsm_is_configured(struct pci_dev *pdev)
{
- return -ENXIO;
+ return false;
}
-static inline void pci_tsm_unbind(struct pci_dev *pdev)
+
+static inline struct pci_tsm_context *
+pci_tsm_context_get(struct pci_dev *pdev)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void pci_tsm_context_put(struct pci_tsm_context *context)
+{
+}
+
+static inline void pci_tsm_context_mark_bound(struct pci_tsm_context *context)
+{
+}
+
+static inline struct tsm_dev *
+pci_tsm_context_tsm_dev(struct pci_tsm_context *context)
+{
+ return NULL;
+}
+
+static inline struct pci_tsm_pf0 *
+pci_tsm_context_pf0(struct pci_tsm_context *context)
+{
+ return NULL;
+}
+
+static inline struct pci_dev *
+pci_tsm_context_dsm_dev(struct pci_tsm_context *context)
+{
+ return NULL;
+}
+
+static inline bool
+pci_tsm_context_match_device(struct pci_tsm_context *context, struct pci_dev *pdev)
{
+ return false;
}
#endif
diff --git a/samples/devsec/link_tsm.c b/samples/devsec/link_tsm.c
index 1d102dd9590a..7fee075d7ab3 100644
--- a/samples/devsec/link_tsm.c
+++ b/samples/devsec/link_tsm.c
@@ -20,10 +20,6 @@ struct devsec_tsm_fn {
struct pci_tsm pci;
};
-struct devsec_tsm_tdi {
- struct pci_tdi pci;
-};
-
static struct devsec_tsm_pf0 *to_devsec_tsm_pf0(struct pci_tsm *tsm)
{
return container_of(tsm, struct devsec_tsm_pf0, pci.base_tsm);
@@ -234,39 +230,11 @@ static void devsec_link_tsm_disconnect(struct pci_dev *pdev)
clear_bit(i, devsec_stream_ids);
}
-static struct pci_tdi *devsec_link_tsm_bind(struct pci_dev *pdev,
- struct kvm *kvm, u32 tdi_id)
-{
- struct devsec_tsm_tdi *devsec_tdi =
- kzalloc(sizeof(struct devsec_tsm_tdi), GFP_KERNEL);
-
- dev_dbg(pci_tsm_host(pdev), "%s\n", pci_name(pdev));
-
- if (!devsec_tdi)
- return ERR_PTR(-ENOMEM);
-
- pci_tsm_tdi_constructor(pdev, &devsec_tdi->pci, kvm, tdi_id);
-
- return &devsec_tdi->pci;
-}
-
-static void devsec_link_tsm_unbind(struct pci_tdi *tdi)
-{
- struct devsec_tsm_tdi *devsec_tdi =
- container_of(tdi, struct devsec_tsm_tdi, pci);
-
- dev_dbg(pci_tsm_host(tdi->pdev), "%s\n", pci_name(tdi->pdev));
-
- kfree(devsec_tdi);
-}
-
static struct pci_tsm_ops devsec_link_pci_ops = {
.probe = devsec_link_tsm_pci_probe,
.remove = devsec_link_tsm_pci_remove,
.connect = devsec_link_tsm_connect,
.disconnect = devsec_link_tsm_disconnect,
- .bind = devsec_link_tsm_bind,
- .unbind = devsec_link_tsm_unbind,
};
static void devsec_link_tsm_remove(void *tsm_dev)
@@ -292,71 +260,7 @@ static const struct faux_device_ops devsec_link_device_ops = {
.probe = devsec_link_tsm_probe,
};
-static struct pci_dev *pci_find_device(const char *name)
-{
- struct device *dev = bus_find_device_by_name(&pci_bus_type, NULL, name);
-
- if (dev)
- return to_pci_dev(dev);
- return NULL;
-}
-
-static ssize_t tsm_bind_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct device *host;
- int rc;
-
- struct pci_dev *pdev __free(pci_dev_put) = pci_find_device(buf);
- if (!pdev)
- return -ENODEV;
-
- host = pci_tsm_host(pdev);
- if (!host || host != &devsec_link_tsm->dev)
- return -ENXIO;
-
- rc = pci_tsm_bind(pdev, (struct kvm *)1, pci_dev_id(pdev));
- if (rc)
- return rc;
- return count;
-}
-static DEVICE_ATTR_WO(tsm_bind);
-
-static ssize_t tsm_unbind_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct device *host;
-
- struct pci_dev *pdev __free(pci_dev_put) = pci_find_device(buf);
- if (!pdev)
- return -ENODEV;
-
- host = pci_tsm_host(pdev);
- if (!host || host != &devsec_link_tsm->dev)
- return -ENXIO;
-
- pci_tsm_unbind(pdev);
- return count;
-}
-static DEVICE_ATTR_WO(tsm_unbind);
-
-/*
- * Facilitate testing of the bind flows in lieu of VFIO/IOMMUFD
- * support to exercise these paths.
- */
-static struct attribute *devsec_link_attrs[] = {
- &dev_attr_tsm_bind.attr,
- &dev_attr_tsm_unbind.attr,
- NULL,
-};
-
-static const struct attribute_group devsec_link_group = {
- .attrs = devsec_link_attrs,
-};
-
static const struct attribute_group *devsec_link_groups[] = {
- &devsec_link_group,
&devsec_evidence_group,
NULL,
};
diff --git a/tools/testing/devsec/devsec.sh b/tools/testing/devsec/devsec.sh
index 6a9313e7104f..159b1a6fb133 100755
--- a/tools/testing/devsec/devsec.sh
+++ b/tools/testing/devsec/devsec.sh
@@ -94,14 +94,10 @@ validate_disconnected() {
fn_dev=${FN_DEVS[$1]}
host_bridge=$(dirname $(dirname $(readlink -f $pci_dev)))
- # validate that the dsm is not yet detected and that the sub-function
- # is aware of any TSM capabilities
+ # validate that the dsm is not yet detected
dsm=$(cat $pci_dev/tsm/dsm) || err "$LINENO from $2"
- bound=$(cat $pci_dev/tsm/bound) || err "$LINENO from $2"
[[ -z $dsm ]] || err "$LINENO from $2"
- [[ -z $bound ]] || err "$LINENO from $2"
[[ ! -e $fn_dev/tsm/dsm ]] || err "$LINENO from $2"
- [[ ! -e $fn_dev/tsm/bound ]] || err "$LINENO from $2"
[[ ! -e $fn_dev/tsm/connect ]] || err "$LINENO from $2"
[[ ! -e $fn_dev/tsm/disconnect ]] || err "$LINENO from $2"
}
@@ -197,27 +193,6 @@ ide_test() {
check_evidence $pci_dev
- # bind both functions and validate that they display bound to
- # the TSM device
- echo $(basename $pci_dev) > $tsm_link/device/tsm_bind
- bound=$(cat $pci_dev/tsm/bound)
- [[ $bound == $(basename $tsm_link) ]] || err "$LINENO"
- echo $(basename $fn_dev) > $tsm_link/device/tsm_bind
- bound=$(cat $fn_dev/tsm/bound)
- [[ $bound == $(basename $tsm_link) ]] || err "$LINENO"
-
- # test manual unbind
- echo $(basename $pci_dev) > $tsm_link/device/tsm_unbind
- bound=$(cat $pci_dev/tsm/bound)
- [[ -z $bound ]] || err "$LINENO"
- echo $(basename $fn_dev) > $tsm_link/device/tsm_unbind
- bound=$(cat $fn_dev/tsm/bound)
- [[ -z $bound ]] || err "$LINENO"
-
- # rebind to test automatic unbind at disconnect
- echo $(basename $pci_dev) > $tsm_link/device/tsm_bind
- echo $(basename $fn_dev) > $tsm_link/device/tsm_bind
-
# check that the links disappear at disconnect and the stream
# pool is refilled
echo $(basename $tsm_link) > $pci_dev/tsm/disconnect
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests
2026-09-17 14:01 [RFC PATCH v6 00/11] iommufd: Infrastructure for vIOMMU creation for confidential guests and guest TSM requests Aneesh Kumar K.V (Arm)
` (10 preceding siblings ...)
2026-09-17 14:01 ` [RFC PATCH v6 11/11] PCI/TSM: Add reference-counted contexts for vdevice providers Aneesh Kumar K.V (Arm)
@ 2026-09-17 14:17 ` Aneesh Kumar K.V
11 siblings, 0 replies; 13+ messages in thread
From: Aneesh Kumar K.V @ 2026-09-17 14:17 UTC (permalink / raw)
To: linux-coco, iommu, linux-kernel, kvm
Cc: Jason Gunthorpe, Alexey Kardashevskiy, Bjorn Helgaas,
Joerg Roedel, Jonathan Cameron, Kevin Tian, Nicolin Chen,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Shameer Kolothum, Paolo Bonzini
"Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org> writes:
> This series adds the IOMMUFD and PCI/TSM infrastructure required for device
> assignment. It introduces an IOMMUFD-owned vIOMMU provider registry and the
> IOMMU_VDEVICE_TSM_REQ ioctl.
>
> The series adds a vIOMMU provider abstraction that allows a subsystem
> other than the physical IOMMU driver to implement a vIOMMU type. It groups
> the vIOMMU operations with their module owner and private data, and makes
> that implementation discoverable during vIOMMU allocation.
>
> External providers are selected by exact vIOMMU type. When no provider
> matches, vIOMMU creation falls back to the physical IOMMU driver. Once a
> provider matches, its result is authoritative and failures do not trigger
> fallback.
>
> Guest TSM requests are dispatched through the vdevice. PCI/TSM uses
> reference-counted contexts to retain the resources needed by providers,
> without introducing separate IOMMUFD TSM bind or unbind ioctls.
>
> Note: Codex was used to assist with commit message formatting and code
> rearrangement.
>
[ ... 99 lines skipped ... ]
Related Arm CCA host changes is below
commit 66225895e48fa38b13aea493e1e6e3babab8f6aa
Author: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Date: Wed Sep 16 14:06:15 2026 +0530
virt: arm-cca-host: Register the Realm vIOMMU provider with IOMMUFD
Register the Realm SMMUv3 provider directly with IOMMUFD. Retain active TSM
resources until the final provider release and remove the registration
before TSM teardown.
Create CCA-owned vdevices using PCI/TSM contexts, balance pSMMU stream-
table lifetime, and mark a function bound only after successful
initialization. Keep the KVM file and active pSMMU alive through the vIOMMU
lifetime.
diff --git a/drivers/virt/coco/arm-cca-host/Kconfig b/drivers/virt/coco/arm-cca-host/Kconfig
index 4a2bc64c2568..d0c636f92d9e 100644
--- a/drivers/virt/coco/arm-cca-host/Kconfig
+++ b/drivers/virt/coco/arm-cca-host/Kconfig
@@ -7,6 +7,7 @@ config ARM_CCA_HOST
depends on ARM64
depends on PCI
depends on ARM_RMM
+ depends on IOMMUFD
depends on HAVE_ARM_SMCCC_DISCOVERY
select PCI_TSM
select KEYS
diff --git a/drivers/virt/coco/arm-cca-host/Makefile b/drivers/virt/coco/arm-cca-host/Makefile
index d48e8940af46..2732352a7ae2 100644
--- a/drivers/virt/coco/arm-cca-host/Makefile
+++ b/drivers/virt/coco/arm-cca-host/Makefile
@@ -2,4 +2,4 @@
#
obj-$(CONFIG_ARM_CCA_HOST) += arm-cca-host.o
-arm-cca-host-y += arm-cca.o rmi-da.o
+arm-cca-host-y += arm-cca.o rmi-da.o viommu.o
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
index 182733e00dfc..b5d50685eaeb 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -5,6 +5,7 @@
#include <linux/arm-smccc.h>
#include <linux/arm-smccc-bus.h>
+#include <linux/iommufd.h>
#include <linux/pci-tsm.h>
#include <linux/pci-ide.h>
#include <linux/module.h>
@@ -427,6 +428,24 @@ static struct pci_tsm_ops cca_link_pci_ops = {
.disconnect = cca_tsm_disconnect,
};
+static void cca_viommu_provider_release(void *data)
+{
+ tsm_put(data);
+}
+
+static const struct iommufd_viommu_provider_ops cca_viommu_provider_ops = {
+ .owner = THIS_MODULE,
+ .type = IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3,
+ .get_size = cca_viommu_get_size,
+ .init = cca_viommu_init,
+ .release = cca_viommu_provider_release,
+};
+
+static void cca_viommu_provider_remove(void *provider)
+{
+ iommufd_unregister_viommu_provider(provider);
+}
+
static void cca_link_tsm_remove(void *tsm_dev)
{
tsm_unregister(tsm_dev);
@@ -439,7 +458,9 @@ static bool rmi_has_reg2_feature(unsigned long feature)
static int cca_link_tsm_probe(struct arm_smccc_device *sdev)
{
+ struct iommufd_viommu_provider *provider;
struct tsm_dev *tsm_dev;
+ int ret;
if (!rmi_has_reg2_feature(RMI_FEATURE_REGISTER_2_DA))
return -ENODEV;
@@ -448,8 +469,22 @@ static int cca_link_tsm_probe(struct arm_smccc_device *sdev)
if (IS_ERR(tsm_dev))
return PTR_ERR(tsm_dev);
- return devm_add_action_or_reset(&sdev->dev, cca_link_tsm_remove,
- tsm_dev);
+ ret = devm_add_action_or_reset(&sdev->dev, cca_link_tsm_remove, tsm_dev);
+ if (ret)
+ return ret;
+
+ /* Retain PCI/TSM resources until the last provider user is released. */
+ tsm_get(tsm_dev);
+ provider = iommufd_register_viommu_provider(&cca_viommu_provider_ops,
+ tsm_dev);
+ if (IS_ERR(provider)) {
+ tsm_put(tsm_dev);
+ return PTR_ERR(provider);
+ }
+
+ /* Remove from lookup before the earlier TSM unregister action runs. */
+ return devm_add_action_or_reset(&sdev->dev, cca_viommu_provider_remove,
+ provider);
}
static const struct arm_smccc_device_id cca_link_tsm_id_table[] = {
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 306f64749f26..07642eeb3245 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -16,6 +16,11 @@
#include <linux/atomic.h>
#include <linux/kref.h>
#include <linux/wait.h>
+#include <uapi/linux/iommufd.h>
+
+struct iommufd_viommu;
+struct iommu_domain;
+struct iommu_user_data;
#define MAX_CACHE_OBJ_SIZE SZ_16M
#define CACHE_CHUNK_SIZE SZ_4K
@@ -119,6 +124,11 @@ struct cca_host_fn_dsc {
struct pci_tsm pci;
};
+size_t cca_viommu_get_size(struct device *dev, enum iommu_viommu_type type);
+int cca_viommu_init(struct iommufd_viommu *viommu, struct device *dev,
+ struct iommu_domain *parent,
+ const struct iommu_user_data *user_data);
+
enum dev_comm_type {
PDEV_COMMUNICATE = 0x1,
VDEV_COMMUNICATE = 0x2,
diff --git a/drivers/virt/coco/arm-cca-host/viommu.c b/drivers/virt/coco/arm-cca-host/viommu.c
index 5cd261551b17..9daa3ff3ca1c 100644
--- a/drivers/virt/coco/arm-cca-host/viommu.c
+++ b/drivers/virt/coco/arm-cca-host/viommu.c
@@ -32,6 +32,13 @@ struct cca_viommu {
struct cca_psmmu *psmmu;
};
+struct cca_vdevice {
+ struct iommufd_vdevice core;
+ struct pci_tsm_context *tsm_context;
+ struct cca_host_tdi host_tdi;
+ u32 l2_sid;
+};
+
static LIST_HEAD(cca_psmmus);
static DEFINE_MUTEX(cca_psmmus_lock);
@@ -40,6 +47,11 @@ static struct cca_viommu *to_cca_viommu(struct iommufd_viommu *viommu)
return container_of(viommu, struct cca_viommu, core);
}
+static struct tsm_dev *cca_viommu_tsm_dev(struct iommufd_viommu *viommu)
+{
+ return viommu->provider_data;
+}
+
static struct cca_psmmu *
cca_psmmu_get(const struct arm_smmu_realm_params *params)
{
@@ -99,3 +111,192 @@ static void cca_psmmu_put(struct cca_psmmu *psmmu)
list_del(&psmmu->node);
kfree(psmmu);
}
+
+static void cca_viommu_destroy(struct iommufd_viommu *viommu)
+{
+ struct cca_viommu *cca = to_cca_viommu(viommu);
+
+ if (cca->psmmu)
+ cca_psmmu_put(cca->psmmu);
+}
+
+static int cca_psmmu_create_l2(struct cca_psmmu *psmmu, u32 l2_sid)
+{
+ unsigned long rmi_ret = 0;
+ int ret;
+
+ guard(mutex)(&cca_psmmus_lock);
+ ret = rmi_psmmu_st_l2_create(psmmu->phys, l2_sid, &rmi_ret);
+ if (!ret && !rmi_ret)
+ return 0;
+ if (RMI_RETURN_STATUS(rmi_ret) == RMI_ERROR_PSMMU_ST &&
+ RMI_RETURN_INDEX(rmi_ret) == 2)
+ return 0;
+ return -EIO;
+}
+
+static void cca_psmmu_destroy_l2(struct cca_psmmu *psmmu, u32 l2_sid,
+ struct device *dev)
+{
+ unsigned long rmi_ret = 0;
+ int ret;
+
+ guard(mutex)(&cca_psmmus_lock);
+ ret = rmi_psmmu_st_l2_destroy(psmmu->phys, l2_sid, &rmi_ret);
+ if (!ret && !rmi_ret)
+ return;
+ if (RMI_RETURN_STATUS(rmi_ret) == RMI_ERROR_PSMMU_ST &&
+ RMI_RETURN_INDEX(rmi_ret) == 2)
+ return;
+ dev_warn(dev, "failed to destroy Realm stream mapping\n");
+}
+
+static void cca_vdevice_destroy(struct iommufd_vdevice *vdev)
+{
+ struct cca_vdevice *cca_vdev =
+ container_of(vdev, struct cca_vdevice, core);
+ struct cca_viommu *cca = to_cca_viommu(vdev->viommu);
+ struct device *dev = iommufd_vdevice_to_device(vdev);
+ struct pci_tsm_context *context = cca_vdev->tsm_context;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct pci_tsm_pf0 *pf0 = pci_tsm_context_pf0(context);
+ struct pci_dev *dsm_dev = pci_tsm_context_dsm_dev(context);
+ struct cca_host_tdi *host_tdi = &cca_vdev->host_tdi;
+ struct realm *realm = &host_tdi->kvm->arch.realm;
+
+ scoped_guard(mutex, &pf0->lock) {
+ cca_vdev_destroy(host_tdi, realm, pdev, dsm_dev);
+ kvfree(host_tdi->interface_report);
+ kvfree(host_tdi->measurements);
+ cca_psmmu_destroy_l2(cca->psmmu, cca_vdev->l2_sid, dev);
+ }
+ pci_tsm_context_put(context);
+}
+
+static int cca_vdevice_init(struct iommufd_vdevice *vdev)
+{
+ struct cca_vdevice *cca_vdev =
+ container_of(vdev, struct cca_vdevice, core);
+ struct arm_smmu_realm_params params;
+ struct cca_viommu *cca = to_cca_viommu(vdev->viommu);
+ struct cca_psmmu *psmmu = cca->psmmu;
+ struct device *dev = iommufd_vdevice_to_device(vdev);
+ struct tsm_dev *tsm_dev = cca_viommu_tsm_dev(vdev->viommu);
+ struct pci_tsm_context *context;
+ struct pci_dev *pdev;
+ struct pci_tsm_pf0 *pf0;
+ struct pci_dev *dsm_dev;
+ struct cca_host_tdi *host_tdi = &cca_vdev->host_tdi;
+ struct kvm *kvm = vdev->viommu->kvm_file->private_data;
+ void *rmm_vdev;
+ int ret;
+
+ if (!dev_is_pci(dev))
+ return 0;
+ /* Devices without a Link TSM remain ordinary vdevices. */
+ pdev = to_pci_dev(dev);
+ context = pci_tsm_context_get(pdev);
+ if (IS_ERR(context)) {
+ if (PTR_ERR(context) == -EOPNOTSUPP)
+ return 0;
+ return PTR_ERR(context);
+ }
+ if (pci_tsm_context_tsm_dev(context) != tsm_dev) {
+ ret = -EXDEV;
+ goto out_put_context;
+ }
+
+ ret = iommu_viommu_get_params(dev,
+ IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3, ¶ms,
+ sizeof(params));
+ if (ret)
+ goto out_put_context;
+ if (params.psmmu_phys != psmmu->phys) {
+ ret = -EINVAL;
+ goto out_put_context;
+ }
+
+ host_tdi->kvm = kvm;
+ cca_vdev->l2_sid = params.l2_sid;
+ pf0 = pci_tsm_context_pf0(context);
+ dsm_dev = pci_tsm_context_dsm_dev(context);
+
+ scoped_guard(mutex, &pf0->lock) {
+ ret = cca_psmmu_create_l2(psmmu, params.l2_sid);
+ if (!ret) {
+ rmm_vdev = cca_vdev_create(host_tdi, &kvm->arch.realm,
+ pdev, dsm_dev, vdev->virt_id);
+ if (IS_ERR_OR_NULL(rmm_vdev)) {
+ ret = rmm_vdev ? PTR_ERR(rmm_vdev) : -ENOMEM;
+ cca_psmmu_destroy_l2(psmmu, params.l2_sid, dev);
+ }
+ }
+ if (!ret)
+ pci_tsm_context_mark_bound(context);
+ }
+ if (ret)
+ goto out_put_context;
+ cca_vdev->tsm_context = context;
+ vdev->destroy = cca_vdevice_destroy;
+ return 0;
+
+out_put_context:
+ pci_tsm_context_put(context);
+ return ret;
+}
+
+static const struct iommufd_viommu_ops cca_viommu_ops = {
+ .destroy = cca_viommu_destroy,
+ .vdevice_size = VDEVICE_STRUCT_SIZE(struct cca_vdevice, core),
+ .vdevice_init = cca_vdevice_init,
+};
+
+size_t cca_viommu_get_size(struct device *dev, enum iommu_viommu_type type)
+{
+ struct arm_smmu_realm_params params;
+
+ if (type != IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3)
+ return 0;
+ if (iommu_viommu_get_params(dev, type, ¶ms, sizeof(params)))
+ return 0;
+ return VIOMMU_STRUCT_SIZE(struct cca_viommu, core);
+}
+
+int cca_viommu_init(struct iommufd_viommu *viommu, struct device *dev,
+ struct iommu_domain *parent,
+ const struct iommu_user_data *user_data)
+{
+ struct arm_smmu_realm_params params;
+ struct cca_viommu *cca = to_cca_viommu(viommu);
+ struct cca_psmmu *psmmu;
+ struct kvm *kvm;
+ int ret;
+
+ (void)user_data;
+
+ if (viommu->type != IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3)
+ return -EOPNOTSUPP;
+ if (!viommu->kvm_file)
+ return -EINVAL;
+ kvm = viommu->kvm_file->private_data;
+ ret = kvm_realm_ensure_created(kvm);
+ if (ret)
+ return ret;
+
+ ret = iommu_viommu_validate_parent(dev, viommu->type, parent);
+ if (ret)
+ return ret;
+ ret = iommu_viommu_get_params(dev, viommu->type, ¶ms,
+ sizeof(params));
+ if (ret)
+ return ret;
+
+ psmmu = cca_psmmu_get(¶ms);
+ if (IS_ERR(psmmu))
+ return PTR_ERR(psmmu);
+ cca->psmmu = psmmu;
+ viommu->ops = &cca_viommu_ops;
+ return 0;
+}
+
+MODULE_IMPORT_NS("IOMMUFD");
^ permalink raw reply [flat|nested] 13+ messages in thread