@@ -7,6 +7,7 @@ iommufd-y := \
ioas.o \
main.o \
pages.o \
+ pasid.o \
vfio_compat.o
iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o
@@ -136,6 +136,7 @@ void iommufd_device_destroy(struct iommufd_object *obj)
struct iommufd_device *idev =
container_of(obj, struct iommufd_device, obj);
+ WARN_ON(!xa_empty(&idev->pasid_hwpts));
iommu_device_release_dma_owner(idev->dev);
iommufd_put_group(idev->igroup);
if (!iommufd_selftest_is_mock_dev(idev->dev))
@@ -217,6 +218,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx,
idev->igroup = igroup;
mutex_init(&idev->iopf_lock);
+ xa_init(&idev->pasid_hwpts);
+
/*
* If the caller fails after this success it must call
* iommufd_unbind_device() which is safe since we hold this refcount.
@@ -298,7 +301,6 @@ iommufd_device_get_attach_handle(struct iommufd_device *idev, ioasid_t pasid)
{
struct iommu_attach_handle *handle;
- WARN_ON(pasid != IOMMU_NO_PASID);
handle = iommu_attach_handle_get(idev->igroup->group, pasid, 0);
if (IS_ERR(handle))
return NULL;
@@ -318,9 +320,12 @@ int iommufd_dev_attach_handle(struct iommufd_hw_pagetable *hwpt,
return -ENOMEM;
handle->idev = idev;
- WARN_ON(pasid != IOMMU_NO_PASID);
- ret = iommu_attach_group_handle(hwpt->domain, idev->igroup->group,
- &handle->handle);
+ if (pasid == IOMMU_NO_PASID)
+ ret = iommu_attach_group_handle(hwpt->domain, idev->igroup->group,
+ &handle->handle);
+ else
+ ret = iommu_attach_device_pasid(hwpt->domain, idev->dev, pasid,
+ &handle->handle);
if (ret)
kfree(handle);
@@ -340,9 +345,12 @@ int iommufd_dev_replace_handle(struct iommufd_device *idev,
return -ENOMEM;
handle->idev = idev;
- WARN_ON(pasid != IOMMU_NO_PASID);
- ret = iommu_replace_group_handle(idev->igroup->group,
- hwpt->domain, &handle->handle);
+ if (pasid == IOMMU_NO_PASID)
+ ret = iommu_replace_group_handle(idev->igroup->group,
+ hwpt->domain, &handle->handle);
+ else
+ ret = iommu_replace_device_pasid(hwpt->domain, idev->dev,
+ pasid, &handle->handle);
if (ret)
kfree(handle);
@@ -589,10 +597,6 @@ iommufd_device_do_replace(struct iommufd_device *idev, ioasid_t pasid,
return ERR_PTR(rc);
}
-typedef struct iommufd_hw_pagetable *(*attach_fn)(
- struct iommufd_device *idev, ioasid_t pasid,
- 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.
@@ -676,9 +680,8 @@ iommufd_device_auto_get_domain(struct iommufd_device *idev, ioasid_t pasid,
return destroy_hwpt;
}
-static int iommufd_device_change_pt(struct iommufd_device *idev,
- ioasid_t pasid,
- u32 *pt_id, attach_fn do_attach)
+int iommufd_device_change_pt(struct iommufd_device *idev, ioasid_t pasid,
+ u32 *pt_id, attach_fn do_attach)
{
struct iommufd_hw_pagetable *destroy_hwpt;
struct iommufd_object *pt_obj;
@@ -111,8 +111,10 @@ void iommufd_fault_domain_detach_dev(struct iommufd_hw_pagetable *hwpt,
struct iommufd_attach_handle *handle;
handle = iommufd_device_get_attach_handle(idev, pasid);
- WARN_ON(pasid != IOMMU_NO_PASID);
- iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
+ if (pasid == IOMMU_NO_PASID)
+ iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
+ else
+ iommu_detach_device_pasid(hwpt->domain, idev->dev, pasid);
iommufd_auto_response_faults(hwpt, handle);
iommufd_fault_iopf_disable(idev);
kfree(handle);
@@ -418,6 +418,7 @@ struct iommufd_device {
struct list_head group_item;
/* always the physical device */
struct device *dev;
+ struct xarray pasid_hwpts;
bool enforce_cache_coherency;
/* protect iopf_enabled counter */
struct mutex iopf_lock;
@@ -435,6 +436,20 @@ iommufd_get_device(struct iommufd_ucmd *ucmd, u32 id)
void iommufd_device_destroy(struct iommufd_object *obj);
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
+typedef struct iommufd_hw_pagetable *(*attach_fn)(
+ struct iommufd_device *idev, ioasid_t pasid,
+ struct iommufd_hw_pagetable *hwpt);
+
+int iommufd_device_change_pt(struct iommufd_device *idev, ioasid_t pasid,
+ u32 *pt_id, attach_fn do_attach);
+
+struct iommufd_hw_pagetable *
+iommufd_device_pasid_do_attach(struct iommufd_device *idev, ioasid_t pasid,
+ struct iommufd_hw_pagetable *hwpt);
+struct iommufd_hw_pagetable *
+iommufd_device_pasid_do_replace(struct iommufd_device *idev, ioasid_t pasid,
+ struct iommufd_hw_pagetable *hwpt);
+
struct iommufd_access {
struct iommufd_object obj;
struct iommufd_ctx *ictx;
@@ -534,8 +549,10 @@ static inline void iommufd_hwpt_detach_device(struct iommufd_hw_pagetable *hwpt,
}
handle = iommufd_device_get_attach_handle(idev, pasid);
- WARN_ON(pasid != IOMMU_NO_PASID);
- iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
+ if (pasid == IOMMU_NO_PASID)
+ iommu_detach_group_handle(hwpt->domain, idev->igroup->group);
+ else
+ iommu_detach_device_pasid(hwpt->domain, idev->dev, pasid);
kfree(handle);
}
new file mode 100644
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2024, Intel Corporation
+ */
+#include <linux/iommufd.h>
+#include <linux/iommu.h>
+#include "../iommu-priv.h"
+
+#include "iommufd_private.h"
+
+struct iommufd_hw_pagetable *
+iommufd_device_pasid_do_attach(struct iommufd_device *idev, ioasid_t pasid,
+ struct iommufd_hw_pagetable *hwpt)
+{
+ void *curr;
+ int rc;
+
+ refcount_inc(&hwpt->obj.users);
+ curr = xa_cmpxchg(&idev->pasid_hwpts, pasid, NULL, hwpt, GFP_KERNEL);
+ if (curr) {
+ if (curr == hwpt)
+ rc = 0;
+ else
+ rc = xa_err(curr) ? : -EBUSY;
+ goto err_put_hwpt;
+ }
+
+ rc = iommufd_hwpt_attach_device(hwpt, idev, pasid);
+ if (rc) {
+ xa_erase(&idev->pasid_hwpts, pasid);
+ goto err_put_hwpt;
+ }
+
+ return NULL;
+
+err_put_hwpt:
+ refcount_dec(&hwpt->obj.users);
+ return rc ? ERR_PTR(rc) : NULL;
+}
+
+struct iommufd_hw_pagetable *
+iommufd_device_pasid_do_replace(struct iommufd_device *idev, ioasid_t pasid,
+ struct iommufd_hw_pagetable *hwpt)
+{
+ void *curr;
+ int rc;
+
+ refcount_inc(&hwpt->obj.users);
+ curr = xa_store(&idev->pasid_hwpts, pasid, hwpt, GFP_KERNEL);
+ rc = xa_err(curr);
+ if (rc)
+ goto out_put_hwpt;
+
+ if (!curr) {
+ xa_erase(&idev->pasid_hwpts, pasid);
+ rc = -EINVAL;
+ goto out_put_hwpt;
+ }
+
+ if (curr == hwpt)
+ goto out_put_hwpt;
+
+ /*
+ * After replacement, the reference on the old hwpt is retained
+ * in this thread as caller would free it.
+ */
+ rc = iommufd_hwpt_replace_device(idev, pasid, hwpt, curr);
+ if (rc) {
+ WARN_ON(xa_err(xa_store(&idev->pasid_hwpts, pasid,
+ curr, GFP_KERNEL)));
+ goto out_put_hwpt;
+ }
+
+ /* Caller must destroy old_hwpt */
+ return curr;
+
+out_put_hwpt:
+ refcount_dec(&hwpt->obj.users);
+ return rc ? ERR_PTR(rc) : NULL;
+}
+
+/**
+ * iommufd_device_pasid_attach - Connect a {device, pasid} to an iommu_domain
+ * @idev: device to attach
+ * @pasid: pasid to attach
+ * @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HW_PAGETABLE
+ * Output the IOMMUFD_OBJ_HW_PAGETABLE ID
+ *
+ * This connects a pasid of the device to an iommu_domain. Once this
+ * completes the device could do DMA with the pasid.
+ *
+ * This function is undone by calling iommufd_device_detach_pasid().
+ *
+ * iommufd does not handle race between iommufd_device_pasid_attach(),
+ * iommufd_device_pasid_replace() and iommufd_device_pasid_detach().
+ * So caller of them should guarantee no concurrent call on the same
+ * device and pasid.
+ */
+int iommufd_device_pasid_attach(struct iommufd_device *idev,
+ ioasid_t pasid, u32 *pt_id)
+{
+ return iommufd_device_change_pt(idev, pasid, pt_id,
+ &iommufd_device_pasid_do_attach);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_pasid_attach, IOMMUFD);
+
+/**
+ * iommufd_device_pasid_replace - Change the {device, pasid}'s iommu_domain
+ * @idev: device to change
+ * @pasid: pasid 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_pasid_detach();
+ * iommufd_device_pasid_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_pasid_attach() has already succeeded.
+ *
+ * iommufd does not handle race between iommufd_device_pasid_replace(),
+ * iommufd_device_pasid_attach() and iommufd_device_pasid_detach().
+ * So caller of them should guarantee no concurrent call on the same
+ * device and pasid.
+ */
+int iommufd_device_pasid_replace(struct iommufd_device *idev,
+ ioasid_t pasid, u32 *pt_id)
+{
+ return iommufd_device_change_pt(idev, pasid, pt_id,
+ &iommufd_device_pasid_do_replace);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_pasid_replace, IOMMUFD);
+
+/**
+ * iommufd_device_pasid_detach - Disconnect a {device, pasid} to an iommu_domain
+ * @idev: device to detach
+ * @pasid: pasid to detach
+ *
+ * Undo iommufd_device_pasid_attach(). This disconnects the idev/pasid from
+ * the previously attached pt_id.
+ *
+ * iommufd does not handle race between iommufd_device_pasid_detach(),
+ * iommufd_device_pasid_attach() and iommufd_device_pasid_replace().
+ * So caller of them should guarantee no concurrent call on the same
+ * device and pasid.
+ */
+void iommufd_device_pasid_detach(struct iommufd_device *idev, ioasid_t pasid)
+{
+ struct iommufd_hw_pagetable *hwpt;
+
+ hwpt = xa_erase(&idev->pasid_hwpts, pasid);
+ if (WARN_ON(!hwpt))
+ return;
+ iommufd_hwpt_detach_device(hwpt, idev, pasid);
+ iommufd_hw_pagetable_put(idev->ictx, hwpt);
+}
+EXPORT_SYMBOL_NS_GPL(iommufd_device_pasid_detach, IOMMUFD);
@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/errno.h>
+#include <linux/iommu.h>
#include <linux/types.h>
struct device;
@@ -26,6 +27,12 @@ int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id);
int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id);
void iommufd_device_detach(struct iommufd_device *idev);
+int iommufd_device_pasid_attach(struct iommufd_device *idev,
+ ioasid_t pasid, u32 *pt_id);
+int iommufd_device_pasid_replace(struct iommufd_device *idev,
+ ioasid_t pasid, u32 *pt_id);
+void iommufd_device_pasid_detach(struct iommufd_device *idev, ioasid_t pasid);
+
struct iommufd_ctx *iommufd_device_to_ictx(struct iommufd_device *idev);
u32 iommufd_device_to_id(struct iommufd_device *idev);