[RFC,v3,2/4] iommufd: Add IOMMUFD_CMD_DEVICE_SET_DATA and IOMMUFD_CMD_DEVICE_UNSET_DATA
Commit Message
Add a new pair of ioctls to allow user space to set and unset its iommu-
specific device data for a passthrough device that's behind the iommu.
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@nvidia.com>
---
drivers/iommu/iommufd/device.c | 81 +++++++++++++++++++++++++
drivers/iommu/iommufd/iommufd_private.h | 3 +
drivers/iommu/iommufd/main.c | 4 ++
include/uapi/linux/iommufd.h | 32 ++++++++++
4 files changed, 120 insertions(+)
Comments
On 4/23/23 3:40 PM, Nicolin Chen wrote:
> Add a new pair of ioctls to allow user space to set and unset its iommu-
> specific device data for a passthrough device that's behind the iommu.
>
> 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@nvidia.com>
> ---
> drivers/iommu/iommufd/device.c | 81 +++++++++++++++++++++++++
> drivers/iommu/iommufd/iommufd_private.h | 3 +
> drivers/iommu/iommufd/main.c | 4 ++
> 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 c649a3403797..9480cd36a8bd 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 (WARN_ON(idev->has_user_data))
> + dev_iommu_ops(idev->dev)->unset_dev_user_data(idev->dev);
Do you really need this WARN_ON()? The user space application can easily
trigger this kernel WARN() by setting the user data and forgetting to
unset it.
Best regards,
baolu
Hi Baolu,
On Mon, Apr 24, 2023 at 10:44:08AM +0800, Baolu Lu wrote:
> On 4/23/23 3:40 PM, Nicolin Chen wrote:
> > Add a new pair of ioctls to allow user space to set and unset its iommu-
> > specific device data for a passthrough device that's behind the iommu.
> >
> > 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@nvidia.com>
> > ---
> > drivers/iommu/iommufd/device.c | 81 +++++++++++++++++++++++++
> > drivers/iommu/iommufd/iommufd_private.h | 3 +
> > drivers/iommu/iommufd/main.c | 4 ++
> > 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 c649a3403797..9480cd36a8bd 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 (WARN_ON(idev->has_user_data))
> > + dev_iommu_ops(idev->dev)->unset_dev_user_data(idev->dev);
>
> Do you really need this WARN_ON()? The user space application can easily
> trigger this kernel WARN() by setting the user data and forgetting to
> unset it.
I can drop that, since it's a user triggerable one.
Thanks
Nic
@@ -136,6 +136,8 @@ void iommufd_device_destroy(struct iommufd_object *obj)
struct iommufd_device *idev =
container_of(obj, struct iommufd_device, obj);
+ if (WARN_ON(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))
@@ -726,6 +728,85 @@ void iommufd_device_detach(struct iommufd_device *idev)
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_detach, IOMMUFD);
+int iommufd_device_set_data(struct iommufd_ucmd *ucmd)
+{
+ struct iommu_device_set_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;
+}
+
+int iommufd_device_unset_data(struct iommufd_ucmd *ucmd)
+{
+ struct iommu_device_unset_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;
+}
+
void iommufd_access_destroy_object(struct iommufd_object *obj)
{
struct iommufd_access *access =
@@ -309,6 +309,7 @@ struct iommufd_device {
/* always the physical device */
struct device *dev;
bool enforce_cache_coherency;
+ bool has_user_data;
};
static inline struct iommufd_device *
@@ -321,6 +322,8 @@ iommufd_get_device(struct iommufd_ucmd *ucmd, u32 id)
void iommufd_device_destroy(struct iommufd_object *obj);
int iommufd_device_get_hw_info(struct iommufd_ucmd *ucmd);
+int iommufd_device_set_data(struct iommufd_ucmd *ucmd);
+int iommufd_device_unset_data(struct iommufd_ucmd *ucmd);
struct iommufd_access {
struct iommufd_object obj;
@@ -326,6 +326,10 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
val64),
IOCTL_OP(IOMMU_VFIO_IOAS, iommufd_vfio_ioas, struct iommu_vfio_ioas,
__reserved),
+ IOCTL_OP(IOMMU_DEVICE_SET_DATA, iommufd_device_set_data,
+ struct iommu_device_set_data, data_len),
+ IOCTL_OP(IOMMU_DEVICE_UNSET_DATA, iommufd_device_unset_data,
+ struct iommu_device_unset_data, dev_id),
#ifdef CONFIG_IOMMUFD_TEST
IOCTL_OP(IOMMU_TEST_CMD, iommufd_test, struct iommu_test_cmd, last),
#endif
@@ -51,6 +51,8 @@ enum {
IOMMUFD_CMD_HWPT_ALLOC,
IOMMUFD_CMD_DEVICE_GET_HW_INFO,
IOMMUFD_CMD_HWPT_INVALIDATE,
+ IOMMUFD_CMD_DEVICE_SET_DATA,
+ IOMMUFD_CMD_DEVICE_UNSET_DATA,
};
/**
@@ -626,4 +628,34 @@ struct iommu_hwpt_invalidate {
__aligned_u64 data_uptr;
};
#define IOMMU_HWPT_INVALIDATE _IO(IOMMUFD_TYPE, IOMMUFD_CMD_HWPT_INVALIDATE)
+
+/**
+ * struct iommu_device_set_data - ioctl(IOMMU_DEVICE_SET_DATA)
+ * @size: sizeof(struct iommu_device_set_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_DEVICE_UNSET_DATA), before
+ * another ioctl(IOMMU_DEVICE_SET_DATA) call or before the device itself gets
+ * unbind'd from the iommufd context.
+ */
+struct iommu_device_set_data {
+ __u32 size;
+ __u32 dev_id;
+ __aligned_u64 data_uptr;
+ __u32 data_len;
+};
+#define IOMMU_DEVICE_SET_DATA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_DEVICE_SET_DATA)
+
+/**
+ * struct iommu_device_unset_data - ioctl(IOMMU_DEVICE_UNSET_DATA)
+ * @size: sizeof(struct iommu_device_unset_data)
+ * @dev_id: The device to unset its device user data
+ */
+struct iommu_device_unset_data {
+ __u32 size;
+ __u32 dev_id;
+};
+#define IOMMU_DEVICE_UNSET_DATA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_DEVICE_UNSET_DATA)
#endif