mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Merge tag 'vfio-v6.18-rc6' of https://github.com/awilliam/linux-vfio
Pull VFIO seftest fixes from Alex Williamson: - Fix vfio selftests to remove the expectation that the IOMMU supports a 64-bit IOVA space. These manifest both in the original set of tests introduced this development cycle in identity mapping the IOVA to buffer virtual address space, as well as the more recent boundary testing. Implement facilities for collecting the valid IOVA ranges from the backend, implement a simple IOVA allocator, and use the information for determining extents (Alex Mastro) * tag 'vfio-v6.18-rc6' of https://github.com/awilliam/linux-vfio: vfio: selftests: replace iova=vaddr with allocated iovas vfio: selftests: add iova allocator vfio: selftests: fix map limit tests to use last available iova vfio: selftests: add iova range query helpers
This commit is contained in:
@@ -4,9 +4,12 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <linux/vfio.h>
|
||||
|
||||
#include <uapi/linux/types.h>
|
||||
#include <linux/iommufd.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/pci_regs.h>
|
||||
#include <linux/vfio.h>
|
||||
|
||||
#include "../../../kselftest.h"
|
||||
|
||||
@@ -185,6 +188,13 @@ struct vfio_pci_device {
|
||||
struct vfio_pci_driver driver;
|
||||
};
|
||||
|
||||
struct iova_allocator {
|
||||
struct iommu_iova_range *ranges;
|
||||
u32 nranges;
|
||||
u32 range_idx;
|
||||
u64 range_offset;
|
||||
};
|
||||
|
||||
/*
|
||||
* Return the BDF string of the device that the test should use.
|
||||
*
|
||||
@@ -206,6 +216,13 @@ struct vfio_pci_device *vfio_pci_device_init(const char *bdf, const char *iommu_
|
||||
void vfio_pci_device_cleanup(struct vfio_pci_device *device);
|
||||
void vfio_pci_device_reset(struct vfio_pci_device *device);
|
||||
|
||||
struct iommu_iova_range *vfio_pci_iova_ranges(struct vfio_pci_device *device,
|
||||
u32 *nranges);
|
||||
|
||||
struct iova_allocator *iova_allocator_init(struct vfio_pci_device *device);
|
||||
void iova_allocator_cleanup(struct iova_allocator *allocator);
|
||||
iova_t iova_allocator_alloc(struct iova_allocator *allocator, size_t size);
|
||||
|
||||
int __vfio_pci_dma_map(struct vfio_pci_device *device,
|
||||
struct vfio_dma_region *region);
|
||||
int __vfio_pci_dma_unmap(struct vfio_pci_device *device,
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <uapi/linux/types.h>
|
||||
#include <linux/iommufd.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/vfio.h>
|
||||
#include <linux/iommufd.h>
|
||||
|
||||
#include "../../../kselftest.h"
|
||||
#include <vfio_util.h>
|
||||
@@ -29,6 +30,249 @@
|
||||
VFIO_ASSERT_EQ(__ret, 0, "ioctl(%s, %s, %s) returned %d\n", #_fd, #_op, #_arg, __ret); \
|
||||
} while (0)
|
||||
|
||||
static struct vfio_info_cap_header *next_cap_hdr(void *buf, u32 bufsz,
|
||||
u32 *cap_offset)
|
||||
{
|
||||
struct vfio_info_cap_header *hdr;
|
||||
|
||||
if (!*cap_offset)
|
||||
return NULL;
|
||||
|
||||
VFIO_ASSERT_LT(*cap_offset, bufsz);
|
||||
VFIO_ASSERT_GE(bufsz - *cap_offset, sizeof(*hdr));
|
||||
|
||||
hdr = (struct vfio_info_cap_header *)((u8 *)buf + *cap_offset);
|
||||
*cap_offset = hdr->next;
|
||||
|
||||
return hdr;
|
||||
}
|
||||
|
||||
static struct vfio_info_cap_header *vfio_iommu_info_cap_hdr(struct vfio_iommu_type1_info *info,
|
||||
u16 cap_id)
|
||||
{
|
||||
struct vfio_info_cap_header *hdr;
|
||||
u32 cap_offset = info->cap_offset;
|
||||
u32 max_depth;
|
||||
u32 depth = 0;
|
||||
|
||||
if (!(info->flags & VFIO_IOMMU_INFO_CAPS))
|
||||
return NULL;
|
||||
|
||||
if (cap_offset)
|
||||
VFIO_ASSERT_GE(cap_offset, sizeof(*info));
|
||||
|
||||
max_depth = (info->argsz - sizeof(*info)) / sizeof(*hdr);
|
||||
|
||||
while ((hdr = next_cap_hdr(info, info->argsz, &cap_offset))) {
|
||||
depth++;
|
||||
VFIO_ASSERT_LE(depth, max_depth, "Capability chain contains a cycle\n");
|
||||
|
||||
if (hdr->id == cap_id)
|
||||
return hdr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return buffer including capability chain, if present. Free with free() */
|
||||
static struct vfio_iommu_type1_info *vfio_iommu_get_info(struct vfio_pci_device *device)
|
||||
{
|
||||
struct vfio_iommu_type1_info *info;
|
||||
|
||||
info = malloc(sizeof(*info));
|
||||
VFIO_ASSERT_NOT_NULL(info);
|
||||
|
||||
*info = (struct vfio_iommu_type1_info) {
|
||||
.argsz = sizeof(*info),
|
||||
};
|
||||
|
||||
ioctl_assert(device->container_fd, VFIO_IOMMU_GET_INFO, info);
|
||||
VFIO_ASSERT_GE(info->argsz, sizeof(*info));
|
||||
|
||||
info = realloc(info, info->argsz);
|
||||
VFIO_ASSERT_NOT_NULL(info);
|
||||
|
||||
ioctl_assert(device->container_fd, VFIO_IOMMU_GET_INFO, info);
|
||||
VFIO_ASSERT_GE(info->argsz, sizeof(*info));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return iova ranges for the device's container. Normalize vfio_iommu_type1 to
|
||||
* report iommufd's iommu_iova_range. Free with free().
|
||||
*/
|
||||
static struct iommu_iova_range *vfio_iommu_iova_ranges(struct vfio_pci_device *device,
|
||||
u32 *nranges)
|
||||
{
|
||||
struct vfio_iommu_type1_info_cap_iova_range *cap_range;
|
||||
struct vfio_iommu_type1_info *info;
|
||||
struct vfio_info_cap_header *hdr;
|
||||
struct iommu_iova_range *ranges = NULL;
|
||||
|
||||
info = vfio_iommu_get_info(device);
|
||||
hdr = vfio_iommu_info_cap_hdr(info, VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE);
|
||||
VFIO_ASSERT_NOT_NULL(hdr);
|
||||
|
||||
cap_range = container_of(hdr, struct vfio_iommu_type1_info_cap_iova_range, header);
|
||||
VFIO_ASSERT_GT(cap_range->nr_iovas, 0);
|
||||
|
||||
ranges = calloc(cap_range->nr_iovas, sizeof(*ranges));
|
||||
VFIO_ASSERT_NOT_NULL(ranges);
|
||||
|
||||
for (u32 i = 0; i < cap_range->nr_iovas; i++) {
|
||||
ranges[i] = (struct iommu_iova_range){
|
||||
.start = cap_range->iova_ranges[i].start,
|
||||
.last = cap_range->iova_ranges[i].end,
|
||||
};
|
||||
}
|
||||
|
||||
*nranges = cap_range->nr_iovas;
|
||||
|
||||
free(info);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/* Return iova ranges of the device's IOAS. Free with free() */
|
||||
static struct iommu_iova_range *iommufd_iova_ranges(struct vfio_pci_device *device,
|
||||
u32 *nranges)
|
||||
{
|
||||
struct iommu_iova_range *ranges;
|
||||
int ret;
|
||||
|
||||
struct iommu_ioas_iova_ranges query = {
|
||||
.size = sizeof(query),
|
||||
.ioas_id = device->ioas_id,
|
||||
};
|
||||
|
||||
ret = ioctl(device->iommufd, IOMMU_IOAS_IOVA_RANGES, &query);
|
||||
VFIO_ASSERT_EQ(ret, -1);
|
||||
VFIO_ASSERT_EQ(errno, EMSGSIZE);
|
||||
VFIO_ASSERT_GT(query.num_iovas, 0);
|
||||
|
||||
ranges = calloc(query.num_iovas, sizeof(*ranges));
|
||||
VFIO_ASSERT_NOT_NULL(ranges);
|
||||
|
||||
query.allowed_iovas = (uintptr_t)ranges;
|
||||
|
||||
ioctl_assert(device->iommufd, IOMMU_IOAS_IOVA_RANGES, &query);
|
||||
*nranges = query.num_iovas;
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
static int iova_range_comp(const void *a, const void *b)
|
||||
{
|
||||
const struct iommu_iova_range *ra = a, *rb = b;
|
||||
|
||||
if (ra->start < rb->start)
|
||||
return -1;
|
||||
|
||||
if (ra->start > rb->start)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return sorted IOVA ranges of the device. Free with free(). */
|
||||
struct iommu_iova_range *vfio_pci_iova_ranges(struct vfio_pci_device *device,
|
||||
u32 *nranges)
|
||||
{
|
||||
struct iommu_iova_range *ranges;
|
||||
|
||||
if (device->iommufd)
|
||||
ranges = iommufd_iova_ranges(device, nranges);
|
||||
else
|
||||
ranges = vfio_iommu_iova_ranges(device, nranges);
|
||||
|
||||
if (!ranges)
|
||||
return NULL;
|
||||
|
||||
VFIO_ASSERT_GT(*nranges, 0);
|
||||
|
||||
/* Sort and check that ranges are sane and non-overlapping */
|
||||
qsort(ranges, *nranges, sizeof(*ranges), iova_range_comp);
|
||||
VFIO_ASSERT_LT(ranges[0].start, ranges[0].last);
|
||||
|
||||
for (u32 i = 1; i < *nranges; i++) {
|
||||
VFIO_ASSERT_LT(ranges[i].start, ranges[i].last);
|
||||
VFIO_ASSERT_LT(ranges[i - 1].last, ranges[i].start);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
struct iova_allocator *iova_allocator_init(struct vfio_pci_device *device)
|
||||
{
|
||||
struct iova_allocator *allocator;
|
||||
struct iommu_iova_range *ranges;
|
||||
u32 nranges;
|
||||
|
||||
ranges = vfio_pci_iova_ranges(device, &nranges);
|
||||
VFIO_ASSERT_NOT_NULL(ranges);
|
||||
|
||||
allocator = malloc(sizeof(*allocator));
|
||||
VFIO_ASSERT_NOT_NULL(allocator);
|
||||
|
||||
*allocator = (struct iova_allocator){
|
||||
.ranges = ranges,
|
||||
.nranges = nranges,
|
||||
.range_idx = 0,
|
||||
.range_offset = 0,
|
||||
};
|
||||
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void iova_allocator_cleanup(struct iova_allocator *allocator)
|
||||
{
|
||||
free(allocator->ranges);
|
||||
free(allocator);
|
||||
}
|
||||
|
||||
iova_t iova_allocator_alloc(struct iova_allocator *allocator, size_t size)
|
||||
{
|
||||
VFIO_ASSERT_GT(size, 0, "Invalid size arg, zero\n");
|
||||
VFIO_ASSERT_EQ(size & (size - 1), 0, "Invalid size arg, non-power-of-2\n");
|
||||
|
||||
for (;;) {
|
||||
struct iommu_iova_range *range;
|
||||
iova_t iova, last;
|
||||
|
||||
VFIO_ASSERT_LT(allocator->range_idx, allocator->nranges,
|
||||
"IOVA allocator out of space\n");
|
||||
|
||||
range = &allocator->ranges[allocator->range_idx];
|
||||
iova = range->start + allocator->range_offset;
|
||||
|
||||
/* Check for sufficient space at the current offset */
|
||||
if (check_add_overflow(iova, size - 1, &last) ||
|
||||
last > range->last)
|
||||
goto next_range;
|
||||
|
||||
/* Align iova to size */
|
||||
iova = last & ~(size - 1);
|
||||
|
||||
/* Check for sufficient space at the aligned iova */
|
||||
if (check_add_overflow(iova, size - 1, &last) ||
|
||||
last > range->last)
|
||||
goto next_range;
|
||||
|
||||
if (last == range->last) {
|
||||
allocator->range_idx++;
|
||||
allocator->range_offset = 0;
|
||||
} else {
|
||||
allocator->range_offset = last - range->start + 1;
|
||||
}
|
||||
|
||||
return iova;
|
||||
|
||||
next_range:
|
||||
allocator->range_idx++;
|
||||
allocator->range_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
iova_t __to_iova(struct vfio_pci_device *device, void *vaddr)
|
||||
{
|
||||
struct vfio_dma_region *region;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <uapi/linux/types.h>
|
||||
#include <linux/iommufd.h>
|
||||
#include <linux/limits.h>
|
||||
#include <linux/mman.h>
|
||||
#include <linux/sizes.h>
|
||||
@@ -93,6 +95,7 @@ static int iommu_mapping_get(const char *bdf, u64 iova,
|
||||
|
||||
FIXTURE(vfio_dma_mapping_test) {
|
||||
struct vfio_pci_device *device;
|
||||
struct iova_allocator *iova_allocator;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT(vfio_dma_mapping_test) {
|
||||
@@ -117,10 +120,12 @@ FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, SZ_1G, MAP_HUGETLB |
|
||||
FIXTURE_SETUP(vfio_dma_mapping_test)
|
||||
{
|
||||
self->device = vfio_pci_device_init(device_bdf, variant->iommu_mode);
|
||||
self->iova_allocator = iova_allocator_init(self->device);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(vfio_dma_mapping_test)
|
||||
{
|
||||
iova_allocator_cleanup(self->iova_allocator);
|
||||
vfio_pci_device_cleanup(self->device);
|
||||
}
|
||||
|
||||
@@ -142,7 +147,7 @@ TEST_F(vfio_dma_mapping_test, dma_map_unmap)
|
||||
else
|
||||
ASSERT_NE(region.vaddr, MAP_FAILED);
|
||||
|
||||
region.iova = (u64)region.vaddr;
|
||||
region.iova = iova_allocator_alloc(self->iova_allocator, size);
|
||||
region.size = size;
|
||||
|
||||
vfio_pci_dma_map(self->device, ®ion);
|
||||
@@ -219,7 +224,10 @@ FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES();
|
||||
FIXTURE_SETUP(vfio_dma_map_limit_test)
|
||||
{
|
||||
struct vfio_dma_region *region = &self->region;
|
||||
struct iommu_iova_range *ranges;
|
||||
u64 region_size = getpagesize();
|
||||
iova_t last_iova;
|
||||
u32 nranges;
|
||||
|
||||
/*
|
||||
* Over-allocate mmap by double the size to provide enough backing vaddr
|
||||
@@ -232,8 +240,13 @@ FIXTURE_SETUP(vfio_dma_map_limit_test)
|
||||
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
ASSERT_NE(region->vaddr, MAP_FAILED);
|
||||
|
||||
/* One page prior to the end of address space */
|
||||
region->iova = ~(iova_t)0 & ~(region_size - 1);
|
||||
ranges = vfio_pci_iova_ranges(self->device, &nranges);
|
||||
VFIO_ASSERT_NOT_NULL(ranges);
|
||||
last_iova = ranges[nranges - 1].last;
|
||||
free(ranges);
|
||||
|
||||
/* One page prior to the last iova */
|
||||
region->iova = last_iova & ~(region_size - 1);
|
||||
region->size = region_size;
|
||||
}
|
||||
|
||||
@@ -276,6 +289,7 @@ TEST_F(vfio_dma_map_limit_test, overflow)
|
||||
struct vfio_dma_region *region = &self->region;
|
||||
int rc;
|
||||
|
||||
region->iova = ~(iova_t)0 & ~(region->size - 1);
|
||||
region->size = self->mmap_size;
|
||||
|
||||
rc = __vfio_pci_dma_map(self->device, region);
|
||||
|
||||
@@ -19,6 +19,7 @@ static const char *device_bdf;
|
||||
} while (0)
|
||||
|
||||
static void region_setup(struct vfio_pci_device *device,
|
||||
struct iova_allocator *iova_allocator,
|
||||
struct vfio_dma_region *region, u64 size)
|
||||
{
|
||||
const int flags = MAP_SHARED | MAP_ANONYMOUS;
|
||||
@@ -29,7 +30,7 @@ static void region_setup(struct vfio_pci_device *device,
|
||||
VFIO_ASSERT_NE(vaddr, MAP_FAILED);
|
||||
|
||||
region->vaddr = vaddr;
|
||||
region->iova = (u64)vaddr;
|
||||
region->iova = iova_allocator_alloc(iova_allocator, size);
|
||||
region->size = size;
|
||||
|
||||
vfio_pci_dma_map(device, region);
|
||||
@@ -44,6 +45,7 @@ static void region_teardown(struct vfio_pci_device *device,
|
||||
|
||||
FIXTURE(vfio_pci_driver_test) {
|
||||
struct vfio_pci_device *device;
|
||||
struct iova_allocator *iova_allocator;
|
||||
struct vfio_dma_region memcpy_region;
|
||||
void *vaddr;
|
||||
int msi_fd;
|
||||
@@ -72,14 +74,15 @@ FIXTURE_SETUP(vfio_pci_driver_test)
|
||||
struct vfio_pci_driver *driver;
|
||||
|
||||
self->device = vfio_pci_device_init(device_bdf, variant->iommu_mode);
|
||||
self->iova_allocator = iova_allocator_init(self->device);
|
||||
|
||||
driver = &self->device->driver;
|
||||
|
||||
region_setup(self->device, &self->memcpy_region, SZ_1G);
|
||||
region_setup(self->device, &driver->region, SZ_2M);
|
||||
region_setup(self->device, self->iova_allocator, &self->memcpy_region, SZ_1G);
|
||||
region_setup(self->device, self->iova_allocator, &driver->region, SZ_2M);
|
||||
|
||||
/* Any IOVA that doesn't overlap memcpy_region and driver->region. */
|
||||
self->unmapped_iova = 8UL * SZ_1G;
|
||||
self->unmapped_iova = iova_allocator_alloc(self->iova_allocator, SZ_1G);
|
||||
|
||||
vfio_pci_driver_init(self->device);
|
||||
self->msi_fd = self->device->msi_eventfds[driver->msi];
|
||||
@@ -108,6 +111,7 @@ FIXTURE_TEARDOWN(vfio_pci_driver_test)
|
||||
region_teardown(self->device, &self->memcpy_region);
|
||||
region_teardown(self->device, &driver->region);
|
||||
|
||||
iova_allocator_cleanup(self->iova_allocator);
|
||||
vfio_pci_device_cleanup(self->device);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user