> -----Original Message----- > From: Alex Williamson [mailto:alex.williamson@xxxxxxxxxx] > Sent: Thursday, September 26, 2013 12:37 AM > To: Bhushan Bharat-R65777 > Cc: joro@xxxxxxxxxx; benh@xxxxxxxxxxxxxxxxxxx; galak@xxxxxxxxxxxxxxxxxxx; linux- > kernel@xxxxxxxxxxxxxxx; linuxppc-dev@xxxxxxxxxxxxxxxx; linux- > pci@xxxxxxxxxxxxxxx; agraf@xxxxxxx; Wood Scott-B07421; iommu@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@xxxxxxxxxxxxx> > > --- > > 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@xxxxxxxxxxxxx> > > + * > > + * 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@xxxxxxxxxxxxx>" > > +#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 */ > > > ��.n��������+%������w��{.n�����{���"�)��jg��������ݢj����G�������j:+v���w�m������w�������h�����٥