On 2022/4/13 22:49, Yi Liu wrote:
On 2022/4/13 22:36, Jason Gunthorpe wrote:
On Wed, Apr 13, 2022 at 10:02:58PM +0800, Yi Liu wrote:
+/**
+ * iopt_unmap_iova() - Remove a range of iova
+ * @iopt: io_pagetable to act on
+ * @iova: Starting iova to unmap
+ * @length: Number of bytes to unmap
+ *
+ * The requested range must exactly match an existing range.
+ * Splitting/truncating IOVA mappings is not allowed.
+ */
+int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova,
+            unsigned long length)
+{
+    struct iopt_pages *pages;
+    struct iopt_area *area;
+    unsigned long iova_end;
+    int rc;
+
+    if (!length)
+        return -EINVAL;
+
+    if (check_add_overflow(iova, length - 1, &iova_end))
+        return -EOVERFLOW;
+
+    down_read(&iopt->domains_rwsem);
+    down_write(&iopt->iova_rwsem);
+    area = iopt_find_exact_area(iopt, iova, iova_end);

when testing vIOMMU with Qemu using iommufd, I hit a problem as log #3
shows. Qemu failed when trying to do map due to an IOVA still in use.
After debugging, the 0xfffff000 IOVA is mapped but not unmapped. But per log
#2, Qemu has issued unmap with a larger range (0xff000000 -
0x100000000) which includes the 0xfffff000. But iopt_find_exact_area()
doesn't find any area. So 0xfffff000 is not unmapped. Is this correct? Same
test passed with vfio iommu type1 driver. any idea?

There are a couple of good reasons why the iopt_unmap_iova() should
proccess any contiguous range of fully contained areas, so I would
consider this something worth fixing. can you send a small patch and
test case and I'll fold it in?

sure. just spotted it, so haven't got fix patch yet. I may work on
it tomorrow.

Hi Jason,

Got below patch for it. Also pushed to the exploration branch.

https://github.com/luxis1999/iommufd/commit/d764f3288de0fd52c578684788a437701ec31b2d

From 22a758c401a1c7f6656625013bb87204c9ea65fe Mon Sep 17 00:00:00 2001
From: Yi Liu <[email protected]>
Date: Sun, 17 Apr 2022 07:39:03 -0700
Subject: [PATCH] iommufd/io_pagetable: Support unmap fully contained areas

Changes:
- return the unmapped bytes to caller
- supports unmap fully containerd contiguous areas
- add a test case in selftest

Signed-off-by: Yi Liu <[email protected]>
---
 drivers/iommu/iommufd/io_pagetable.c    | 90 ++++++++++++-------------
 drivers/iommu/iommufd/ioas.c            |  8 ++-
 drivers/iommu/iommufd/iommufd_private.h |  4 +-
 drivers/iommu/iommufd/vfio_compat.c     |  8 ++-
 include/uapi/linux/iommufd.h            |  2 +-
 tools/testing/selftests/iommu/iommufd.c | 40 +++++++++++
 6 files changed, 99 insertions(+), 53 deletions(-)

diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index f9f3b06946bf..5142f797a812 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -315,61 +315,26 @@ static int __iopt_unmap_iova(struct io_pagetable *iopt, struct iopt_area *area,
        return 0;
 }

-/**
- * iopt_unmap_iova() - Remove a range of iova
- * @iopt: io_pagetable to act on
- * @iova: Starting iova to unmap
- * @length: Number of bytes to unmap
- *
- * The requested range must exactly match an existing range.
- * Splitting/truncating IOVA mappings is not allowed.
- */
-int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova,
-                   unsigned long length)
-{
-       struct iopt_pages *pages;
-       struct iopt_area *area;
-       unsigned long iova_end;
-       int rc;
-
-       if (!length)
-               return -EINVAL;
-
-       if (check_add_overflow(iova, length - 1, &iova_end))
-               return -EOVERFLOW;
-
-       down_read(&iopt->domains_rwsem);
-       down_write(&iopt->iova_rwsem);
-       area = iopt_find_exact_area(iopt, iova, iova_end);
-       if (!area) {
-               up_write(&iopt->iova_rwsem);
-               up_read(&iopt->domains_rwsem);
-               return -ENOENT;
-       }
-       pages = area->pages;
-       area->pages = NULL;
-       up_write(&iopt->iova_rwsem);
-
-       rc = __iopt_unmap_iova(iopt, area, pages);
-       up_read(&iopt->domains_rwsem);
-       return rc;
-}
-
-int iopt_unmap_all(struct io_pagetable *iopt)
+static int __iopt_unmap_iova_range(struct io_pagetable *iopt,
+                                  unsigned long start,
+                                  unsigned long end,
+                                  unsigned long *unmapped)
 {
        struct iopt_area *area;
+       unsigned long unmapped_bytes = 0;
        int rc;

        down_read(&iopt->domains_rwsem);
        down_write(&iopt->iova_rwsem);
-       while ((area = iopt_area_iter_first(iopt, 0, ULONG_MAX))) {
+       while ((area = iopt_area_iter_first(iopt, start, end))) {
                struct iopt_pages *pages;

-               /* Userspace should not race unmap all and map */
-               if (!area->pages) {
-                       rc = -EBUSY;
+               if (!area->pages || iopt_area_iova(area) < start ||
+                   iopt_area_last_iova(area) > end) {
+                       rc = -ENOENT;
                        goto out_unlock_iova;
                }
+
                pages = area->pages;
                area->pages = NULL;
                up_write(&iopt->iova_rwsem);
@@ -378,6 +343,10 @@ int iopt_unmap_all(struct io_pagetable *iopt)
                if (rc)
                        goto out_unlock_domains;

+               start = iopt_area_last_iova(area) + 1;
+               unmapped_bytes +=
+                       iopt_area_last_iova(area) - iopt_area_iova(area) + 1;
+
                down_write(&iopt->iova_rwsem);
        }
        rc = 0;
@@ -386,9 +355,40 @@ int iopt_unmap_all(struct io_pagetable *iopt)
        up_write(&iopt->iova_rwsem);
 out_unlock_domains:
        up_read(&iopt->domains_rwsem);
+       if (unmapped)
+               *unmapped = unmapped_bytes;
        return rc;
 }

+/**
+ * iopt_unmap_iova() - Remove a range of iova
+ * @iopt: io_pagetable to act on
+ * @iova: Starting iova to unmap
+ * @length: Number of bytes to unmap
+ * @unmapped: Return number of bytes unmapped
+ *
+ * The requested range must exactly match an existing range.
+ * Splitting/truncating IOVA mappings is not allowed.
+ */
+int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova,
+                   unsigned long length, unsigned long *unmapped)
+{
+       unsigned long iova_end;
+
+       if (!length)
+               return -EINVAL;
+
+       if (check_add_overflow(iova, length - 1, &iova_end))
+               return -EOVERFLOW;
+
+       return __iopt_unmap_iova_range(iopt, iova, iova_end, unmapped);
+}
+
+int iopt_unmap_all(struct io_pagetable *iopt, unsigned long *unmapped)
+{
+       return __iopt_unmap_iova_range(iopt, 0, ULONG_MAX, unmapped);
+}
+
 /**
  * iopt_access_pages() - Return a list of pages under the iova
  * @iopt: io_pagetable to act on
diff --git a/drivers/iommu/iommufd/ioas.c b/drivers/iommu/iommufd/ioas.c
index 48149988c84b..4e701d053ed6 100644
--- a/drivers/iommu/iommufd/ioas.c
+++ b/drivers/iommu/iommufd/ioas.c
@@ -14,7 +14,7 @@ void iommufd_ioas_destroy(struct iommufd_object *obj)
        struct iommufd_ioas *ioas = container_of(obj, struct iommufd_ioas, obj);
        int rc;

-       rc = iopt_unmap_all(&ioas->iopt);
+       rc = iopt_unmap_all(&ioas->iopt, NULL);
        WARN_ON(rc);
        iopt_destroy_table(&ioas->iopt);
        mutex_destroy(&ioas->mutex);
@@ -230,6 +230,7 @@ int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd)
 {
        struct iommu_ioas_unmap *cmd = ucmd->cmd;
        struct iommufd_ioas *ioas;
+       unsigned long unmapped;
        int rc;

        ioas = iommufd_get_ioas(ucmd, cmd->ioas_id);
@@ -237,16 +238,17 @@ int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd)
                return PTR_ERR(ioas);

        if (cmd->iova == 0 && cmd->length == U64_MAX) {
-               rc = iopt_unmap_all(&ioas->iopt);
+               rc = iopt_unmap_all(&ioas->iopt, &unmapped);
        } else {
                if (cmd->iova >= ULONG_MAX || cmd->length >= ULONG_MAX) {
                        rc = -EOVERFLOW;
                        goto out_put;
                }
-               rc = iopt_unmap_iova(&ioas->iopt, cmd->iova, cmd->length);
+               rc = iopt_unmap_iova(&ioas->iopt, cmd->iova, cmd->length, 
&unmapped);
        }

 out_put:
        iommufd_put_object(&ioas->obj);
+       cmd->length = unmapped;
        return rc;
 }
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index f55654278ac4..382704f4d698 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -46,8 +46,8 @@ int iopt_map_pages(struct io_pagetable *iopt, struct iopt_pages *pages,
                   unsigned long *dst_iova, unsigned long start_byte,
                   unsigned long length, int iommu_prot, unsigned int flags);
 int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova,
-                   unsigned long length);
-int iopt_unmap_all(struct io_pagetable *iopt);
+                   unsigned long length, unsigned long *unmapped);
+int iopt_unmap_all(struct io_pagetable *iopt, unsigned long *unmapped);

 int iopt_access_pages(struct io_pagetable *iopt, unsigned long iova,
                      unsigned long npages, struct page **out_pages, bool 
write);
diff --git a/drivers/iommu/iommufd/vfio_compat.c b/drivers/iommu/iommufd/vfio_compat.c
index 5b196de00ff9..4539ff45efd9 100644
--- a/drivers/iommu/iommufd/vfio_compat.c
+++ b/drivers/iommu/iommufd/vfio_compat.c
@@ -133,6 +133,7 @@ static int iommufd_vfio_unmap_dma(struct iommufd_ctx *ictx, unsigned int cmd,
        u32 supported_flags = VFIO_DMA_UNMAP_FLAG_ALL;
        struct vfio_iommu_type1_dma_unmap unmap;
        struct iommufd_ioas *ioas;
+       unsigned long unmapped;
        int rc;

        if (copy_from_user(&unmap, arg, minsz))
@@ -146,10 +147,13 @@ static int iommufd_vfio_unmap_dma(struct iommufd_ctx *ictx, unsigned int cmd,
                return PTR_ERR(ioas);

        if (unmap.flags & VFIO_DMA_UNMAP_FLAG_ALL)
-               rc = iopt_unmap_all(&ioas->iopt);
+               rc = iopt_unmap_all(&ioas->iopt, &unmapped);
        else
-               rc = iopt_unmap_iova(&ioas->iopt, unmap.iova, unmap.size);
+               rc = iopt_unmap_iova(&ioas->iopt, unmap.iova,
+                                    unmap.size, &unmapped);
        iommufd_put_object(&ioas->obj);
+       unmap.size = unmapped;
+
        return rc;
 }

diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 2c0f5ced4173..8cbc6a083156 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -172,7 +172,7 @@ struct iommu_ioas_copy {
  * @size: sizeof(struct iommu_ioas_copy)
  * @ioas_id: IOAS ID to change the mapping of
  * @iova: IOVA to start the unmapping at
- * @length: Number of bytes to unmap
+ * @length: Number of bytes to unmap, and return back the bytes unmapped
  *
  * Unmap an IOVA range. The iova/length must exactly match a range
  * used with IOMMU_IOAS_PAGETABLE_MAP, or be the values 0 & U64_MAX.
diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c
index 5c47d706ed94..42956acd2c04 100644
--- a/tools/testing/selftests/iommu/iommufd.c
+++ b/tools/testing/selftests/iommu/iommufd.c
@@ -357,6 +357,47 @@ TEST_F(iommufd_ioas, area)
        ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_UNMAP, &unmap_cmd));
 }

+TEST_F(iommufd_ioas, unmap_fully_contained_area)
+{
+       struct iommu_ioas_map map_cmd = {
+               .size = sizeof(map_cmd),
+               .ioas_id = self->ioas_id,
+               .flags = IOMMU_IOAS_MAP_FIXED_IOVA,
+               .length = PAGE_SIZE,
+               .user_va = (uintptr_t)buffer,
+       };
+       struct iommu_ioas_unmap unmap_cmd = {
+               .size = sizeof(unmap_cmd),
+               .ioas_id = self->ioas_id,
+               .length = PAGE_SIZE,
+       };
+       int i;
+
+       for (i = 0; i != 4; i++) {
+               map_cmd.iova = self->base_iova + i * 16 * PAGE_SIZE;
+               map_cmd.length = 8 * PAGE_SIZE;
+               ASSERT_EQ(0,
+                         ioctl(self->fd, IOMMU_IOAS_MAP, &map_cmd));
+       }
+
+       /* Unmap not fully contained area doesn't work */
+       unmap_cmd.iova = self->base_iova - 4 * PAGE_SIZE;
+       unmap_cmd.length = 8 * PAGE_SIZE;
+       ASSERT_EQ(ENOENT,
+                 ioctl(self->fd, IOMMU_IOAS_UNMAP, &unmap_cmd));
+
+ unmap_cmd.iova = self->base_iova + 3 * 16 * PAGE_SIZE + 8 * PAGE_SIZE - 4 * PAGE_SIZE;
+       unmap_cmd.length = 8 * PAGE_SIZE;
+       ASSERT_EQ(ENOENT,
+                 ioctl(self->fd, IOMMU_IOAS_UNMAP, &unmap_cmd));
+
+       /* Unmap fully contained areas works */
+       unmap_cmd.iova = self->base_iova - 4 * PAGE_SIZE;
+       unmap_cmd.length = 3 * 16 * PAGE_SIZE + 8 * PAGE_SIZE + 4 * PAGE_SIZE;
+       ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_UNMAP, &unmap_cmd));
+       ASSERT_EQ(32, unmap_cmd.length);
+}
+
 TEST_F(iommufd_ioas, area_auto_iova)
 {
        struct iommu_test_cmd test_cmd = {
--
2.27.0

--
Regards,
Yi Liu
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu

Reply via email to