Skip to content

Commit

Permalink
iommufd: Add iommufd_device_replace()
Browse files Browse the repository at this point in the history
Replace allows all the devices in a group to move in one step to a new
HWPT. Further, the HWPT move is done without going through a blocking
domain so that the IOMMU driver can implement some level of
non-distruption to ongoing DMA if that has meaning for it (eg for future
special driver domains)

Replace uses a lot of the same logic as normal attach, except the actual
domain change over has different restrictions, and we are careful to
sequence things so that failure is going to leave everything the way it
was, and not get trapped in a blocking domain or something if there is
ENOMEM.

Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
  • Loading branch information
jgunthorpe committed Feb 27, 2023
1 parent 40ad7dc commit 5cccd4a
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 42 deletions.
216 changes: 174 additions & 42 deletions drivers/iommu/iommufd/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <linux/iommufd.h>
#include <linux/slab.h>
#include <linux/iommu.h>
#include "../iommu-priv.h"

#include "io_pagetable.h"
#include "iommufd_private.h"
Expand Down Expand Up @@ -338,27 +339,101 @@ iommufd_hw_pagetable_detach(struct iommufd_device *idev)
return hwpt;
}

static int iommufd_device_do_attach(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
static struct iommufd_hw_pagetable *
iommufd_device_do_attach(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
{
int rc;

mutex_lock(&idev->igroup->lock);
rc = iommufd_hw_pagetable_attach(hwpt, idev);
mutex_unlock(&idev->igroup->lock);
return rc;
if (rc)
return ERR_PTR(rc);
return NULL;
}

static struct iommufd_hw_pagetable *
iommufd_device_do_replace_locked(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
{
struct iommufd_hw_pagetable *old_hwpt;
int rc;

lockdep_assert_held(&idev->igroup->lock);

/* Try to upgrade the domain we have */
if (idev->enforce_cache_coherency) {
rc = iommufd_hw_pagetable_enforce_cc(hwpt);
if (rc)
return ERR_PTR(-EINVAL);
}

rc = iommufd_device_setup_msi(idev, hwpt);
if (rc)
return ERR_PTR(rc);

old_hwpt = idev->igroup->hwpt;
if (hwpt->ioas != old_hwpt->ioas) {
rc = iopt_table_enforce_group_resv_regions(
&hwpt->ioas->iopt, idev->igroup->group, NULL);
if (rc)
return ERR_PTR(rc);
}

rc = iommu_group_replace_domain(idev->igroup->group, hwpt->domain);
if (rc)
goto err_unresv;

if (hwpt->ioas != old_hwpt->ioas)
iopt_remove_reserved_iova(&old_hwpt->ioas->iopt,
idev->igroup->group);

idev->igroup->hwpt = hwpt;
refcount_inc(&hwpt->obj.users);
return old_hwpt;
err_unresv:
iopt_remove_reserved_iova(&hwpt->ioas->iopt, idev->igroup->group);
return ERR_PTR(rc);
}

static struct iommufd_hw_pagetable *
iommufd_device_do_replace(struct iommufd_device *idev,
struct iommufd_hw_pagetable *hwpt)
{
struct iommufd_hw_pagetable *destroy_hwpt = NULL;
int rc;

mutex_lock(&idev->igroup->lock);
destroy_hwpt = iommufd_device_do_replace_locked(idev, hwpt);
if (IS_ERR(destroy_hwpt)) {
rc = PTR_ERR(destroy_hwpt);
goto out_unlock;
}
mutex_unlock(&idev->igroup->lock);
return destroy_hwpt;

out_unlock:
mutex_unlock(&idev->igroup->lock);
return ERR_PTR(rc);
}

typedef struct iommufd_hw_pagetable *(*attach_fn)(
struct iommufd_device *idev, struct iommufd_hw_pagetable *hwpt);

/*
* When automatically managing the domains we search for a compatible domain in
* the iopt and if one is found use it, otherwise create a new domain.
* Automatic domain selection will never pick a manually created domain.
*/
static int iommufd_device_auto_get_domain(struct iommufd_device *idev,
struct iommufd_ioas *ioas, u32 *pt_id)
static struct iommufd_hw_pagetable *
iommufd_device_auto_get_domain(struct iommufd_device *idev,
struct iommufd_ioas *ioas, u32 *pt_id,
attach_fn do_attach)
{
bool immediate_attach = do_attach == iommufd_device_do_attach;
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_hw_pagetable *hwpt;
int rc;

/*
* There is no differentiation when domains are allocated, so any domain
Expand All @@ -372,52 +447,57 @@ static int iommufd_device_auto_get_domain(struct iommufd_device *idev,

if (!iommufd_lock_obj(&hwpt->obj))
continue;
rc = iommufd_device_do_attach(idev, hwpt);
iommufd_put_object(&hwpt->obj);

/*
* -EINVAL means the domain is incompatible with the device.
* Other error codes should propagate to userspace as failure.
* Success means the domain is attached.
*/
if (rc == -EINVAL)
continue;
destroy_hwpt = (*do_attach)(idev, hwpt);
*pt_id = hwpt->obj.id;
iommufd_put_object(&hwpt->obj);
if (IS_ERR(destroy_hwpt)) {
/*
* -EINVAL means the domain is incompatible with the
* device. Other error codes should propagate to
* userspace as failure. Success means the domain is
* attached.
*/
if (PTR_ERR(destroy_hwpt) == -EINVAL)
continue;
goto out_unlock;
}
goto out_unlock;
}

hwpt = iommufd_hw_pagetable_alloc(idev->ictx, ioas, idev, true);
hwpt = iommufd_hw_pagetable_alloc(idev->ictx, ioas, idev,
immediate_attach);
if (IS_ERR(hwpt)) {
rc = PTR_ERR(hwpt);
destroy_hwpt = ERR_CAST(hwpt);
goto out_unlock;
}

if (!immediate_attach) {
destroy_hwpt = (*do_attach)(idev, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_abort;
} else {
destroy_hwpt = NULL;
}

hwpt->auto_domain = true;
*pt_id = hwpt->obj.id;

mutex_unlock(&ioas->mutex);
iommufd_object_finalize(idev->ictx, &hwpt->obj);
return 0;
return destroy_hwpt;

out_abort:
iommufd_object_abort_and_destroy(idev->ictx, &hwpt->obj);
out_unlock:
mutex_unlock(&ioas->mutex);
return rc;
return destroy_hwpt;
}

/**
* iommufd_device_attach - Connect a device from an iommu_domain
* @idev: device to attach
* @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HW_PAGETABLE
* Output the IOMMUFD_OBJ_HW_PAGETABLE ID
*
* This connects the device to an iommu_domain, either automatically or manually
* selected. Once this completes the device could do DMA.
*
* The caller should return the resulting pt_id back to userspace.
* This function is undone by calling iommufd_device_detach().
*/
int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
static int iommufd_device_change_pt(struct iommufd_device *idev, u32 *pt_id,
attach_fn do_attach)
{
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_object *pt_obj;
int rc;

pt_obj = iommufd_get_object(idev->ictx, *pt_id, IOMMUFD_OBJ_ANY);
if (IS_ERR(pt_obj))
Expand All @@ -428,34 +508,86 @@ int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
struct iommufd_hw_pagetable *hwpt =
container_of(pt_obj, struct iommufd_hw_pagetable, obj);

rc = iommufd_device_do_attach(idev, hwpt);
if (rc)
destroy_hwpt = (*do_attach)(idev, hwpt);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
}
case IOMMUFD_OBJ_IOAS: {
struct iommufd_ioas *ioas =
container_of(pt_obj, struct iommufd_ioas, obj);

rc = iommufd_device_auto_get_domain(idev, ioas, pt_id);
if (rc)
destroy_hwpt = iommufd_device_auto_get_domain(idev, ioas, pt_id,
do_attach);
if (IS_ERR(destroy_hwpt))
goto out_put_pt_obj;
break;
}
default:
rc = -EINVAL;
destroy_hwpt = ERR_PTR(-EINVAL);
goto out_put_pt_obj;
}
iommufd_put_object(pt_obj);

refcount_inc(&idev->obj.users);
rc = 0;
/* This destruction has to be after we unlock everything */
if (destroy_hwpt)
iommufd_hw_pagetable_put(idev->ictx, destroy_hwpt);
return 0;

out_put_pt_obj:
iommufd_put_object(pt_obj);
return rc;
return PTR_ERR(destroy_hwpt);
}

/**
* iommufd_device_attach - Connect a device to an iommu_domain
* @idev: device to attach
* @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HW_PAGETABLE
* Output the IOMMUFD_OBJ_HW_PAGETABLE ID
*
* This connects the device to an iommu_domain, either automatically or manually
* selected. Once this completes the device could do DMA.
*
* The caller should return the resulting pt_id back to userspace.
* This function is undone by calling iommufd_device_detach().
*/
int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
{
int rc;

rc = iommufd_device_change_pt(idev, pt_id, &iommufd_device_do_attach);
if (rc)
return rc;

/*
* Pairs with iommufd_device_detach() - catches caller bugs attempting
* to destroy a device with an attachment.
*/
refcount_inc(&idev->obj.users);
return 0;
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, IOMMUFD);

/**
* iommufd_device_replace - Change the device's iommu_domain
* @idev: device to change
* @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HW_PAGETABLE
* Output the IOMMUFD_OBJ_HW_PAGETABLE ID
*
* This is the same as:
* iommufd_device_detach();
* iommufd_device_attach()
* If it fails then no change is made to the attachment. The iommu driver may
* implement this so there is no disruption in translation. This can only be
* called if iommufd_device_attach() has already succeeded.
*/
int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id)
{
return iommufd_device_change_pt(idev, pt_id,
&iommufd_device_do_replace);
}
EXPORT_SYMBOL_NS_GPL(iommufd_device_replace, IOMMUFD);

/**
* iommufd_device_detach - Disconnect a device to an iommu_domain
* @idev: device to detach
Expand Down
1 change: 1 addition & 0 deletions drivers/iommu/iommufd/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -461,5 +461,6 @@ module_exit(iommufd_exit);
MODULE_ALIAS_MISCDEV(VFIO_MINOR);
MODULE_ALIAS("devname:vfio/vfio");
#endif
MODULE_IMPORT_NS(IOMMUFD_INTERNAL);
MODULE_DESCRIPTION("I/O Address Space Management for passthrough devices");
MODULE_LICENSE("GPL");

0 comments on commit 5cccd4a

Please sign in to comment.