diff options
| author | Alex Williamson <alex.williamson@redhat.com> | 2012-07-31 10:16:23 -0400 |
|---|---|---|
| committer | Alex Williamson <alex.williamson@redhat.com> | 2012-07-31 10:16:23 -0400 |
| commit | 73fa0d10d077d9521ee2dace2307ae2c9a965336 (patch) | |
| tree | 2c820b194dd8ea00f23d85c382e86ea6c3beb498 | |
| parent | 4a5b2a20ec87384eeb19e70991e7e15a00cad87b (diff) | |
vfio: Type1 IOMMU implementation
This VFIO IOMMU backend is designed primarily for AMD-Vi and Intel
VT-d hardware, but is potentially usable by anything supporting
similar mapping functionality. We arbitrarily call this a Type1
backend for lack of a better name. This backend has no IOVA
or host memory mapping restrictions for the user and is optimized
for relatively static mappings. Mapped areas are pinned into system
memory.
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
| -rw-r--r-- | drivers/vfio/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/vfio/Makefile | 2 | ||||
| -rw-r--r-- | drivers/vfio/vfio.c | 7 | ||||
| -rw-r--r-- | drivers/vfio/vfio_iommu_type1.c | 753 | ||||
| -rw-r--r-- | include/linux/vfio.h | 54 |
5 files changed, 821 insertions, 1 deletions
diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig index 9acb1e729bd6..128b97910b8e 100644 --- a/drivers/vfio/Kconfig +++ b/drivers/vfio/Kconfig | |||
| @@ -1,6 +1,12 @@ | |||
| 1 | config VFIO_IOMMU_TYPE1 | ||
| 2 | tristate | ||
| 3 | depends on VFIO | ||
| 4 | default n | ||
| 5 | |||
| 1 | menuconfig VFIO | 6 | menuconfig VFIO |
| 2 | tristate "VFIO Non-Privileged userspace driver framework" | 7 | tristate "VFIO Non-Privileged userspace driver framework" |
| 3 | depends on IOMMU_API | 8 | depends on IOMMU_API |
| 9 | select VFIO_IOMMU_TYPE1 if X86 | ||
| 4 | help | 10 | help |
| 5 | VFIO provides a framework for secure userspace device drivers. | 11 | VFIO provides a framework for secure userspace device drivers. |
| 6 | See Documentation/vfio.txt for more details. | 12 | See Documentation/vfio.txt for more details. |
diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile index 7500a67a42a0..2398d4a0e38b 100644 --- a/drivers/vfio/Makefile +++ b/drivers/vfio/Makefile | |||
| @@ -1 +1,3 @@ | |||
| 1 | obj-$(CONFIG_VFIO) += vfio.o | 1 | obj-$(CONFIG_VFIO) += vfio.o |
| 2 | obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_type1.o | ||
| 3 | obj-$(CONFIG_VFIO_PCI) += pci/ | ||
diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c index 052e310aed72..9591e2b509d7 100644 --- a/drivers/vfio/vfio.c +++ b/drivers/vfio/vfio.c | |||
| @@ -1376,6 +1376,13 @@ static int __init vfio_init(void) | |||
| 1376 | 1376 | ||
| 1377 | pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); | 1377 | pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); |
| 1378 | 1378 | ||
| 1379 | /* | ||
| 1380 | * Attempt to load known iommu-drivers. This gives us a working | ||
| 1381 | * environment without the user needing to explicitly load iommu | ||
| 1382 | * drivers. | ||
| 1383 | */ | ||
| 1384 | request_module_nowait("vfio_iommu_type1"); | ||
| 1385 | |||
| 1379 | return 0; | 1386 | return 0; |
| 1380 | 1387 | ||
| 1381 | err_groups_cdev: | 1388 | err_groups_cdev: |
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c new file mode 100644 index 000000000000..6f3fbc48a6c7 --- /dev/null +++ b/drivers/vfio/vfio_iommu_type1.c | |||
| @@ -0,0 +1,753 @@ | |||
| 1 | /* | ||
| 2 | * VFIO: IOMMU DMA mapping support for Type1 IOMMU | ||
| 3 | * | ||
| 4 | * Copyright (C) 2012 Red Hat, Inc. All rights reserved. | ||
| 5 | * Author: Alex Williamson <alex.williamson@redhat.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU General Public License version 2 as | ||
| 9 | * published by the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * Derived from original vfio: | ||
| 12 | * Copyright 2010 Cisco Systems, Inc. All rights reserved. | ||
| 13 | * Author: Tom Lyon, pugs@cisco.com | ||
| 14 | * | ||
| 15 | * We arbitrarily define a Type1 IOMMU as one matching the below code. | ||
| 16 | * It could be called the x86 IOMMU as it's designed for AMD-Vi & Intel | ||
| 17 | * VT-d, but that makes it harder to re-use as theoretically anyone | ||
| 18 | * implementing a similar IOMMU could make use of this. We expect the | ||
| 19 | * IOMMU to support the IOMMU API and have few to no restrictions around | ||
| 20 | * the IOVA range that can be mapped. The Type1 IOMMU is currently | ||
| 21 | * optimized for relatively static mappings of a userspace process with | ||
| 22 | * userpsace pages pinned into memory. We also assume devices and IOMMU | ||
| 23 | * domains are PCI based as the IOMMU API is still centered around a | ||
| 24 | * device/bus interface rather than a group interface. | ||
| 25 | */ | ||
| 26 | |||
| 27 | #include <linux/compat.h> | ||
| 28 | #include <linux/device.h> | ||
| 29 | #include <linux/fs.h> | ||
| 30 | #include <linux/iommu.h> | ||
| 31 | #include <linux/module.h> | ||
| 32 | #include <linux/mm.h> | ||
| 33 | #include <linux/pci.h> /* pci_bus_type */ | ||
| 34 | #include <linux/sched.h> | ||
| 35 | #include <linux/slab.h> | ||
| 36 | #include <linux/uaccess.h> | ||
| 37 | #include <linux/vfio.h> | ||
| 38 | #include <linux/workqueue.h> | ||
| 39 | |||
| 40 | #define DRIVER_VERSION "0.2" | ||
| 41 | #define DRIVER_AUTHOR "Alex Williamson <alex.williamson@redhat.com>" | ||
| 42 | #define DRIVER_DESC "Type1 IOMMU driver for VFIO" | ||
| 43 | |||
| 44 | static bool allow_unsafe_interrupts; | ||
| 45 | module_param_named(allow_unsafe_interrupts, | ||
| 46 | allow_unsafe_interrupts, bool, S_IRUGO | S_IWUSR); | ||
| 47 | MODULE_PARM_DESC(allow_unsafe_interrupts, | ||
| 48 | "Enable VFIO IOMMU support for on platforms without interrupt remapping support."); | ||
| 49 | |||
| 50 | struct vfio_iommu { | ||
| 51 | struct iommu_domain *domain; | ||
| 52 | struct mutex lock; | ||
| 53 | struct list_head dma_list; | ||
| 54 | struct list_head group_list; | ||
| 55 | bool cache; | ||
| 56 | }; | ||
| 57 | |||
| 58 | struct vfio_dma { | ||
| 59 | struct list_head next; | ||
| 60 | dma_addr_t iova; /* Device address */ | ||
| 61 | unsigned long vaddr; /* Process virtual addr */ | ||
| 62 | long npage; /* Number of pages */ | ||
| 63 | int prot; /* IOMMU_READ/WRITE */ | ||
| 64 | }; | ||
| 65 | |||
| 66 | struct vfio_group { | ||
| 67 | struct iommu_group *iommu_group; | ||
| 68 | struct list_head next; | ||
| 69 | }; | ||
| 70 | |||
| 71 | /* | ||
| 72 | * This code handles mapping and unmapping of user data buffers | ||
| 73 | * into DMA'ble space using the IOMMU | ||
| 74 | */ | ||
| 75 | |||
| 76 | #define NPAGE_TO_SIZE(npage) ((size_t)(npage) << PAGE_SHIFT) | ||
| 77 | |||
| 78 | struct vwork { | ||
| 79 | struct mm_struct *mm; | ||
| 80 | long npage; | ||
| 81 | struct work_struct work; | ||
| 82 | }; | ||
| 83 | |||
| 84 | /* delayed decrement/increment for locked_vm */ | ||
| 85 | static void vfio_lock_acct_bg(struct work_struct *work) | ||
| 86 | { | ||
| 87 | struct vwork *vwork = container_of(work, struct vwork, work); | ||
| 88 | struct mm_struct *mm; | ||
| 89 | |||
| 90 | mm = vwork->mm; | ||
| 91 | down_write(&mm->mmap_sem); | ||
| 92 | mm->locked_vm += vwork->npage; | ||
| 93 | up_write(&mm->mmap_sem); | ||
| 94 | mmput(mm); | ||
| 95 | kfree(vwork); | ||
| 96 | } | ||
| 97 | |||
| 98 | static void vfio_lock_acct(long npage) | ||
| 99 | { | ||
| 100 | struct vwork *vwork; | ||
| 101 | struct mm_struct *mm; | ||
| 102 | |||
| 103 | if (!current->mm) | ||
| 104 | return; /* process exited */ | ||
| 105 | |||
| 106 | if (down_write_trylock(¤t->mm->mmap_sem)) { | ||
| 107 | current->mm->locked_vm += npage; | ||
| 108 | up_write(¤t->mm->mmap_sem); | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | |||
| 112 | /* | ||
| 113 | * Couldn't get mmap_sem lock, so must setup to update | ||
| 114 | * mm->locked_vm later. If locked_vm were atomic, we | ||
| 115 | * wouldn't need this silliness | ||
| 116 | */ | ||
| 117 | vwork = kmalloc(sizeof(struct vwork), GFP_KERNEL); | ||
| 118 | if (!vwork) | ||
| 119 | return; | ||
| 120 | mm = get_task_mm(current); | ||
| 121 | if (!mm) { | ||
| 122 | kfree(vwork); | ||
| 123 | return; | ||
| 124 | } | ||
| 125 | INIT_WORK(&vwork->work, vfio_lock_acct_bg); | ||
| 126 | vwork->mm = mm; | ||
| 127 | vwork->npage = npage; | ||
| 128 | schedule_work(&vwork->work); | ||
| 129 | } | ||
| 130 | |||
| 131 | /* | ||
| 132 | * Some mappings aren't backed by a struct page, for example an mmap'd | ||
| 133 | * MMIO range for our own or another device. These use a different | ||
| 134 | * pfn conversion and shouldn't be tracked as locked pages. | ||
| 135 | */ | ||
| 136 | static bool is_invalid_reserved_pfn(unsigned long pfn) | ||
| 137 | { | ||
| 138 | if (pfn_valid(pfn)) { | ||
| 139 | bool reserved; | ||
| 140 | struct page *tail = pfn_to_page(pfn); | ||
| 141 | struct page *head = compound_trans_head(tail); | ||
| 142 | reserved = !!(PageReserved(head)); | ||
| 143 | if (head != tail) { | ||
| 144 | /* | ||
| 145 | * "head" is not a dangling pointer | ||
| 146 | * (compound_trans_head takes care of that) | ||
| 147 | * but the hugepage may have been split | ||
