mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
iommufd: Allow binding to a noiommu device
Allow iommufd to bind devices without an IOMMU (noiommu mode) by creating a dummy igroup for such devices and skipping hwpt operations. This enables noiommu devices to operate through the same iommufd API as IOMMU- capable devices. Link: https://patch.msgid.link/r/2c6cf6ab1564426050e637863c9b0d3b25541c63.1783360051.git.jacob.pan@linux.microsoft.com Reviewed-by: Kevin Tian <kevin.tian@intel.com> Reviewed-by: Yi Liu <yi.l.liu@intel.com> Reviewed-by: Lu Baolu <baolu.lu@linux.intel.com> Reviewed-by: Pranjal Shrivastava <praan@google.com> Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com> Fixes: 2c6cf6ab1564 ("iommufd: Allow binding to a noiommu device") Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
This commit is contained in:
parent
cfa8214875
commit
072f454c38
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user