mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-12 00:34:43 +08:00
65c619ae06
iommufd wants to use more infrastructure, like the iommu_group, that the mock device does not support. Create a more complete mock device that can go through the whole cycle of ownership, blocking domain, and has an iommu_group. This requires creating a real struct device on a real bus to be able to connect it to a iommu_group. Unfortunately we cannot formally attach the mock iommu driver as an actual driver as the iommu core does not allow more than one driver or provide a general way for busses to link to iommus. This can be solved with a little hack to open code the dev_iommus struct. With this infrastructure things work exactly the same as the normal domain path, including the auto domains mechanism and direct attach of hwpts. As the created hwpt is now an autodomain it is no longer required to destroy it and trying to do so will trigger a failure. Link: https://lore.kernel.org/r/11-v3-ae9c2975a131+2e1e8-iommufd_hwpt_jgg@nvidia.com Reviewed-by: Kevin Tian <kevin.tian@intel.com> Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
1653 lines
45 KiB
C
1653 lines
45 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES */
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/eventfd.h>
|
|
|
|
#define __EXPORTED_HEADERS__
|
|
#include <linux/vfio.h>
|
|
|
|
#include "iommufd_utils.h"
|
|
|
|
static void *buffer;
|
|
|
|
static unsigned long PAGE_SIZE;
|
|
static unsigned long HUGEPAGE_SIZE;
|
|
|
|
#define MOCK_PAGE_SIZE (PAGE_SIZE / 2)
|
|
|
|
static unsigned long get_huge_page_size(void)
|
|
{
|
|
char buf[80];
|
|
int ret;
|
|
int fd;
|
|
|
|
fd = open("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size",
|
|
O_RDONLY);
|
|
if (fd < 0)
|
|
return 2 * 1024 * 1024;
|
|
|
|
ret = read(fd, buf, sizeof(buf));
|
|
close(fd);
|
|
if (ret <= 0 || ret == sizeof(buf))
|
|
return 2 * 1024 * 1024;
|
|
buf[ret] = 0;
|
|
return strtoul(buf, NULL, 10);
|
|
}
|
|
|
|
static __attribute__((constructor)) void setup_sizes(void)
|
|
{
|
|
void *vrc;
|
|
int rc;
|
|
|
|
PAGE_SIZE = sysconf(_SC_PAGE_SIZE);
|
|
HUGEPAGE_SIZE = get_huge_page_size();
|
|
|
|
BUFFER_SIZE = PAGE_SIZE * 16;
|
|
rc = posix_memalign(&buffer, HUGEPAGE_SIZE, BUFFER_SIZE);
|
|
assert(!rc);
|
|
assert(buffer);
|
|
assert((uintptr_t)buffer % HUGEPAGE_SIZE == 0);
|
|
vrc = mmap(buffer, BUFFER_SIZE, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
|
|
assert(vrc == buffer);
|
|
}
|
|
|
|
FIXTURE(iommufd)
|
|
{
|
|
int fd;
|
|
};
|
|
|
|
FIXTURE_SETUP(iommufd)
|
|
{
|
|
self->fd = open("/dev/iommu", O_RDWR);
|
|
ASSERT_NE(-1, self->fd);
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(iommufd)
|
|
{
|
|
teardown_iommufd(self->fd, _metadata);
|
|
}
|
|
|
|
TEST_F(iommufd, simple_close)
|
|
{
|
|
}
|
|
|
|
TEST_F(iommufd, cmd_fail)
|
|
{
|
|
struct iommu_destroy cmd = { .size = sizeof(cmd), .id = 0 };
|
|
|
|
/* object id is invalid */
|
|
EXPECT_ERRNO(ENOENT, _test_ioctl_destroy(self->fd, 0));
|
|
/* Bad pointer */
|
|
EXPECT_ERRNO(EFAULT, ioctl(self->fd, IOMMU_DESTROY, NULL));
|
|
/* Unknown ioctl */
|
|
EXPECT_ERRNO(ENOTTY,
|
|
ioctl(self->fd, _IO(IOMMUFD_TYPE, IOMMUFD_CMD_BASE - 1),
|
|
&cmd));
|
|
}
|
|
|
|
TEST_F(iommufd, cmd_length)
|
|
{
|
|
#define TEST_LENGTH(_struct, _ioctl) \
|
|
{ \
|
|
struct { \
|
|
struct _struct cmd; \
|
|
uint8_t extra; \
|
|
} cmd = { .cmd = { .size = sizeof(struct _struct) - 1 }, \
|
|
.extra = UINT8_MAX }; \
|
|
int old_errno; \
|
|
int rc; \
|
|
\
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, _ioctl, &cmd)); \
|
|
cmd.cmd.size = sizeof(struct _struct) + 1; \
|
|
EXPECT_ERRNO(E2BIG, ioctl(self->fd, _ioctl, &cmd)); \
|
|
cmd.cmd.size = sizeof(struct _struct); \
|
|
rc = ioctl(self->fd, _ioctl, &cmd); \
|
|
old_errno = errno; \
|
|
cmd.cmd.size = sizeof(struct _struct) + 1; \
|
|
cmd.extra = 0; \
|
|
if (rc) { \
|
|
EXPECT_ERRNO(old_errno, \
|
|
ioctl(self->fd, _ioctl, &cmd)); \
|
|
} else { \
|
|
ASSERT_EQ(0, ioctl(self->fd, _ioctl, &cmd)); \
|
|
} \
|
|
}
|
|
|
|
TEST_LENGTH(iommu_destroy, IOMMU_DESTROY);
|
|
TEST_LENGTH(iommu_ioas_alloc, IOMMU_IOAS_ALLOC);
|
|
TEST_LENGTH(iommu_ioas_iova_ranges, IOMMU_IOAS_IOVA_RANGES);
|
|
TEST_LENGTH(iommu_ioas_allow_iovas, IOMMU_IOAS_ALLOW_IOVAS);
|
|
TEST_LENGTH(iommu_ioas_map, IOMMU_IOAS_MAP);
|
|
TEST_LENGTH(iommu_ioas_copy, IOMMU_IOAS_COPY);
|
|
TEST_LENGTH(iommu_ioas_unmap, IOMMU_IOAS_UNMAP);
|
|
TEST_LENGTH(iommu_option, IOMMU_OPTION);
|
|
TEST_LENGTH(iommu_vfio_ioas, IOMMU_VFIO_IOAS);
|
|
#undef TEST_LENGTH
|
|
}
|
|
|
|
TEST_F(iommufd, cmd_ex_fail)
|
|
{
|
|
struct {
|
|
struct iommu_destroy cmd;
|
|
__u64 future;
|
|
} cmd = { .cmd = { .size = sizeof(cmd), .id = 0 } };
|
|
|
|
/* object id is invalid and command is longer */
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_DESTROY, &cmd));
|
|
/* future area is non-zero */
|
|
cmd.future = 1;
|
|
EXPECT_ERRNO(E2BIG, ioctl(self->fd, IOMMU_DESTROY, &cmd));
|
|
/* Original command "works" */
|
|
cmd.cmd.size = sizeof(cmd.cmd);
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_DESTROY, &cmd));
|
|
/* Short command fails */
|
|
cmd.cmd.size = sizeof(cmd.cmd) - 1;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, IOMMU_DESTROY, &cmd));
|
|
}
|
|
|
|
TEST_F(iommufd, global_options)
|
|
{
|
|
struct iommu_option cmd = {
|
|
.size = sizeof(cmd),
|
|
.option_id = IOMMU_OPTION_RLIMIT_MODE,
|
|
.op = IOMMU_OPTION_OP_GET,
|
|
.val64 = 1,
|
|
};
|
|
|
|
cmd.option_id = IOMMU_OPTION_RLIMIT_MODE;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
ASSERT_EQ(0, cmd.val64);
|
|
|
|
/* This requires root */
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
cmd.val64 = 1;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
cmd.val64 = 2;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
cmd.op = IOMMU_OPTION_OP_GET;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
ASSERT_EQ(1, cmd.val64);
|
|
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
cmd.val64 = 0;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
cmd.op = IOMMU_OPTION_OP_GET;
|
|
cmd.option_id = IOMMU_OPTION_HUGE_PAGES;
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
}
|
|
|
|
FIXTURE(iommufd_ioas)
|
|
{
|
|
int fd;
|
|
uint32_t ioas_id;
|
|
uint32_t stdev_id;
|
|
uint64_t base_iova;
|
|
};
|
|
|
|
FIXTURE_VARIANT(iommufd_ioas)
|
|
{
|
|
unsigned int mock_domains;
|
|
unsigned int memory_limit;
|
|
};
|
|
|
|
FIXTURE_SETUP(iommufd_ioas)
|
|
{
|
|
unsigned int i;
|
|
|
|
|
|
self->fd = open("/dev/iommu", O_RDWR);
|
|
ASSERT_NE(-1, self->fd);
|
|
test_ioctl_ioas_alloc(&self->ioas_id);
|
|
|
|
if (!variant->memory_limit) {
|
|
test_ioctl_set_default_memory_limit();
|
|
} else {
|
|
test_ioctl_set_temp_memory_limit(variant->memory_limit);
|
|
}
|
|
|
|
for (i = 0; i != variant->mock_domains; i++) {
|
|
test_cmd_mock_domain(self->ioas_id, &self->stdev_id, NULL);
|
|
self->base_iova = MOCK_APERTURE_START;
|
|
}
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(iommufd_ioas)
|
|
{
|
|
test_ioctl_set_default_memory_limit();
|
|
teardown_iommufd(self->fd, _metadata);
|
|
}
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_ioas, no_domain)
|
|
{
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_ioas, mock_domain)
|
|
{
|
|
.mock_domains = 1,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_ioas, two_mock_domain)
|
|
{
|
|
.mock_domains = 2,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_ioas, mock_domain_limit)
|
|
{
|
|
.mock_domains = 1,
|
|
.memory_limit = 16,
|
|
};
|
|
|
|
TEST_F(iommufd_ioas, ioas_auto_destroy)
|
|
{
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_destroy)
|
|
{
|
|
if (self->stdev_id) {
|
|
/* IOAS cannot be freed while a device has a HWPT using it */
|
|
EXPECT_ERRNO(EBUSY,
|
|
_test_ioctl_destroy(self->fd, self->ioas_id));
|
|
} else {
|
|
/* Can allocate and manually free an IOAS table */
|
|
test_ioctl_destroy(self->ioas_id);
|
|
}
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_area_destroy)
|
|
{
|
|
/* Adding an area does not change ability to destroy */
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE, self->base_iova);
|
|
if (self->stdev_id)
|
|
EXPECT_ERRNO(EBUSY,
|
|
_test_ioctl_destroy(self->fd, self->ioas_id));
|
|
else
|
|
test_ioctl_destroy(self->ioas_id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_area_auto_destroy)
|
|
{
|
|
int i;
|
|
|
|
/* Can allocate and automatically free an IOAS table with many areas */
|
|
for (i = 0; i != 10; i++) {
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE,
|
|
self->base_iova + i * PAGE_SIZE);
|
|
}
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, area)
|
|
{
|
|
int i;
|
|
|
|
/* Unmap fails if nothing is mapped */
|
|
for (i = 0; i != 10; i++)
|
|
test_err_ioctl_ioas_unmap(ENOENT, i * PAGE_SIZE, PAGE_SIZE);
|
|
|
|
/* Unmap works */
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE,
|
|
self->base_iova + i * PAGE_SIZE);
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_unmap(self->base_iova + i * PAGE_SIZE,
|
|
PAGE_SIZE);
|
|
|
|
/* Split fails */
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE * 2,
|
|
self->base_iova + 16 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_unmap(ENOENT, self->base_iova + 16 * PAGE_SIZE,
|
|
PAGE_SIZE);
|
|
test_err_ioctl_ioas_unmap(ENOENT, self->base_iova + 17 * PAGE_SIZE,
|
|
PAGE_SIZE);
|
|
|
|
/* Over map fails */
|
|
test_err_ioctl_ioas_map_fixed(EEXIST, buffer, PAGE_SIZE * 2,
|
|
self->base_iova + 16 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_map_fixed(EEXIST, buffer, PAGE_SIZE,
|
|
self->base_iova + 16 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_map_fixed(EEXIST, buffer, PAGE_SIZE,
|
|
self->base_iova + 17 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_map_fixed(EEXIST, buffer, PAGE_SIZE * 2,
|
|
self->base_iova + 15 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_map_fixed(EEXIST, buffer, PAGE_SIZE * 3,
|
|
self->base_iova + 15 * PAGE_SIZE);
|
|
|
|
/* unmap all works */
|
|
test_ioctl_ioas_unmap(0, UINT64_MAX);
|
|
|
|
/* Unmap all succeeds on an empty IOAS */
|
|
test_ioctl_ioas_unmap(0, UINT64_MAX);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, unmap_fully_contained_areas)
|
|
{
|
|
uint64_t unmap_len;
|
|
int i;
|
|
|
|
/* Give no_domain some space to rewind base_iova */
|
|
self->base_iova += 4 * PAGE_SIZE;
|
|
|
|
for (i = 0; i != 4; i++)
|
|
test_ioctl_ioas_map_fixed(buffer, 8 * PAGE_SIZE,
|
|
self->base_iova + i * 16 * PAGE_SIZE);
|
|
|
|
/* Unmap not fully contained area doesn't work */
|
|
test_err_ioctl_ioas_unmap(ENOENT, self->base_iova - 4 * PAGE_SIZE,
|
|
8 * PAGE_SIZE);
|
|
test_err_ioctl_ioas_unmap(ENOENT,
|
|
self->base_iova + 3 * 16 * PAGE_SIZE +
|
|
8 * PAGE_SIZE - 4 * PAGE_SIZE,
|
|
8 * PAGE_SIZE);
|
|
|
|
/* Unmap fully contained areas works */
|
|
ASSERT_EQ(0, _test_ioctl_ioas_unmap(self->fd, self->ioas_id,
|
|
self->base_iova - 4 * PAGE_SIZE,
|
|
3 * 16 * PAGE_SIZE + 8 * PAGE_SIZE +
|
|
4 * PAGE_SIZE,
|
|
&unmap_len));
|
|
ASSERT_EQ(32 * PAGE_SIZE, unmap_len);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, area_auto_iova)
|
|
{
|
|
struct iommu_test_cmd test_cmd = {
|
|
.size = sizeof(test_cmd),
|
|
.op = IOMMU_TEST_OP_ADD_RESERVED,
|
|
.id = self->ioas_id,
|
|
.add_reserved = { .start = PAGE_SIZE * 4,
|
|
.length = PAGE_SIZE * 100 },
|
|
};
|
|
struct iommu_iova_range ranges[1] = {};
|
|
struct iommu_ioas_allow_iovas allow_cmd = {
|
|
.size = sizeof(allow_cmd),
|
|
.ioas_id = self->ioas_id,
|
|
.num_iovas = 1,
|
|
.allowed_iovas = (uintptr_t)ranges,
|
|
};
|
|
__u64 iovas[10];
|
|
int i;
|
|
|
|
/* Simple 4k pages */
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_map(buffer, PAGE_SIZE, &iovas[i]);
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_unmap(iovas[i], PAGE_SIZE);
|
|
|
|
/* Kernel automatically aligns IOVAs properly */
|
|
for (i = 0; i != 10; i++) {
|
|
size_t length = PAGE_SIZE * (i + 1);
|
|
|
|
if (self->stdev_id) {
|
|
test_ioctl_ioas_map(buffer, length, &iovas[i]);
|
|
} else {
|
|
test_ioctl_ioas_map((void *)(1UL << 31), length,
|
|
&iovas[i]);
|
|
}
|
|
EXPECT_EQ(0, iovas[i] % (1UL << (ffs(length) - 1)));
|
|
}
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_unmap(iovas[i], PAGE_SIZE * (i + 1));
|
|
|
|
/* Avoids a reserved region */
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ADD_RESERVED),
|
|
&test_cmd));
|
|
for (i = 0; i != 10; i++) {
|
|
size_t length = PAGE_SIZE * (i + 1);
|
|
|
|
test_ioctl_ioas_map(buffer, length, &iovas[i]);
|
|
EXPECT_EQ(0, iovas[i] % (1UL << (ffs(length) - 1)));
|
|
EXPECT_EQ(false,
|
|
iovas[i] > test_cmd.add_reserved.start &&
|
|
iovas[i] <
|
|
test_cmd.add_reserved.start +
|
|
test_cmd.add_reserved.length);
|
|
}
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_unmap(iovas[i], PAGE_SIZE * (i + 1));
|
|
|
|
/* Allowed region intersects with a reserved region */
|
|
ranges[0].start = PAGE_SIZE;
|
|
ranges[0].last = PAGE_SIZE * 600;
|
|
EXPECT_ERRNO(EADDRINUSE,
|
|
ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd));
|
|
|
|
/* Allocate from an allowed region */
|
|
if (self->stdev_id) {
|
|
ranges[0].start = MOCK_APERTURE_START + PAGE_SIZE;
|
|
ranges[0].last = MOCK_APERTURE_START + PAGE_SIZE * 600 - 1;
|
|
} else {
|
|
ranges[0].start = PAGE_SIZE * 200;
|
|
ranges[0].last = PAGE_SIZE * 600 - 1;
|
|
}
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd));
|
|
for (i = 0; i != 10; i++) {
|
|
size_t length = PAGE_SIZE * (i + 1);
|
|
|
|
test_ioctl_ioas_map(buffer, length, &iovas[i]);
|
|
EXPECT_EQ(0, iovas[i] % (1UL << (ffs(length) - 1)));
|
|
EXPECT_EQ(true, iovas[i] >= ranges[0].start);
|
|
EXPECT_EQ(true, iovas[i] <= ranges[0].last);
|
|
EXPECT_EQ(true, iovas[i] + length > ranges[0].start);
|
|
EXPECT_EQ(true, iovas[i] + length <= ranges[0].last + 1);
|
|
}
|
|
for (i = 0; i != 10; i++)
|
|
test_ioctl_ioas_unmap(iovas[i], PAGE_SIZE * (i + 1));
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, area_allowed)
|
|
{
|
|
struct iommu_test_cmd test_cmd = {
|
|
.size = sizeof(test_cmd),
|
|
.op = IOMMU_TEST_OP_ADD_RESERVED,
|
|
.id = self->ioas_id,
|
|
.add_reserved = { .start = PAGE_SIZE * 4,
|
|
.length = PAGE_SIZE * 100 },
|
|
};
|
|
struct iommu_iova_range ranges[1] = {};
|
|
struct iommu_ioas_allow_iovas allow_cmd = {
|
|
.size = sizeof(allow_cmd),
|
|
.ioas_id = self->ioas_id,
|
|
.num_iovas = 1,
|
|
.allowed_iovas = (uintptr_t)ranges,
|
|
};
|
|
|
|
/* Reserved intersects an allowed */
|
|
allow_cmd.num_iovas = 1;
|
|
ranges[0].start = self->base_iova;
|
|
ranges[0].last = ranges[0].start + PAGE_SIZE * 600;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd));
|
|
test_cmd.add_reserved.start = ranges[0].start + PAGE_SIZE;
|
|
test_cmd.add_reserved.length = PAGE_SIZE;
|
|
EXPECT_ERRNO(EADDRINUSE,
|
|
ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_ADD_RESERVED),
|
|
&test_cmd));
|
|
allow_cmd.num_iovas = 0;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd));
|
|
|
|
/* Allowed intersects a reserved */
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ADD_RESERVED),
|
|
&test_cmd));
|
|
allow_cmd.num_iovas = 1;
|
|
ranges[0].start = self->base_iova;
|
|
ranges[0].last = ranges[0].start + PAGE_SIZE * 600;
|
|
EXPECT_ERRNO(EADDRINUSE,
|
|
ioctl(self->fd, IOMMU_IOAS_ALLOW_IOVAS, &allow_cmd));
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, copy_area)
|
|
{
|
|
struct iommu_ioas_copy copy_cmd = {
|
|
.size = sizeof(copy_cmd),
|
|
.flags = IOMMU_IOAS_MAP_FIXED_IOVA,
|
|
.dst_ioas_id = self->ioas_id,
|
|
.src_ioas_id = self->ioas_id,
|
|
.length = PAGE_SIZE,
|
|
};
|
|
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE, self->base_iova);
|
|
|
|
/* Copy inside a single IOAS */
|
|
copy_cmd.src_iova = self->base_iova;
|
|
copy_cmd.dst_iova = self->base_iova + PAGE_SIZE;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_COPY, ©_cmd));
|
|
|
|
/* Copy between IOAS's */
|
|
copy_cmd.src_iova = self->base_iova;
|
|
copy_cmd.dst_iova = 0;
|
|
test_ioctl_ioas_alloc(©_cmd.dst_ioas_id);
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_COPY, ©_cmd));
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, iova_ranges)
|
|
{
|
|
struct iommu_test_cmd test_cmd = {
|
|
.size = sizeof(test_cmd),
|
|
.op = IOMMU_TEST_OP_ADD_RESERVED,
|
|
.id = self->ioas_id,
|
|
.add_reserved = { .start = PAGE_SIZE, .length = PAGE_SIZE },
|
|
};
|
|
struct iommu_iova_range *ranges = buffer;
|
|
struct iommu_ioas_iova_ranges ranges_cmd = {
|
|
.size = sizeof(ranges_cmd),
|
|
.ioas_id = self->ioas_id,
|
|
.num_iovas = BUFFER_SIZE / sizeof(*ranges),
|
|
.allowed_iovas = (uintptr_t)ranges,
|
|
};
|
|
|
|
/* Range can be read */
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd));
|
|
EXPECT_EQ(1, ranges_cmd.num_iovas);
|
|
if (!self->stdev_id) {
|
|
EXPECT_EQ(0, ranges[0].start);
|
|
EXPECT_EQ(SIZE_MAX, ranges[0].last);
|
|
EXPECT_EQ(1, ranges_cmd.out_iova_alignment);
|
|
} else {
|
|
EXPECT_EQ(MOCK_APERTURE_START, ranges[0].start);
|
|
EXPECT_EQ(MOCK_APERTURE_LAST, ranges[0].last);
|
|
EXPECT_EQ(MOCK_PAGE_SIZE, ranges_cmd.out_iova_alignment);
|
|
}
|
|
|
|
/* Buffer too small */
|
|
memset(ranges, 0, BUFFER_SIZE);
|
|
ranges_cmd.num_iovas = 0;
|
|
EXPECT_ERRNO(EMSGSIZE,
|
|
ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd));
|
|
EXPECT_EQ(1, ranges_cmd.num_iovas);
|
|
EXPECT_EQ(0, ranges[0].start);
|
|
EXPECT_EQ(0, ranges[0].last);
|
|
|
|
/* 2 ranges */
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ADD_RESERVED),
|
|
&test_cmd));
|
|
ranges_cmd.num_iovas = BUFFER_SIZE / sizeof(*ranges);
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd));
|
|
if (!self->stdev_id) {
|
|
EXPECT_EQ(2, ranges_cmd.num_iovas);
|
|
EXPECT_EQ(0, ranges[0].start);
|
|
EXPECT_EQ(PAGE_SIZE - 1, ranges[0].last);
|
|
EXPECT_EQ(PAGE_SIZE * 2, ranges[1].start);
|
|
EXPECT_EQ(SIZE_MAX, ranges[1].last);
|
|
} else {
|
|
EXPECT_EQ(1, ranges_cmd.num_iovas);
|
|
EXPECT_EQ(MOCK_APERTURE_START, ranges[0].start);
|
|
EXPECT_EQ(MOCK_APERTURE_LAST, ranges[0].last);
|
|
}
|
|
|
|
/* Buffer too small */
|
|
memset(ranges, 0, BUFFER_SIZE);
|
|
ranges_cmd.num_iovas = 1;
|
|
if (!self->stdev_id) {
|
|
EXPECT_ERRNO(EMSGSIZE, ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES,
|
|
&ranges_cmd));
|
|
EXPECT_EQ(2, ranges_cmd.num_iovas);
|
|
EXPECT_EQ(0, ranges[0].start);
|
|
EXPECT_EQ(PAGE_SIZE - 1, ranges[0].last);
|
|
} else {
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, IOMMU_IOAS_IOVA_RANGES, &ranges_cmd));
|
|
EXPECT_EQ(1, ranges_cmd.num_iovas);
|
|
EXPECT_EQ(MOCK_APERTURE_START, ranges[0].start);
|
|
EXPECT_EQ(MOCK_APERTURE_LAST, ranges[0].last);
|
|
}
|
|
EXPECT_EQ(0, ranges[1].start);
|
|
EXPECT_EQ(0, ranges[1].last);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, access_pin)
|
|
{
|
|
struct iommu_test_cmd access_cmd = {
|
|
.size = sizeof(access_cmd),
|
|
.op = IOMMU_TEST_OP_ACCESS_PAGES,
|
|
.access_pages = { .iova = MOCK_APERTURE_START,
|
|
.length = BUFFER_SIZE,
|
|
.uptr = (uintptr_t)buffer },
|
|
};
|
|
struct iommu_test_cmd check_map_cmd = {
|
|
.size = sizeof(check_map_cmd),
|
|
.op = IOMMU_TEST_OP_MD_CHECK_MAP,
|
|
.check_map = { .iova = MOCK_APERTURE_START,
|
|
.length = BUFFER_SIZE,
|
|
.uptr = (uintptr_t)buffer },
|
|
};
|
|
uint32_t access_pages_id;
|
|
unsigned int npages;
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_cmd.id,
|
|
MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES);
|
|
|
|
for (npages = 1; npages < BUFFER_SIZE / PAGE_SIZE; npages++) {
|
|
uint32_t mock_stdev_id;
|
|
uint32_t mock_hwpt_id;
|
|
|
|
access_cmd.access_pages.length = npages * PAGE_SIZE;
|
|
|
|
/* Single map/unmap */
|
|
test_ioctl_ioas_map_fixed(buffer, BUFFER_SIZE,
|
|
MOCK_APERTURE_START);
|
|
ASSERT_EQ(0, ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_cmd));
|
|
test_cmd_destroy_access_pages(
|
|
access_cmd.id,
|
|
access_cmd.access_pages.out_access_pages_id);
|
|
|
|
/* Double user */
|
|
ASSERT_EQ(0, ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_cmd));
|
|
access_pages_id = access_cmd.access_pages.out_access_pages_id;
|
|
ASSERT_EQ(0, ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_cmd));
|
|
test_cmd_destroy_access_pages(
|
|
access_cmd.id,
|
|
access_cmd.access_pages.out_access_pages_id);
|
|
test_cmd_destroy_access_pages(access_cmd.id, access_pages_id);
|
|
|
|
/* Add/remove a domain with a user */
|
|
ASSERT_EQ(0, ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_cmd));
|
|
test_cmd_mock_domain(self->ioas_id, &mock_stdev_id,
|
|
&mock_hwpt_id);
|
|
check_map_cmd.id = mock_hwpt_id;
|
|
ASSERT_EQ(0, ioctl(self->fd,
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_MD_CHECK_MAP),
|
|
&check_map_cmd));
|
|
|
|
test_ioctl_destroy(mock_stdev_id);
|
|
test_cmd_destroy_access_pages(
|
|
access_cmd.id,
|
|
access_cmd.access_pages.out_access_pages_id);
|
|
|
|
test_ioctl_ioas_unmap(MOCK_APERTURE_START, BUFFER_SIZE);
|
|
}
|
|
test_cmd_destroy_access(access_cmd.id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, access_pin_unmap)
|
|
{
|
|
struct iommu_test_cmd access_pages_cmd = {
|
|
.size = sizeof(access_pages_cmd),
|
|
.op = IOMMU_TEST_OP_ACCESS_PAGES,
|
|
.access_pages = { .iova = MOCK_APERTURE_START,
|
|
.length = BUFFER_SIZE,
|
|
.uptr = (uintptr_t)buffer },
|
|
};
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_pages_cmd.id,
|
|
MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES);
|
|
test_ioctl_ioas_map_fixed(buffer, BUFFER_SIZE, MOCK_APERTURE_START);
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_pages_cmd));
|
|
|
|
/* Trigger the unmap op */
|
|
test_ioctl_ioas_unmap(MOCK_APERTURE_START, BUFFER_SIZE);
|
|
|
|
/* kernel removed the item for us */
|
|
test_err_destroy_access_pages(
|
|
ENOENT, access_pages_cmd.id,
|
|
access_pages_cmd.access_pages.out_access_pages_id);
|
|
}
|
|
|
|
static void check_access_rw(struct __test_metadata *_metadata, int fd,
|
|
unsigned int access_id, uint64_t iova,
|
|
unsigned int def_flags)
|
|
{
|
|
uint16_t tmp[32];
|
|
struct iommu_test_cmd access_cmd = {
|
|
.size = sizeof(access_cmd),
|
|
.op = IOMMU_TEST_OP_ACCESS_RW,
|
|
.id = access_id,
|
|
.access_rw = { .uptr = (uintptr_t)tmp },
|
|
};
|
|
uint16_t *buffer16 = buffer;
|
|
unsigned int i;
|
|
void *tmp2;
|
|
|
|
for (i = 0; i != BUFFER_SIZE / sizeof(*buffer16); i++)
|
|
buffer16[i] = rand();
|
|
|
|
for (access_cmd.access_rw.iova = iova + PAGE_SIZE - 50;
|
|
access_cmd.access_rw.iova < iova + PAGE_SIZE + 50;
|
|
access_cmd.access_rw.iova++) {
|
|
for (access_cmd.access_rw.length = 1;
|
|
access_cmd.access_rw.length < sizeof(tmp);
|
|
access_cmd.access_rw.length++) {
|
|
access_cmd.access_rw.flags = def_flags;
|
|
ASSERT_EQ(0, ioctl(fd,
|
|
_IOMMU_TEST_CMD(
|
|
IOMMU_TEST_OP_ACCESS_RW),
|
|
&access_cmd));
|
|
ASSERT_EQ(0,
|
|
memcmp(buffer + (access_cmd.access_rw.iova -
|
|
iova),
|
|
tmp, access_cmd.access_rw.length));
|
|
|
|
for (i = 0; i != ARRAY_SIZE(tmp); i++)
|
|
tmp[i] = rand();
|
|
access_cmd.access_rw.flags = def_flags |
|
|
MOCK_ACCESS_RW_WRITE;
|
|
ASSERT_EQ(0, ioctl(fd,
|
|
_IOMMU_TEST_CMD(
|
|
IOMMU_TEST_OP_ACCESS_RW),
|
|
&access_cmd));
|
|
ASSERT_EQ(0,
|
|
memcmp(buffer + (access_cmd.access_rw.iova -
|
|
iova),
|
|
tmp, access_cmd.access_rw.length));
|
|
}
|
|
}
|
|
|
|
/* Multi-page test */
|
|
tmp2 = malloc(BUFFER_SIZE);
|
|
ASSERT_NE(NULL, tmp2);
|
|
access_cmd.access_rw.iova = iova;
|
|
access_cmd.access_rw.length = BUFFER_SIZE;
|
|
access_cmd.access_rw.flags = def_flags;
|
|
access_cmd.access_rw.uptr = (uintptr_t)tmp2;
|
|
ASSERT_EQ(0, ioctl(fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_RW),
|
|
&access_cmd));
|
|
ASSERT_EQ(0, memcmp(buffer, tmp2, access_cmd.access_rw.length));
|
|
free(tmp2);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, access_rw)
|
|
{
|
|
__u32 access_id;
|
|
__u64 iova;
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_id, 0);
|
|
test_ioctl_ioas_map(buffer, BUFFER_SIZE, &iova);
|
|
check_access_rw(_metadata, self->fd, access_id, iova, 0);
|
|
check_access_rw(_metadata, self->fd, access_id, iova,
|
|
MOCK_ACCESS_RW_SLOW_PATH);
|
|
test_ioctl_ioas_unmap(iova, BUFFER_SIZE);
|
|
test_cmd_destroy_access(access_id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, access_rw_unaligned)
|
|
{
|
|
__u32 access_id;
|
|
__u64 iova;
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_id, 0);
|
|
|
|
/* Unaligned pages */
|
|
iova = self->base_iova + MOCK_PAGE_SIZE;
|
|
test_ioctl_ioas_map_fixed(buffer, BUFFER_SIZE, iova);
|
|
check_access_rw(_metadata, self->fd, access_id, iova, 0);
|
|
test_ioctl_ioas_unmap(iova, BUFFER_SIZE);
|
|
test_cmd_destroy_access(access_id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, fork_gone)
|
|
{
|
|
__u32 access_id;
|
|
pid_t child;
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_id, 0);
|
|
|
|
/* Create a mapping with a different mm */
|
|
child = fork();
|
|
if (!child) {
|
|
test_ioctl_ioas_map_fixed(buffer, BUFFER_SIZE,
|
|
MOCK_APERTURE_START);
|
|
exit(0);
|
|
}
|
|
ASSERT_NE(-1, child);
|
|
ASSERT_EQ(child, waitpid(child, NULL, 0));
|
|
|
|
if (self->stdev_id) {
|
|
/*
|
|
* If a domain already existed then everything was pinned within
|
|
* the fork, so this copies from one domain to another.
|
|
*/
|
|
test_cmd_mock_domain(self->ioas_id, NULL, NULL);
|
|
check_access_rw(_metadata, self->fd, access_id,
|
|
MOCK_APERTURE_START, 0);
|
|
|
|
} else {
|
|
/*
|
|
* Otherwise we need to actually pin pages which can't happen
|
|
* since the fork is gone.
|
|
*/
|
|
test_err_mock_domain(EFAULT, self->ioas_id, NULL, NULL);
|
|
}
|
|
|
|
test_cmd_destroy_access(access_id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, fork_present)
|
|
{
|
|
__u32 access_id;
|
|
int pipefds[2];
|
|
uint64_t tmp;
|
|
pid_t child;
|
|
int efd;
|
|
|
|
test_cmd_create_access(self->ioas_id, &access_id, 0);
|
|
|
|
ASSERT_EQ(0, pipe2(pipefds, O_CLOEXEC));
|
|
efd = eventfd(0, EFD_CLOEXEC);
|
|
ASSERT_NE(-1, efd);
|
|
|
|
/* Create a mapping with a different mm */
|
|
child = fork();
|
|
if (!child) {
|
|
__u64 iova;
|
|
uint64_t one = 1;
|
|
|
|
close(pipefds[1]);
|
|
test_ioctl_ioas_map_fixed(buffer, BUFFER_SIZE,
|
|
MOCK_APERTURE_START);
|
|
if (write(efd, &one, sizeof(one)) != sizeof(one))
|
|
exit(100);
|
|
if (read(pipefds[0], &iova, 1) != 1)
|
|
exit(100);
|
|
exit(0);
|
|
}
|
|
close(pipefds[0]);
|
|
ASSERT_NE(-1, child);
|
|
ASSERT_EQ(8, read(efd, &tmp, sizeof(tmp)));
|
|
|
|
/* Read pages from the remote process */
|
|
test_cmd_mock_domain(self->ioas_id, NULL, NULL);
|
|
check_access_rw(_metadata, self->fd, access_id, MOCK_APERTURE_START, 0);
|
|
|
|
ASSERT_EQ(0, close(pipefds[1]));
|
|
ASSERT_EQ(child, waitpid(child, NULL, 0));
|
|
|
|
test_cmd_destroy_access(access_id);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_option_huge_pages)
|
|
{
|
|
struct iommu_option cmd = {
|
|
.size = sizeof(cmd),
|
|
.option_id = IOMMU_OPTION_HUGE_PAGES,
|
|
.op = IOMMU_OPTION_OP_GET,
|
|
.val64 = 3,
|
|
.object_id = self->ioas_id,
|
|
};
|
|
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
ASSERT_EQ(1, cmd.val64);
|
|
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
cmd.val64 = 0;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
cmd.op = IOMMU_OPTION_OP_GET;
|
|
cmd.val64 = 3;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
ASSERT_EQ(0, cmd.val64);
|
|
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
cmd.val64 = 2;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
cmd.op = IOMMU_OPTION_OP_SET;
|
|
cmd.val64 = 1;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_iova_alloc)
|
|
{
|
|
unsigned int length;
|
|
__u64 iova;
|
|
|
|
for (length = 1; length != PAGE_SIZE * 2; length++) {
|
|
if (variant->mock_domains && (length % MOCK_PAGE_SIZE)) {
|
|
test_err_ioctl_ioas_map(EINVAL, buffer, length, &iova);
|
|
} else {
|
|
test_ioctl_ioas_map(buffer, length, &iova);
|
|
test_ioctl_ioas_unmap(iova, length);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, ioas_align_change)
|
|
{
|
|
struct iommu_option cmd = {
|
|
.size = sizeof(cmd),
|
|
.option_id = IOMMU_OPTION_HUGE_PAGES,
|
|
.op = IOMMU_OPTION_OP_SET,
|
|
.object_id = self->ioas_id,
|
|
/* 0 means everything must be aligned to PAGE_SIZE */
|
|
.val64 = 0,
|
|
};
|
|
|
|
/*
|
|
* We cannot upgrade the alignment using OPTION_HUGE_PAGES when a domain
|
|
* and map are present.
|
|
*/
|
|
if (variant->mock_domains)
|
|
return;
|
|
|
|
/*
|
|
* We can upgrade to PAGE_SIZE alignment when things are aligned right
|
|
*/
|
|
test_ioctl_ioas_map_fixed(buffer, PAGE_SIZE, MOCK_APERTURE_START);
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
/* Misalignment is rejected at map time */
|
|
test_err_ioctl_ioas_map_fixed(EINVAL, buffer + MOCK_PAGE_SIZE,
|
|
PAGE_SIZE,
|
|
MOCK_APERTURE_START + PAGE_SIZE);
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
/* Reduce alignment */
|
|
cmd.val64 = 1;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
/* Confirm misalignment is rejected during alignment upgrade */
|
|
test_ioctl_ioas_map_fixed(buffer + MOCK_PAGE_SIZE, PAGE_SIZE,
|
|
MOCK_APERTURE_START + PAGE_SIZE);
|
|
cmd.val64 = 0;
|
|
EXPECT_ERRNO(EADDRINUSE, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
|
|
test_ioctl_ioas_unmap(MOCK_APERTURE_START + PAGE_SIZE, PAGE_SIZE);
|
|
test_ioctl_ioas_unmap(MOCK_APERTURE_START, PAGE_SIZE);
|
|
}
|
|
|
|
TEST_F(iommufd_ioas, copy_sweep)
|
|
{
|
|
struct iommu_ioas_copy copy_cmd = {
|
|
.size = sizeof(copy_cmd),
|
|
.flags = IOMMU_IOAS_MAP_FIXED_IOVA,
|
|
.src_ioas_id = self->ioas_id,
|
|
.dst_iova = MOCK_APERTURE_START,
|
|
.length = MOCK_PAGE_SIZE,
|
|
};
|
|
unsigned int dst_ioas_id;
|
|
uint64_t last_iova;
|
|
uint64_t iova;
|
|
|
|
test_ioctl_ioas_alloc(&dst_ioas_id);
|
|
copy_cmd.dst_ioas_id = dst_ioas_id;
|
|
|
|
if (variant->mock_domains)
|
|
last_iova = MOCK_APERTURE_START + BUFFER_SIZE - 1;
|
|
else
|
|
last_iova = MOCK_APERTURE_START + BUFFER_SIZE - 2;
|
|
|
|
test_ioctl_ioas_map_fixed(buffer, last_iova - MOCK_APERTURE_START + 1,
|
|
MOCK_APERTURE_START);
|
|
|
|
for (iova = MOCK_APERTURE_START - PAGE_SIZE; iova <= last_iova;
|
|
iova += 511) {
|
|
copy_cmd.src_iova = iova;
|
|
if (iova < MOCK_APERTURE_START ||
|
|
iova + copy_cmd.length - 1 > last_iova) {
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_IOAS_COPY,
|
|
©_cmd));
|
|
} else {
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, IOMMU_IOAS_COPY, ©_cmd));
|
|
test_ioctl_ioas_unmap_id(dst_ioas_id, copy_cmd.dst_iova,
|
|
copy_cmd.length);
|
|
}
|
|
}
|
|
|
|
test_ioctl_destroy(dst_ioas_id);
|
|
}
|
|
|
|
FIXTURE(iommufd_mock_domain)
|
|
{
|
|
int fd;
|
|
uint32_t ioas_id;
|
|
uint32_t hwpt_id;
|
|
uint32_t hwpt_ids[2];
|
|
int mmap_flags;
|
|
size_t mmap_buf_size;
|
|
};
|
|
|
|
FIXTURE_VARIANT(iommufd_mock_domain)
|
|
{
|
|
unsigned int mock_domains;
|
|
bool hugepages;
|
|
};
|
|
|
|
FIXTURE_SETUP(iommufd_mock_domain)
|
|
{
|
|
unsigned int i;
|
|
|
|
self->fd = open("/dev/iommu", O_RDWR);
|
|
ASSERT_NE(-1, self->fd);
|
|
test_ioctl_ioas_alloc(&self->ioas_id);
|
|
|
|
ASSERT_GE(ARRAY_SIZE(self->hwpt_ids), variant->mock_domains);
|
|
|
|
for (i = 0; i != variant->mock_domains; i++)
|
|
test_cmd_mock_domain(self->ioas_id, NULL, &self->hwpt_ids[i]);
|
|
self->hwpt_id = self->hwpt_ids[0];
|
|
|
|
self->mmap_flags = MAP_SHARED | MAP_ANONYMOUS;
|
|
self->mmap_buf_size = PAGE_SIZE * 8;
|
|
if (variant->hugepages) {
|
|
/*
|
|
* MAP_POPULATE will cause the kernel to fail mmap if THPs are
|
|
* not available.
|
|
*/
|
|
self->mmap_flags |= MAP_HUGETLB | MAP_POPULATE;
|
|
self->mmap_buf_size = HUGEPAGE_SIZE * 2;
|
|
}
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(iommufd_mock_domain)
|
|
{
|
|
teardown_iommufd(self->fd, _metadata);
|
|
}
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_mock_domain, one_domain)
|
|
{
|
|
.mock_domains = 1,
|
|
.hugepages = false,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_mock_domain, two_domains)
|
|
{
|
|
.mock_domains = 2,
|
|
.hugepages = false,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_mock_domain, one_domain_hugepage)
|
|
{
|
|
.mock_domains = 1,
|
|
.hugepages = true,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(iommufd_mock_domain, two_domains_hugepage)
|
|
{
|
|
.mock_domains = 2,
|
|
.hugepages = true,
|
|
};
|
|
|
|
/* Have the kernel check that the user pages made it to the iommu_domain */
|
|
#define check_mock_iova(_ptr, _iova, _length) \
|
|
({ \
|
|
struct iommu_test_cmd check_map_cmd = { \
|
|
.size = sizeof(check_map_cmd), \
|
|
.op = IOMMU_TEST_OP_MD_CHECK_MAP, \
|
|
.id = self->hwpt_id, \
|
|
.check_map = { .iova = _iova, \
|
|
.length = _length, \
|
|
.uptr = (uintptr_t)(_ptr) }, \
|
|
}; \
|
|
ASSERT_EQ(0, \
|
|
ioctl(self->fd, \
|
|
_IOMMU_TEST_CMD(IOMMU_TEST_OP_MD_CHECK_MAP), \
|
|
&check_map_cmd)); \
|
|
if (self->hwpt_ids[1]) { \
|
|
check_map_cmd.id = self->hwpt_ids[1]; \
|
|
ASSERT_EQ(0, \
|
|
ioctl(self->fd, \
|
|
_IOMMU_TEST_CMD( \
|
|
IOMMU_TEST_OP_MD_CHECK_MAP), \
|
|
&check_map_cmd)); \
|
|
} \
|
|
})
|
|
|
|
TEST_F(iommufd_mock_domain, basic)
|
|
{
|
|
size_t buf_size = self->mmap_buf_size;
|
|
uint8_t *buf;
|
|
__u64 iova;
|
|
|
|
/* Simple one page map */
|
|
test_ioctl_ioas_map(buffer, PAGE_SIZE, &iova);
|
|
check_mock_iova(buffer, iova, PAGE_SIZE);
|
|
|
|
buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, self->mmap_flags, -1,
|
|
0);
|
|
ASSERT_NE(MAP_FAILED, buf);
|
|
|
|
/* EFAULT half way through mapping */
|
|
ASSERT_EQ(0, munmap(buf + buf_size / 2, buf_size / 2));
|
|
test_err_ioctl_ioas_map(EFAULT, buf, buf_size, &iova);
|
|
|
|
/* EFAULT on first page */
|
|
ASSERT_EQ(0, munmap(buf, buf_size / 2));
|
|
test_err_ioctl_ioas_map(EFAULT, buf, buf_size, &iova);
|
|
}
|
|
|
|
TEST_F(iommufd_mock_domain, ro_unshare)
|
|
{
|
|
uint8_t *buf;
|
|
__u64 iova;
|
|
int fd;
|
|
|
|
fd = open("/proc/self/exe", O_RDONLY);
|
|
ASSERT_NE(-1, fd);
|
|
|
|
buf = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
|
ASSERT_NE(MAP_FAILED, buf);
|
|
close(fd);
|
|
|
|
/*
|
|
* There have been lots of changes to the "unshare" mechanism in
|
|
* get_user_pages(), make sure it works right. The write to the page
|
|
* after we map it for reading should not change the assigned PFN.
|
|
*/
|
|
ASSERT_EQ(0,
|
|
_test_ioctl_ioas_map(self->fd, self->ioas_id, buf, PAGE_SIZE,
|
|
&iova, IOMMU_IOAS_MAP_READABLE));
|
|
check_mock_iova(buf, iova, PAGE_SIZE);
|
|
memset(buf, 1, PAGE_SIZE);
|
|
check_mock_iova(buf, iova, PAGE_SIZE);
|
|
ASSERT_EQ(0, munmap(buf, PAGE_SIZE));
|
|
}
|
|
|
|
TEST_F(iommufd_mock_domain, all_aligns)
|
|
{
|
|
size_t test_step = variant->hugepages ? (self->mmap_buf_size / 16) :
|
|
MOCK_PAGE_SIZE;
|
|
size_t buf_size = self->mmap_buf_size;
|
|
unsigned int start;
|
|
unsigned int end;
|
|
uint8_t *buf;
|
|
|
|
buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, self->mmap_flags, -1,
|
|
0);
|
|
ASSERT_NE(MAP_FAILED, buf);
|
|
check_refs(buf, buf_size, 0);
|
|
|
|
/*
|
|
* Map every combination of page size and alignment within a big region,
|
|
* less for hugepage case as it takes so long to finish.
|
|
*/
|
|
for (start = 0; start < buf_size; start += test_step) {
|
|
if (variant->hugepages)
|
|
end = buf_size;
|
|
else
|
|
end = start + MOCK_PAGE_SIZE;
|
|
for (; end < buf_size; end += MOCK_PAGE_SIZE) {
|
|
size_t length = end - start;
|
|
__u64 iova;
|
|
|
|
test_ioctl_ioas_map(buf + start, length, &iova);
|
|
check_mock_iova(buf + start, iova, length);
|
|
check_refs(buf + start / PAGE_SIZE * PAGE_SIZE,
|
|
end / PAGE_SIZE * PAGE_SIZE -
|
|
start / PAGE_SIZE * PAGE_SIZE,
|
|
1);
|
|
|
|
test_ioctl_ioas_unmap(iova, length);
|
|
}
|
|
}
|
|
check_refs(buf, buf_size, 0);
|
|
ASSERT_EQ(0, munmap(buf, buf_size));
|
|
}
|
|
|
|
TEST_F(iommufd_mock_domain, all_aligns_copy)
|
|
{
|
|
size_t test_step = variant->hugepages ? self->mmap_buf_size / 16 :
|
|
MOCK_PAGE_SIZE;
|
|
size_t buf_size = self->mmap_buf_size;
|
|
unsigned int start;
|
|
unsigned int end;
|
|
uint8_t *buf;
|
|
|
|
buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, self->mmap_flags, -1,
|
|
0);
|
|
ASSERT_NE(MAP_FAILED, buf);
|
|
check_refs(buf, buf_size, 0);
|
|
|
|
/*
|
|
* Map every combination of page size and alignment within a big region,
|
|
* less for hugepage case as it takes so long to finish.
|
|
*/
|
|
for (start = 0; start < buf_size; start += test_step) {
|
|
if (variant->hugepages)
|
|
end = buf_size;
|
|
else
|
|
end = start + MOCK_PAGE_SIZE;
|
|
for (; end < buf_size; end += MOCK_PAGE_SIZE) {
|
|
size_t length = end - start;
|
|
unsigned int old_id;
|
|
uint32_t mock_stdev_id;
|
|
__u64 iova;
|
|
|
|
test_ioctl_ioas_map(buf + start, length, &iova);
|
|
|
|
/* Add and destroy a domain while the area exists */
|
|
old_id = self->hwpt_ids[1];
|
|
test_cmd_mock_domain(self->ioas_id, &mock_stdev_id,
|
|
&self->hwpt_ids[1]);
|
|
|
|
check_mock_iova(buf + start, iova, length);
|
|
check_refs(buf + start / PAGE_SIZE * PAGE_SIZE,
|
|
end / PAGE_SIZE * PAGE_SIZE -
|
|
start / PAGE_SIZE * PAGE_SIZE,
|
|
1);
|
|
|
|
test_ioctl_destroy(mock_stdev_id);
|
|
self->hwpt_ids[1] = old_id;
|
|
|
|
test_ioctl_ioas_unmap(iova, length);
|
|
}
|
|
}
|
|
check_refs(buf, buf_size, 0);
|
|
ASSERT_EQ(0, munmap(buf, buf_size));
|
|
}
|
|
|
|
TEST_F(iommufd_mock_domain, user_copy)
|
|
{
|
|
struct iommu_test_cmd access_cmd = {
|
|
.size = sizeof(access_cmd),
|
|
.op = IOMMU_TEST_OP_ACCESS_PAGES,
|
|
.access_pages = { .length = BUFFER_SIZE,
|
|
.uptr = (uintptr_t)buffer },
|
|
};
|
|
struct iommu_ioas_copy copy_cmd = {
|
|
.size = sizeof(copy_cmd),
|
|
.flags = IOMMU_IOAS_MAP_FIXED_IOVA,
|
|
.dst_ioas_id = self->ioas_id,
|
|
.dst_iova = MOCK_APERTURE_START,
|
|
.length = BUFFER_SIZE,
|
|
};
|
|
unsigned int ioas_id;
|
|
|
|
/* Pin the pages in an IOAS with no domains then copy to an IOAS with domains */
|
|
test_ioctl_ioas_alloc(&ioas_id);
|
|
test_ioctl_ioas_map_id(ioas_id, buffer, BUFFER_SIZE,
|
|
©_cmd.src_iova);
|
|
|
|
test_cmd_create_access(ioas_id, &access_cmd.id,
|
|
MOCK_FLAGS_ACCESS_CREATE_NEEDS_PIN_PAGES);
|
|
|
|
access_cmd.access_pages.iova = copy_cmd.src_iova;
|
|
ASSERT_EQ(0,
|
|
ioctl(self->fd, _IOMMU_TEST_CMD(IOMMU_TEST_OP_ACCESS_PAGES),
|
|
&access_cmd));
|
|
copy_cmd.src_ioas_id = ioas_id;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_IOAS_COPY, ©_cmd));
|
|
check_mock_iova(buffer, MOCK_APERTURE_START, BUFFER_SIZE);
|
|
|
|
test_cmd_destroy_access_pages(
|
|
access_cmd.id, access_cmd.access_pages.out_access_pages_id);
|
|
test_cmd_destroy_access(access_cmd.id);
|
|
|
|
test_ioctl_destroy(ioas_id);
|
|
}
|
|
|
|
/* VFIO compatibility IOCTLs */
|
|
|
|
TEST_F(iommufd, simple_ioctls)
|
|
{
|
|
ASSERT_EQ(VFIO_API_VERSION, ioctl(self->fd, VFIO_GET_API_VERSION));
|
|
ASSERT_EQ(1, ioctl(self->fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU));
|
|
}
|
|
|
|
TEST_F(iommufd, unmap_cmd)
|
|
{
|
|
struct vfio_iommu_type1_dma_unmap unmap_cmd = {
|
|
.iova = MOCK_APERTURE_START,
|
|
.size = PAGE_SIZE,
|
|
};
|
|
|
|
unmap_cmd.argsz = 1;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
|
|
unmap_cmd.argsz = sizeof(unmap_cmd);
|
|
unmap_cmd.flags = 1 << 31;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
|
|
unmap_cmd.flags = 0;
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
}
|
|
|
|
TEST_F(iommufd, map_cmd)
|
|
{
|
|
struct vfio_iommu_type1_dma_map map_cmd = {
|
|
.iova = MOCK_APERTURE_START,
|
|
.size = PAGE_SIZE,
|
|
.vaddr = (__u64)buffer,
|
|
};
|
|
|
|
map_cmd.argsz = 1;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
|
|
map_cmd.argsz = sizeof(map_cmd);
|
|
map_cmd.flags = 1 << 31;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
|
|
/* Requires a domain to be attached */
|
|
map_cmd.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
}
|
|
|
|
TEST_F(iommufd, info_cmd)
|
|
{
|
|
struct vfio_iommu_type1_info info_cmd = {};
|
|
|
|
/* Invalid argsz */
|
|
info_cmd.argsz = 1;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_GET_INFO, &info_cmd));
|
|
|
|
info_cmd.argsz = sizeof(info_cmd);
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, VFIO_IOMMU_GET_INFO, &info_cmd));
|
|
}
|
|
|
|
TEST_F(iommufd, set_iommu_cmd)
|
|
{
|
|
/* Requires a domain to be attached */
|
|
EXPECT_ERRNO(ENODEV,
|
|
ioctl(self->fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU));
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU));
|
|
}
|
|
|
|
TEST_F(iommufd, vfio_ioas)
|
|
{
|
|
struct iommu_vfio_ioas vfio_ioas_cmd = {
|
|
.size = sizeof(vfio_ioas_cmd),
|
|
.op = IOMMU_VFIO_IOAS_GET,
|
|
};
|
|
__u32 ioas_id;
|
|
|
|
/* ENODEV if there is no compat ioas */
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
|
|
/* Invalid id for set */
|
|
vfio_ioas_cmd.op = IOMMU_VFIO_IOAS_SET;
|
|
EXPECT_ERRNO(ENOENT, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
|
|
/* Valid id for set*/
|
|
test_ioctl_ioas_alloc(&ioas_id);
|
|
vfio_ioas_cmd.ioas_id = ioas_id;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
|
|
/* Same id comes back from get */
|
|
vfio_ioas_cmd.op = IOMMU_VFIO_IOAS_GET;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
ASSERT_EQ(ioas_id, vfio_ioas_cmd.ioas_id);
|
|
|
|
/* Clear works */
|
|
vfio_ioas_cmd.op = IOMMU_VFIO_IOAS_CLEAR;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
vfio_ioas_cmd.op = IOMMU_VFIO_IOAS_GET;
|
|
EXPECT_ERRNO(ENODEV, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
}
|
|
|
|
FIXTURE(vfio_compat_mock_domain)
|
|
{
|
|
int fd;
|
|
uint32_t ioas_id;
|
|
};
|
|
|
|
FIXTURE_VARIANT(vfio_compat_mock_domain)
|
|
{
|
|
unsigned int version;
|
|
};
|
|
|
|
FIXTURE_SETUP(vfio_compat_mock_domain)
|
|
{
|
|
struct iommu_vfio_ioas vfio_ioas_cmd = {
|
|
.size = sizeof(vfio_ioas_cmd),
|
|
.op = IOMMU_VFIO_IOAS_SET,
|
|
};
|
|
|
|
self->fd = open("/dev/iommu", O_RDWR);
|
|
ASSERT_NE(-1, self->fd);
|
|
|
|
/* Create what VFIO would consider a group */
|
|
test_ioctl_ioas_alloc(&self->ioas_id);
|
|
test_cmd_mock_domain(self->ioas_id, NULL, NULL);
|
|
|
|
/* Attach it to the vfio compat */
|
|
vfio_ioas_cmd.ioas_id = self->ioas_id;
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_VFIO_IOAS, &vfio_ioas_cmd));
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_SET_IOMMU, variant->version));
|
|
}
|
|
|
|
FIXTURE_TEARDOWN(vfio_compat_mock_domain)
|
|
{
|
|
teardown_iommufd(self->fd, _metadata);
|
|
}
|
|
|
|
FIXTURE_VARIANT_ADD(vfio_compat_mock_domain, Ver1v2)
|
|
{
|
|
.version = VFIO_TYPE1v2_IOMMU,
|
|
};
|
|
|
|
FIXTURE_VARIANT_ADD(vfio_compat_mock_domain, Ver1v0)
|
|
{
|
|
.version = VFIO_TYPE1_IOMMU,
|
|
};
|
|
|
|
TEST_F(vfio_compat_mock_domain, simple_close)
|
|
{
|
|
}
|
|
|
|
TEST_F(vfio_compat_mock_domain, option_huge_pages)
|
|
{
|
|
struct iommu_option cmd = {
|
|
.size = sizeof(cmd),
|
|
.option_id = IOMMU_OPTION_HUGE_PAGES,
|
|
.op = IOMMU_OPTION_OP_GET,
|
|
.val64 = 3,
|
|
.object_id = self->ioas_id,
|
|
};
|
|
|
|
ASSERT_EQ(0, ioctl(self->fd, IOMMU_OPTION, &cmd));
|
|
if (variant->version == VFIO_TYPE1_IOMMU) {
|
|
ASSERT_EQ(0, cmd.val64);
|
|
} else {
|
|
ASSERT_EQ(1, cmd.val64);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute an ioctl command stored in buffer and check that the result does not
|
|
* overflow memory.
|
|
*/
|
|
static bool is_filled(const void *buf, uint8_t c, size_t len)
|
|
{
|
|
const uint8_t *cbuf = buf;
|
|
|
|
for (; len; cbuf++, len--)
|
|
if (*cbuf != c)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
#define ioctl_check_buf(fd, cmd) \
|
|
({ \
|
|
size_t _cmd_len = *(__u32 *)buffer; \
|
|
\
|
|
memset(buffer + _cmd_len, 0xAA, BUFFER_SIZE - _cmd_len); \
|
|
ASSERT_EQ(0, ioctl(fd, cmd, buffer)); \
|
|
ASSERT_EQ(true, is_filled(buffer + _cmd_len, 0xAA, \
|
|
BUFFER_SIZE - _cmd_len)); \
|
|
})
|
|
|
|
static void check_vfio_info_cap_chain(struct __test_metadata *_metadata,
|
|
struct vfio_iommu_type1_info *info_cmd)
|
|
{
|
|
const struct vfio_info_cap_header *cap;
|
|
|
|
ASSERT_GE(info_cmd->argsz, info_cmd->cap_offset + sizeof(*cap));
|
|
cap = buffer + info_cmd->cap_offset;
|
|
while (true) {
|
|
size_t cap_size;
|
|
|
|
if (cap->next)
|
|
cap_size = (buffer + cap->next) - (void *)cap;
|
|
else
|
|
cap_size = (buffer + info_cmd->argsz) - (void *)cap;
|
|
|
|
switch (cap->id) {
|
|
case VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE: {
|
|
struct vfio_iommu_type1_info_cap_iova_range *data =
|
|
(void *)cap;
|
|
|
|
ASSERT_EQ(1, data->header.version);
|
|
ASSERT_EQ(1, data->nr_iovas);
|
|
EXPECT_EQ(MOCK_APERTURE_START,
|
|
data->iova_ranges[0].start);
|
|
EXPECT_EQ(MOCK_APERTURE_LAST, data->iova_ranges[0].end);
|
|
break;
|
|
}
|
|
case VFIO_IOMMU_TYPE1_INFO_DMA_AVAIL: {
|
|
struct vfio_iommu_type1_info_dma_avail *data =
|
|
(void *)cap;
|
|
|
|
ASSERT_EQ(1, data->header.version);
|
|
ASSERT_EQ(sizeof(*data), cap_size);
|
|
break;
|
|
}
|
|
default:
|
|
ASSERT_EQ(false, true);
|
|
break;
|
|
}
|
|
if (!cap->next)
|
|
break;
|
|
|
|
ASSERT_GE(info_cmd->argsz, cap->next + sizeof(*cap));
|
|
ASSERT_GE(buffer + cap->next, (void *)cap);
|
|
cap = buffer + cap->next;
|
|
}
|
|
}
|
|
|
|
TEST_F(vfio_compat_mock_domain, get_info)
|
|
{
|
|
struct vfio_iommu_type1_info *info_cmd = buffer;
|
|
unsigned int i;
|
|
size_t caplen;
|
|
|
|
/* Pre-cap ABI */
|
|
*info_cmd = (struct vfio_iommu_type1_info){
|
|
.argsz = offsetof(struct vfio_iommu_type1_info, cap_offset),
|
|
};
|
|
ioctl_check_buf(self->fd, VFIO_IOMMU_GET_INFO);
|
|
ASSERT_NE(0, info_cmd->iova_pgsizes);
|
|
ASSERT_EQ(VFIO_IOMMU_INFO_PGSIZES | VFIO_IOMMU_INFO_CAPS,
|
|
info_cmd->flags);
|
|
|
|
/* Read the cap chain size */
|
|
*info_cmd = (struct vfio_iommu_type1_info){
|
|
.argsz = sizeof(*info_cmd),
|
|
};
|
|
ioctl_check_buf(self->fd, VFIO_IOMMU_GET_INFO);
|
|
ASSERT_NE(0, info_cmd->iova_pgsizes);
|
|
ASSERT_EQ(VFIO_IOMMU_INFO_PGSIZES | VFIO_IOMMU_INFO_CAPS,
|
|
info_cmd->flags);
|
|
ASSERT_EQ(0, info_cmd->cap_offset);
|
|
ASSERT_LT(sizeof(*info_cmd), info_cmd->argsz);
|
|
|
|
/* Read the caps, kernel should never create a corrupted caps */
|
|
caplen = info_cmd->argsz;
|
|
for (i = sizeof(*info_cmd); i < caplen; i++) {
|
|
*info_cmd = (struct vfio_iommu_type1_info){
|
|
.argsz = i,
|
|
};
|
|
ioctl_check_buf(self->fd, VFIO_IOMMU_GET_INFO);
|
|
ASSERT_EQ(VFIO_IOMMU_INFO_PGSIZES | VFIO_IOMMU_INFO_CAPS,
|
|
info_cmd->flags);
|
|
if (!info_cmd->cap_offset)
|
|
continue;
|
|
check_vfio_info_cap_chain(_metadata, info_cmd);
|
|
}
|
|
}
|
|
|
|
static void shuffle_array(unsigned long *array, size_t nelms)
|
|
{
|
|
unsigned int i;
|
|
|
|
/* Shuffle */
|
|
for (i = 0; i != nelms; i++) {
|
|
unsigned long tmp = array[i];
|
|
unsigned int other = rand() % (nelms - i);
|
|
|
|
array[i] = array[other];
|
|
array[other] = tmp;
|
|
}
|
|
}
|
|
|
|
TEST_F(vfio_compat_mock_domain, map)
|
|
{
|
|
struct vfio_iommu_type1_dma_map map_cmd = {
|
|
.argsz = sizeof(map_cmd),
|
|
.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
|
.vaddr = (uintptr_t)buffer,
|
|
.size = BUFFER_SIZE,
|
|
.iova = MOCK_APERTURE_START,
|
|
};
|
|
struct vfio_iommu_type1_dma_unmap unmap_cmd = {
|
|
.argsz = sizeof(unmap_cmd),
|
|
.size = BUFFER_SIZE,
|
|
.iova = MOCK_APERTURE_START,
|
|
};
|
|
unsigned long pages_iova[BUFFER_SIZE / PAGE_SIZE];
|
|
unsigned int i;
|
|
|
|
/* Simple map/unmap */
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
ASSERT_EQ(BUFFER_SIZE, unmap_cmd.size);
|
|
|
|
/* UNMAP_FLAG_ALL requres 0 iova/size */
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
unmap_cmd.flags = VFIO_DMA_UNMAP_FLAG_ALL;
|
|
EXPECT_ERRNO(EINVAL, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
|
|
unmap_cmd.iova = 0;
|
|
unmap_cmd.size = 0;
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
ASSERT_EQ(BUFFER_SIZE, unmap_cmd.size);
|
|
|
|
/* Small pages */
|
|
for (i = 0; i != ARRAY_SIZE(pages_iova); i++) {
|
|
map_cmd.iova = pages_iova[i] =
|
|
MOCK_APERTURE_START + i * PAGE_SIZE;
|
|
map_cmd.vaddr = (uintptr_t)buffer + i * PAGE_SIZE;
|
|
map_cmd.size = PAGE_SIZE;
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
}
|
|
shuffle_array(pages_iova, ARRAY_SIZE(pages_iova));
|
|
|
|
unmap_cmd.flags = 0;
|
|
unmap_cmd.size = PAGE_SIZE;
|
|
for (i = 0; i != ARRAY_SIZE(pages_iova); i++) {
|
|
unmap_cmd.iova = pages_iova[i];
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA, &unmap_cmd));
|
|
}
|
|
}
|
|
|
|
TEST_F(vfio_compat_mock_domain, huge_map)
|
|
{
|
|
size_t buf_size = HUGEPAGE_SIZE * 2;
|
|
struct vfio_iommu_type1_dma_map map_cmd = {
|
|
.argsz = sizeof(map_cmd),
|
|
.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
|
|
.size = buf_size,
|
|
.iova = MOCK_APERTURE_START,
|
|
};
|
|
struct vfio_iommu_type1_dma_unmap unmap_cmd = {
|
|
.argsz = sizeof(unmap_cmd),
|
|
};
|
|
unsigned long pages_iova[16];
|
|
unsigned int i;
|
|
void *buf;
|
|
|
|
/* Test huge pages and splitting */
|
|
buf = mmap(0, buf_size, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB | MAP_POPULATE, -1,
|
|
0);
|
|
ASSERT_NE(MAP_FAILED, buf);
|
|
map_cmd.vaddr = (uintptr_t)buf;
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_MAP_DMA, &map_cmd));
|
|
|
|
unmap_cmd.size = buf_size / ARRAY_SIZE(pages_iova);
|
|
for (i = 0; i != ARRAY_SIZE(pages_iova); i++)
|
|
pages_iova[i] = MOCK_APERTURE_START + (i * unmap_cmd.size);
|
|
shuffle_array(pages_iova, ARRAY_SIZE(pages_iova));
|
|
|
|
/* type1 mode can cut up larger mappings, type1v2 always fails */
|
|
for (i = 0; i != ARRAY_SIZE(pages_iova); i++) {
|
|
unmap_cmd.iova = pages_iova[i];
|
|
unmap_cmd.size = buf_size / ARRAY_SIZE(pages_iova);
|
|
if (variant->version == VFIO_TYPE1_IOMMU) {
|
|
ASSERT_EQ(0, ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA,
|
|
&unmap_cmd));
|
|
} else {
|
|
EXPECT_ERRNO(ENOENT,
|
|
ioctl(self->fd, VFIO_IOMMU_UNMAP_DMA,
|
|
&unmap_cmd));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_HARNESS_MAIN
|