diff options
| author | Alex Williamson <alex.williamson@redhat.com> | 2014-02-26 13:38:36 -0500 |
|---|---|---|
| committer | Alex Williamson <alex.williamson@redhat.com> | 2014-02-26 13:38:36 -0500 |
| commit | 1ef3e2bc04223ff956dc62abaf2dff1f3322a431 (patch) | |
| tree | ff3d2b15264d6a8fec4b7780d80fc8ca79a997f4 /drivers/vfio | |
| parent | cfbf8d4857c26a8a307fb7cd258074c9dcd8c691 (diff) | |
vfio/iommu_type1: Multi-IOMMU domain support
We currently have a problem that we cannot support advanced features
of an IOMMU domain (ex. IOMMU_CACHE), because we have no guarantee
that those features will be supported by all of the hardware units
involved with the domain over its lifetime. For instance, the Intel
VT-d architecture does not require that all DRHDs support snoop
control. If we create a domain based on a device behind a DRHD that
does support snoop control and enable SNP support via the IOMMU_CACHE
mapping option, we cannot then add a device behind a DRHD which does
not support snoop control or we'll get reserved bit faults from the
SNP bit in the pagetables. To add to the complexity, we can't know
the properties of a domain until a device is attached.
We could pass this problem off to userspace and require that a
separate vfio container be used, but we don't know how to handle page
accounting in that case. How do we know that a page pinned in one
container is the same page as a different container and avoid double
billing the user for the page.
The solution is therefore to support multiple IOMMU domains per
container. In the majority of cases, only one domain will be required
since hardware is typically consistent within a system. However, this
provides us the ability to validate compatibility of domains and
support mixed environments where page table flags can be different
between domains.
To do this, our DMA tracking needs to change. We currently try to
coalesce user mappings into as few tracking entries as possible. The
problem then becomes that we lose granularity of user mappings. We've
never guaranteed that a user is able to unmap at a finer granularity
than the original mapping, but we must honor the granularity of the
original mapping. This coalescing code is therefore removed, allowing
only unmaps covering complete maps. The change in accounting is
fairly small here, a typical QEMU VM will start out with roughly a
dozen entries, so it's arguable if this coalescing was ever needed.
We also move IOMMU domain creation to the point where a group is
attached to the container. An interesting side-effect of this is that
we now have access to the device at the time of domain creation and
can probe the devices within the group to determine the bus_type.
This finally makes vfio_iommu_type1 completely device/bus agnostic.
In fact, each IOMMU domain can host devices on different buses managed
by different physical IOMMUs, and present a single DMA mapping
interface to the user. When a new domain is created, mappings are
replayed to bring the IOMMU pagetables up to the state of the current
container. And of course, DMA mapping and unmapping automatically
traverse all of the configured IOMMU domains.
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Cc: Varun Sethi <Varun.Sethi@freescale.com>
Diffstat (limited to 'drivers/vfio')
| -rw-r--r-- | drivers/vfio/vfio_iommu_type1.c | 637 |
1 files changed, 335 insertions, 302 deletions
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 4fb7a8f83c8a..8c7bb9befdab 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c | |||
| @@ -30,7 +30,6 @@ | |||
| 30 | #include <linux/iommu.h> | 30 | #include <linux/iommu.h> |
| 31 | #include <linux/module.h> | 31 | #include <linux/module.h> |
| 32 | #include <linux/mm.h> | 32 | #include <linux/mm.h> |
| 33 | #include <linux/pci.h> /* pci_bus_type */ | ||
| 34 | #include <linux/rbtree.h> | 33 | #include <linux/rbtree.h> |
| 35 | #include <linux/sched.h> | 34 | #include <linux/sched.h> |
| 36 | #include <linux/slab.h> | 35 | #include <linux/slab.h> |
| @@ -55,11 +54,17 @@ MODULE_PARM_DESC(disable_hugepages, | |||
| 55 | "Disable VFIO IOMMU support for IOMMU hugepages."); | 54 | "Disable VFIO IOMMU support for IOMMU hugepages."); |
| 56 | 55 | ||
| 57 | struct vfio_iommu { | 56 | struct vfio_iommu { |
| 58 | struct iommu_domain *domain; | 57 | struct list_head domain_list; |
| 59 | struct mutex lock; | 58 | struct mutex lock; |
| 60 | struct rb_root dma_list; | 59 | struct rb_root dma_list; |
| 60 | bool v2; | ||
| 61 | }; | ||
| 62 | |||
| 63 | struct vfio_domain { | ||
| 64 | struct iommu_domain *domain; | ||
| 65 | struct list_head next; | ||
| 61 | struct list_head group_list; | 66 | struct list_head group_list; |
| 62 | bool cache; | 67 | int prot; /* IOMMU_CACHE */ |
| 63 | }; | 68 | }; |
| 64 | 69 | ||
| 65 | struct vfio_dma { | 70 | struct vfio_dma { |
| @@ -99,7 +104,7 @@ static struct vfio_dma *vfio_find_dma(struct vfio_iommu *iommu, | |||
| 99 | return NULL; | 104 | return NULL; |
| 100 | } | 105 | } |
| 101 | 106 | ||
| 102 | static void vfio_insert_dma(struct vfio_iommu *iommu, struct vfio_dma *new) | 107 | static void vfio_link_dma(struct vfio_iommu *iommu, struct vfio_dma *new) |
| 103 | { | 108 | { |
| 104 | struct rb_node **link = &iommu->dma_list.rb_node, *parent = NULL; | 109 | struct rb_node **link = &iommu->dma_list.rb_node, *parent = NULL; |
| 105 | struct vfio_dma *dma; | 110 | struct vfio_dma *dma; |
| @@ -118,7 +123,7 @@ static void vfio_insert_dma(struct vfio_iommu *iommu, struct vfio_dma *new) | |||
| 118 | rb_insert_color(&new->node, &iommu->dma_list); | 123 | rb_insert_color(&new->node, &iommu->dma_list); |
| 119 | } | 124 | } |
| 120 | 125 | ||
| 121 | static void vfio_remove_dma(struct vfio_iommu *iommu, struct vfio_dma *old) | 126 | static void vfio_unlink_dma(struct vfio_iommu *iommu, struct vfio_dma *old) |
| 122 | { | 127 | { |
| 123 | rb_erase(&old->node, &iommu->dma_list); | 128 | rb_erase(&old->node, &iommu->dma_list); |
| 124 | } | 129 | } |
| @@ -322,32 +327,39 @@ static long vfio_unpin_pages(unsigned long pfn, long npage, | |||
| 322 | return unlocked; | 327 | return unlocked; |
| 323 | } | 328 | } |
| 324 | 329 | ||
| 325 | static int vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma, | 330 | static void vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma) |
| 326 | dma_addr_t iova, size_t *size) | ||
| 327 | { | 331 | { |
| 328 | dma_addr_t start = iova, end = iova + *size; | 332 | dma_addr_t iova = dma->iova, end = dma->iova + dma->size; |
| 333 | struct vfio_domain *domain, *d; | ||
| 329 | long unlocked = 0; | 334 | long unlocked = 0; |
| 330 | 335 | ||
| 336 | if (!dma->size) | ||
| 337 | return; | ||
| 338 | /* | ||
| 339 | * We use the IOMMU to track the physical addresses, otherwise we'd | ||
| 340 | * need a much more complicated tracking system. Unfortunately that | ||
| 341 | * means we need to use one of the iommu domains to figure out the | ||
| 342 | * pfns to unpin. The rest need to be unmapped in advance so we have | ||
| 343 | * no iommu translations remaining when the pages are unpinned. | ||
| 344 | */ | ||
| 345 | domain = d = list_first_entry(&iommu->domain_list, | ||
| 346 | struct vfio_domain, next); | ||
| 347 | |||
| 348 | list_for_each_entry_continue(d, &iommu->domain_list, next) | ||
| 349 | iommu_unmap(d->domain, dma->iova, dma->size); | ||
| 350 | |||
| 331 | while (iova < end) { | 351 | while (iova < end) { |
| 332 | size_t unmapped; | 352 | size_t unmapped; |
| 333 | phys_addr_t phys; | 353 | phys_addr_t phys; |
| 334 | 354 | ||
| 335 | /* | 355 | phys = iommu_iova_to_phys(domain->domain, iova); |
| 336 | * We use the IOMMU to track the physical address. This | ||
| 337 | * saves us from having a lot more entries in our mapping | ||
| 338 | * tree. The downside is that we don't track the size | ||
| 339 | * used to do the mapping. We request unmap of a single | ||
| 340 | * page, but expect IOMMUs that support large pages to | ||
| 341 | * unmap a larger chunk. | ||
| 342 | */ | ||
| 343 | phys = iommu_iova_to_phys(iommu->domain, iova); | ||
| 344 | if (WARN_ON(!phys)) { | 356 | if (WARN_ON(!phys)) { |
| 345 | iova += PAGE_SIZE; | 357 | iova += PAGE_SIZE; |
| 346 | continue; | 358 | continue; |
| 347 | } | 359 | } |
| 348 | 360 | ||
| 349 | unmapped = iommu_unmap(iommu->domain, iova, PAGE_SIZE); | 361 | unmapped = iommu_unmap(domain->domain, iova, PAGE_SIZE); |
| 350 | if (!unmapped) | 362 | if (WARN_ON(!unmapped)) |
| 351 | break; | 363 | break; |
| 352 | 364 | ||
| 353 | unlocked += vfio_unpin_pages(phys >> PAGE_SHIFT, | 365 | unlocked += vfio_unpin_pages(phys >> PAGE_SHIFT, |
| @@ -357,119 +369,26 @@ static int vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma, | |||
| 357 | } | 369 | } |
| 358 | 370 | ||
| 359 | vfio_lock_acct(-unlocked); | 371 | vfio_lock_acct(-unlocked); |
| 360 | |||
| 361 | *size = iova - start; | ||
| 362 | |||
| 363 | return 0; | ||
| 364 | } | 372 | } |
| 365 | 373 | ||
| 366 | static int vfio_remove_dma_overlap(struct vfio_iommu *iommu, dma_addr_t start, | 374 | static void vfio_remove_dma(struct vfio_iommu *iommu, struct vfio_dma *dma) |
| 367 | size_t *size, struct vfio_dma *dma) | ||
| 368 | { | 375 | { |
| 369 | size_t offset, overlap, tmp; | 376 | vfio_unmap_unpin(iommu, dma); |
| 370 | struct vfio_dma *split; | 377 | vfio_unlink_dma(iommu, dma); |
| 371 | int ret; | 378 | kfree(dma); |
| 372 | 379 | } | |
| 373 | if (!*size) | ||
| 374 | return 0; | ||
| 375 | |||
| 376 | /* | ||
| 377 | * Existing dma region is completely covered, unmap all. This is | ||
| 378 | * the likely case since userspace tends to map and unmap buffers | ||
| 379 | * in one shot rather than multiple mappings within a buffer. | ||
| 380 | */ | ||
| 381 | if (likely(start <= dma->iova && | ||
| 382 | start + *size >= dma->iova + dma->size)) { | ||
| 383 | *size = dma->size; | ||
| 384 | ret = vfio_unmap_unpin(iommu, dma, dma->iova, size); | ||
| 385 | if (ret) | ||
| 386 | return ret; | ||
| 387 | |||
| 388 | /* | ||
| 389 | * Did we remove more than we have? Should never happen | ||
| 390 | * since a vfio_dma is contiguous in iova and vaddr. | ||
| 391 | */ | ||
| 392 | WARN_ON(*size != dma->size); | ||
| 393 | |||
| 394 | vfio_remove_dma(iommu, dma); | ||
| 395 | kfree(dma); | ||
| 396 | return 0; | ||
| 397 | } | ||
| 398 | |||
| 399 | /* Overlap low address of existing range */ | ||
| 400 | if (start <= dma->iova) { | ||
| 401 | overlap = start + *size - dma->iova; | ||
| 402 | ret = vfio_unmap_unpin(iommu, dma, dma->iova, &overlap); | ||
| 403 | if (ret) | ||
| 404 | return ret; | ||
| 405 | |||
| 406 | vfio_remove_dma(iommu, dma); | ||
| 407 | |||
| 408 | /* | ||
