[PATCH 7/7] vfio pci: Add vfio iommu implementation for FSL_PAMU
Bhushan Bharat-R65777
R65777 at freescale.com
Thu Sep 26 15:27:28 EST 2013
> -----Original Message-----
> From: Alex Williamson [mailto:alex.williamson at redhat.com]
> Sent: Thursday, September 26, 2013 12:37 AM
> To: Bhushan Bharat-R65777
> Cc: joro at 8bytes.org; benh at kernel.crashing.org; galak at kernel.crashing.org; linux-
> kernel at vger.kernel.org; linuxppc-dev at lists.ozlabs.org; linux-
> pci at vger.kernel.org; agraf at suse.de; Wood Scott-B07421; iommu at lists.linux-
> foundation.org; Bhushan Bharat-R65777
> Subject: Re: [PATCH 7/7] vfio pci: Add vfio iommu implementation for FSL_PAMU
>
> On Thu, 2013-09-19 at 12:59 +0530, Bharat Bhushan wrote:
> > This patch adds vfio iommu support for Freescale IOMMU (PAMU -
> > Peripheral Access Management Unit).
> >
> > 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
> >
> > Some of the code is derived from TYPE1 iommu (driver/vfio/vfio_iommu_type1.c).
> >
> > Signed-off-by: Bharat Bhushan <bharat.bhushan at freescale.com>
> > ---
> > drivers/vfio/Kconfig | 6 +
> > drivers/vfio/Makefile | 1 +
> > drivers/vfio/vfio_iommu_fsl_pamu.c | 952
> ++++++++++++++++++++++++++++++++++++
> > include/uapi/linux/vfio.h | 100 ++++
> > 4 files changed, 1059 insertions(+), 0 deletions(-) create mode
> > 100644 drivers/vfio/vfio_iommu_fsl_pamu.c
> >
> > diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig index
> > 26b3d9d..7d1da26 100644
> > --- a/drivers/vfio/Kconfig
> > +++ b/drivers/vfio/Kconfig
> > @@ -8,11 +8,17 @@ config VFIO_IOMMU_SPAPR_TCE
> > depends on VFIO && SPAPR_TCE_IOMMU
> > 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_SPAPR_TCE if (PPC_POWERNV || PPC_PSERIES)
> > + 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
> > c5792ec..7461350 100644
> > --- a/drivers/vfio/Makefile
> > +++ b/drivers/vfio/Makefile
> > @@ -1,4 +1,5 @@
> > obj-$(CONFIG_VFIO) += vfio.o
> > obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_common.o
> > vfio_iommu_type1.o
> > obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_common.o
> > vfio_iommu_spapr_tce.o
> > +obj-$(CONFIG_VFIO_IOMMU_FSL_PAMU) += vfio_iommu_common.o
> > +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..b29365f
> > --- /dev/null
> > +++ b/drivers/vfio/vfio_iommu_fsl_pamu.c
> > @@ -0,0 +1,952 @@
> > +/*
> > + * 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 at 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>
> > +#include <asm/fsl_pamu_stash.h>
> > +
> > +#include "vfio_iommu_common.h"
> > +
> > +#define DRIVER_VERSION "0.1"
> > +#define DRIVER_AUTHOR "Bharat Bhushan <bharat.bhushan at 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 rb_root dma_list;
> > + struct list_head msi_dma_list;
> > + struct list_head group_list;
> > +};
> > +
> > +struct vfio_dma {
> > + struct rb_node node;
> > + dma_addr_t iova; /* Device address */
> > + unsigned long vaddr; /* Process virtual addr */
> > + size_t size; /* Number of pages */
>
> Is this really pages?
Comment is leftover of previous implementation.
>
> > + 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;
> > +};
> > +
> > +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)
>
> because this looks more like it's bytes...
>
> > + 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)
>
> so does this...
>
> > + 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); }
>
>
> So if your vfio_dma.size is actually in bytes, why isn't all this code in
> common?
This takes "struct vfio_iommu " which is not same on type1 and fsl_pamu, so I have not done that in first phase.
But yes I completely agree that much more consolidation can be done. I would like to do that in next step.
>
> > +
> > +static int iova_to_win(struct vfio_iommu *iommu, dma_addr_t iova) {
> > + u64 offset = iova - iommu->aperture_start;
> > + do_div(offset, iommu->page_size);
> > + return (int) offset;
> > +}
> > +
> > +static int vfio_disable_iommu_domain(struct vfio_iommu *iommu) {
> > + int enable = 0;
> > + return iommu_domain_set_attr(iommu->domain,
> > + DOMAIN_ATTR_FSL_PAMU_ENABLE, &enable); }
>
> This is never called?!
>
> > +
> > +static int vfio_enable_iommu_domain(struct vfio_iommu *iommu) {
> > + int enable = 1;
> > + return iommu_domain_set_attr(iommu->domain,
> > + DOMAIN_ATTR_FSL_PAMU_ENABLE, &enable); }
> > +
> > +/* Unmap DMA region */
> > +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;
> > + int win, win_start, win_end;
> > + long unlocked = 0;
> > + unsigned int nr_pages;
> > +
> > + nr_pages = iommu->page_size / PAGE_SIZE;
> > + win_start = iova_to_win(iommu, iova);
> > + win_end = iova_to_win(iommu, iova + *size - 1);
> > +
> > + /* Release the pinned pages */
> > + for (win = win_start; win <= win_end; iova += iommu->page_size, win++) {
> > + unsigned long pfn;
> > +
> > + pfn = iommu_iova_to_phys(iommu->domain, iova) >> PAGE_SHIFT;
> > + if (!pfn)
> > + continue;
> > +
> > + iommu_domain_window_disable(iommu->domain, win);
> > +
> > + unlocked += vfio_unpin_pages(pfn, nr_pages, dma->prot, 1);
> > + }
> > +
> > + 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;
> > +}
>
> Hmm, this looks identical to type1, can we share more?
Yes, as I said this uses " struct vfio_iommu", vfio_unmap_unpin() etc which are different for type1 and fsl_pamu.
In this patchset I only moved the function which are straight forward. But yes I am working on doing more consolidation patches.
>
> > +
> > +/* Map DMA region */
> > +static int vfio_dma_map(struct vfio_iommu *iommu, dma_addr_t iova,
> > + unsigned long vaddr, long npage, int prot) {
> > + int ret = 0, i;
> > + size_t size;
> > + unsigned int win, nr_subwindows;
> > + dma_addr_t iovamap;
> > +
> > + /* total size to be mapped */
> > + size = npage << PAGE_SHIFT;
> > + do_div(size, iommu->page_size);
> > + nr_subwindows = size;
> > + size = npage << PAGE_SHIFT;
>
> Is all this do_div() stuff necessary? If page_size is a power of two, just
> shift it.
Will do
>
> > + iovamap = iova;
> > + for (i = 0; i < nr_subwindows; i++) {
> > + unsigned long pfn;
> > + unsigned long nr_pages;
> > + dma_addr_t mapsize;
> > + struct vfio_dma *dma = NULL;
> > +
> > + win = iova_to_win(iommu, iovamap);
>
> Aren't these consecutive, why can't we just increment?
Yes, will do
>
> > + if (iovamap != iommu->aperture_start + iommu->page_size * win) {
> > + pr_err("%s iova(%llx) unalligned to window size %llx\n",
> > + __func__, iovamap, iommu->page_size);
> > + ret = -EINVAL;
> > + break;
> > + }
>
> Can't this only happen on the first one?
iova to be mapped in a window must be (iommu->aperture_start + iommu->page_size * win)
But as you pointed out that "win" can incremented iovamap is always incremented by page_size then checking this outside the look can be done. But this is the requirement of our iommu and we should error out if this is not met.
> Seems like it should be outside of the
> loop. What about alignment with the end of the window, do you care? Check
> spelling in your warning, but better yet, get rid of it, this doesn't seem like
> something we need to error on.
>
> > +
> > + mapsize = min(iova + size - iovamap, iommu->page_size);
> > + /*
> > + * FIXME: Currently we only support mapping page-size
> > + * of subwindow-size.
> > + */
> > + if (mapsize < iommu->page_size) {
> > + pr_err("%s iova (%llx) not alligned to window size %llx\n",
> > + __func__, iovamap, iommu->page_size);
> > + ret = -EINVAL;
> > + break;
> > + }
>
> So you do care about the end alignment, but why can't we error for both of these
> in advance?
Eventually this should go away, I will remove this :)
>
> > +
> > + nr_pages = mapsize >> PAGE_SHIFT;
> > +
> > + /* Pin a contiguous chunk of memory */
> > + ret = vfio_pin_pages(vaddr, nr_pages, prot, &pfn);
> > + if (ret != nr_pages) {
> > + pr_err("%s unable to pin pages = %lx, pinned(%lx/%lx)\n",
> > + __func__, vaddr, npage, nr_pages);
> > + ret = -EINVAL;
> > + break;
> > + }
>
> How likely is this to succeed? It seems like we're relying on userspace to use
> hugepages to make this work.
Yes, userspace will first have hugepages and than calls DMA_MAP()
>
> > +
> > + 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__);
> > + ret = -EINVAL;
> > + break;
> > + }
>
> You might consider how many cases you're returning EINVAL and think about how
> difficult this will be to debug. I don't think we can leave all these pr_err()s
> since it gives userspace a trivial way to spam log files.
>
> > +
> > + /*
> > + * 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(iovamap)) {
> > + struct vfio_dma *tmp;
> > + tmp = vfio_find_dma(iommu, iovamap - 1, 1);
> > + if (tmp && tmp->prot == prot &&
> > + tmp->vaddr + tmp->size == vaddr) {
> > + tmp->size += mapsize;
> > + 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(iovamap + mapsize)) {
> > + struct vfio_dma *tmp;
> > + tmp = vfio_find_dma(iommu, iovamap + mapsize, 1);
> > + if (tmp && tmp->prot == prot &&
> > + tmp->vaddr == vaddr + mapsize) {
> > + vfio_remove_dma(iommu, tmp);
> > + if (dma) {
> > + dma->size += tmp->size;
> > + kfree(tmp);
> > + } else {
> > + tmp->size += mapsize;
> > + tmp->iova = iovamap;
> > + tmp->vaddr = vaddr;
> > + vfio_insert_dma(iommu, tmp);
> > + dma = tmp;
> > + }
> > + }
> > + }
> > +
> > + if (!dma) {
> > + dma = kzalloc(sizeof(*dma), GFP_KERNEL);
> > + if (!dma) {
> > + iommu_unmap(iommu->domain, iovamap, mapsize);
> > + vfio_unpin_pages(pfn, npage, prot, true);
> > + ret = -ENOMEM;
> > + break;
> > + }
> > +
> > + dma->size = mapsize;
> > + dma->iova = iovamap;
> > + dma->vaddr = vaddr;
> > + dma->prot = prot;
> > + vfio_insert_dma(iommu, dma);
> > + }
> > +
> > + iovamap += mapsize;
> > + vaddr += mapsize;
>
> Another chunk that looks like it's probably identical to type1. Can we rip this
> out to another function and add it to common?
Yes, and same answer :)
>
> > + }
> > +
> > + if (ret) {
> > + struct vfio_dma *tmp;
> > + while ((tmp = vfio_find_dma(iommu, iova, size))) {
> > + int r = vfio_remove_dma_overlap(iommu, iova,
> > + &size, tmp);
> > + if (WARN_ON(r || !size))
> > + break;
> > + }
> > + }
>
>
> Broken whitespace, please run scripts/checkpatch.pl before posting.
>
> > +
> > + vfio_enable_iommu_domain(iommu);
>
> I don't quite understand your semantics here since you never use the disable
> version, is this just redundant after the first mapping? When dma_list is empty
> should it be disabled?
Yes, I intended to do that but somehow in final version it is not there :(
> Is there a bug here that an error will enable the iommu
> domain even if there are no entries?
Will correct this.
>
> > + return 0;
> > +}
> > +
> > +static int vfio_dma_do_map(struct vfio_iommu *iommu,
> > + struct vfio_iommu_type1_dma_map *map) {
> > + dma_addr_t iova = map->iova;
> > + size_t size = map->size;
> > + unsigned long vaddr = map->vaddr;
> > + 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;
> > +
> > + /*
> > + * FIXME: Currently we only support mapping page-size
> > + * of subwindow-size.
> > + */
> > + if (size < iommu->page_size)
> > + return -EINVAL;
> > +
>
> I'd think the start and end alignment could be tested here.
>
> > + npage = size >> PAGE_SHIFT;
> > + if (!npage)
> > + return -EINVAL;
> > +
> > + mutex_lock(&iommu->lock);
> > +
> > + if (vfio_find_dma(iommu, iova, size)) {
> > + ret = -EEXIST;
> > + goto out_lock;
> > + }
> > +
> > + vfio_dma_map(iommu, iova, vaddr, npage, prot);
> > +
> > +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) {
> > + struct vfio_dma *dma;
> > + size_t unmapped = 0, size;
> > + int ret = 0;
> > +
> > + 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;
> > +}
> > +
> > +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 pamu_stash_attribute stash;
> > + if (iommu_domain_get_attr(iommu->domain,
> > + DOMAIN_ATTR_FSL_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;
> > + do_div(size, count);
> > + iommu->page_size = size;
> > + 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 pamu_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_FSL_PAMU_STASH, &stash)) {
> > + pr_err("%s Error getting domain windows\n",
> > + __func__);
> > + return -EFAULT;
> > + }
> > + break;
>
> Why do we throw away the return value of iommu_domain_set_attr and replace it
> with EFAULT in all these cases?
Will use the return of iommu_domain_set_attr().
> I assume all these pr_err()s are leftover
> debug. Can the user do anything they shouldn't through these? How do we
> guarantee that?
I will move these to pr_debug()
>
> > + }
> > +
> > + 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, ®ion);
> > + 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;
>
> What happens when the user creates multiple MSI mappings at the same iova? What
> happens when DMA mappings overlap MSI mappings?
Good point, will correct this.
> Shouldn't there be some locking
> around list manipulation?
Yes, will correct this as well.
Thanks
-Bharat
>
> > +}
> > +
> > +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);
> > + iommu->dma_list = RB_ROOT;
> > + 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_msi_dma *mdma, *mdma_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;
> > + }
> > +
> > + 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;
> > + 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);
> > + } 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);
> > diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
> > index 0fd47f5..d359055 100644
> > --- a/include/uapi/linux/vfio.h
> > +++ b/include/uapi/linux/vfio.h
> > @@ -23,6 +23,7 @@
> >
> > #define VFIO_TYPE1_IOMMU 1
> > #define VFIO_SPAPR_TCE_IOMMU 2
> > +#define VFIO_FSL_PAMU_IOMMU 3
> >
> > /*
> > * The IOCTL interface is designed for extensibility by embedding the
> > @@ -451,4 +452,103 @@ struct vfio_iommu_spapr_tce_info {
> >
> > /* *****************************************************************
> > */
> >
> > +/*********** APIs for VFIO_PAMU type only ****************/
> > +/*
> > + * VFIO_IOMMU_PAMU_GET_ATTR - _IO(VFIO_TYPE, VFIO_BASE + 17,
> > + * struct vfio_pamu_attr)
> > + *
> > + * Gets the iommu attributes for the current vfio container.
> > + * Caller sets argsz and attribute. The ioctl fills in
> > + * the provided struct vfio_pamu_attr based on the attribute
> > + * value that was set.
> > + * Return: 0 on success, -errno on failure */ struct vfio_pamu_attr
> > +{
> > + __u32 argsz;
> > + __u32 flags; /* no flags currently */
> > +#define VFIO_ATTR_GEOMETRY 0
> > +#define VFIO_ATTR_WINDOWS 1
> > +#define VFIO_ATTR_PAMU_STASH 2
> > + __u32 attribute;
> > +
> > + union {
> > + /* VFIO_ATTR_GEOMETRY */
> > + struct {
> > + /* first addr that can be mapped */
> > + __u64 aperture_start;
> > + /* last addr that can be mapped */
> > + __u64 aperture_end;
> > + } attr;
> > +
> > + /* VFIO_ATTR_WINDOWS */
> > + __u32 windows; /* number of windows in the aperture
> > + * initially this will be the max number
> > + * of windows that can be set
> > + */
> > + /* VFIO_ATTR_PAMU_STASH */
> > + struct {
> > + __u32 cpu; /* CPU number for stashing */
> > + __u32 cache; /* cache ID for stashing */
> > + } stash;
> > + } attr_info;
> > +};
> > +#define VFIO_IOMMU_PAMU_GET_ATTR _IO(VFIO_TYPE, VFIO_BASE + 17)
> > +
> > +/*
> > + * VFIO_IOMMU_PAMU_SET_ATTR - _IO(VFIO_TYPE, VFIO_BASE + 18,
> > + * struct vfio_pamu_attr)
> > + *
> > + * Sets the iommu attributes for the current vfio container.
> > + * Caller sets struct vfio_pamu attr, including argsz and attribute
> > +and
> > + * setting any fields that are valid for the attribute.
> > + * Return: 0 on success, -errno on failure */ #define
> > +VFIO_IOMMU_PAMU_SET_ATTR _IO(VFIO_TYPE, VFIO_BASE + 18)
> > +
> > +/*
> > + * VFIO_IOMMU_PAMU_GET_MSI_BANK_COUNT - _IO(VFIO_TYPE, VFIO_BASE +
> > +19, __u32)
> > + *
> > + * Returns the number of MSI banks for this platform. This tells
> > +user space
> > + * how many aperture windows should be reserved for MSI banks when
> > +setting
> > + * the PAMU geometry and window count.
> > + * Return: __u32 bank count on success, -errno on failure */ #define
> > +VFIO_IOMMU_PAMU_GET_MSI_BANK_COUNT _IO(VFIO_TYPE, VFIO_BASE + 19)
> > +
> > +/*
> > + * VFIO_IOMMU_PAMU_MAP_MSI_BANK - _IO(VFIO_TYPE, VFIO_BASE + 20,
> > + * struct vfio_pamu_msi_bank_map)
> > + *
> > + * Maps the MSI bank at the specified index and iova. User space
> > +must
> > + * call this ioctl once for each MSI bank (count of banks is returned
> > +by
> > + * VFIO_IOMMU_PAMU_GET_MSI_BANK_COUNT).
> > + * Caller provides struct vfio_pamu_msi_bank_map with all fields set.
> > + * Return: 0 on success, -errno on failure */
> > +
> > +struct vfio_pamu_msi_bank_map {
> > + __u32 argsz;
> > + __u32 flags; /* no flags currently */
> > + __u32 msi_bank_index; /* the index of the MSI bank */
> > + __u64 iova; /* the iova the bank is to be mapped to */
> > +};
> > +#define VFIO_IOMMU_PAMU_MAP_MSI_BANK _IO(VFIO_TYPE, VFIO_BASE + 20)
> > +
> > +/*
> > + * VFIO_IOMMU_PAMU_UNMAP_MSI_BANK - _IO(VFIO_TYPE, VFIO_BASE + 21,
> > + * struct vfio_pamu_msi_bank_unmap)
> > + *
> > + * Unmaps the MSI bank at the specified iova.
> > + * Caller provides struct vfio_pamu_msi_bank_unmap with all fields set.
> > + * Operates on VFIO file descriptor (/dev/vfio/vfio).
> > + * Return: 0 on success, -errno on failure */
> > +
> > +struct vfio_pamu_msi_bank_unmap {
> > + __u32 argsz;
> > + __u32 flags; /* no flags currently */
> > + __u64 iova; /* the iova to be unmapped to */
> > +};
> > +#define VFIO_IOMMU_PAMU_UNMAP_MSI_BANK _IO(VFIO_TYPE, VFIO_BASE +
> > +21)
> > +
> > #endif /* _UAPIVFIO_H */
>
>
>
More information about the Linuxppc-dev
mailing list