Skip to content

Commit 7c44656

Browse files
Alex Mastroawilliam
authored andcommitted
vfio: selftests: add iova range query helpers
VFIO selftests need to map IOVAs from legally accessible ranges, which could vary between hardware. Tests in vfio_dma_mapping_test.c are making excessively strong assumptions about which IOVAs can be mapped. Add vfio_iommu_iova_ranges(), which queries IOVA ranges from the IOMMUFD or VFIO container associated with the device. The queried ranges are normalized to IOMMUFD's iommu_iova_range representation so that handling of IOVA ranges up the stack can be implementation-agnostic. iommu_iova_range and vfio_iova_range are equivalent, so bias to using the new interface's struct. Query IOMMUFD's ranges with IOMMU_IOAS_IOVA_RANGES. Query VFIO container's ranges with VFIO_IOMMU_GET_INFO and VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE. The underlying vfio_iommu_type1_info buffer-related functionality has been kept generic so the same helpers can be used to query other capability chain information, if needed. Reviewed-by: David Matlack <[email protected]> Tested-by: David Matlack <[email protected]> Signed-off-by: Alex Mastro <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alex Williamson <[email protected]>
1 parent 6146a0f commit 7c44656

2 files changed

Lines changed: 179 additions & 1 deletion

File tree

tools/testing/selftests/vfio/lib/include/vfio_util.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
#include <fcntl.h>
66
#include <string.h>
7-
#include <linux/vfio.h>
7+
8+
#include <uapi/linux/types.h>
9+
#include <linux/iommufd.h>
810
#include <linux/list.h>
911
#include <linux/pci_regs.h>
12+
#include <linux/vfio.h>
1013

1114
#include "../../../kselftest.h"
1215

@@ -206,6 +209,9 @@ struct vfio_pci_device *vfio_pci_device_init(const char *bdf, const char *iommu_
206209
void vfio_pci_device_cleanup(struct vfio_pci_device *device);
207210
void vfio_pci_device_reset(struct vfio_pci_device *device);
208211

212+
struct iommu_iova_range *vfio_pci_iova_ranges(struct vfio_pci_device *device,
213+
u32 *nranges);
214+
209215
int __vfio_pci_dma_map(struct vfio_pci_device *device,
210216
struct vfio_dma_region *region);
211217
int __vfio_pci_dma_unmap(struct vfio_pci_device *device,

tools/testing/selftests/vfio/lib/vfio_pci_device.c

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,178 @@
2929
VFIO_ASSERT_EQ(__ret, 0, "ioctl(%s, %s, %s) returned %d\n", #_fd, #_op, #_arg, __ret); \
3030
} while (0)
3131

32+
static struct vfio_info_cap_header *next_cap_hdr(void *buf, u32 bufsz,
33+
u32 *cap_offset)
34+
{
35+
struct vfio_info_cap_header *hdr;
36+
37+
if (!*cap_offset)
38+
return NULL;
39+
40+
VFIO_ASSERT_LT(*cap_offset, bufsz);
41+
VFIO_ASSERT_GE(bufsz - *cap_offset, sizeof(*hdr));
42+
43+
hdr = (struct vfio_info_cap_header *)((u8 *)buf + *cap_offset);
44+
*cap_offset = hdr->next;
45+
46+
return hdr;
47+
}
48+
49+
static struct vfio_info_cap_header *vfio_iommu_info_cap_hdr(struct vfio_iommu_type1_info *info,
50+
u16 cap_id)
51+
{
52+
struct vfio_info_cap_header *hdr;
53+
u32 cap_offset = info->cap_offset;
54+
u32 max_depth;
55+
u32 depth = 0;
56+
57+
if (!(info->flags & VFIO_IOMMU_INFO_CAPS))
58+
return NULL;
59+
60+
if (cap_offset)
61+
VFIO_ASSERT_GE(cap_offset, sizeof(*info));
62+
63+
max_depth = (info->argsz - sizeof(*info)) / sizeof(*hdr);
64+
65+
while ((hdr = next_cap_hdr(info, info->argsz, &cap_offset))) {
66+
depth++;
67+
VFIO_ASSERT_LE(depth, max_depth, "Capability chain contains a cycle\n");
68+
69+
if (hdr->id == cap_id)
70+
return hdr;
71+
}
72+
73+
return NULL;
74+
}
75+
76+
/* Return buffer including capability chain, if present. Free with free() */
77+
static struct vfio_iommu_type1_info *vfio_iommu_get_info(struct vfio_pci_device *device)
78+
{
79+
struct vfio_iommu_type1_info *info;
80+
81+
info = malloc(sizeof(*info));
82+
VFIO_ASSERT_NOT_NULL(info);
83+
84+
*info = (struct vfio_iommu_type1_info) {
85+
.argsz = sizeof(*info),
86+
};
87+
88+
ioctl_assert(device->container_fd, VFIO_IOMMU_GET_INFO, info);
89+
VFIO_ASSERT_GE(info->argsz, sizeof(*info));
90+
91+
info = realloc(info, info->argsz);
92+
VFIO_ASSERT_NOT_NULL(info);
93+
94+
ioctl_assert(device->container_fd, VFIO_IOMMU_GET_INFO, info);
95+
VFIO_ASSERT_GE(info->argsz, sizeof(*info));
96+
97+
return info;
98+
}
99+
100+
/*
101+
* Return iova ranges for the device's container. Normalize vfio_iommu_type1 to
102+
* report iommufd's iommu_iova_range. Free with free().
103+
*/
104+
static struct iommu_iova_range *vfio_iommu_iova_ranges(struct vfio_pci_device *device,
105+
u32 *nranges)
106+
{
107+
struct vfio_iommu_type1_info_cap_iova_range *cap_range;
108+
struct vfio_iommu_type1_info *info;
109+
struct vfio_info_cap_header *hdr;
110+
struct iommu_iova_range *ranges = NULL;
111+
112+
info = vfio_iommu_get_info(device);
113+
hdr = vfio_iommu_info_cap_hdr(info, VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE);
114+
VFIO_ASSERT_NOT_NULL(hdr);
115+
116+
cap_range = container_of(hdr, struct vfio_iommu_type1_info_cap_iova_range, header);
117+
VFIO_ASSERT_GT(cap_range->nr_iovas, 0);
118+
119+
ranges = calloc(cap_range->nr_iovas, sizeof(*ranges));
120+
VFIO_ASSERT_NOT_NULL(ranges);
121+
122+
for (u32 i = 0; i < cap_range->nr_iovas; i++) {
123+
ranges[i] = (struct iommu_iova_range){
124+
.start = cap_range->iova_ranges[i].start,
125+
.last = cap_range->iova_ranges[i].end,
126+
};
127+
}
128+
129+
*nranges = cap_range->nr_iovas;
130+
131+
free(info);
132+
return ranges;
133+
}
134+
135+
/* Return iova ranges of the device's IOAS. Free with free() */
136+
static struct iommu_iova_range *iommufd_iova_ranges(struct vfio_pci_device *device,
137+
u32 *nranges)
138+
{
139+
struct iommu_iova_range *ranges;
140+
int ret;
141+
142+
struct iommu_ioas_iova_ranges query = {
143+
.size = sizeof(query),
144+
.ioas_id = device->ioas_id,
145+
};
146+
147+
ret = ioctl(device->iommufd, IOMMU_IOAS_IOVA_RANGES, &query);
148+
VFIO_ASSERT_EQ(ret, -1);
149+
VFIO_ASSERT_EQ(errno, EMSGSIZE);
150+
VFIO_ASSERT_GT(query.num_iovas, 0);
151+
152+
ranges = calloc(query.num_iovas, sizeof(*ranges));
153+
VFIO_ASSERT_NOT_NULL(ranges);
154+
155+
query.allowed_iovas = (uintptr_t)ranges;
156+
157+
ioctl_assert(device->iommufd, IOMMU_IOAS_IOVA_RANGES, &query);
158+
*nranges = query.num_iovas;
159+
160+
return ranges;
161+
}
162+
163+
static int iova_range_comp(const void *a, const void *b)
164+
{
165+
const struct iommu_iova_range *ra = a, *rb = b;
166+
167+
if (ra->start < rb->start)
168+
return -1;
169+
170+
if (ra->start > rb->start)
171+
return 1;
172+
173+
return 0;
174+
}
175+
176+
/* Return sorted IOVA ranges of the device. Free with free(). */
177+
struct iommu_iova_range *vfio_pci_iova_ranges(struct vfio_pci_device *device,
178+
u32 *nranges)
179+
{
180+
struct iommu_iova_range *ranges;
181+
182+
if (device->iommufd)
183+
ranges = iommufd_iova_ranges(device, nranges);
184+
else
185+
ranges = vfio_iommu_iova_ranges(device, nranges);
186+
187+
if (!ranges)
188+
return NULL;
189+
190+
VFIO_ASSERT_GT(*nranges, 0);
191+
192+
/* Sort and check that ranges are sane and non-overlapping */
193+
qsort(ranges, *nranges, sizeof(*ranges), iova_range_comp);
194+
VFIO_ASSERT_LT(ranges[0].start, ranges[0].last);
195+
196+
for (u32 i = 1; i < *nranges; i++) {
197+
VFIO_ASSERT_LT(ranges[i].start, ranges[i].last);
198+
VFIO_ASSERT_LT(ranges[i - 1].last, ranges[i].start);
199+
}
200+
201+
return ranges;
202+
}
203+
32204
iova_t __to_iova(struct vfio_pci_device *device, void *vaddr)
33205
{
34206
struct vfio_dma_region *region;

0 commit comments

Comments
 (0)