1b477e7bcSJosh Hilke // SPDX-License-Identifier: GPL-2.0-only
2751f6b5dSJosh Hilke #include <stdio.h>
3b477e7bcSJosh Hilke #include <sys/mman.h>
4751f6b5dSJosh Hilke #include <unistd.h>
5b477e7bcSJosh Hilke
6751f6b5dSJosh Hilke #include <linux/limits.h>
7751f6b5dSJosh Hilke #include <linux/mman.h>
8b477e7bcSJosh Hilke #include <linux/sizes.h>
9b477e7bcSJosh Hilke #include <linux/vfio.h>
10b477e7bcSJosh Hilke
11b477e7bcSJosh Hilke #include <vfio_util.h>
12b477e7bcSJosh Hilke
13b477e7bcSJosh Hilke #include "../kselftest_harness.h"
14b477e7bcSJosh Hilke
15b477e7bcSJosh Hilke static const char *device_bdf;
16b477e7bcSJosh Hilke
1747f86104SJosh Hilke struct iommu_mapping {
1847f86104SJosh Hilke u64 pgd;
1947f86104SJosh Hilke u64 p4d;
2047f86104SJosh Hilke u64 pud;
2147f86104SJosh Hilke u64 pmd;
2247f86104SJosh Hilke u64 pte;
2347f86104SJosh Hilke };
2447f86104SJosh Hilke
parse_next_value(char ** line,u64 * value)2547f86104SJosh Hilke static void parse_next_value(char **line, u64 *value)
2647f86104SJosh Hilke {
2747f86104SJosh Hilke char *token;
2847f86104SJosh Hilke
2947f86104SJosh Hilke token = strtok_r(*line, " \t|\n", line);
3047f86104SJosh Hilke if (!token)
3147f86104SJosh Hilke return;
3247f86104SJosh Hilke
3347f86104SJosh Hilke /* Caller verifies `value`. No need to check return value. */
3447f86104SJosh Hilke sscanf(token, "0x%lx", value);
3547f86104SJosh Hilke }
3647f86104SJosh Hilke
intel_iommu_mapping_get(const char * bdf,u64 iova,struct iommu_mapping * mapping)3747f86104SJosh Hilke static int intel_iommu_mapping_get(const char *bdf, u64 iova,
3847f86104SJosh Hilke struct iommu_mapping *mapping)
3947f86104SJosh Hilke {
4047f86104SJosh Hilke char iommu_mapping_path[PATH_MAX], line[PATH_MAX];
4147f86104SJosh Hilke u64 line_iova = -1;
4247f86104SJosh Hilke int ret = -ENOENT;
4347f86104SJosh Hilke FILE *file;
4447f86104SJosh Hilke char *rest;
4547f86104SJosh Hilke
4647f86104SJosh Hilke snprintf(iommu_mapping_path, sizeof(iommu_mapping_path),
4747f86104SJosh Hilke "/sys/kernel/debug/iommu/intel/%s/domain_translation_struct",
4847f86104SJosh Hilke bdf);
4947f86104SJosh Hilke
5047f86104SJosh Hilke printf("Searching for IOVA 0x%lx in %s\n", iova, iommu_mapping_path);
5147f86104SJosh Hilke
5247f86104SJosh Hilke file = fopen(iommu_mapping_path, "r");
5347f86104SJosh Hilke VFIO_ASSERT_NOT_NULL(file, "fopen(%s) failed", iommu_mapping_path);
5447f86104SJosh Hilke
5547f86104SJosh Hilke while (fgets(line, sizeof(line), file)) {
5647f86104SJosh Hilke rest = line;
5747f86104SJosh Hilke
5847f86104SJosh Hilke parse_next_value(&rest, &line_iova);
5947f86104SJosh Hilke if (line_iova != (iova / getpagesize()))
6047f86104SJosh Hilke continue;
6147f86104SJosh Hilke
6247f86104SJosh Hilke /*
6347f86104SJosh Hilke * Ensure each struct field is initialized in case of empty
6447f86104SJosh Hilke * page table values.
6547f86104SJosh Hilke */
6647f86104SJosh Hilke memset(mapping, 0, sizeof(*mapping));
6747f86104SJosh Hilke parse_next_value(&rest, &mapping->pgd);
6847f86104SJosh Hilke parse_next_value(&rest, &mapping->p4d);
6947f86104SJosh Hilke parse_next_value(&rest, &mapping->pud);
7047f86104SJosh Hilke parse_next_value(&rest, &mapping->pmd);
7147f86104SJosh Hilke parse_next_value(&rest, &mapping->pte);
7247f86104SJosh Hilke
7347f86104SJosh Hilke ret = 0;
7447f86104SJosh Hilke break;
7547f86104SJosh Hilke }
7647f86104SJosh Hilke
7747f86104SJosh Hilke fclose(file);
7847f86104SJosh Hilke
7947f86104SJosh Hilke if (ret)
8047f86104SJosh Hilke printf("IOVA not found\n");
8147f86104SJosh Hilke
8247f86104SJosh Hilke return ret;
8347f86104SJosh Hilke }
8447f86104SJosh Hilke
iommu_mapping_get(const char * bdf,u64 iova,struct iommu_mapping * mapping)8547f86104SJosh Hilke static int iommu_mapping_get(const char *bdf, u64 iova,
8647f86104SJosh Hilke struct iommu_mapping *mapping)
8747f86104SJosh Hilke {
8847f86104SJosh Hilke if (!access("/sys/kernel/debug/iommu/intel", F_OK))
8947f86104SJosh Hilke return intel_iommu_mapping_get(bdf, iova, mapping);
9047f86104SJosh Hilke
9147f86104SJosh Hilke return -EOPNOTSUPP;
9247f86104SJosh Hilke }
9347f86104SJosh Hilke
FIXTURE(vfio_dma_mapping_test)94b477e7bcSJosh Hilke FIXTURE(vfio_dma_mapping_test) {
95b477e7bcSJosh Hilke struct vfio_pci_device *device;
96b477e7bcSJosh Hilke };
97b477e7bcSJosh Hilke
FIXTURE_VARIANT(vfio_dma_mapping_test)98751f6b5dSJosh Hilke FIXTURE_VARIANT(vfio_dma_mapping_test) {
99892aff14SDavid Matlack const char *iommu_mode;
100751f6b5dSJosh Hilke u64 size;
101751f6b5dSJosh Hilke int mmap_flags;
102751f6b5dSJosh Hilke };
103751f6b5dSJosh Hilke
104892aff14SDavid Matlack #define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode, _name, _size, _mmap_flags) \
105892aff14SDavid Matlack FIXTURE_VARIANT_ADD(vfio_dma_mapping_test, _iommu_mode ## _ ## _name) { \
106892aff14SDavid Matlack .iommu_mode = #_iommu_mode, \
107892aff14SDavid Matlack .size = (_size), \
108892aff14SDavid Matlack .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE | (_mmap_flags), \
109892aff14SDavid Matlack }
110751f6b5dSJosh Hilke
111892aff14SDavid Matlack FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous, 0, 0);
112892aff14SDavid Matlack FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_2mb, SZ_2M, MAP_HUGETLB | MAP_HUGE_2MB);
113892aff14SDavid Matlack FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, SZ_1G, MAP_HUGETLB | MAP_HUGE_1GB);
114751f6b5dSJosh Hilke
115*de8d1f2fSAlex Mastro #undef FIXTURE_VARIANT_ADD_IOMMU_MODE
116*de8d1f2fSAlex Mastro
FIXTURE_SETUP(vfio_dma_mapping_test)117b477e7bcSJosh Hilke FIXTURE_SETUP(vfio_dma_mapping_test)
118b477e7bcSJosh Hilke {
119892aff14SDavid Matlack self->device = vfio_pci_device_init(device_bdf, variant->iommu_mode);
120b477e7bcSJosh Hilke }
121b477e7bcSJosh Hilke
FIXTURE_TEARDOWN(vfio_dma_mapping_test)122b477e7bcSJosh Hilke FIXTURE_TEARDOWN(vfio_dma_mapping_test)
123b477e7bcSJosh Hilke {
124b477e7bcSJosh Hilke vfio_pci_device_cleanup(self->device);
125b477e7bcSJosh Hilke }
126b477e7bcSJosh Hilke
TEST_F(vfio_dma_mapping_test,dma_map_unmap)127b477e7bcSJosh Hilke TEST_F(vfio_dma_mapping_test, dma_map_unmap)
128b477e7bcSJosh Hilke {
129751f6b5dSJosh Hilke const u64 size = variant->size ?: getpagesize();
130751f6b5dSJosh Hilke const int flags = variant->mmap_flags;
131346cd58fSDavid Matlack struct vfio_dma_region region;
13247f86104SJosh Hilke struct iommu_mapping mapping;
133d1a17495SDavid Matlack u64 mapping_size = size;
13416950b60SAlex Mastro u64 unmapped;
13547f86104SJosh Hilke int rc;
136b477e7bcSJosh Hilke
137346cd58fSDavid Matlack region.vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0);
138751f6b5dSJosh Hilke
139751f6b5dSJosh Hilke /* Skip the test if there aren't enough HugeTLB pages available. */
140346cd58fSDavid Matlack if (flags & MAP_HUGETLB && region.vaddr == MAP_FAILED)
141751f6b5dSJosh Hilke SKIP(return, "mmap() failed: %s (%d)\n", strerror(errno), errno);
142751f6b5dSJosh Hilke else
143346cd58fSDavid Matlack ASSERT_NE(region.vaddr, MAP_FAILED);
144b477e7bcSJosh Hilke
145346cd58fSDavid Matlack region.iova = (u64)region.vaddr;
146346cd58fSDavid Matlack region.size = size;
147b477e7bcSJosh Hilke
148346cd58fSDavid Matlack vfio_pci_dma_map(self->device, ®ion);
149346cd58fSDavid Matlack printf("Mapped HVA %p (size 0x%lx) at IOVA 0x%lx\n", region.vaddr, size, region.iova);
150751f6b5dSJosh Hilke
151346cd58fSDavid Matlack ASSERT_EQ(region.iova, to_iova(self->device, region.vaddr));
152346cd58fSDavid Matlack
153346cd58fSDavid Matlack rc = iommu_mapping_get(device_bdf, region.iova, &mapping);
15447f86104SJosh Hilke if (rc == -EOPNOTSUPP)
15547f86104SJosh Hilke goto unmap;
15647f86104SJosh Hilke
157d1a17495SDavid Matlack /*
158d1a17495SDavid Matlack * IOMMUFD compatibility-mode does not support huge mappings when
159d1a17495SDavid Matlack * using VFIO_TYPE1_IOMMU.
160d1a17495SDavid Matlack */
161d1a17495SDavid Matlack if (!strcmp(variant->iommu_mode, "iommufd_compat_type1"))
162d1a17495SDavid Matlack mapping_size = SZ_4K;
163d1a17495SDavid Matlack
16447f86104SJosh Hilke ASSERT_EQ(0, rc);
165346cd58fSDavid Matlack printf("Found IOMMU mappings for IOVA 0x%lx:\n", region.iova);
16647f86104SJosh Hilke printf("PGD: 0x%016lx\n", mapping.pgd);
16747f86104SJosh Hilke printf("P4D: 0x%016lx\n", mapping.p4d);
16847f86104SJosh Hilke printf("PUD: 0x%016lx\n", mapping.pud);
16947f86104SJosh Hilke printf("PMD: 0x%016lx\n", mapping.pmd);
17047f86104SJosh Hilke printf("PTE: 0x%016lx\n", mapping.pte);
17147f86104SJosh Hilke
172d1a17495SDavid Matlack switch (mapping_size) {
17347f86104SJosh Hilke case SZ_4K:
17447f86104SJosh Hilke ASSERT_NE(0, mapping.pte);
17547f86104SJosh Hilke break;
17647f86104SJosh Hilke case SZ_2M:
17747f86104SJosh Hilke ASSERT_EQ(0, mapping.pte);
17847f86104SJosh Hilke ASSERT_NE(0, mapping.pmd);
17947f86104SJosh Hilke break;
18047f86104SJosh Hilke case SZ_1G:
18147f86104SJosh Hilke ASSERT_EQ(0, mapping.pte);
18247f86104SJosh Hilke ASSERT_EQ(0, mapping.pmd);
18347f86104SJosh Hilke ASSERT_NE(0, mapping.pud);
18447f86104SJosh Hilke break;
18547f86104SJosh Hilke default:
186d1a17495SDavid Matlack VFIO_FAIL("Unrecognized size: 0x%lx\n", mapping_size);
18747f86104SJosh Hilke }
18847f86104SJosh Hilke
18947f86104SJosh Hilke unmap:
19016950b60SAlex Mastro rc = __vfio_pci_dma_unmap(self->device, ®ion, &unmapped);
19116950b60SAlex Mastro ASSERT_EQ(rc, 0);
19216950b60SAlex Mastro ASSERT_EQ(unmapped, region.size);
193346cd58fSDavid Matlack printf("Unmapped IOVA 0x%lx\n", region.iova);
194346cd58fSDavid Matlack ASSERT_EQ(INVALID_IOVA, __to_iova(self->device, region.vaddr));
195346cd58fSDavid Matlack ASSERT_NE(0, iommu_mapping_get(device_bdf, region.iova, &mapping));
196b477e7bcSJosh Hilke
197346cd58fSDavid Matlack ASSERT_TRUE(!munmap(region.vaddr, size));
198b477e7bcSJosh Hilke }
199b477e7bcSJosh Hilke
FIXTURE(vfio_dma_map_limit_test)200*de8d1f2fSAlex Mastro FIXTURE(vfio_dma_map_limit_test) {
201*de8d1f2fSAlex Mastro struct vfio_pci_device *device;
202*de8d1f2fSAlex Mastro struct vfio_dma_region region;
203*de8d1f2fSAlex Mastro size_t mmap_size;
204*de8d1f2fSAlex Mastro };
205*de8d1f2fSAlex Mastro
FIXTURE_VARIANT(vfio_dma_map_limit_test)206*de8d1f2fSAlex Mastro FIXTURE_VARIANT(vfio_dma_map_limit_test) {
207*de8d1f2fSAlex Mastro const char *iommu_mode;
208*de8d1f2fSAlex Mastro };
209*de8d1f2fSAlex Mastro
210*de8d1f2fSAlex Mastro #define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode) \
211*de8d1f2fSAlex Mastro FIXTURE_VARIANT_ADD(vfio_dma_map_limit_test, _iommu_mode) { \
212*de8d1f2fSAlex Mastro .iommu_mode = #_iommu_mode, \
213*de8d1f2fSAlex Mastro }
214*de8d1f2fSAlex Mastro
215*de8d1f2fSAlex Mastro FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES();
216*de8d1f2fSAlex Mastro
217*de8d1f2fSAlex Mastro #undef FIXTURE_VARIANT_ADD_IOMMU_MODE
218*de8d1f2fSAlex Mastro
FIXTURE_SETUP(vfio_dma_map_limit_test)219*de8d1f2fSAlex Mastro FIXTURE_SETUP(vfio_dma_map_limit_test)
220*de8d1f2fSAlex Mastro {
221*de8d1f2fSAlex Mastro struct vfio_dma_region *region = &self->region;
222*de8d1f2fSAlex Mastro u64 region_size = getpagesize();
223*de8d1f2fSAlex Mastro
224*de8d1f2fSAlex Mastro /*
225*de8d1f2fSAlex Mastro * Over-allocate mmap by double the size to provide enough backing vaddr
226*de8d1f2fSAlex Mastro * for overflow tests
227*de8d1f2fSAlex Mastro */
228*de8d1f2fSAlex Mastro self->mmap_size = 2 * region_size;
229*de8d1f2fSAlex Mastro
230*de8d1f2fSAlex Mastro self->device = vfio_pci_device_init(device_bdf, variant->iommu_mode);
231*de8d1f2fSAlex Mastro region->vaddr = mmap(NULL, self->mmap_size, PROT_READ | PROT_WRITE,
232*de8d1f2fSAlex Mastro MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
233*de8d1f2fSAlex Mastro ASSERT_NE(region->vaddr, MAP_FAILED);
234*de8d1f2fSAlex Mastro
235*de8d1f2fSAlex Mastro /* One page prior to the end of address space */
236*de8d1f2fSAlex Mastro region->iova = ~(iova_t)0 & ~(region_size - 1);
237*de8d1f2fSAlex Mastro region->size = region_size;
238*de8d1f2fSAlex Mastro }
239*de8d1f2fSAlex Mastro
FIXTURE_TEARDOWN(vfio_dma_map_limit_test)240*de8d1f2fSAlex Mastro FIXTURE_TEARDOWN(vfio_dma_map_limit_test)
241*de8d1f2fSAlex Mastro {
242*de8d1f2fSAlex Mastro vfio_pci_device_cleanup(self->device);
243*de8d1f2fSAlex Mastro ASSERT_EQ(munmap(self->region.vaddr, self->mmap_size), 0);
244*de8d1f2fSAlex Mastro }
245*de8d1f2fSAlex Mastro
TEST_F(vfio_dma_map_limit_test,unmap_range)246*de8d1f2fSAlex Mastro TEST_F(vfio_dma_map_limit_test, unmap_range)
247*de8d1f2fSAlex Mastro {
248*de8d1f2fSAlex Mastro struct vfio_dma_region *region = &self->region;
249*de8d1f2fSAlex Mastro u64 unmapped;
250*de8d1f2fSAlex Mastro int rc;
251*de8d1f2fSAlex Mastro
252*de8d1f2fSAlex Mastro vfio_pci_dma_map(self->device, region);
253*de8d1f2fSAlex Mastro ASSERT_EQ(region->iova, to_iova(self->device, region->vaddr));
254*de8d1f2fSAlex Mastro
255*de8d1f2fSAlex Mastro rc = __vfio_pci_dma_unmap(self->device, region, &unmapped);
256*de8d1f2fSAlex Mastro ASSERT_EQ(rc, 0);
257*de8d1f2fSAlex Mastro ASSERT_EQ(unmapped, region->size);
258*de8d1f2fSAlex Mastro }
259*de8d1f2fSAlex Mastro
TEST_F(vfio_dma_map_limit_test,unmap_all)260*de8d1f2fSAlex Mastro TEST_F(vfio_dma_map_limit_test, unmap_all)
261*de8d1f2fSAlex Mastro {
262*de8d1f2fSAlex Mastro struct vfio_dma_region *region = &self->region;
263*de8d1f2fSAlex Mastro u64 unmapped;
264*de8d1f2fSAlex Mastro int rc;
265*de8d1f2fSAlex Mastro
266*de8d1f2fSAlex Mastro vfio_pci_dma_map(self->device, region);
267*de8d1f2fSAlex Mastro ASSERT_EQ(region->iova, to_iova(self->device, region->vaddr));
268*de8d1f2fSAlex Mastro
269*de8d1f2fSAlex Mastro rc = __vfio_pci_dma_unmap_all(self->device, &unmapped);
270*de8d1f2fSAlex Mastro ASSERT_EQ(rc, 0);
271*de8d1f2fSAlex Mastro ASSERT_EQ(unmapped, region->size);
272*de8d1f2fSAlex Mastro }
273*de8d1f2fSAlex Mastro
TEST_F(vfio_dma_map_limit_test,overflow)274*de8d1f2fSAlex Mastro TEST_F(vfio_dma_map_limit_test, overflow)
275*de8d1f2fSAlex Mastro {
276*de8d1f2fSAlex Mastro struct vfio_dma_region *region = &self->region;
277*de8d1f2fSAlex Mastro int rc;
278*de8d1f2fSAlex Mastro
279*de8d1f2fSAlex Mastro region->size = self->mmap_size;
280*de8d1f2fSAlex Mastro
281*de8d1f2fSAlex Mastro rc = __vfio_pci_dma_map(self->device, region);
282*de8d1f2fSAlex Mastro ASSERT_EQ(rc, -EOVERFLOW);
283*de8d1f2fSAlex Mastro
284*de8d1f2fSAlex Mastro rc = __vfio_pci_dma_unmap(self->device, region, NULL);
285*de8d1f2fSAlex Mastro ASSERT_EQ(rc, -EOVERFLOW);
286*de8d1f2fSAlex Mastro }
287*de8d1f2fSAlex Mastro
main(int argc,char * argv[])288b477e7bcSJosh Hilke int main(int argc, char *argv[])
289b477e7bcSJosh Hilke {
290b477e7bcSJosh Hilke device_bdf = vfio_selftests_get_bdf(&argc, argv);
291b477e7bcSJosh Hilke return test_harness_run(argc, argv);
292b477e7bcSJosh Hilke }
293