diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c index ac60a914a30e..9faabce974c6 100644 --- a/drivers/iommu/iommufd/device.c +++ b/drivers/iommu/iommufd/device.c @@ -23,6 +23,19 @@ struct iommufd_attach { struct xarray device_array; }; +/* + * Detect a noiommu device for the cdev path. We check dev->iommu rather than + * using device_iommu_mapped() (which checks dev->iommu_group) because when + * both group and cdev interfaces coexist, the group path assigns a fake + * noiommu iommu_group to the device. That would cause device_iommu_mapped() + * to return true and hide the noiommu case from the cdev path. dev->iommu is + * reliably NULL when no IOMMU driver is managing the device. + */ +static bool iommufd_device_is_noiommu(struct iommufd_device *idev) +{ + return IS_ENABLED(CONFIG_IOMMUFD_NOIOMMU) && !idev->dev->iommu; +} + static void iommufd_group_release(struct kref *kref) { struct iommufd_group *igroup = @@ -30,9 +43,11 @@ static void iommufd_group_release(struct kref *kref) WARN_ON(!xa_empty(&igroup->pasid_attach)); - xa_cmpxchg(&igroup->ictx->groups, iommu_group_id(igroup->group), igroup, - NULL, GFP_KERNEL); - iommu_group_put(igroup->group); + if (igroup->group) { + xa_cmpxchg(&igroup->ictx->groups, iommu_group_id(igroup->group), + igroup, NULL, GFP_KERNEL); + iommu_group_put(igroup->group); + } mutex_destroy(&igroup->lock); kfree(igroup); } @@ -197,32 +212,20 @@ void iommufd_device_destroy(struct iommufd_object *obj) struct iommufd_device *idev = container_of(obj, struct iommufd_device, obj); - iommu_device_release_dma_owner(idev->dev); + /* igroup is NULL when destroy called during bind error cleanup */ + if (!idev->igroup) + return; + if (!iommufd_device_is_noiommu(idev)) + iommu_device_release_dma_owner(idev->dev); iommufd_put_group(idev->igroup); if (!iommufd_selftest_is_mock_dev(idev->dev)) iommufd_ctx_put(idev->ictx); } -/** - * iommufd_device_bind - Bind a physical device to an iommu fd - * @ictx: iommufd file descriptor - * @dev: Pointer to a physical device struct - * @id: Output ID number to return to userspace for this device - * - * A successful bind establishes an ownership over the device and returns - * struct iommufd_device pointer, otherwise returns error pointer. - * - * A driver using this API must set driver_managed_dma and must not touch - * the device until this routine succeeds and establishes ownership. - * - * Binding a PCI device places the entire RID under iommufd control. - * - * The caller must undo this with iommufd_device_unbind() - */ -struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, - struct device *dev, u32 *id) +static int iommufd_bind_iommu(struct iommufd_device *idev) { - struct iommufd_device *idev; + struct iommufd_ctx *ictx = idev->ictx; + struct device *dev = idev->dev; struct iommufd_group *igroup; int rc; @@ -231,11 +234,11 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, * to restore cache coherency. */ if (!device_iommu_capable(dev, IOMMU_CAP_CACHE_COHERENCY)) - return ERR_PTR(-EINVAL); + return -EINVAL; igroup = iommufd_get_group(ictx, dev); if (IS_ERR(igroup)) - return ERR_CAST(igroup); + return PTR_ERR(igroup); /* * For historical compat with VFIO the insecure interrupt path is @@ -261,21 +264,77 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, if (rc) goto out_group_put; - idev = iommufd_object_alloc(ictx, idev, IOMMUFD_OBJ_DEVICE); - if (IS_ERR(idev)) { - rc = PTR_ERR(idev); - goto out_release_owner; - } - idev->ictx = ictx; - if (!iommufd_selftest_is_mock_dev(dev)) - iommufd_ctx_get(ictx); - idev->dev = dev; - idev->enforce_cache_coherency = - device_iommu_capable(dev, IOMMU_CAP_ENFORCE_CACHE_COHERENCY); - /* The calling driver is a user until iommufd_device_unbind() */ - refcount_inc(&idev->obj.users); /* igroup refcount moves into iommufd_device */ idev->igroup = igroup; + idev->enforce_cache_coherency = + device_iommu_capable(dev, IOMMU_CAP_ENFORCE_CACHE_COHERENCY); + return 0; + +out_group_put: + iommufd_put_group(igroup); + return rc; +} + +/* + * Noiommu devices have no real IOMMU group. Create a dummy igroup so that + * internal code paths that expect idev->igroup to be present still work. + * A NULL igroup->group distinguishes this from a real IOMMU-backed group. + */ +static int iommufd_bind_noiommu(struct iommufd_device *idev) +{ + struct iommufd_group *igroup; + + igroup = iommufd_alloc_group(idev->ictx, NULL); + if (IS_ERR(igroup)) + return PTR_ERR(igroup); + idev->igroup = igroup; + return 0; +} + +/** + * iommufd_device_bind - Bind a physical device to an iommu fd + * @ictx: iommufd file descriptor + * @dev: Pointer to a physical device struct + * @id: Output ID number to return to userspace for this device + * + * A successful bind establishes an ownership over the device and returns + * struct iommufd_device pointer, otherwise returns error pointer. + * + * A driver using this API must set driver_managed_dma and must not touch + * the device until this routine succeeds and establishes ownership. + * + * Binding a PCI device places the entire RID under iommufd control. + * + * 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 iommufd_device *idev; + int rc; + + idev = iommufd_object_alloc(ictx, idev, IOMMUFD_OBJ_DEVICE); + if (IS_ERR(idev)) + return idev; + + idev->ictx = ictx; + idev->dev = dev; + + if (!iommufd_device_is_noiommu(idev)) + rc = iommufd_bind_iommu(idev); + else + rc = iommufd_bind_noiommu(idev); + if (rc) + goto err_out; + + /* + * Take a ctx reference after bind succeeds. This must happen here + * so that iommufd_device_destroy() can handle partial initialization + */ + if (!iommufd_selftest_is_mock_dev(dev)) + iommufd_ctx_get(ictx); + /* The calling driver is a user until iommufd_device_unbind() */ + refcount_inc(&idev->obj.users); /* * If the caller fails after this success it must call @@ -287,11 +346,14 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, *id = idev->obj.id; return idev; -out_release_owner: - iommu_device_release_dma_owner(dev); -out_group_put: - iommufd_put_group(igroup); +err_out: + /* + * iommufd_device_destroy() handles partially initialized idev, + * so iommufd_object_abort_and_destroy() is safe to call here. + */ + iommufd_object_abort_and_destroy(ictx, &idev->obj); return ERR_PTR(rc); + } EXPORT_SYMBOL_NS_GPL(iommufd_device_bind, "IOMMUFD"); @@ -512,6 +574,9 @@ static int iommufd_hwpt_attach_device(struct iommufd_hw_pagetable *hwpt, if (rc) return rc; + if (iommufd_device_is_noiommu(idev)) + return 0; + handle = kzalloc_obj(*handle); if (!handle) return -ENOMEM; @@ -552,6 +617,9 @@ static void iommufd_hwpt_detach_device(struct iommufd_hw_pagetable *hwpt, { struct iommufd_attach_handle *handle; + if (iommufd_device_is_noiommu(idev)) + return; + handle = iommufd_device_get_attach_handle(idev, pasid); if (pasid == IOMMU_NO_PASID) iommu_detach_group_handle(hwpt->domain, idev->igroup->group); @@ -577,6 +645,9 @@ static int iommufd_hwpt_replace_device(struct iommufd_device *idev, if (rc) return rc; + if (iommufd_device_is_noiommu(idev)) + return 0; + old_handle = iommufd_device_get_attach_handle(idev, pasid); handle = kzalloc_obj(*handle); @@ -607,7 +678,8 @@ int iommufd_hw_pagetable_attach(struct iommufd_hw_pagetable *hwpt, struct iommufd_device *idev, ioasid_t pasid) { struct iommufd_hwpt_paging *hwpt_paging = find_hwpt_paging(hwpt); - bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID; + bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID && + !iommufd_device_is_noiommu(idev); struct iommufd_group *igroup = idev->igroup; struct iommufd_hw_pagetable *old_hwpt; struct iommufd_attach *attach; @@ -786,7 +858,8 @@ iommufd_device_do_replace(struct iommufd_device *idev, ioasid_t pasid, struct iommufd_hw_pagetable *hwpt) { struct iommufd_hwpt_paging *hwpt_paging = find_hwpt_paging(hwpt); - bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID; + bool attach_resv = hwpt_paging && pasid == IOMMU_NO_PASID && + !iommufd_device_is_noiommu(idev); struct iommufd_hwpt_paging *old_hwpt_paging; struct iommufd_group *igroup = idev->igroup; struct iommufd_hw_pagetable *old_hwpt; @@ -1578,6 +1651,11 @@ int iommufd_get_hw_info(struct iommufd_ucmd *ucmd) if (IS_ERR(idev)) return PTR_ERR(idev); + if (iommufd_device_is_noiommu(idev)) { + rc = -EOPNOTSUPP; + goto out_put; + } + ops = dev_iommu_ops(idev->dev); if (ops->hw_info) { data = ops->hw_info(idev->dev, &data_len, &cmd->out_data_type);