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:
Jason Gunthorpe 2026-07-06 11:48:31 -07:00
parent cfa8214875
commit 072f454c38

View File

@ -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);