mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 08:19:28 +00:00
![David Rientjes](/assets/img/avatar_default.png)
Commit bf6bddf192
("mm: introduce compaction and migration for
ballooned pages") introduces page_count(page) into memory compaction
which dereferences page->first_page if PageTail(page).
This results in a very rare NULL pointer dereference on the
aforementioned page_count(page). Indeed, anything that does
compound_head(), including page_count() is susceptible to racing with
prep_compound_page() and seeing a NULL or dangling page->first_page
pointer.
This patch uses Andrea's implementation of compound_trans_head() that
deals with such a race and makes it the default compound_head()
implementation. This includes a read memory barrier that ensures that
if PageTail(head) is true that we return a head page that is neither
NULL nor dangling. The patch then adds a store memory barrier to
prep_compound_page() to ensure page->first_page is set.
This is the safest way to ensure we see the head page that we are
expecting, PageTail(page) is already in the unlikely() path and the
memory barriers are unfortunately required.
Hugetlbfs is the exception, we don't enforce a store memory barrier
during init since no race is possible.
Signed-off-by: David Rientjes <rientjes@google.com>
Cc: Holger Kiehl <Holger.Kiehl@dwd.de>
Cc: Christoph Lameter <cl@linux.com>
Cc: Rafael Aquini <aquini@redhat.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Michal Hocko <mhocko@suse.cz>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
931 lines
22 KiB
C
931 lines
22 KiB
C
/*
|
|
* VFIO: IOMMU DMA mapping support for Type1 IOMMU
|
|
*
|
|
* Copyright (C) 2012 Red Hat, Inc. All rights reserved.
|
|
* Author: Alex Williamson <alex.williamson@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* Derived from original vfio:
|
|
* Copyright 2010 Cisco Systems, Inc. All rights reserved.
|
|
* Author: Tom Lyon, pugs@cisco.com
|
|
*
|
|
* We arbitrarily define a Type1 IOMMU as one matching the below code.
|
|
* It could be called the x86 IOMMU as it's designed for AMD-Vi & Intel
|
|
* VT-d, but that makes it harder to re-use as theoretically anyone
|
|
* implementing a similar IOMMU could make use of this. We expect the
|
|
* IOMMU to support the IOMMU API and have few to no restrictions around
|
|
* the IOVA range that can be mapped. The Type1 IOMMU is currently
|
|
* optimized for relatively static mappings of a userspace process with
|
|
* userpsace pages pinned into memory. We also assume devices and IOMMU
|
|
* domains are PCI based as the IOMMU API is still centered around a
|
|
* device/bus interface rather than a group interface.
|
|
*/
|
|
|
|
#include <linux/compat.h>
|
|
#include <linux/device.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/pci.h> /* pci_bus_type */
|
|
#include <linux/rbtree.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vfio.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#define DRIVER_VERSION "0.2"
|
|
#define DRIVER_AUTHOR "Alex Williamson <alex.williamson@redhat.com>"
|
|
#define DRIVER_DESC "Type1 IOMMU driver for VFIO"
|
|
|
|
static bool allow_unsafe_interrupts;
|
|
module_param_named(allow_unsafe_interrupts,
|
|
allow_unsafe_interrupts, bool, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(allow_unsafe_interrupts,
|
|
"Enable VFIO IOMMU support for on platforms without interrupt remapping support.");
|
|
|
|
static bool disable_hugepages;
|
|
module_param_named(disable_hugepages,
|
|
disable_hugepages, bool, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(disable_hugepages,
|
|
"Disable VFIO IOMMU support for IOMMU hugepages.");
|
|
|
|
struct vfio_iommu {
|
|
struct iommu_domain *domain;
|
|
struct mutex lock;
|
|
struct rb_root dma_list;
|
|
struct list_head group_list;
|
|
bool cache;
|
|
};
|
|
|
|
struct vfio_dma {
|
|
struct rb_node node;
|
|
dma_addr_t iova; /* Device address */
|
|
unsigned long vaddr; /* Process virtual addr */
|
|
size_t size; /* Map size (bytes) */
|
|
int prot; /* IOMMU_READ/WRITE */
|
|
};
|
|
|
|
struct vfio_group {
|
|
struct iommu_group *iommu_group;
|
|
struct list_head next;
|
|
};
|
|
|
|
/*
|
|
* This code handles mapping and unmapping of user data buffers
|
|
* into DMA'ble space using the IOMMU
|
|
*/
|
|
|
|
static struct vfio_dma *vfio_find_dma(struct vfio_iommu *iommu,
|
|
dma_addr_t start, size_t size)
|
|
{
|
|
struct rb_node *node = iommu->dma_list.rb_node;
|
|
|
|
while (node) {
|
|
struct vfio_dma *dma = rb_entry(node, struct vfio_dma, node);
|
|
|
|
if (start + size <= dma->iova)
|
|
node = node->rb_left;
|
|
else if (start >= dma->iova + dma->size)
|
|
node = node->rb_right;
|
|
else
|
|
return dma;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void vfio_insert_dma(struct vfio_iommu *iommu, struct vfio_dma *new)
|
|
{
|
|
struct rb_node **link = &iommu->dma_list.rb_node, *parent = NULL;
|
|
struct vfio_dma *dma;
|
|
|
|
while (*link) {
|
|
parent = *link;
|
|
dma = rb_entry(parent, struct vfio_dma, node);
|
|
|
|
if (new->iova + new->size <= dma->iova)
|
|
link = &(*link)->rb_left;
|
|
else
|
|
link = &(*link)->rb_right;
|
|
}
|
|
|
|
rb_link_node(&new->node, parent, link);
|
|
rb_insert_color(&new->node, &iommu->dma_list);
|
|
}
|
|
|
|
static void vfio_remove_dma(struct vfio_iommu *iommu, struct vfio_dma *old)
|
|
{
|
|
rb_erase(&old->node, &iommu->dma_list);
|
|
}
|
|
|
|
struct vwork {
|
|
struct mm_struct *mm;
|
|
long npage;
|
|
struct work_struct work;
|
|
};
|
|
|
|
/* delayed decrement/increment for locked_vm */
|
|
static void vfio_lock_acct_bg(struct work_struct *work)
|
|
{
|
|
struct vwork *vwork = container_of(work, struct vwork, work);
|
|
struct mm_struct *mm;
|
|
|
|
mm = vwork->mm;
|
|
down_write(&mm->mmap_sem);
|
|
mm->locked_vm += vwork->npage;
|
|
up_write(&mm->mmap_sem);
|
|
mmput(mm);
|
|
kfree(vwork);
|
|
}
|
|
|
|
static void vfio_lock_acct(long npage)
|
|
{
|
|
struct vwork *vwork;
|
|
struct mm_struct *mm;
|
|
|
|
if (!current->mm || !npage)
|
|
return; /* process exited or nothing to do */
|
|
|
|
if (down_write_trylock(¤t->mm->mmap_sem)) {
|
|
current->mm->locked_vm += npage;
|
|
up_write(¤t->mm->mmap_sem);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Couldn't get mmap_sem lock, so must setup to update
|
|
* mm->locked_vm later. If locked_vm were atomic, we
|
|
* wouldn't need this silliness
|
|
*/
|
|
vwork = kmalloc(sizeof(struct vwork), GFP_KERNEL);
|
|
if (!vwork)
|
|
return;
|
|
mm = get_task_mm(current);
|
|
if (!mm) {
|
|
kfree(vwork);
|
|
return;
|
|
}
|
|
INIT_WORK(&vwork->work, vfio_lock_acct_bg);
|
|
vwork->mm = mm;
|
|
vwork->npage = npage;
|
|
schedule_work(&vwork->work);
|
|
}
|
|
|
|
/*
|
|
* Some mappings aren't backed by a struct page, for example an mmap'd
|
|
* MMIO range for our own or another device. These use a different
|
|
* pfn conversion and shouldn't be tracked as locked pages.
|
|
*/
|
|
static bool is_invalid_reserved_pfn(unsigned long pfn)
|
|
{
|
|
if (pfn_valid(pfn)) {
|
|
bool reserved;
|
|
struct page *tail = pfn_to_page(pfn);
|
|
struct page *head = compound_head(tail);
|
|
reserved = !!(PageReserved(head));
|
|
if (head != tail) {
|
|
/*
|
|
* "head" is not a dangling pointer
|
|
* (compound_head takes care of that)
|
|
* but the hugepage may have been split
|
|
* from under us (and we may not hold a
|
|
* reference count on the head page so it can
|
|
* be reused before we run PageReferenced), so
|
|
* we've to check PageTail before returning
|
|
* what we just read.
|
|
*/
|
|
smp_rmb();
|
|
if (PageTail(tail))
|
|
return reserved;
|
|
}
|
|
return PageReserved(tail);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int put_pfn(unsigned long pfn, int prot)
|
|
{
|
|
if (!is_invalid_reserved_pfn(pfn)) {
|
|
struct page *page = pfn_to_page(pfn);
|
|
if (prot & IOMMU_WRITE)
|
|
SetPageDirty(page);
|
|
put_page(page);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vaddr_get_pfn(unsigned long vaddr, int prot, unsigned long *pfn)
|
|
{
|
|
struct page *page[1];
|
|
struct vm_area_struct *vma;
|
|
int ret = -EFAULT;
|
|
|
|
if (get_user_pages_fast(vaddr, 1, !!(prot & IOMMU_WRITE), page) == 1) {
|
|
*pfn = page_to_pfn(page[0]);
|
|
return 0;
|
|
}
|
|
|
|
down_read(¤t->mm->mmap_sem);
|
|
|
|
vma = find_vma_intersection(current->mm, vaddr, vaddr + 1);
|
|
|
|
if (vma && vma->vm_flags & VM_PFNMAP) {
|
|
*pfn = ((vaddr - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;
|
|
if (is_invalid_reserved_pfn(*pfn))
|
|
ret = 0;
|
|
}
|
|
|
|
up_read(¤t->mm->mmap_sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Attempt to pin pages. We really don't want to track all the pfns and
|
|
* the iommu can only map chunks of consecutive pfns anyway, so get the
|
|
* first page and all consecutive pages with the same locking.
|
|
*/
|
|
static long vfio_pin_pages(unsigned long vaddr, long npage,
|
|
int prot, unsigned long *pfn_base)
|
|
{
|
|
unsigned long limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
|
|
bool lock_cap = capable(CAP_IPC_LOCK);
|
|
long ret, i;
|
|
|
|
if (!current->mm)
|
|
return -ENODEV;
|
|
|
|
ret = vaddr_get_pfn(vaddr, prot, pfn_base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (is_invalid_reserved_pfn(*pfn_base))
|
|
return 1;
|
|
|
|
if (!lock_cap && current->mm->locked_vm + 1 > limit) {
|
|
put_pfn(*pfn_base, prot);
|
|
pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", __func__,
|
|
limit << PAGE_SHIFT);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (unlikely(disable_hugepages)) {
|
|
vfio_lock_acct(1);
|
|
return 1;
|
|
}
|
|
|
|
/* Lock all the consecutive pages from pfn_base */
|
|
for (i = 1, vaddr += PAGE_SIZE; i < npage; i++, vaddr += PAGE_SIZE) {
|
|
unsigned long pfn = 0;
|
|
|
|
ret = vaddr_get_pfn(vaddr, prot, &pfn);
|
|
if (ret)
|
|
break;
|
|
|
|
if (pfn != *pfn_base + i || is_invalid_reserved_pfn(pfn)) {
|
|
put_pfn(pfn, prot);
|
|
break;
|
|
}
|
|
|
|
if (!lock_cap && current->mm->locked_vm + i + 1 > limit) {
|
|
put_pfn(pfn, prot);
|
|
pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n",
|
|
__func__, limit << PAGE_SHIFT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
vfio_lock_acct(i);
|
|
|
|
return i;
|
|
}
|
|
|
|
static long vfio_unpin_pages(unsigned long pfn, long npage,
|
|
int prot, bool do_accounting)
|
|
{
|
|
unsigned long unlocked = 0;
|
|
long i;
|
|
|
|
for (i = 0; i < npage; i++)
|
|
unlocked += put_pfn(pfn++, prot);
|
|
|
|
if (do_accounting)
|
|
vfio_lock_acct(-unlocked);
|
|
|
|
return unlocked;
|
|
}
|
|
|
|
static int vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma,
|
|
dma_addr_t iova, size_t *size)
|
|
{
|
|
dma_addr_t start = iova, end = iova + *size;
|
|
long unlocked = 0;
|
|
|
|
while (iova < end) {
|
|
size_t unmapped;
|
|
phys_addr_t phys;
|
|
|
|
/*
|
|
* We use the IOMMU to track the physical address. This
|
|
* saves us from having a lot more entries in our mapping
|
|
* tree. The downside is that we don't track the size
|
|
* used to do the mapping. We request unmap of a single
|
|
* page, but expect IOMMUs that support large pages to
|
|
* unmap a larger chunk.
|
|
*/
|
|
phys = iommu_iova_to_phys(iommu->domain, iova);
|
|
if (WARN_ON(!phys)) {
|
|
iova += PAGE_SIZE;
|
|
continue;
|
|
}
|
|
|
|
unmapped = iommu_unmap(iommu->domain, iova, PAGE_SIZE);
|
|
if (!unmapped)
|
|
break;
|
|
|
|
unlocked += vfio_unpin_pages(phys >> PAGE_SHIFT,
|
|
unmapped >> PAGE_SHIFT,
|
|
dma->prot, false);
|
|
iova += unmapped;
|
|
}
|
|
|
|
vfio_lock_acct(-unlocked);
|
|
|
|
*size = iova - start;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vfio_remove_dma_overlap(struct vfio_iommu *iommu, dma_addr_t start,
|
|
size_t *size, struct vfio_dma *dma)
|
|
{
|
|
size_t offset, overlap, tmp;
|
|
struct vfio_dma *split;
|
|
int ret;
|
|
|
|
if (!*size)
|
|
return 0;
|
|
|
|
/*
|
|
* Existing dma region is completely covered, unmap all. This is
|
|
* the likely case since userspace tends to map and unmap buffers
|
|
* in one shot rather than multiple mappings within a buffer.
|
|
*/
|
|
if (likely(start <= dma->iova &&
|
|
start + *size >= dma->iova + dma->size)) {
|
|
*size = dma->size;
|
|
ret = vfio_unmap_unpin(iommu, dma, dma->iova, size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* Did we remove more than we have? Should never happen
|
|
* since a vfio_dma is contiguous in iova and vaddr.
|
|
*/
|
|
WARN_ON(*size != dma->size);
|
|
|
|
vfio_remove_dma(iommu, dma);
|
|
kfree(dma);
|
|
return 0;
|
|
}
|
|
|
|
/* Overlap low address of existing range */
|
|
if (start <= dma->iova) {
|
|
overlap = start + *size - dma->iova;
|
|
ret = vfio_unmap_unpin(iommu, dma, dma->iova, &overlap);
|
|
if (ret)
|
|
return ret;
|
|
|
|
vfio_remove_dma(iommu, dma);
|
|
|
|
/*
|
|
* Check, we may have removed to whole vfio_dma. If not
|
|
* fixup and re-insert.
|
|
*/
|
|
if (overlap < dma->size) {
|
|
dma->iova += overlap;
|
|
dma->vaddr += overlap;
|
|
dma->size -= overlap;
|
|
vfio_insert_dma(iommu, dma);
|
|
} else
|
|
kfree(dma);
|
|
|
|
*size = overlap;
|
|
return 0;
|
|
}
|
|
|
|
/* Overlap high address of existing range */
|
|
if (start + *size >= dma->iova + dma->size) {
|
|
offset = start - dma->iova;
|
|
overlap = dma->size - offset;
|
|
|
|
ret = vfio_unmap_unpin(iommu, dma, start, &overlap);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dma->size -= overlap;
|
|
*size = overlap;
|
|
return 0;
|
|
}
|
|
|
|
/* Split existing */
|
|
|
|
/*
|
|
* Allocate our tracking structure early even though it may not
|
|
* be used. An Allocation failure later loses track of pages and
|
|
* is more difficult to unwind.
|
|
*/
|
|
split = kzalloc(sizeof(*split), GFP_KERNEL);
|
|
if (!split)
|
|
return -ENOMEM;
|
|
|
|
offset = start - dma->iova;
|
|
|
|
ret = vfio_unmap_unpin(iommu, dma, start, size);
|
|
if (ret || !*size) {
|
|
kfree(split);
|
|
return ret;
|
|
}
|
|
|
|
tmp = dma->size;
|
|
|
|
/* Resize the lower vfio_dma in place, before the below insert */
|
|
dma->size = offset;
|
|
|
|
/* Insert new for remainder, assuming it didn't all get unmapped */
|
|
if (likely(offset + *size < tmp)) {
|
|
split->size = tmp - offset - *size;
|
|
split->iova = dma->iova + offset + *size;
|
|
split->vaddr = dma->vaddr + offset + *size;
|
|
split->prot = dma->prot;
|
|
vfio_insert_dma(iommu, split);
|
|
} else
|
|
kfree(split);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int vfio_dma_do_unmap(struct vfio_iommu *iommu,
|
|
struct vfio_iommu_type1_dma_unmap *unmap)
|
|
{
|
|
uint64_t mask;
|
|
struct vfio_dma *dma;
|
|
size_t unmapped = 0, size;
|
|
int ret = 0;
|
|
|
|
mask = ((uint64_t)1 << __ffs(iommu->domain->ops->pgsize_bitmap)) - 1;
|
|
|
|
if (unmap->iova & mask)
|
|
return -EINVAL;
|
|
if (!unmap->size || unmap->size & mask)
|
|
return -EINVAL;
|
|
|
|
WARN_ON(mask & PAGE_MASK);
|
|
|
|
mutex_lock(&iommu->lock);
|
|
|
|
while ((dma = vfio_find_dma(iommu, unmap->iova, unmap->size))) {
|
|
size = unmap->size;
|
|
ret = vfio_remove_dma_overlap(iommu, unmap->iova, &size, dma);
|
|
if (ret || !size)
|
|
break;
|
|
unmapped += size;
|
|
}
|
|
|
|
mutex_unlock(&iommu->lock);
|
|
|
|
/*
|
|
* We may unmap more than requested, update the unmap struct so
|
|
* userspace can know.
|
|
*/
|
|
unmap->size = unmapped;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Turns out AMD IOMMU has a page table bug where it won't map large pages
|
|
* to a region that previously mapped smaller pages. This should be fixed
|
|
* soon, so this is just a temporary workaround to break mappings down into
|
|
* PAGE_SIZE. Better to map smaller pages than nothing.
|
|
*/
|
|
static int map_try_harder(struct vfio_iommu *iommu, dma_addr_t iova,
|
|
unsigned long pfn, long npage, int prot)
|
|
{
|
|
long i;
|
|
int ret;
|
|
|
|
for (i = 0; i < npage; i++, pfn++, iova += PAGE_SIZE) {
|
|
ret = iommu_map(iommu->domain, iova,
|
|
(phys_addr_t)pfn << PAGE_SHIFT,
|
|
PAGE_SIZE, prot);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
for (; i < npage && i > 0; i--, iova -= PAGE_SIZE)
|
|
iommu_unmap(iommu->domain, iova, PAGE_SIZE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int vfio_dma_do_map(struct vfio_iommu *iommu,
|
|
struct vfio_iommu_type1_dma_map *map)
|
|
{
|
|
dma_addr_t end, iova;
|
|
unsigned long vaddr = map->vaddr;
|
|
size_t size = map->size;
|
|
long npage;
|
|
int ret = 0, prot = 0;
|
|
uint64_t mask;
|
|
struct vfio_dma *dma = NULL;
|
|
unsigned long pfn;
|
|
|
|
end = map->iova + map->size;
|
|
|
|
mask = ((uint64_t)1 << __ffs(iommu->domain->ops->pgsize_bitmap)) - 1;
|
|
|
|
/* READ/WRITE from device perspective */
|
|
if (map->flags & VFIO_DMA_MAP_FLAG_WRITE)
|
|
prot |= IOMMU_WRITE;
|
|
if (map->flags & VFIO_DMA_MAP_FLAG_READ)
|
|
prot |= IOMMU_READ;
|
|
|
|
if (!prot)
|
|
return -EINVAL; /* No READ/WRITE? */
|
|
|
|
if (iommu->cache)
|
|
prot |= IOMMU_CACHE;
|
|
|
|
if (vaddr & mask)
|
|
return -EINVAL;
|
|
if (map->iova & mask)
|
|
return -EINVAL;
|
|
if (!map->size || map->size & mask)
|
|
return -EINVAL;
|
|
|
|
WARN_ON(mask & PAGE_MASK);
|
|
|
|
/* Don't allow IOVA wrap */
|
|
if (end && end < map->iova)
|
|
return -EINVAL;
|
|
|
|
/* Don't allow virtual address wrap */
|
|
if (vaddr + map->size && vaddr + map->size < vaddr)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&iommu->lock);
|
|
|
|
if (vfio_find_dma(iommu, map->iova, map->size)) {
|
|
mutex_unlock(&iommu->lock);
|
|
return -EEXIST;
|
|
}
|
|
|
|
for (iova = map->iova; iova < end; iova += size, vaddr += size) {
|
|
long i;
|
|
|
|
/* Pin a contiguous chunk of memory */
|
|
npage = vfio_pin_pages(vaddr, (end - iova) >> PAGE_SHIFT,
|
|
prot, &pfn);
|
|
if (npage <= 0) {
|
|
WARN_ON(!npage);
|
|
ret = (int)npage;
|
|
goto out;
|
|
}
|
|
|
|
/* Verify pages are not already mapped */
|
|
for (i = 0; i < npage; i++) {
|
|
if (iommu_iova_to_phys(iommu->domain,
|
|
iova + (i << PAGE_SHIFT))) {
|
|
ret = -EBUSY;
|
|
goto out_unpin;
|
|
}
|
|
}
|
|
|
|
ret = iommu_map(iommu->domain, iova,
|
|
(phys_addr_t)pfn << PAGE_SHIFT,
|
|
npage << PAGE_SHIFT, prot);
|
|
if (ret) {
|
|
if (ret != -EBUSY ||
|
|
map_try_harder(iommu, iova, pfn, npage, prot)) {
|
|
goto out_unpin;
|
|
}
|
|
}
|
|
|
|
size = npage << PAGE_SHIFT;
|
|
|
|
/*
|
|
* Check if we abut a region below - nothing below 0.
|
|
* This is the most likely case when mapping chunks of
|
|
* physically contiguous regions within a virtual address
|
|
* range. Update the abutting entry in place since iova
|
|
* doesn't change.
|
|
*/
|
|
if (likely(iova)) {
|
|
struct vfio_dma *tmp;
|
|
tmp = vfio_find_dma(iommu, iova - 1, 1);
|
|
if (tmp && tmp->prot == prot &&
|
|
tmp->vaddr + tmp->size == vaddr) {
|
|
tmp->size += size;
|
|
iova = tmp->iova;
|
|
size = tmp->size;
|
|
vaddr = tmp->vaddr;
|
|
dma = tmp;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if we abut a region above - nothing above ~0 + 1.
|
|
* If we abut above and below, remove and free. If only
|
|
* abut above, remove, modify, reinsert.
|
|
*/
|
|
if (likely(iova + size)) {
|
|
struct vfio_dma *tmp;
|
|
tmp = vfio_find_dma(iommu, iova + size, 1);
|
|
if (tmp && tmp->prot == prot &&
|
|
tmp->vaddr == vaddr + size) {
|
|
vfio_remove_dma(iommu, tmp);
|
|
if (dma) {
|
|
dma->size += tmp->size;
|
|
kfree(tmp);
|
|
} else {
|
|
size += tmp->size;
|
|
tmp->size = size;
|
|
tmp->iova = iova;
|
|
tmp->vaddr = vaddr;
|
|
vfio_insert_dma(iommu, tmp);
|
|
dma = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!dma) {
|
|
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
|
|
if (!dma) {
|
|
iommu_unmap(iommu->domain, iova, size);
|
|
ret = -ENOMEM;
|
|
goto out_unpin;
|
|
}
|
|
|
|
dma->size = size;
|
|
dma->iova = iova;
|
|
dma->vaddr = vaddr;
|
|
dma->prot = prot;
|
|
vfio_insert_dma(iommu, dma);
|
|
}
|
|
}
|
|
|
|
WARN_ON(ret);
|
|
mutex_unlock(&iommu->lock);
|
|
return ret;
|
|
|
|
out_unpin:
|
|
vfio_unpin_pages(pfn, npage, prot, true);
|
|
|
|
out:
|
|
iova = map->iova;
|
|
size = map->size;
|
|
while ((dma = vfio_find_dma(iommu, iova, size))) {
|
|
int r = vfio_remove_dma_overlap(iommu, iova,
|
|
&size, dma);
|
|
if (WARN_ON(r || !size))
|
|
break;
|
|
}
|
|
|
|
mutex_unlock(&iommu->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int vfio_iommu_type1_attach_group(void *iommu_data,
|
|
struct iommu_group *iommu_group)
|
|
{
|
|
struct vfio_iommu *iommu = iommu_data;
|
|
struct vfio_group *group, *tmp;
|
|
int ret;
|
|
|
|
group = kzalloc(sizeof(*group), GFP_KERNEL);
|
|
if (!group)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&iommu->lock);
|
|
|
|
list_for_each_entry(tmp, &iommu->group_list, next) {
|
|
if (tmp->iommu_group == iommu_group) {
|
|
mutex_unlock(&iommu->lock);
|
|
kfree(group);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* TODO: Domain have capabilities that might change as we add
|
|
* groups (see iommu->cache, currently never set). Check for
|
|
* them and potentially disallow groups to be attached when it
|
|
* would change capabilities (ugh).
|
|
*/
|
|
ret = iommu_attach_group(iommu->domain, iommu_group);
|
|
if (ret) {
|
|
mutex_unlock(&iommu->lock);
|
|
kfree(group);
|
|
return ret;
|
|
}
|
|
|
|
group->iommu_group = iommu_group;
|
|
list_add(&group->next, &iommu->group_list);
|
|
|
|
mutex_unlock(&iommu->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void vfio_iommu_type1_detach_group(void *iommu_data,
|
|
struct iommu_group *iommu_group)
|
|
{
|
|
struct vfio_iommu *iommu = iommu_data;
|
|
struct vfio_group *group;
|
|
|
|
mutex_lock(&iommu->lock);
|
|
|
|
list_for_each_entry(group, &iommu->group_list, next) {
|
|
if (group->iommu_group == iommu_group) {
|
|
iommu_detach_group(iommu->domain, iommu_group);
|
|
list_del(&group->next);
|
|
kfree(group);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&iommu->lock);
|
|
}
|
|
|
|
static void *vfio_iommu_type1_open(unsigned long arg)
|
|
{
|
|
struct vfio_iommu *iommu;
|
|
|
|
if (arg != VFIO_TYPE1_IOMMU)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
|
|
if (!iommu)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
INIT_LIST_HEAD(&iommu->group_list);
|
|
iommu->dma_list = RB_ROOT;
|
|
mutex_init(&iommu->lock);
|
|
|
|
/*
|
|
* Wish we didn't have to know about bus_type here.
|
|
*/
|
|
iommu->domain = iommu_domain_alloc(&pci_bus_type);
|
|
if (!iommu->domain) {
|
|
kfree(iommu);
|
|
return ERR_PTR(-EIO);
|
|
}
|
|
|
|
/*
|
|
* Wish we could specify required capabilities rather than create
|
|
* a domain, see what comes out and hope it doesn't change along
|
|
* the way. Fortunately we know interrupt remapping is global for
|
|
* our iommus.
|
|
*/
|
|
if (!allow_unsafe_interrupts &&
|
|
!iommu_domain_has_cap(iommu->domain, IOMMU_CAP_INTR_REMAP)) {
|
|
pr_warn("%s: No interrupt remapping support. Use the module param \"allow_unsafe_interrupts\" to enable VFIO IOMMU support on this platform\n",
|
|
__func__);
|
|
iommu_domain_free(iommu->domain);
|
|
kfree(iommu);
|
|
return ERR_PTR(-EPERM);
|
|
}
|
|
|
|
return iommu;
|
|
}
|
|
|
|
static void vfio_iommu_type1_release(void *iommu_data)
|
|
{
|
|
struct vfio_iommu *iommu = iommu_data;
|
|
struct vfio_group *group, *group_tmp;
|
|
struct rb_node *node;
|
|
|
|
list_for_each_entry_safe(group, group_tmp, &iommu->group_list, next) {
|
|
iommu_detach_group(iommu->domain, group->iommu_group);
|
|
list_del(&group->next);
|
|
kfree(group);
|
|
}
|
|
|
|
while ((node = rb_first(&iommu->dma_list))) {
|
|
struct vfio_dma *dma = rb_entry(node, struct vfio_dma, node);
|
|
size_t size = dma->size;
|
|
vfio_remove_dma_overlap(iommu, dma->iova, &size, dma);
|
|
if (WARN_ON(!size))
|
|
break;
|
|
}
|
|
|
|
iommu_domain_free(iommu->domain);
|
|
iommu->domain = NULL;
|
|
kfree(iommu);
|
|
}
|
|
|
|
static long vfio_iommu_type1_ioctl(void *iommu_data,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct vfio_iommu *iommu = iommu_data;
|
|
unsigned long minsz;
|
|
|
|
if (cmd == VFIO_CHECK_EXTENSION) {
|
|
switch (arg) {
|
|
case VFIO_TYPE1_IOMMU:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
} else if (cmd == VFIO_IOMMU_GET_INFO) {
|
|
struct vfio_iommu_type1_info info;
|
|
|
|
minsz = offsetofend(struct vfio_iommu_type1_info, iova_pgsizes);
|
|
|
|
if (copy_from_user(&info, (void __user *)arg, minsz))
|
|
return -EFAULT;
|
|
|
|
if (info.argsz < minsz)
|
|
return -EINVAL;
|
|
|
|
info.flags = 0;
|
|
|
|
info.iova_pgsizes = iommu->domain->ops->pgsize_bitmap;
|
|
|
|
return copy_to_user((void __user *)arg, &info, minsz);
|
|
|
|
} else if (cmd == VFIO_IOMMU_MAP_DMA) {
|
|
struct vfio_iommu_type1_dma_map map;
|
|
uint32_t mask = VFIO_DMA_MAP_FLAG_READ |
|
|
VFIO_DMA_MAP_FLAG_WRITE;
|
|
|
|
minsz = offsetofend(struct vfio_iommu_type1_dma_map, size);
|
|
|
|
if (copy_from_user(&map, (void __user *)arg, minsz))
|
|
return -EFAULT;
|
|
|
|
if (map.argsz < minsz || map.flags & ~mask)
|
|
return -EINVAL;
|
|
|
|
return vfio_dma_do_map(iommu, &map);
|
|
|
|
} else if (cmd == VFIO_IOMMU_UNMAP_DMA) {
|
|
struct vfio_iommu_type1_dma_unmap unmap;
|
|
long ret;
|
|
|
|
minsz = offsetofend(struct vfio_iommu_type1_dma_unmap, size);
|
|
|
|
if (copy_from_user(&unmap, (void __user *)arg, minsz))
|
|
return -EFAULT;
|
|
|
|
if (unmap.argsz < minsz || unmap.flags)
|
|
return -EINVAL;
|
|
|
|
ret = vfio_dma_do_unmap(iommu, &unmap);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return copy_to_user((void __user *)arg, &unmap, minsz);
|
|
}
|
|
|
|
return -ENOTTY;
|
|
}
|
|
|
|
static const struct vfio_iommu_driver_ops vfio_iommu_driver_ops_type1 = {
|
|
.name = "vfio-iommu-type1",
|
|
.owner = THIS_MODULE,
|
|
.open = vfio_iommu_type1_open,
|
|
.release = vfio_iommu_type1_release,
|
|
.ioctl = vfio_iommu_type1_ioctl,
|
|
.attach_group = vfio_iommu_type1_attach_group,
|
|
.detach_group = vfio_iommu_type1_detach_group,
|
|
};
|
|
|
|
static int __init vfio_iommu_type1_init(void)
|
|
{
|
|
if (!iommu_present(&pci_bus_type))
|
|
return -ENODEV;
|
|
|
|
return vfio_register_iommu_driver(&vfio_iommu_driver_ops_type1);
|
|
}
|
|
|
|
static void __exit vfio_iommu_type1_cleanup(void)
|
|
{
|
|
vfio_unregister_iommu_driver(&vfio_iommu_driver_ops_type1);
|
|
}
|
|
|
|
module_init(vfio_iommu_type1_init);
|
|
module_exit(vfio_iommu_type1_cleanup);
|
|
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR(DRIVER_AUTHOR);
|
|
MODULE_DESCRIPTION(DRIVER_DESC);
|