Improve the mock DMA-buf to have multiple physical ranges and add a method to compare the values loaded into the iommu_domain with the allocated page array.
Signed-off-by: Jason Gunthorpe jgg@nvidia.com --- drivers/iommu/iommufd/iommufd_test.h | 7 ++ drivers/iommu/iommufd/selftest.c | 107 +++++++++++++++--- tools/testing/selftests/iommu/iommufd.c | 43 +++++++ tools/testing/selftests/iommu/iommufd_utils.h | 17 +++ 4 files changed, 160 insertions(+), 14 deletions(-)
diff --git a/drivers/iommu/iommufd/iommufd_test.h b/drivers/iommu/iommufd/iommufd_test.h index 73e73e1ec15837..dae7d808b7bade 100644 --- a/drivers/iommu/iommufd/iommufd_test.h +++ b/drivers/iommu/iommufd/iommufd_test.h @@ -31,6 +31,7 @@ enum { IOMMU_TEST_OP_PASID_CHECK_HWPT, IOMMU_TEST_OP_DMABUF_GET, IOMMU_TEST_OP_DMABUF_REVOKE, + IOMMU_TEST_OP_MD_CHECK_DMABUF, };
enum { @@ -194,6 +195,12 @@ struct iommu_test_cmd { __s32 dmabuf_fd; __u32 revoked; } dmabuf_revoke; + struct { + __s32 dmabuf_fd; + __aligned_u64 iova; + __aligned_u64 length; + __aligned_u64 offset; + } check_dmabuf; }; __u32 last; }; diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c index 06820a50d5d24c..e924281840a07e 100644 --- a/drivers/iommu/iommufd/selftest.c +++ b/drivers/iommu/iommufd/selftest.c @@ -1957,16 +1957,19 @@ void iommufd_selftest_destroy(struct iommufd_object *obj) }
struct iommufd_test_dma_buf { - void *memory; size_t length; + unsigned int npages; bool revoked; + struct page *pages[] __counted_by(npages); };
static void iommufd_test_dma_buf_release(struct dma_buf *dmabuf) { struct iommufd_test_dma_buf *priv = dmabuf->priv; + unsigned int i;
- kfree(priv->memory); + for (i = 0; i < priv->npages; i++) + __free_page(priv->pages[i]); kfree(priv); }
@@ -1981,19 +1984,22 @@ iommufd_dma_pal_map_phys(struct dma_buf_attachment *attachment) if (priv->revoked) return ERR_PTR(-ENODEV);
- phys = kvmalloc(struct_size(phys, phys, 1), GFP_KERNEL); + phys = kvmalloc(struct_size(phys, phys, priv->npages), GFP_KERNEL); if (!phys) return ERR_PTR(-ENOMEM);
- phys->length = 1; - phys->phys[0].paddr = virt_to_phys(priv->memory); - phys->phys[0].len = priv->length; + phys->length = priv->npages; + for (unsigned int i = 0; i < priv->npages; i++) { + phys->phys[i].paddr = page_to_phys(priv->pages[i]); + phys->phys[i].len = PAGE_SIZE; + } return phys; }
static void iommufd_dma_pal_unmap_phys(struct dma_buf_attachment *attach, struct dma_buf_phys_list *phys) { + kfree(phys); }
static const struct dma_buf_mapping_pal_exp_ops iommufd_test_dma_buf_pal_ops = { @@ -2022,21 +2028,27 @@ static int iommufd_test_dmabuf_get(struct iommufd_ucmd *ucmd, DEFINE_DMA_BUF_EXPORT_INFO(exp_info); struct iommufd_test_dma_buf *priv; struct dma_buf *dmabuf; + size_t i; int rc;
- len = ALIGN(len, PAGE_SIZE); - if (len == 0 || len > PAGE_SIZE * 512) + unsigned int npages; + + if (len == 0 || len % PAGE_SIZE || len > PAGE_SIZE * 512) return -EINVAL;
- priv = kzalloc(sizeof(*priv), GFP_KERNEL); + npages = len >> PAGE_SHIFT; + priv = kzalloc(struct_size(priv, pages, npages), GFP_KERNEL); if (!priv) return -ENOMEM;
priv->length = len; - priv->memory = kzalloc(len, GFP_KERNEL); - if (!priv->memory) { - rc = -ENOMEM; - goto err_free; + priv->npages = npages; + for (i = 0; i < npages; i++) { + priv->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); + if (!priv->pages[i]) { + rc = -ENOMEM; + goto err_free; + } }
exp_info.ops = &iommufd_test_dmabuf_ops; @@ -2053,7 +2065,11 @@ static int iommufd_test_dmabuf_get(struct iommufd_ucmd *ucmd, return dma_buf_fd(dmabuf, open_flags);
err_free: - kfree(priv->memory); + for (unsigned int i = 0; i < npages; i++) { + if (!priv->pages[i]) + break; + __free_page(priv->pages[i]); + } kfree(priv); return rc; } @@ -2085,6 +2101,64 @@ static int iommufd_test_dmabuf_revoke(struct iommufd_ucmd *ucmd, int fd, return rc; }
+static int iommufd_test_md_check_dmabuf(struct iommufd_ucmd *ucmd, + unsigned int mockpt_id, int dmabuf_fd, + unsigned long iova, size_t length, + unsigned long offset) +{ + struct iommufd_hw_pagetable *hwpt; + struct iommufd_test_dma_buf *priv; + struct mock_iommu_domain *mock; + struct dma_buf *dmabuf; + unsigned int page_size; + unsigned long end; + size_t i; + int rc; + + hwpt = get_md_pagetable(ucmd, mockpt_id, &mock); + if (IS_ERR(hwpt)) + return PTR_ERR(hwpt); + + dmabuf = dma_buf_get(dmabuf_fd); + if (IS_ERR(dmabuf)) { + rc = PTR_ERR(dmabuf); + goto out_put_hwpt; + } + + if (dmabuf->ops != &iommufd_test_dmabuf_ops) { + rc = -EINVAL; + goto out_put_dmabuf; + } + + priv = dmabuf->priv; + page_size = 1 << __ffs(mock->domain.pgsize_bitmap); + if (iova % page_size || length % page_size || offset % page_size || + check_add_overflow(offset, length, &end) || end > priv->length) { + rc = -EINVAL; + goto out_put_dmabuf; + } + + for (i = 0; i < length; i += page_size) { + phys_addr_t expected = + page_to_phys(priv->pages[(offset + i) / PAGE_SIZE]) + + ((offset + i) % PAGE_SIZE); + phys_addr_t io_phys = + mock->domain.ops->iova_to_phys(&mock->domain, iova + i); + + if (io_phys != expected) { + rc = -EINVAL; + goto out_put_dmabuf; + } + } + rc = 0; + +out_put_dmabuf: + dma_buf_put(dmabuf); +out_put_hwpt: + iommufd_put_object(ucmd->ictx, &hwpt->obj); + return rc; +} + int iommufd_test(struct iommufd_ucmd *ucmd) { struct iommu_test_cmd *cmd = ucmd->cmd; @@ -2170,6 +2244,11 @@ 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_DMABUF: + return iommufd_test_md_check_dmabuf( + ucmd, cmd->id, cmd->check_dmabuf.dmabuf_fd, + cmd->check_dmabuf.iova, cmd->check_dmabuf.length, + cmd->check_dmabuf.offset); default: return -EOPNOTSUPP; } diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c index dadad277f4eb2e..2673f9f153392f 100644 --- a/tools/testing/selftests/iommu/iommufd.c +++ b/tools/testing/selftests/iommu/iommufd.c @@ -1580,10 +1580,53 @@ TEST_F(iommufd_ioas, dmabuf_simple) test_err_ioctl_ioas_map_file(EINVAL, dfd, buf_size, buf_size, &iova); test_err_ioctl_ioas_map_file(EINVAL, dfd, 0, buf_size + 1, &iova); test_ioctl_ioas_map_file(dfd, 0, buf_size, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, buf_size, 0);
close(dfd); }
+TEST_F(iommufd_ioas, dmabuf_multi_page) +{ + __u64 iova; + int dfd; + + /* Single page */ + test_cmd_get_dmabuf(PAGE_SIZE, &dfd); + test_ioctl_ioas_map_file(dfd, 0, PAGE_SIZE, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, PAGE_SIZE, 0); + close(dfd); + + /* Many pages - exercises batch filling across multiple phys entries */ + test_cmd_get_dmabuf(PAGE_SIZE * 64, &dfd); + test_ioctl_ioas_map_file(dfd, 0, PAGE_SIZE * 64, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, PAGE_SIZE * 64, + 0); + close(dfd); + + /* Sub-range from the middle - exercises seeking into the phys array */ + test_cmd_get_dmabuf(PAGE_SIZE * 16, &dfd); + test_ioctl_ioas_map_file(dfd, PAGE_SIZE * 4, PAGE_SIZE * 8, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, PAGE_SIZE * 8, + PAGE_SIZE * 4); + close(dfd); + + /* Multiple sub-ranges from the same dmabuf */ + test_cmd_get_dmabuf(PAGE_SIZE * 16, &dfd); + test_ioctl_ioas_map_file(dfd, 0, PAGE_SIZE * 4, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, PAGE_SIZE * 4, + 0); + test_ioctl_ioas_map_file(dfd, PAGE_SIZE * 8, PAGE_SIZE * 4, &iova); + if (variant->mock_domains) + test_cmd_check_dmabuf(self->hwpt_id, dfd, iova, PAGE_SIZE * 4, + PAGE_SIZE * 8); + close(dfd); +} + TEST_F(iommufd_ioas, dmabuf_revoke) { size_t buf_size = PAGE_SIZE*4; diff --git a/tools/testing/selftests/iommu/iommufd_utils.h b/tools/testing/selftests/iommu/iommufd_utils.h index 5502751d500c89..35fd91d354f998 100644 --- a/tools/testing/selftests/iommu/iommufd_utils.h +++ b/tools/testing/selftests/iommu/iommufd_utils.h @@ -593,6 +593,23 @@ static int _test_cmd_revoke_dmabuf(int fd, int dmabuf_fd, bool revoked) #define test_cmd_revoke_dmabuf(dmabuf_fd, revoke) \ ASSERT_EQ(0, _test_cmd_revoke_dmabuf(self->fd, dmabuf_fd, revoke))
+#define test_cmd_check_dmabuf(_hwpt_id, _dmabuf_fd, _iova, _length, _offset) \ + ({ \ + struct iommu_test_cmd check_cmd = { \ + .size = sizeof(check_cmd), \ + .op = IOMMU_TEST_OP_MD_CHECK_DMABUF, \ + .id = _hwpt_id, \ + .check_dmabuf = { .dmabuf_fd = _dmabuf_fd, \ + .iova = _iova, \ + .length = _length, \ + .offset = _offset }, \ + }; \ + ASSERT_EQ(0, ioctl(self->fd, \ + _IOMMU_TEST_CMD( \ + IOMMU_TEST_OP_MD_CHECK_DMABUF), \ + &check_cmd)); \ + }) + static int _test_ioctl_destroy(int fd, unsigned int id) { struct iommu_destroy cmd = {