From: Fred Griffoul <[email protected]>

A test of memory that can be taken back or replaced does not know in
advance which frame a device reaches. IOMMU_TEST_OP_MD_CHECK_MAP only
compares against a known buffer.

Add MD_CHECK_MAPPED, which checks that a range is fully mapped or fully
unmapped, and MD_IOVA_TO_PHYS, which returns the frame behind an IOVA,
or 0 if it is unmapped. Both refuse an IOVA outside the domain's
aperture.

Both hold domains_rwsem for writing so that a userspace unmap cannot
free a page-table level during the walk.

Signed-off-by: Fred Griffoul <[email protected]>
---
 drivers/iommu/iommufd/iommufd_test.h |  16 ++++
 drivers/iommu/iommufd/selftest.c     | 109 +++++++++++++++++++++++++++
 2 files changed, 125 insertions(+)

diff --git a/drivers/iommu/iommufd/iommufd_test.h 
b/drivers/iommu/iommufd/iommufd_test.h
index 52b78cbcc920..28fd9c43edc4 100644
--- a/drivers/iommu/iommufd/iommufd_test.h
+++ b/drivers/iommu/iommufd/iommufd_test.h
@@ -31,6 +31,8 @@ enum {
        IOMMU_TEST_OP_PASID_CHECK_HWPT,
        IOMMU_TEST_OP_DMABUF_GET,
        IOMMU_TEST_OP_DMABUF_REVOKE,
+       IOMMU_TEST_OP_MD_CHECK_MAPPED,
+       IOMMU_TEST_OP_MD_IOVA_TO_PHYS,
 };
 
 enum {
@@ -193,6 +195,20 @@ struct iommu_test_cmd {
                        __s32 dmabuf_fd;
                        __u32 revoked;
                } dmabuf_revoke;
+               struct {
+                       /*
+                        * 1: every page in [iova, iova+length) must be mapped;
+                        * 0: none of them may be. Mixed is an error.
+                        */
+                       __u32 mapped;
+                       __u32 __reserved;
+                       __aligned_u64 iova;
+                       __aligned_u64 length;
+               } check_mapped;
+               struct {
+                       __aligned_u64 iova;
+                       __aligned_u64 out_phys; /* 0 if unmapped */
+               } iova_to_phys;
        };
        __u32 last;
 };
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..1f5cd2d00fda 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -2031,6 +2031,107 @@ static int iommufd_test_dmabuf_get(struct iommufd_ucmd 
*ucmd,
        return rc;
 }
 
+/*
+ * True if [iova, iova + length) lies inside the domain's aperture.  Outside
+ * it the page table returns an error code from iova_to_phys(), not 0.
+ */
+static bool mock_domain_covers(struct mock_iommu_domain *mock,
+                              unsigned long iova, size_t length)
+{
+       struct iommu_domain_geometry *geo = &mock->domain.geometry;
+       unsigned long last;
+
+       if (check_add_overflow(iova, length - 1, &last))
+               return false;
+       return iova >= geo->aperture_start && last <= geo->aperture_end;
+}
+
+/*
+ * iova_to_phys() walks the page table, so it must not run while an unmap
+ * frees a level of it.  A userspace unmap holds the IOAS domains_rwsem for
+ * reading while it unmaps the domains, so holding it for writing keeps such
+ * unmaps away.  A memory provider or dma-buf revoke unmaps under its pages
+ * mutex only, so tests must not run these queries while a revoke of the
+ * range is in progress.
+ */
+static struct rw_semaphore *
+mock_domain_unmap_lock(struct iommufd_hw_pagetable *hwpt)
+{
+       return &to_hwpt_paging(hwpt)->ioas->iopt.domains_rwsem;
+}
+
+/*
+ * Report the physical address the mock domain resolves @iova to, or 0 if
+ * it is unmapped.  Lets a test check that two IOVAs share one frame, or that
+ * an IOVA moved to another frame, without knowing the frames in advance.
+ */
+static int iommufd_test_md_iova_to_phys(struct iommufd_ucmd *ucmd,
+                                       unsigned int mockpt_id,
+                                       unsigned long iova)
+{
+       struct iommu_test_cmd *cmd = ucmd->cmd;
+       struct iommufd_hw_pagetable *hwpt;
+       struct mock_iommu_domain *mock;
+       unsigned int page_size;
+       int rc;
+
+       hwpt = get_md_pagetable(ucmd, mockpt_id, &mock);
+       if (IS_ERR(hwpt))
+               return PTR_ERR(hwpt);
+
+       page_size = 1 << __ffs(mock->domain.pgsize_bitmap);
+       if (iova % page_size || !mock_domain_covers(mock, iova, page_size)) {
+               rc = -EINVAL;
+               goto out_put;
+       }
+       down_write(mock_domain_unmap_lock(hwpt));
+       cmd->iova_to_phys.out_phys =
+               mock->domain.ops->iova_to_phys(&mock->domain, iova);
+       up_write(mock_domain_unmap_lock(hwpt));
+       rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+out_put:
+       iommufd_put_object(ucmd->ictx, &hwpt->obj);
+       return rc;
+}
+
+static int iommufd_test_md_check_mapped(struct iommufd_ucmd *ucmd,
+                                       unsigned int mockpt_id,
+                                       unsigned long iova, size_t length,
+                                       bool mapped)
+{
+       struct iommufd_hw_pagetable *hwpt;
+       struct mock_iommu_domain *mock;
+       unsigned int page_size;
+       int rc = 0;
+
+       hwpt = get_md_pagetable(ucmd, mockpt_id, &mock);
+       if (IS_ERR(hwpt))
+               return PTR_ERR(hwpt);
+
+       page_size = 1 << __ffs(mock->domain.pgsize_bitmap);
+       if (iova % page_size || length % page_size || !length ||
+           !mock_domain_covers(mock, iova, length)) {
+               rc = -EINVAL;
+               goto out_put;
+       }
+
+       down_write(mock_domain_unmap_lock(hwpt));
+       for (; length; length -= page_size, iova += page_size) {
+               bool is_mapped =
+                       mock->domain.ops->iova_to_phys(&mock->domain, iova) != 
0;
+
+               if (is_mapped != mapped) {
+                       rc = -ENOENT;
+                       break;
+               }
+       }
+       up_write(mock_domain_unmap_lock(hwpt));
+
+out_put:
+       iommufd_put_object(ucmd->ictx, &hwpt->obj);
+       return rc;
+}
+
 static int iommufd_test_dmabuf_revoke(struct iommufd_ucmd *ucmd, int fd,
                                      bool revoked)
 {
@@ -2143,6 +2244,14 @@ int iommufd_test(struct iommufd_ucmd *ucmd)
                return iommufd_test_dmabuf_revoke(ucmd,
                                                  cmd->dmabuf_revoke.dmabuf_fd,
                                                  cmd->dmabuf_revoke.revoked);
+       case IOMMU_TEST_OP_MD_CHECK_MAPPED:
+               return iommufd_test_md_check_mapped(ucmd, cmd->id,
+                                                   cmd->check_mapped.iova,
+                                                   cmd->check_mapped.length,
+                                                   cmd->check_mapped.mapped);
+       case IOMMU_TEST_OP_MD_IOVA_TO_PHYS:
+               return iommufd_test_md_iova_to_phys(ucmd, cmd->id,
+                                                   cmd->iova_to_phys.iova);
        default:
                return -EOPNOTSUPP;
        }

Reply via email to