mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 16:44:58 +02:00
VFIO fixes for v6.18-rc6
- 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)
-----BEGIN PGP SIGNATURE-----
iQJFBAABCgAvFiEEQvbATlQL0amee4qQI5ubbjuwiyIFAmkWXwYRHGFsZXhAc2hh
emJvdC5vcmcACgkQI5ubbjuwiyK96w//ep97M1B5RSA4/AIP4j+fovnlM2RVUrPy
RKxVYK+rzftO4Hq8sg1Co3aB68hItbYiw/0BDUgQBFFCEoInQ9zht89tsHhuXq55
lYq9hyYMnp60FLzpX8Fi2HKf8SKTdD+6P1hX5UWZfYExc/tCD/9FPE2QsfUTbnh/
u2aguEp0RhgC8xCcR/5EsGFzcBgioDcTBQq5UQ/5By51nUNvdmHKRHKRC7ZmQfiK
bwFpYseWHRdzdBH2KggNaDZUh0q7Y9+cwL2UndQV5IfwGhGWODztOsVhhoxAAu0n
c6gXYDg34i5i1epPrr8Yvv8trDzMFCRYJi4g8BsLPOBXe8Lr2bWj/4wA1ftdW6Ha
0ii4Gx1UO1vz0KZQBajMHv5pO06lJZshzEQgHr3sYgjaRg8AVTV6hI3IVaty2JWk
DPKPNSGKhjkWvWnx73KFiiepz5zXEL7r7ooFBimtBwFr/5bDB8hfPjpXMh+mhiZu
QW6DV+Mrqg1G1KDCwdHSvbBwH8IvfX/tQbs9eKvGt+6ICpH1GJ1cUr58vjcc6ESi
sJzKI0ceTjGZAhHJogO4xAzhI8H6kwtDHB1ZC9QLHIwbkXFN1m64VjOwwXuHk0HT
559gjqjH0yKNSABwRMV1nKzn9EFnivQn1tAoGkUGSFE9ZI/VfCNNlspPziZEz0z9
u14SILGMqWU=
=ywvT
-----END PGP SIGNATURE-----
Merge tag 'vfio-v6.18-rc6' into v6.19/vfio/next
Merge mainline vfio-selftest updates for ongoing v6.19 work.
Signed-off-by: Alex Williamson <alex@shazbot.org>
This commit is contained in:
commit
a63a03afd8
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user