The access object has been used externally by VFIO mdev devices, allowing
them to pin/unpin physical pages (via needs_pin_pages). Meanwhile, a racy
unmap can occur in this case, so these devices usually implement an unmap
handler, invoked by iommufd_access_notify_unmap().

The new HW queue object will need the same pin/unpin feature, although it
(unlike the mdev case) wants to reject any unmap attempt, during its life
cycle. Instead, it would not implement an unmap handler. Thus, bypass any
access->ops->unmap call when the access is marked as internal. Also error
out the internal case in iommufd_access_notify_unmap() to reject an unmap
operation and propagatethe errno upwards.

Suggested-by: Jason Gunthorpe <j...@nvidia.com>
Reviewed-by: Pranjal Shrivastava <pr...@google.com>
Reviewed-by: Jason Gunthorpe <j...@nvidia.com>
Reviewed-by: Lu Baolu <baolu...@linux.intel.com>
Signed-off-by: Nicolin Chen <nicol...@nvidia.com>
---
 drivers/iommu/iommufd/iommufd_private.h |  4 ++--
 drivers/iommu/iommufd/device.c          | 21 +++++++++++++++++----
 drivers/iommu/iommufd/io_pagetable.c    | 10 +++++++++-
 3 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/iommufd/iommufd_private.h 
b/drivers/iommu/iommufd/iommufd_private.h
index 9d1f55deb9ca..b849099e804b 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -111,8 +111,8 @@ int iopt_read_and_clear_dirty_data(struct io_pagetable 
*iopt,
 int iopt_set_dirty_tracking(struct io_pagetable *iopt,
                            struct iommu_domain *domain, bool enable);
 
-void iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
-                                unsigned long length);
+int iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
+                               unsigned long length);
 int iopt_table_add_domain(struct io_pagetable *iopt,
                          struct iommu_domain *domain);
 void iopt_table_remove_domain(struct io_pagetable *iopt,
diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 07a4ff753c12..8f078fda795a 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -1048,7 +1048,7 @@ static int iommufd_access_change_ioas(struct 
iommufd_access *access,
        }
 
        if (cur_ioas) {
-               if (access->ops->unmap) {
+               if (!iommufd_access_is_internal(access) && access->ops->unmap) {
                        mutex_unlock(&access->ioas_lock);
                        access->ops->unmap(access->data, 0, ULONG_MAX);
                        mutex_lock(&access->ioas_lock);
@@ -1245,15 +1245,24 @@ EXPORT_SYMBOL_NS_GPL(iommufd_access_replace, "IOMMUFD");
  * run in the future. Due to this a driver must not create locking that 
prevents
  * unmap to complete while iommufd_access_destroy() is running.
  */
-void iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
-                                unsigned long length)
+int iommufd_access_notify_unmap(struct io_pagetable *iopt, unsigned long iova,
+                               unsigned long length)
 {
        struct iommufd_ioas *ioas =
                container_of(iopt, struct iommufd_ioas, iopt);
        struct iommufd_access *access;
        unsigned long index;
+       int ret = 0;
 
        xa_lock(&ioas->iopt.access_list);
+       /* Bypass any unmap if there is an internal access */
+       xa_for_each(&ioas->iopt.access_list, index, access) {
+               if (iommufd_access_is_internal(access)) {
+                       ret = -EBUSY;
+                       goto unlock;
+               }
+       }
+
        xa_for_each(&ioas->iopt.access_list, index, access) {
                if (!iommufd_lock_obj(&access->obj))
                        continue;
@@ -1264,7 +1273,9 @@ void iommufd_access_notify_unmap(struct io_pagetable 
*iopt, unsigned long iova,
                iommufd_put_object(access->ictx, &access->obj);
                xa_lock(&ioas->iopt.access_list);
        }
+unlock:
        xa_unlock(&ioas->iopt.access_list);
+       return ret;
 }
 
 /**
@@ -1362,7 +1373,9 @@ int iommufd_access_pin_pages(struct iommufd_access 
*access, unsigned long iova,
 
        /* Driver's ops don't support pin_pages */
        if (IS_ENABLED(CONFIG_IOMMUFD_TEST) &&
-           WARN_ON(access->iova_alignment != PAGE_SIZE || !access->ops->unmap))
+           WARN_ON(access->iova_alignment != PAGE_SIZE ||
+                   (!iommufd_access_is_internal(access) &&
+                    !access->ops->unmap)))
                return -EINVAL;
 
        if (!length)
diff --git a/drivers/iommu/iommufd/io_pagetable.c 
b/drivers/iommu/iommufd/io_pagetable.c
index 22fc3a12109f..6b8477b1f94b 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -740,7 +740,15 @@ static int iopt_unmap_iova_range(struct io_pagetable 
*iopt, unsigned long start,
                        up_write(&iopt->iova_rwsem);
                        up_read(&iopt->domains_rwsem);
 
-                       iommufd_access_notify_unmap(iopt, area_first, length);
+                       rc = iommufd_access_notify_unmap(iopt, area_first,
+                                                        length);
+                       if (rc) {
+                               down_read(&iopt->domains_rwsem);
+                               down_write(&iopt->iova_rwsem);
+                               area->prevent_access = false;
+                               goto out_unlock_iova;
+                       }
+
                        /* Something is not responding to unmap requests. */
                        tries++;
                        if (WARN_ON(tries > 100)) {
-- 
2.43.0


Reply via email to