[PATCH v5 2/4] iommufd: Add IOMMUFD_CMD_SET_DEV_DATA and IOMMUFD_CMD_UNSET_DEV_DATA

From: Nicolin Chen
Date: Thu Jul 27 2023 - 17:10:38 EST


Add a new pair of ioctls to allow user space to set and unset its iommu-
specific device data at the iommu that a passthrough device is behind.

On platforms with SMMUv3, this new uAPIs will be used to forward a user
space virtual Stream ID of a passthrough device to link to its physical
Stream ID and log into a lookup table, in order for the host kernel to
later run sanity on ATC invalidation requests from the user space, with
ATC_INV commands that have SID fields (virtual Stream IDs).

Signed-off-by: Nicolin Chen <nicolinc@xxxxxxxxxx>
---
drivers/iommu/iommufd/device.c | 2 +
drivers/iommu/iommufd/iommufd_private.h | 1 +
drivers/iommu/iommufd/main.c | 85 +++++++++++++++++++++++++
include/uapi/linux/iommufd.h | 32 ++++++++++
4 files changed, 120 insertions(+)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 7f238c583b61..37a234371645 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -136,6 +136,8 @@ void iommufd_device_destroy(struct iommufd_object *obj)
struct iommufd_device *idev =
container_of(obj, struct iommufd_device, obj);

+ if (idev->has_user_data)
+ dev_iommu_ops(idev->dev)->unset_dev_user_data(idev->dev);
iommu_device_release_dma_owner(idev->dev);
iommufd_put_group(idev->igroup);
if (!iommufd_selftest_is_mock_dev(idev->dev))
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 9ae6edea697f..b8abf664d3f8 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -313,6 +313,7 @@ struct iommufd_device {
/* always the physical device */
struct device *dev;
bool enforce_cache_coherency;
+ bool has_user_data;
};

static inline struct iommufd_device *
diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c
index 255e8a3c5b0e..d49837397dfa 100644
--- a/drivers/iommu/iommufd/main.c
+++ b/drivers/iommu/iommufd/main.c
@@ -387,6 +387,85 @@ static int iommufd_option(struct iommufd_ucmd *ucmd)
return 0;
}

+static int iommufd_set_dev_data(struct iommufd_ucmd *ucmd)
+{
+ struct iommu_set_dev_data *cmd = ucmd->cmd;
+ struct iommufd_device *idev;
+ const struct iommu_ops *ops;
+ void *data = NULL;
+ int rc;
+
+ if (!cmd->data_uptr || !cmd->data_len)
+ return -EINVAL;
+
+ idev = iommufd_get_device(ucmd, cmd->dev_id);
+ if (IS_ERR(idev))
+ return PTR_ERR(idev);
+
+ mutex_lock(&idev->igroup->lock);
+ if (idev->has_user_data) {
+ rc = -EEXIST;
+ goto out_unlock;
+ }
+
+ ops = dev_iommu_ops(idev->dev);
+ if (!ops->dev_user_data_len ||
+ !ops->set_dev_user_data ||
+ !ops->unset_dev_user_data) {
+ rc = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ data = kzalloc(ops->dev_user_data_len, GFP_KERNEL);
+ if (!data) {
+ rc = -ENOMEM;
+ goto out_unlock;
+ }
+
+ if (copy_struct_from_user(data, ops->dev_user_data_len,
+ u64_to_user_ptr(cmd->data_uptr),
+ cmd->data_len)) {
+ rc = -EFAULT;
+ goto out_free_data;
+ }
+
+ rc = ops->set_dev_user_data(idev->dev, data);
+ if (rc)
+ goto out_free_data;
+
+ idev->has_user_data = true;
+out_free_data:
+ kfree(data);
+out_unlock:
+ mutex_unlock(&idev->igroup->lock);
+ iommufd_put_object(&idev->obj);
+ return rc;
+}
+
+static int iommufd_unset_dev_data(struct iommufd_ucmd *ucmd)
+{
+ struct iommu_unset_dev_data *cmd = ucmd->cmd;
+ struct iommufd_device *idev;
+ int rc = 0;
+
+ idev = iommufd_get_device(ucmd, cmd->dev_id);
+ if (IS_ERR(idev))
+ return PTR_ERR(idev);
+
+ mutex_lock(&idev->igroup->lock);
+ if (!idev->has_user_data) {
+ rc = -ENOENT;
+ goto out_unlock;
+ }
+
+ dev_iommu_ops(idev->dev)->unset_dev_user_data(idev->dev);
+ idev->has_user_data = false;
+out_unlock:
+ mutex_unlock(&idev->igroup->lock);
+ iommufd_put_object(&idev->obj);
+ return rc;
+}
+
union ucmd_buffer {
struct iommu_destroy destroy;
struct iommu_hw_info info;
@@ -400,6 +479,8 @@ union ucmd_buffer {
struct iommu_ioas_unmap unmap;
struct iommu_option option;
struct iommu_resv_iova_ranges resv_ranges;
+ struct iommu_set_dev_data set_dev_data;
+ struct iommu_unset_dev_data unset_dev_data;
struct iommu_vfio_ioas vfio_ioas;
#ifdef CONFIG_IOMMUFD_TEST
struct iommu_test_cmd test;
@@ -446,6 +527,10 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
val64),
IOCTL_OP(IOMMU_RESV_IOVA_RANGES, iommufd_resv_iova_ranges,
struct iommu_resv_iova_ranges, resv_iovas),
+ IOCTL_OP(IOMMU_SET_DEV_DATA, iommufd_set_dev_data,
+ struct iommu_set_dev_data, data_len),
+ IOCTL_OP(IOMMU_UNSET_DEV_DATA, iommufd_unset_dev_data,
+ struct iommu_unset_dev_data, dev_id),
IOCTL_OP(IOMMU_VFIO_IOAS, iommufd_vfio_ioas, struct iommu_vfio_ioas,
__reserved),
#ifdef CONFIG_IOMMUFD_TEST
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 034da283cd3a..ede822e5acbb 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -49,6 +49,8 @@ enum {
IOMMUFD_CMD_GET_HW_INFO,
IOMMUFD_CMD_RESV_IOVA_RANGES,
IOMMUFD_CMD_HWPT_INVALIDATE,
+ IOMMUFD_CMD_SET_DEV_DATA,
+ IOMMUFD_CMD_UNSET_DEV_DATA,
};

/**
@@ -508,4 +510,34 @@ struct iommu_hwpt_invalidate {
__aligned_u64 data_uptr;
};
#define IOMMU_HWPT_INVALIDATE _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HWPT_INVALIDATE)
+
+/**
+ * struct iommu_set_dev_data - ioctl(IOMMU_SET_DEV_DATA)
+ * @size: sizeof(struct iommu_set_dev_data)
+ * @dev_id: The device to set an iommu specific device data
+ * @data_uptr: User pointer of the device user data
+ * @data_len: Length of the device user data
+ *
+ * The device data must be unset using ioctl(IOMMU_UNSET_DEV_DATA), before
+ * another ioctl(IOMMU_SET_DEV_DATA) call or before the device itself gets
+ * unbind'd from the iommufd context.
+ */
+struct iommu_set_dev_data {
+ __u32 size;
+ __u32 dev_id;
+ __aligned_u64 data_uptr;
+ __u32 data_len;
+};
+#define IOMMU_SET_DEV_DATA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_SET_DEV_DATA)
+
+/**
+ * struct iommu_unset_dev_data - ioctl(IOMMU_UNSET_DEV_DATA)
+ * @size: sizeof(struct iommu_unset_dev_data)
+ * @dev_id: The device to unset its device user data
+ */
+struct iommu_unset_dev_data {
+ __u32 size;
+ __u32 dev_id;
+};
+#define IOMMU_UNSET_DEV_DATA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_UNSET_DEV_DATA)
#endif
--
2.41.0