This patch adds VFIO_IOMMU_PASID_REQUEST ioctl which aims to passdown PASID allocation/free request from the virtual iommu. This is required to get PASID managed in system-wide. Cc: Kevin Tian <kevin.tian@xxxxxxxxx> Signed-off-by: Liu Yi L <yi.l.liu@xxxxxxxxx> Signed-off-by: Yi Sun <yi.y.sun@xxxxxxxxxxxxxxx> Signed-off-by: Jacob Pan <jacob.jun.pan@xxxxxxxxxxxxxxx> --- drivers/vfio/vfio_iommu_type1.c | 114 ++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/vfio.h | 25 +++++++++ 2 files changed, 139 insertions(+) diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index cd8d3a5..3d73a7d 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -2248,6 +2248,83 @@ static int vfio_cache_inv_fn(struct device *dev, void *data) return iommu_cache_invalidate(dc->domain, dev, &ustruct->info); } +static int vfio_iommu_type1_pasid_alloc(struct vfio_iommu *iommu, + int min_pasid, + int max_pasid) +{ + int ret; + ioasid_t pasid; + struct mm_struct *mm = NULL; + + mutex_lock(&iommu->lock); + if (!IS_IOMMU_CAP_DOMAIN_IN_CONTAINER(iommu)) { + ret = -EINVAL; + goto out_unlock; + } + mm = get_task_mm(current); + /* Track ioasid allocation owner by mm */ + pasid = ioasid_alloc((struct ioasid_set *)mm, min_pasid, + max_pasid, NULL); + if (pasid == INVALID_IOASID) { + ret = -ENOSPC; + goto out_unlock; + } + ret = pasid; +out_unlock: + mutex_unlock(&iommu->lock); + if (mm) + mmput(mm); + return ret; +} + +static int vfio_iommu_type1_pasid_free(struct vfio_iommu *iommu, + unsigned int pasid) +{ + struct mm_struct *mm = NULL; + void *pdata; + int ret = 0; + + mutex_lock(&iommu->lock); + if (!IS_IOMMU_CAP_DOMAIN_IN_CONTAINER(iommu)) { + ret = -EINVAL; + goto out_unlock; + } + + /** + * REVISIT: + * There are two cases free could fail: + * 1. free pasid by non-owner, we use ioasid_set to track mm, if + * the set does not match, caller is not permitted to free. + * 2. free before unbind all devices, we can check if ioasid private + * data, if data != NULL, then fail to free. + */ + mm = get_task_mm(current); + pdata = ioasid_find((struct ioasid_set *)mm, pasid, NULL); + if (IS_ERR(pdata)) { + if (pdata == ERR_PTR(-ENOENT)) + pr_err("PASID %u is not allocated\n", pasid); + else if (pdata == ERR_PTR(-EACCES)) + pr_err("Free PASID %u by non-owner, denied", pasid); + else + pr_err("Error searching PASID %u\n", pasid); + ret = -EPERM; + goto out_unlock; + } + if (pdata) { + pr_debug("Cannot free pasid %d with private data\n", pasid); + /* Expect PASID has no private data if not bond */ + ret = -EBUSY; + goto out_unlock; + } + ioasid_free(pasid); + +out_unlock: + if (mm) + mmput(mm); + mutex_unlock(&iommu->lock); + return ret; +} + static long vfio_iommu_type1_ioctl(void *iommu_data, unsigned int cmd, unsigned long arg) { @@ -2370,6 +2447,43 @@ static long vfio_iommu_type1_ioctl(void *iommu_data, &ustruct); mutex_unlock(&iommu->lock); return ret; + + } else if (cmd == VFIO_IOMMU_PASID_REQUEST) { + struct vfio_iommu_type1_pasid_request req; + int min_pasid, max_pasid, pasid; + + minsz = offsetofend(struct vfio_iommu_type1_pasid_request, + flag); + + if (copy_from_user(&req, (void __user *)arg, minsz)) + return -EFAULT; + + if (req.argsz < minsz) + return -EINVAL; + + switch (req.flag) { + /** + * TODO: min_pasid and max_pasid align with + * typedef unsigned int ioasid_t + */ + case VFIO_IOMMU_PASID_ALLOC: + if (copy_from_user(&min_pasid, + (void __user *)arg + minsz, sizeof(min_pasid))) + return -EFAULT; + if (copy_from_user(&max_pasid, + (void __user *)arg + minsz + sizeof(min_pasid), + sizeof(max_pasid))) + return -EFAULT; + return vfio_iommu_type1_pasid_alloc(iommu, + min_pasid, max_pasid); + case VFIO_IOMMU_PASID_FREE: + if (copy_from_user(&pasid, + (void __user *)arg + minsz, sizeof(pasid))) + return -EFAULT; + return vfio_iommu_type1_pasid_free(iommu, pasid); + default: + return -EINVAL; + } } return -ENOTTY; diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index ccf60a2..04de290 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -807,6 +807,31 @@ struct vfio_iommu_type1_cache_invalidate { }; #define VFIO_IOMMU_CACHE_INVALIDATE _IO(VFIO_TYPE, VFIO_BASE + 24) +/* + * @flag=VFIO_IOMMU_PASID_ALLOC, refer to the @min_pasid and @max_pasid fields + * @flag=VFIO_IOMMU_PASID_FREE, refer to @pasid field + */ +struct vfio_iommu_type1_pasid_request { + __u32 argsz; +#define VFIO_IOMMU_PASID_ALLOC (1 << 0) +#define VFIO_IOMMU_PASID_FREE (1 << 1) + __u32 flag; + union { + struct { + int min_pasid; + int max_pasid; + }; + int pasid; + }; +}; + +/** + * VFIO_IOMMU_PASID_REQUEST - _IOWR(VFIO_TYPE, VFIO_BASE + 27, + * struct vfio_iommu_type1_pasid_request) + * + */ +#define VFIO_IOMMU_PASID_REQUEST _IO(VFIO_TYPE, VFIO_BASE + 27) + /* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */ /* -- 2.7.4