summaryrefslogtreecommitdiff
path: root/drivers/vfio
diff options
context:
space:
mode:
authorBharat Bhushan <Bharat.Bhushan@freescale.com>2013-04-18 07:54:01 (GMT)
committerFleming Andrew-AFLEMING <AFLEMING@freescale.com>2013-04-19 23:10:48 (GMT)
commit52ce6bb411319e286ac8e58e732be636d2b4110a (patch)
treed476986c14e3dd815b9c9733b777c839e677553f /drivers/vfio
parent515c1ec00dc0a4e825561d6edfcf3f3a61fb02ce (diff)
downloadlinux-fsl-qoriq-52ce6bb411319e286ac8e58e732be636d2b4110a.tar.xz
vfio pci: add fsl pamu implementation
This patch adds support for Freescale IOMMU (PAMU - Peripheral Access Management Unit). This is an aperture type of IOMMU. Some of the code is derived from TYPE1 iommu (driver/vfio/vfio_iommu_type1.c). Signed-off-by: Bharat Bhushan <bharat.bhushan@freescale.com> Change-Id: I1e8fe8ef421a258d15e5f6bb9705d671dfcc37ba Reviewed-on: http://git.am.freescale.net:8181/1493 Reviewed-by: Yoder Stuart-B08248 <stuart.yoder@freescale.com> Reviewed-by: Fleming Andrew-AFLEMING <AFLEMING@freescale.com> Tested-by: Fleming Andrew-AFLEMING <AFLEMING@freescale.com>
Diffstat (limited to 'drivers/vfio')
-rw-r--r--drivers/vfio/Kconfig7
-rw-r--r--drivers/vfio/Makefile1
-rw-r--r--drivers/vfio/vfio_iommu_fsl_pamu.c1023
3 files changed, 1031 insertions, 0 deletions
diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig
index 7cd5dec..dc55a1c 100644
--- a/drivers/vfio/Kconfig
+++ b/drivers/vfio/Kconfig
@@ -3,10 +3,17 @@ config VFIO_IOMMU_TYPE1
depends on VFIO
default n
+config VFIO_IOMMU_FSL_PAMU
+ tristate
+ depends on VFIO
+ default n
+
+
menuconfig VFIO
tristate "VFIO Non-Privileged userspace driver framework"
depends on IOMMU_API
select VFIO_IOMMU_TYPE1 if X86
+ select VFIO_IOMMU_FSL_PAMU if FSL_PAMU
help
VFIO provides a framework for secure userspace device drivers.
See Documentation/vfio.txt for more details.
diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile
index 2398d4a..293901c 100644
--- a/drivers/vfio/Makefile
+++ b/drivers/vfio/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_VFIO) += vfio.o
obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_type1.o
+obj-$(CONFIG_VFIO_IOMMU_FSL_PAMU) += vfio_iommu_fsl_pamu.o
obj-$(CONFIG_VFIO_PCI) += pci/
diff --git a/drivers/vfio/vfio_iommu_fsl_pamu.c b/drivers/vfio/vfio_iommu_fsl_pamu.c
new file mode 100644
index 0000000..75dd13b
--- /dev/null
+++ b/drivers/vfio/vfio_iommu_fsl_pamu.c
@@ -0,0 +1,1023 @@
+/*
+ * VFIO: IOMMU DMA mapping support for FSL PAMU IOMMU
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ *
+ * Author: Bharat Bhushan <bharat.bhushan@freescale.com>
+ *
+ * This file is derived from driver/vfio/vfio_iommu_type1.c
+ *
+ * The Freescale PAMU is an aperture-based IOMMU with the following
+ * characteristics. Each device has an entry in a table in memory
+ * describing the iova->phys mapping. The mapping has:
+ * -an overall aperture that is power of 2 sized, and has a start iova that
+ * is naturally aligned
+ * -has 1 or more windows within the aperture
+ * -number of windows must be power of 2, max is 256
+ * -size of each window is determined by aperture size / # of windows
+ * -iova of each window is determined by aperture start iova / # of windows
+ * -the mapped region in each window can be different than
+ * the window size...mapping must power of 2
+ * -physical address of the mapping must be naturally aligned
+ * with the mapping size
+ */
+
+#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/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/vfio.h>
+#include <linux/workqueue.h>
+#include <linux/hugetlb.h>
+#include <linux/msi.h>
+
+#define DRIVER_VERSION "0.1"
+#define DRIVER_AUTHOR "Bharat Bhushan <bharat.bhushan@freescale.com>"
+#define DRIVER_DESC "FSL PAMU IOMMU driver for VFIO"
+
+struct vfio_iommu {
+ struct iommu_domain *domain;
+ struct mutex lock;
+ dma_addr_t aperture_start;
+ dma_addr_t aperture_end;
+ dma_addr_t page_size; /* Maximum mapped Page size */
+ int nsubwindows; /* Number of subwindows */
+ struct list_head dma_list;
+ struct list_head msi_dma_list;
+ struct list_head group_list;
+};
+
+struct vfio_dma {
+ struct list_head next;
+ dma_addr_t iova; /* Device address */
+ unsigned long vaddr; /* Process virtual addr */
+ long npage; /* Number of pages */
+ int prot; /* IOMMU_READ/WRITE */
+};
+
+struct vfio_msi_dma {
+ struct list_head next;
+ dma_addr_t iova; /* Device address */
+ int bank_id;
+ 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
+ */
+
+#define NPAGE_TO_SIZE(npage) ((size_t)(npage) << PAGE_SHIFT)
+
+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)
+ return; /* process exited */
+
+ if (down_write_trylock(&current->mm->mmap_sem)) {
+ current->mm->locked_vm += npage;
+ up_write(&current->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_trans_head(tail);
+ reserved = !!(PageReserved(head));
+ if (head != tail) {
+ /*
+ * "head" is not a dangling pointer
+ * (compound_trans_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,
+ unsigned int nr_pages)
+{
+ struct page **pages;
+ int ret = -EFAULT;
+ int i;
+
+ pages = kzalloc(sizeof(*pages) * nr_pages, GFP_KERNEL);
+ ret = get_user_pages_fast(vaddr, nr_pages, !!(prot & IOMMU_WRITE), pages);
+ if (ret != nr_pages)
+ goto error;
+
+ /* All Pages should be contigious */
+ for (i = 1; i < nr_pages; i++) {
+ if (page_to_pfn(pages[i]) != page_to_pfn(pages[i - 1]) + 1)
+ goto error;
+ }
+
+ *pfn = page_to_pfn(pages[0]);
+ kfree(pages);
+ return 0;
+error:
+ for (i = 0; i < nr_pages; i++)
+ put_page(pages[i]);
+ kfree(pages);
+ return -EFAULT;
+}
+
+static int iova_to_win(struct vfio_iommu *iommu, dma_addr_t iova)
+{
+ return (int) ((iova - iommu->aperture_start) / iommu->page_size);
+}
+
+/* Unmap DMA region */
+static long __vfio_dma_do_unmap(struct vfio_iommu *iommu, dma_addr_t iova,
+ long npage, int prot)
+{
+ int win, win_start, win_end, i;
+ long unlocked = 0;
+ unsigned long size;
+ unsigned int nr_pages;
+
+ size = npage << PAGE_SHIFT;
+ nr_pages = iommu->page_size / PAGE_SIZE;
+ /* Release the pinned pages */
+ while (size) {
+ unsigned long pfn;
+
+ pfn = iommu_iova_to_phys(iommu->domain, iova) >> PAGE_SHIFT;
+ if (pfn) {
+ for (i = 0; i < nr_pages; i++, pfn++)
+ unlocked += put_pfn(pfn, prot);
+ }
+
+ iova += iommu->page_size;
+ size -= iommu->page_size;
+ }
+
+ /* Disable the subwindows */
+ win_start = iova_to_win(iommu, iova);
+ win_end = iova_to_win(iommu, iova + (npage << PAGE_SHIFT) - 1);
+ for (win = win_start; win <= win_end ; win++)
+ iommu_domain_window_disable(iommu->domain, win);
+
+ return unlocked;
+}
+
+static void vfio_dma_unmap(struct vfio_iommu *iommu, dma_addr_t iova,
+ long npage, int prot)
+{
+ long unlocked;
+
+ unlocked = __vfio_dma_do_unmap(iommu, iova, npage, prot);
+ vfio_lock_acct(-unlocked);
+}
+
+static int vfio_disable_iommu_domain(struct vfio_iommu *iommu)
+{
+ int enable = 0;
+ return iommu_domain_set_attr(iommu->domain,
+ DOMAIN_ATTR_PAMU_ENABLE, &enable);
+}
+
+static int vfio_enable_iommu_domain(struct vfio_iommu *iommu)
+{
+ int enable = 1;
+ return iommu_domain_set_attr(iommu->domain,
+ DOMAIN_ATTR_PAMU_ENABLE, &enable);
+}
+
+static inline bool ranges_overlap(dma_addr_t start1, size_t size1,
+ dma_addr_t start2, size_t size2)
+{
+ if (start1 < start2)
+ return (start2 - start1 < size1);
+ else if (start2 < start1)
+ return (start1 - start2 < size2);
+ return (size1 > 0 && size2 > 0);
+}
+
+static struct vfio_dma *vfio_find_dma(struct vfio_iommu *iommu,
+ dma_addr_t start, size_t size)
+{
+ struct vfio_dma *dma;
+
+ list_for_each_entry(dma, &iommu->dma_list, next) {
+ if (ranges_overlap(dma->iova, NPAGE_TO_SIZE(dma->npage),
+ start, size))
+ return dma;
+ }
+ return NULL;
+}
+
+static long vfio_remove_dma_overlap(struct vfio_iommu *iommu, dma_addr_t start,
+ size_t size, struct vfio_dma *dma)
+{
+ struct vfio_dma *split;
+ long npage_lo, npage_hi;
+
+ /* Existing dma region is completely covered, unmap all */
+ if (start <= dma->iova &&
+ start + size >= dma->iova + NPAGE_TO_SIZE(dma->npage)) {
+ vfio_dma_unmap(iommu, dma->iova, dma->npage, dma->prot);
+ list_del(&dma->next);
+ npage_lo = dma->npage;
+ kfree(dma);
+ return npage_lo;
+ }
+
+ /* Overlap low address of existing range */
+ if (start <= dma->iova) {
+ size_t overlap;
+
+ overlap = start + size - dma->iova;
+ npage_lo = overlap >> PAGE_SHIFT;
+
+ vfio_dma_unmap(iommu, dma->iova, npage_lo, dma->prot);
+ dma->iova += overlap;
+ dma->vaddr += overlap;
+ dma->npage -= npage_lo;
+ return npage_lo;
+ }
+
+ /* Overlap high address of existing range */
+ if (start + size >= dma->iova + NPAGE_TO_SIZE(dma->npage)) {
+ size_t overlap;
+
+ overlap = dma->iova + NPAGE_TO_SIZE(dma->npage) - start;
+ npage_hi = overlap >> PAGE_SHIFT;
+
+ vfio_dma_unmap(iommu, start, npage_hi, dma->prot);
+ dma->npage -= npage_hi;
+ return npage_hi;
+ }
+
+ /* Split existing */
+ npage_lo = (start - dma->iova) >> PAGE_SHIFT;
+ npage_hi = dma->npage - (size >> PAGE_SHIFT) - npage_lo;
+
+ split = kzalloc(sizeof(*split), GFP_KERNEL);
+ if (!split)
+ return -ENOMEM;
+
+ vfio_dma_unmap(iommu, start, size >> PAGE_SHIFT, dma->prot);
+
+ dma->npage = npage_lo;
+
+ split->npage = npage_hi;
+ split->iova = start + size;
+ split->vaddr = dma->vaddr + NPAGE_TO_SIZE(npage_lo) + size;
+ split->prot = dma->prot;
+ list_add(&split->next, &iommu->dma_list);
+ return size >> PAGE_SHIFT;
+}
+
+/* Map DMA region */
+static int __vfio_dma_map(struct vfio_iommu *iommu, dma_addr_t iova,
+ unsigned long vaddr, long npage, int prot)
+{
+ dma_addr_t start = iova;
+ long locked = 0;
+ int ret, i;
+ unsigned long size;
+ unsigned int win, nr_subwindows;
+ dma_addr_t iova_map, iova_end;
+
+ /*
+ * XXX We break mappings into pages and use get_user_pages_fast to
+ * pin the pages in memory. It's been suggested that mlock might
+ * provide a more efficient mechanism, but nothing prevents the
+ * user from munlocking the pages, which could then allow the user
+ * access to random host memory. We also have no guarantee from the
+ * IOMMU API that the iommu driver can unmap sub-pages of previous
+ * mappings. This means we might lose an entire range if a single
+ * page within it is unmapped. Single page mappings are inefficient,
+ * but provide the most flexibility for now.
+ */
+ /* total size to be mapped */
+ size = npage << PAGE_SHIFT;
+ nr_subwindows = size / iommu->page_size;
+ iova_map = iova;
+ iova_end = iova + size;
+
+ for (i = 0; i < nr_subwindows; i++) {
+ unsigned long pfn;
+ unsigned long nr_pages;
+ dma_addr_t mapsize;
+
+ win = iova_to_win(iommu, iova_map);
+ if (iova_map != iommu->aperture_start + iommu->page_size * win) {
+ pr_err("%s iova (%llx) not alligned to window size %llx\n",
+ __func__, iova, iommu->page_size);
+ __vfio_dma_do_unmap(iommu, start, npage, prot);
+ return -EINVAL;
+ }
+
+ mapsize = min(iova_map - iova_end, iommu->page_size);
+ if (mapsize < iommu->page_size) {
+ pr_err("%s iova (%llx) not alligned to window size %llx\n",
+ __func__, iova, iommu->page_size);
+ __vfio_dma_do_unmap(iommu, start, npage, prot);
+ return -EINVAL;
+ }
+
+ nr_pages = mapsize >> PAGE_SHIFT;
+ ret = vaddr_get_pfn(vaddr, prot, &pfn, nr_pages);
+ if (ret) {
+ pr_err("%s unable to map vaddr = %lx\n",
+ __func__, vaddr);
+ __vfio_dma_do_unmap(iommu, start, npage, prot);
+ return ret;
+ }
+
+ if (!is_invalid_reserved_pfn(pfn))
+ locked++;
+
+
+ ret = iommu_domain_window_enable(iommu->domain, win,
+ (phys_addr_t)pfn << PAGE_SHIFT,
+ mapsize, prot);
+ if (ret) {
+ pr_err("%s unable to iommu_map()\n", __func__);
+ /* Back out mappings on error */
+ put_pfn(pfn, prot);
+ __vfio_dma_do_unmap(iommu, start, npage, prot);
+ return ret;
+ }
+
+ iova_map += mapsize;
+ vaddr += mapsize;
+ }
+ vfio_enable_iommu_domain(iommu);
+
+ vfio_lock_acct(locked);
+ return 0;
+}
+
+static int vfio_dma_do_map(struct vfio_iommu *iommu,
+ struct vfio_iommu_type1_dma_map *map)
+{
+ struct vfio_dma *dma, *pdma = NULL;
+ dma_addr_t iova = map->iova;
+ unsigned long locked, lock_limit, vaddr = map->vaddr;
+ size_t size = map->size;
+ int ret = 0, prot = 0;
+ long npage;
+
+ /* 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? */
+
+ /* Don't allow IOVA wrap */
+ if (iova + size && iova + size < iova)
+ return -EINVAL;
+
+ /* Don't allow virtual address wrap */
+ if (vaddr + size && vaddr + size < vaddr)
+ return -EINVAL;
+
+ npage = size >> PAGE_SHIFT;
+ if (!npage)
+ return -EINVAL;
+
+ mutex_lock(&iommu->lock);
+
+ if (vfio_find_dma(iommu, iova, size)) {
+ ret = -EBUSY;
+ goto out_lock;
+ }
+
+ /* account for locked pages */
+ locked = current->mm->locked_vm + npage;
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+ if (locked > lock_limit && !capable(CAP_IPC_LOCK)) {
+ pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n",
+ __func__, rlimit(RLIMIT_MEMLOCK));
+ ret = -ENOMEM;
+ goto out_lock;
+ }
+
+ ret = __vfio_dma_map(iommu, iova, vaddr, npage, prot);
+ if (ret)
+ goto out_lock;
+
+ /* Check if we about a region below - nothing below 0 */
+ if (iova) {
+ dma = vfio_find_dma(iommu, iova - 1, 1);
+ if (dma && dma->prot == prot &&
+ dma->vaddr + NPAGE_TO_SIZE(dma->npage) == vaddr) {
+
+ dma->npage += npage;
+ iova = dma->iova;
+ vaddr = dma->vaddr;
+ npage = dma->npage;
+ size = NPAGE_TO_SIZE(npage);
+
+ pdma = dma;
+ }
+ }
+
+ /* Check if we abut a region above - nothing above ~0 + 1 */
+ if (iova + size) {
+ dma = vfio_find_dma(iommu, iova + size, 1);
+ if (dma && dma->prot == prot &&
+ dma->vaddr == vaddr + size) {
+
+ dma->npage += npage;
+ dma->iova = iova;
+ dma->vaddr = vaddr;
+
+ /*
+ * If merged above and below, remove previously
+ * merged entry. New entry covers it.
+ */
+ if (pdma) {
+ list_del(&pdma->next);
+ kfree(pdma);
+ }
+ pdma = dma;
+ }
+ }
+
+ /* Isolated, new region */
+ if (!pdma) {
+ dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+ if (!dma) {
+ ret = -ENOMEM;
+ vfio_dma_unmap(iommu, iova, npage, prot);
+ goto out_lock;
+ }
+
+ dma->npage = npage;
+ dma->iova = iova;
+ dma->vaddr = vaddr;
+ dma->prot = prot;
+ list_add(&dma->next, &iommu->dma_list);
+ }
+
+out_lock:
+ mutex_unlock(&iommu->lock);
+ return ret;
+}
+
+static int vfio_dma_do_unmap(struct vfio_iommu *iommu,
+ struct vfio_iommu_type1_dma_unmap *unmap)
+{
+ long ret = 0, npage = unmap->size >> PAGE_SHIFT;
+ struct vfio_dma *dma, *tmp;
+
+ mutex_lock(&iommu->lock);
+
+ list_for_each_entry_safe(dma, tmp, &iommu->dma_list, next) {
+ if (ranges_overlap(dma->iova, NPAGE_TO_SIZE(dma->npage),
+ unmap->iova, unmap->size)) {
+ ret = vfio_remove_dma_overlap(iommu, unmap->iova,
+ unmap->size, dma);
+ if (ret > 0)
+ npage -= ret;
+ if (ret < 0 || npage == 0)
+ break;
+ }
+ }
+
+ /* disable iommu if no mapping */
+ if (list_empty(&iommu->dma_list))
+ vfio_disable_iommu_domain(iommu);
+
+ mutex_unlock(&iommu->lock);
+ return ret > 0 ? 0 : (int)ret;
+}
+
+static int vfio_handle_get_attr(struct vfio_iommu *iommu,
+ struct vfio_pamu_attr *pamu_attr)
+{
+ switch (pamu_attr->attribute) {
+ case VFIO_ATTR_GEOMETRY: {
+ struct iommu_domain_geometry geom;
+ if (iommu_domain_get_attr(iommu->domain,
+ DOMAIN_ATTR_GEOMETRY, &geom)) {
+ pr_err("%s Error getting domain geometry\n",
+ __func__);
+ return -EFAULT;
+ }
+
+ pamu_attr->attr_info.attr.aperture_start = geom.aperture_start;
+ pamu_attr->attr_info.attr.aperture_end = geom.aperture_end;
+ break;
+ }
+ case VFIO_ATTR_WINDOWS: {
+ u32 count;
+ if (iommu_domain_get_attr(iommu->domain,
+ DOMAIN_ATTR_WINDOWS, &count)) {
+ pr_err("%s Error getting domain windows\n",
+ __func__);
+ return -EFAULT;
+ }
+
+ pamu_attr->attr_info.windows = count;
+ break;
+ }
+ case VFIO_ATTR_PAMU_STASH: {
+ struct iommu_stash_attribute stash;
+ if (iommu_domain_get_attr(iommu->domain,
+ DOMAIN_ATTR_PAMU_STASH, &stash)) {
+ pr_err("%s Error getting domain windows\n",
+ __func__);
+ return -EFAULT;
+ }
+
+ pamu_attr->attr_info.stash.cpu = stash.cpu;
+ pamu_attr->attr_info.stash.cache = stash.cache;
+ break;
+ }
+
+ default:
+ pr_err("%s Error: Invalid attribute (%d)\n",
+ __func__, pamu_attr->attribute);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vfio_handle_set_attr(struct vfio_iommu *iommu,
+ struct vfio_pamu_attr *pamu_attr)
+{
+ switch (pamu_attr->attribute) {
+ case VFIO_ATTR_GEOMETRY: {
+ struct iommu_domain_geometry geom;
+
+ geom.aperture_start = pamu_attr->attr_info.attr.aperture_start;
+ geom.aperture_end = pamu_attr->attr_info.attr.aperture_end;
+ iommu->aperture_start = geom.aperture_start;
+ iommu->aperture_end = geom.aperture_end;
+ geom.force_aperture = 1;
+ if (iommu_domain_set_attr(iommu->domain,
+ DOMAIN_ATTR_GEOMETRY, &geom)) {
+ pr_err("%s Error setting domain geometry\n", __func__);
+ return -EFAULT;
+ }
+
+ break;
+ }
+ case VFIO_ATTR_WINDOWS: {
+ u32 count = pamu_attr->attr_info.windows;
+ u64 size;
+ if (count > 256) {
+ pr_err("Number of subwindows requested (%d) is 256\n",
+ count);
+ return -EINVAL;
+ }
+ iommu->nsubwindows = pamu_attr->attr_info.windows;
+ size = iommu->aperture_end - iommu->aperture_start + 1;
+ iommu->page_size = size / count;
+ if (iommu_domain_set_attr(iommu->domain,
+ DOMAIN_ATTR_WINDOWS, &count)) {
+ pr_err("%s Error getting domain windows\n",
+ __func__);
+ return -EFAULT;
+ }
+
+ break;
+ }
+ case VFIO_ATTR_PAMU_STASH: {
+ struct iommu_stash_attribute stash;
+
+ stash.cpu = pamu_attr->attr_info.stash.cpu;
+ stash.cache = pamu_attr->attr_info.stash.cache;
+ if (iommu_domain_set_attr(iommu->domain,
+ DOMAIN_ATTR_PAMU_STASH, &stash)) {
+ pr_err("%s Error getting domain windows\n",
+ __func__);
+ return -EFAULT;
+ }
+ break;
+ }
+
+ default:
+ pr_err("%s Error: Invalid attribute (%d)\n",
+ __func__, pamu_attr->attribute);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vfio_msi_map(struct vfio_iommu *iommu,
+ struct vfio_pamu_msi_bank_map *msi_map, int prot)
+{
+ struct msi_region region;
+ int window;
+ int ret;
+
+ ret = msi_get_region(msi_map->msi_bank_index, &region);
+ if (ret) {
+ pr_err("%s MSI region (%d) not found\n", __func__,
+ msi_map->msi_bank_index);
+ return ret;
+ }
+
+ window = iova_to_win(iommu, msi_map->iova);
+ ret = iommu_domain_window_enable(iommu->domain, window, region.addr,
+ region.size, prot);
+ if (ret) {
+ pr_err("%s Error: unable to map msi region\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int vfio_do_msi_map(struct vfio_iommu *iommu,
+ struct vfio_pamu_msi_bank_map *msi_map)
+{
+ struct vfio_msi_dma *msi_dma;
+ int ret, prot = 0;
+
+ /* READ/WRITE from device perspective */
+ if (msi_map->flags & VFIO_DMA_MAP_FLAG_WRITE)
+ prot |= IOMMU_WRITE;
+ if (msi_map->flags & VFIO_DMA_MAP_FLAG_READ)
+ prot |= IOMMU_READ;
+
+ if (!prot)
+ return -EINVAL; /* No READ/WRITE? */
+
+ ret = vfio_msi_map(iommu, msi_map, prot);
+ if (ret)
+ return ret;
+
+ msi_dma = kzalloc(sizeof(*msi_dma), GFP_KERNEL);
+ if (!msi_dma)
+ return -ENOMEM;
+
+ msi_dma->iova = msi_map->iova;
+ msi_dma->bank_id = msi_map->msi_bank_index;
+ list_add(&msi_dma->next, &iommu->msi_dma_list);
+ return 0;
+}
+
+static void vfio_msi_unmap(struct vfio_iommu *iommu, dma_addr_t iova)
+{
+ int window;
+ window = iova_to_win(iommu, iova);
+ iommu_domain_window_disable(iommu->domain, window);
+}
+
+static int vfio_do_msi_unmap(struct vfio_iommu *iommu,
+ struct vfio_pamu_msi_bank_unmap *msi_unmap)
+{
+ struct vfio_msi_dma *mdma, *mdma_tmp;
+
+ list_for_each_entry_safe(mdma, mdma_tmp, &iommu->msi_dma_list, next) {
+ if (mdma->iova == msi_unmap->iova) {
+ vfio_msi_unmap(iommu, mdma->iova);
+ list_del(&mdma->next);
+ kfree(mdma);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+static void *vfio_iommu_fsl_pamu_open(unsigned long arg)
+{
+ struct vfio_iommu *iommu;
+
+ if (arg != VFIO_FSL_PAMU_IOMMU)
+ return ERR_PTR(-EINVAL);
+
+ iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
+ if (!iommu)
+ return ERR_PTR(-ENOMEM);
+
+ INIT_LIST_HEAD(&iommu->group_list);
+ INIT_LIST_HEAD(&iommu->dma_list);
+ INIT_LIST_HEAD(&iommu->msi_dma_list);
+ 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);
+ }
+
+ return iommu;
+}
+
+static void vfio_iommu_fsl_pamu_release(void *iommu_data)
+{
+ struct vfio_iommu *iommu = iommu_data;
+ struct vfio_group *group, *group_tmp;
+ struct vfio_dma *dma, *dma_tmp;
+ struct vfio_msi_dma *mdma, *mdma_tmp;
+
+ 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);
+ }
+
+ list_for_each_entry_safe(dma, dma_tmp, &iommu->dma_list, next) {
+ vfio_dma_unmap(iommu, dma->iova, dma->npage, dma->prot);
+ list_del(&dma->next);
+ kfree(dma);
+ }
+
+ list_for_each_entry_safe(mdma, mdma_tmp, &iommu->msi_dma_list, next) {
+ vfio_msi_unmap(iommu, mdma->iova);
+ list_del(&mdma->next);
+ kfree(mdma);
+ }
+
+ iommu_domain_free(iommu->domain);
+ iommu->domain = NULL;
+ kfree(iommu);
+}
+
+static long vfio_iommu_fsl_pamu_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_FSL_PAMU_IOMMU:
+ return 1;
+ default:
+ return 0;
+ }
+ } 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;
+
+ 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;
+
+ return vfio_dma_do_unmap(iommu, &unmap);
+ } else if (cmd == VFIO_IOMMU_PAMU_GET_ATTR) {
+ struct vfio_pamu_attr pamu_attr;
+
+ minsz = offsetofend(struct vfio_pamu_attr, attr_info);
+ if (copy_from_user(&pamu_attr, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (pamu_attr.argsz < minsz)
+ return -EINVAL;
+
+ vfio_handle_get_attr(iommu, &pamu_attr);
+
+ copy_to_user((void __user *)arg, &pamu_attr, minsz);
+ return 0;
+ } else if (cmd == VFIO_IOMMU_PAMU_SET_ATTR) {
+ struct vfio_pamu_attr pamu_attr;
+
+ minsz = offsetofend(struct vfio_pamu_attr, attr_info);
+ if (copy_from_user(&pamu_attr, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (pamu_attr.argsz < minsz)
+ return -EINVAL;
+
+ vfio_handle_set_attr(iommu, &pamu_attr);
+ return 0;
+ } else if (cmd == VFIO_IOMMU_PAMU_GET_MSI_BANK_COUNT) {
+ return msi_get_region_count();
+ } else if (cmd == VFIO_IOMMU_PAMU_MAP_MSI_BANK) {
+ struct vfio_pamu_msi_bank_map msi_map;
+
+ minsz = offsetofend(struct vfio_pamu_msi_bank_map, iova);
+ if (copy_from_user(&msi_map, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (msi_map.argsz < minsz)
+ return -EINVAL;
+
+ vfio_do_msi_map(iommu, &msi_map);
+ return 0;
+ } else if (cmd == VFIO_IOMMU_PAMU_UNMAP_MSI_BANK) {
+ struct vfio_pamu_msi_bank_unmap msi_unmap;
+
+ minsz = offsetofend(struct vfio_pamu_msi_bank_unmap, iova);
+ if (copy_from_user(&msi_unmap, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (msi_unmap.argsz < minsz)
+ return -EINVAL;
+
+ vfio_do_msi_unmap(iommu, &msi_unmap);
+ return 0;
+
+ }
+
+ return -ENOTTY;
+}
+
+static int vfio_iommu_fsl_pamu_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;
+ }
+ }
+
+ 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_fsl_pamu_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 const struct vfio_iommu_driver_ops vfio_iommu_driver_ops_fsl_pamu = {
+ .name = "vfio-iommu-fsl_pamu",
+ .owner = THIS_MODULE,
+ .open = vfio_iommu_fsl_pamu_open,
+ .release = vfio_iommu_fsl_pamu_release,
+ .ioctl = vfio_iommu_fsl_pamu_ioctl,
+ .attach_group = vfio_iommu_fsl_pamu_attach_group,
+ .detach_group = vfio_iommu_fsl_pamu_detach_group,
+};
+
+static int __init vfio_iommu_fsl_pamu_init(void)
+{
+ if (!iommu_present(&pci_bus_type))
+ return -ENODEV;
+
+ return vfio_register_iommu_driver(&vfio_iommu_driver_ops_fsl_pamu);
+}
+
+static void __exit vfio_iommu_fsl_pamu_cleanup(void)
+{
+ vfio_unregister_iommu_driver(&vfio_iommu_driver_ops_fsl_pamu);
+}
+
+module_init(vfio_iommu_fsl_pamu_init);
+module_exit(vfio_iommu_fsl_pamu_cleanup);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);