Comments inline... On Sunday, September 26, 2010 07:54:19 am Michael S. Tsirkin wrote: > I did a quick pass, mostly on memory locking/DMA code. > Some comments inside. > > > +/* > > + * This code handles mapping and unmapping of user data buffers > > + * into DMA'ble space using the IOMMU > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/device.h> > > +#include <linux/pci.h> > > +#include <linux/mm.h> > > +#include <linux/mmu_notifier.h> > > +#include <linux/iommu.h> > > +#include <linux/uiommu.h> > > +#include <linux/sched.h> > > +#include <linux/vfio.h> > > + > > +/* Unmap DMA region */ > > +/* dgate must be held */ > > +static void vfio_dma_unmap(struct vfio_listener *listener, > > + struct dma_map_page *mlp) > > +{ > > + int i; > > + struct vfio_dev *vdev = listener->vdev; > > + > > + list_del(&mlp->list); > > + for (i = 0; i < mlp->npage; i++) > > + (void) uiommu_unmap(vdev->udomain, > > + mlp->daddr + i*PAGE_SIZE, 0); > > Pls put spaces around *, + etc. > I think recent checkpatch versions even warn around this ... OK, cleaned up. > > > + for (i = 0; i < mlp->npage; i++) { > > + if (mlp->rdwr) > > + SetPageDirty(mlp->pages[i]); > > + put_page(mlp->pages[i]); > > + } > > + vdev->mapcount--; > > + listener->mm->locked_vm -= mlp->npage; > > Is there a race against mlock call here? Alas, yes. I took another look at the related infiniband code, and now have adopted their way of doing it. > > > + vdev->locked_pages -= mlp->npage; > > + vfree(mlp->pages); > > + kfree(mlp); > > +} > > + > > +/* Unmap ALL DMA regions */ > > +void vfio_dma_unmapall(struct vfio_listener *listener) > > +{ > > + struct list_head *pos, *pos2; > > + struct dma_map_page *mlp; > > + > > + mutex_lock(&listener->vdev->dgate); > > + list_for_each_safe(pos, pos2, &listener->dm_list) { > > + mlp = list_entry(pos, struct dma_map_page, list); > > + vfio_dma_unmap(listener, mlp); > > + } > > + mutex_unlock(&listener->vdev->dgate); > > +} > > + > > +int vfio_dma_unmap_dm(struct vfio_listener *listener, struct > > vfio_dma_map *dmp) +{ > > + unsigned long start, npage; > > + struct dma_map_page *mlp; > > + struct list_head *pos, *pos2; > > + int ret; > > + > > + start = dmp->vaddr & ~PAGE_SIZE; > > Can address become unaligned? Most logic seems to assume > an aligned address ... Just extra paranoia. > > > + npage = dmp->size >> PAGE_SHIFT; > > + > > + ret = -ENXIO; > > + mutex_lock(&listener->vdev->dgate); > > + list_for_each_safe(pos, pos2, &listener->dm_list) { > > + mlp = list_entry(pos, struct dma_map_page, list); > > + if (dmp->vaddr != mlp->vaddr || mlp->npage != npage) > > + continue; > > + ret = 0; > > + vfio_dma_unmap(listener, mlp); > > + break; > > + } > > + mutex_unlock(&listener->vdev->dgate); > > + return ret; > > +} > > + > > +#ifdef CONFIG_MMU_NOTIFIER > > +/* Handle MMU notifications - user process freed or realloced memory > > + * which may be in use in a DMA region. Clean up region if so. > > + */ > > +static void vfio_dma_handle_mmu_notify(struct mmu_notifier *mn, > > + unsigned long start, unsigned long end) > > +{ > > + struct vfio_listener *listener; > > + unsigned long myend; > > + struct list_head *pos, *pos2; > > + struct dma_map_page *mlp; > > + > > + listener = container_of(mn, struct vfio_listener, mmu_notifier); > > + mutex_lock(&listener->vdev->dgate); > > + list_for_each_safe(pos, pos2, &listener->dm_list) { > > + mlp = list_entry(pos, struct dma_map_page, list); > > + if (mlp->vaddr >= end) > > + continue; > > + /* > > + * Ranges overlap if they're not disjoint; and they're > > + * disjoint if the end of one is before the start of > > + * the other one. > > + */ > > + myend = mlp->vaddr + (mlp->npage << PAGE_SHIFT) - 1; > > + if (!(myend <= start || end <= mlp->vaddr)) { > > I suggest open the () and ivert the condition. I can understand the code better this way. > > > + printk(KERN_WARNING > > + "%s: demap start %lx end %lx va %lx pa %lx\n", > > + __func__, start, end, > > + mlp->vaddr, (long)mlp->daddr); > > + vfio_dma_unmap(listener, mlp); > > And then what would happen? How does user interpret this warning? > How can driver/device recover? It's just a warning that the buffer was demapped due to mmu notifier, instead of explicitly. If the user code accidentally frees or reuses its buffers this can happen. > > > + } > > + } > > + mutex_unlock(&listener->vdev->dgate); > > +} > > + > > +static void vfio_dma_inval_page(struct mmu_notifier *mn, > > + struct mm_struct *mm, unsigned long addr) > > +{ > > + vfio_dma_handle_mmu_notify(mn, addr, addr + PAGE_SIZE); > > +} > > + > > +static void vfio_dma_inval_range_start(struct mmu_notifier *mn, > > + struct mm_struct *mm, unsigned long start, unsigned long end) > > +{ > > + vfio_dma_handle_mmu_notify(mn, start, end); > > +} > > + > > +static const struct mmu_notifier_ops vfio_dma_mmu_notifier_ops = { > > + .invalidate_page = vfio_dma_inval_page, > > + .invalidate_range_start = vfio_dma_inval_range_start, > > +}; > > +#endif /* CONFIG_MMU_NOTIFIER */ > > + > > +/* > > + * Map usr buffer at specific IO virtual address > > + */ > > +static struct dma_map_page *vfio_dma_map_iova( > > + struct vfio_listener *listener, > > + unsigned long start_iova, > > + struct page **pages, > > + int npage, > > + int rdwr) > > +{ > > + struct vfio_dev *vdev = listener->vdev; > > + int ret; > > + int i; > > + phys_addr_t hpa; > > + struct dma_map_page *mlp; > > + unsigned long iova = start_iova; > > + > > + if (vdev->udomain == NULL) > > + return ERR_PTR(-EINVAL); > > + > > + for (i = 0; i < npage; i++) { > > + if (uiommu_iova_to_phys(vdev->udomain, iova + i*PAGE_SIZE)) > > + return ERR_PTR(-EBUSY); > > + } > > + > > + mlp = kzalloc(sizeof *mlp, GFP_KERNEL); > > + if (mlp == NULL) > > + return ERR_PTR(-ENOMEM); > > + rdwr = rdwr ? IOMMU_READ|IOMMU_WRITE : IOMMU_READ; > > + if (vdev->cachec) > > + rdwr |= IOMMU_CACHE; > > + for (i = 0; i < npage; i++) { > > + hpa = page_to_phys(pages[i]); > > + ret = uiommu_map(vdev->udomain, iova, hpa, 0, rdwr); > > + if (ret) { > > + while (--i > 0) { > > + iova -= PAGE_SIZE; > > + (void) uiommu_unmap(vdev->udomain, > > + iova, 0); > > + } > > + kfree(mlp); > > + return ERR_PTR(ret); > > + } > > + iova += PAGE_SIZE; > > + } > > + vdev->mapcount++; > > + > > + mlp->pages = pages; > > + mlp->daddr = start_iova; > > + mlp->npage = npage; > > + return mlp; > > +} > > + > > +int vfio_dma_map_common(struct vfio_listener *listener, > > + unsigned int cmd, struct vfio_dma_map *dmp) > > +{ > > + int locked, lock_limit; > > + struct page **pages; > > + int npage; > > + struct dma_map_page *mlp; > > + int rdwr = (dmp->flags & VFIO_FLAG_WRITE) ? 1 : 0; > > + int ret = 0; > > + > > + if (dmp->vaddr & (PAGE_SIZE-1)) > > + return -EINVAL; > > + if (dmp->size & (PAGE_SIZE-1)) > > + return -EINVAL; > > size must be full pages? Maybe document this? Its in the header file and Doc file. > > > + if (dmp->size <= 0) > > It's u64. Can it be < 0? More paranoia. > > > + return -EINVAL; > > + npage = dmp->size >> PAGE_SHIFT; Added a check for max size - 4G for now. > > This assignment can overflow the integer. > > > + if (npage <= 0) > > + return -EINVAL; > > + > > + mutex_lock(&listener->vdev->dgate); > > + > > + /* account for locked pages */ > > + locked = npage + current->mm->locked_vm; > > Again this can race against mlock I think. Yes. > > > + lock_limit = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur > > + >> PAGE_SHIFT; > > + if ((locked > lock_limit) && !capable(CAP_IPC_LOCK)) { > > rlimit/capability access might also be racy: don't we need > task lock for that? Noone else seems to take a task lock for this sort of thing. Can you point me at task lock code? > > > + printk(KERN_WARNING "%s: RLIMIT_MEMLOCK exceeded\n", > > + __func__); > > + ret = -ENOMEM; > > + goto out_lock; > > + } > > + /* only 1 address space per fd */ > > + if (current->mm != listener->mm) { > > + if (listener->mm != NULL) { > > + ret = -EINVAL; > > + goto out_lock; > > + } > > + listener->mm = current->mm; > > +#ifdef CONFIG_MMU_NOTIFIER > > + listener->mmu_notifier.ops = &vfio_dma_mmu_notifier_ops; > > + ret = mmu_notifier_register(&listener->mmu_notifier, > > + listener->mm); > > + if (ret) > > + printk(KERN_ERR "%s: mmu_notifier_register failed %d\n", > > + __func__, ret); > > + ret = 0; > > What exactly are you doing with the notifiers? > This driver seems to lock all DMA memory, how can > it get moved? > And why is an error ignored? The physical pages get locked, but the mmu notifier detects when the virtual pages get re-used without an intervening de-map. > > > +#endif > > + } > > + > > + pages = vmalloc(npage * sizeof(struct page *)); > > npage comes from userspace? What if it's a huge value? > Also, on a 32 bit system, we will run out of vmalloc space > quickly if we let userspace tie it up indefinitely ... > This is slow path - maybe just lock pages one by one? Still have to lock and remember all the locked pages. Max lock size of 4G will help this. > > > + if (pages == NULL) { > > + ret = ENOMEM; > > + goto out_lock; > > + } > > + ret = get_user_pages_fast(dmp->vaddr, npage, rdwr, pages); > > + if (ret != npage) { > > + printk(KERN_ERR "%s: get_user_pages_fast returns %d, not %d\n", > > + __func__, ret, npage); > > + kfree(pages); > > + ret = -EFAULT; > > + goto out_lock; > > + } > > + ret = 0; > > + > > + mlp = vfio_dma_map_iova(listener, dmp->dmaaddr, > > + pages, npage, rdwr); > > + if (IS_ERR(mlp)) { > > + ret = PTR_ERR(mlp); > > + vfree(pages); > > + goto out_lock; > > + } > > + mlp->vaddr = dmp->vaddr; > > + mlp->rdwr = rdwr; > > + dmp->dmaaddr = mlp->daddr; > > + list_add(&mlp->list, &listener->dm_list); > > + > > + current->mm->locked_vm += npage; > > + listener->vdev->locked_pages += npage; > > This looks too aggressive. > So if you want to use 2 devices, you will > have to double the mlock rlimit for the process? If you know 2 devices are in the same domain, you don't have to repeat the call. If you don't know, then you might double lock pages. > > I think this ioctl would be better done > on the iommu device than on vfio: all it does > is pass calls to iommu anyway. > The you can share locking between devices. Yes, but you have to carry around another fd > > > +out_lock: > > + mutex_unlock(&listener->vdev->dgate); > > + return ret; > > +} > > + > > +int vfio_domain_unset(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + > > + if (vdev->udomain == NULL) > > !vdev->udomain Got rid of all NULL comparisons. > > > + return 0; > > + if (vdev->mapcount) > > + return -EBUSY; > > + uiommu_detach_device(vdev->udomain, &pdev->dev); > > + uiommu_put(vdev->udomain); > > + vdev->udomain = NULL; > > + return 0; > > +} > > + > > +int vfio_domain_set(struct vfio_dev *vdev, int fd, int unsafe_ok) > > +{ > > + struct uiommu_domain *udomain; > > + struct pci_dev *pdev = vdev->pdev; > > + int ret; > > + int safe; > > + > > + if (vdev->udomain) > > + return -EBUSY; > > + udomain = uiommu_fdget(fd); > > + if (IS_ERR(udomain)) > > + return PTR_ERR(udomain); > > + > > + safe = 0; > > +#ifdef IOMMU_CAP_INTR_REMAP /* >= 2.6.36 */ > > + /* iommu domain must also isolate dev interrupts */ > > + if (uiommu_domain_has_cap(udomain, IOMMU_CAP_INTR_REMAP)) > > + safe = 1; > > +#endif > > + if (!safe && !unsafe_ok) { > > + printk(KERN_WARNING "%s: no interrupt remapping!\n", __func__); > > + return -EINVAL; > > + } > > + > > + vfio_domain_unset(vdev); > > + ret = uiommu_attach_device(udomain, &pdev->dev); > > + if (ret) { > > + printk(KERN_ERR "%s: attach_device failed %d\n", > > + __func__, ret); > > + uiommu_put(udomain); > > + return ret; > > + } > > + vdev->cachec = iommu_domain_has_cap(udomain->domain, > > + IOMMU_CAP_CACHE_COHERENCY); > > + vdev->udomain = udomain; > > + return 0; > > +} > > diff --git a/drivers/vfio/vfio_intrs.c b/drivers/vfio/vfio_intrs.c > > new file mode 100644 > > index 0000000..4ced09c > > --- /dev/null > > +++ b/drivers/vfio/vfio_intrs.c > > @@ -0,0 +1,257 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > + > > +/* > > + * This code handles catching interrupts and translating > > + * them to events on eventfds > > + */ > > + > > +#include <linux/device.h> > > +#include <linux/interrupt.h> > > +#include <linux/eventfd.h> > > +#include <linux/pci.h> > > +#include <linux/mmu_notifier.h> > > + > > +#include <linux/vfio.h> > > + > > + > > +/* > > + * vfio_interrupt - IRQ hardware interrupt handler > > + */ > > +irqreturn_t vfio_interrupt(int irq, void *dev_id) > > +{ > > + struct vfio_dev *vdev = (struct vfio_dev *)dev_id; > > don't cast void pointers OK. > > > + struct pci_dev *pdev = vdev->pdev; > > + irqreturn_t ret = IRQ_NONE; > > + u32 cmd_status_dword; > > + u16 origcmd, newcmd, status; > > + > > + spin_lock_irq(&vdev->irqlock); > > + pci_block_user_cfg_access(pdev); > > + > > + /* Read both command and status registers in a single 32-bit operation. > > + * Note: we could cache the value for command and move the status read > > + * out of the lock if there was a way to get notified of user changes > > + * to command register through sysfs. Should be good for shared irqs. > > */ + pci_read_config_dword(pdev, PCI_COMMAND, &cmd_status_dword); > > + origcmd = cmd_status_dword; > > + status = cmd_status_dword >> 16; > > + > > + /* Check interrupt status register to see whether our device > > + * triggered the interrupt. */ > > + if (!(status & PCI_STATUS_INTERRUPT)) > > + goto done; > > + > > + /* We triggered the interrupt, disable it. */ > > + newcmd = origcmd | PCI_COMMAND_INTX_DISABLE; > > + if (newcmd != origcmd) > > + pci_write_config_word(pdev, PCI_COMMAND, newcmd); > > + > > + ret = IRQ_HANDLED; > > +done: > > + pci_unblock_user_cfg_access(pdev); > > + spin_unlock_irq(&vdev->irqlock); > > + if (ret != IRQ_HANDLED) > > + return ret; > > + if (vdev->ev_irq) > > + eventfd_signal(vdev->ev_irq, 1); > > + return ret; > > +} > > + > > +/* > > + * MSI and MSI-X Interrupt handler. > > + * Just signal an event > > + */ > > +static irqreturn_t msihandler(int irq, void *arg) > > +{ > > + struct eventfd_ctx *ctx = arg; > > + > > + eventfd_signal(ctx, 1); > > + return IRQ_HANDLED; > > +} > > + > > +void vfio_drop_msi(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + int i; > > + > > + if (vdev->ev_msi) { > > + for (i = 0; i < vdev->msi_nvec; i++) { > > + free_irq(pdev->irq + i, vdev->ev_msi[i]); > > + if (vdev->ev_msi[i]) > > + eventfd_ctx_put(vdev->ev_msi[i]); > > + } > > + } > > + kfree(vdev->ev_msi); > > + vdev->ev_msi = NULL; > > + vdev->msi_nvec = 0; > > + pci_disable_msi(pdev); > > +} > > + > > +int vfio_setup_msi(struct vfio_dev *vdev, int nvec, void __user *uarg) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + struct eventfd_ctx *ctx; > > + int i, n, l2; > > + int ret = 0; > > + int fd; > > + > > + if (nvec < 1 || nvec > 32) > > + return -EINVAL; > > + vdev->ev_msi = kzalloc(nvec * sizeof(struct eventfd_ctx *), > > + GFP_KERNEL); > > + if (vdev->ev_msi == NULL) > > + return -ENOMEM; > > + > > + for (i = 0; i < nvec; i++) { > > + if (copy_from_user(&fd, uarg, sizeof fd)) { > > + ret = -EFAULT; > > + break; > > + } > > + uarg += sizeof fd; > > + ctx = eventfd_ctx_fdget(fd); > > + if (IS_ERR(ctx)) { > > + ret = PTR_ERR(ctx); > > + break; > > so goto out here? Why? > > > + } > > + vdev->ev_msi[i] = ctx; > > + } > > + if (ret) > > + goto out; > > + ret = pci_enable_msi_block(pdev, nvec); > > + if (ret) { > > + if (ret > 0) > > + ret = -EINVAL; > > + goto out; > > + } > > + for (i = 0; i < nvec; i++) { > > + ret = request_irq(pdev->irq + i, msihandler, 0, > > + vdev->name, vdev->ev_msi[i]); > > + if (ret) > > + break; > > + vdev->msi_nvec = i+1; > > + } > > + > > + /* > > + * compute the virtual hardware field for max msi vectors - > > + * it is the log base 2 of the number of vectors > > + */ > > + l2 = 0; > > + n = vdev->msi_nvec; > > + if (n >= (1 << 4)) { > > + n >>= 4; > > + l2 += 4; > > + } > > + if (n >= (1 << 2)) { > > + n >>= 2; > > + l2 += 2; > > + } > > + if (n >= (1 << 1)) > > + l2 += 1; > > what is this doing? Will using fls() help? It is computing log2(n) for n <= 32. I added a comment. > > > + vdev->msi_qmax = l2; > > +out: > > + if (ret) > > + vfio_drop_msi(vdev); > > + return ret; > > +} > > + > > +void vfio_drop_msix(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + int i; > > + > > + if (vdev->ev_msix && vdev->msix) { > > + for (i = 0; i < vdev->msix_nvec; i++) { > > + free_irq(vdev->msix[i].vector, vdev->ev_msix[i]); > > + if (vdev->ev_msix[i]) > > + eventfd_ctx_put(vdev->ev_msix[i]); > > + } > > + } > > No need for external {} OK. > > > + kfree(vdev->ev_msix); > > + vdev->ev_msix = NULL; > > + kfree(vdev->msix); > > + vdev->msix = NULL; > > + vdev->msix_nvec = 0; > > + pci_disable_msix(pdev); > > +} > > + > > +int vfio_setup_msix(struct vfio_dev *vdev, int nvec, void __user *uarg) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + struct eventfd_ctx *ctx; > > + int ret = 0; > > + int i; > > + int fd; > > + int pos; > > + u16 flags = 0; > > + > > + pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); > > + if (!pos) > > + return -EINVAL; > > + pci_read_config_word(pdev, pos + PCI_MSIX_FLAGS, &flags); > > + if (nvec < 1 || nvec > (flags & PCI_MSIX_FLAGS_QSIZE) + 1) > > + return -EINVAL; > > + > > + vdev->msix = kzalloc(nvec * sizeof(struct msix_entry), > > + GFP_KERNEL); > > + if (vdev->msix == NULL) > > + return -ENOMEM; > > + vdev->ev_msix = kzalloc(nvec * sizeof(struct eventfd_ctx *), > > + GFP_KERNEL); > > + if (vdev->ev_msix == NULL) { > > + kfree(vdev->msix); > > + return -ENOMEM; > > + } > > + for (i = 0; i < nvec; i++) { > > + if (copy_from_user(&fd, uarg, sizeof fd)) { > > + ret = -EFAULT; > > + break; > > + } > > + uarg += sizeof fd; > > + ctx = eventfd_ctx_fdget(fd); > > + if (IS_ERR(ctx)) { > > + ret = PTR_ERR(ctx); > > + break; > > + } > > + vdev->msix[i].entry = i; > > + vdev->ev_msix[i] = ctx; > > + } > > + if (!ret) > > + ret = pci_enable_msix(pdev, vdev->msix, nvec); > > + vdev->msix_nvec = 0; > > + for (i = 0; i < nvec && !ret; i++) { > > + ret = request_irq(vdev->msix[i].vector, msihandler, 0, > > + vdev->name, vdev->ev_msix[i]); > > + if (ret) > > + break; > > + vdev->msix_nvec = i+1; > > + } > > + if (ret) > > + vfio_drop_msix(vdev); > > + return ret; > > +} > > diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c > > new file mode 100644 > > index 0000000..a18e39a > > --- /dev/null > > +++ b/drivers/vfio/vfio_main.c > > @@ -0,0 +1,768 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > + > > +/* > > + * VFIO main module: driver to allow non-privileged user programs > > + * to imlpement direct mapped device drivers for PCI* devices > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/device.h> > > +#include <linux/mm.h> > > +#include <linux/idr.h> > > +#include <linux/string.h> > > +#include <linux/interrupt.h> > > +#include <linux/fs.h> > > +#include <linux/eventfd.h> > > +#include <linux/pci.h> > > +#include <linux/iommu.h> > > +#include <linux/mmu_notifier.h> > > +#include <linux/uaccess.h> > > +#include <linux/suspend.h> > > + > > +#include <linux/vfio.h> > > + > > + > > +#define DRIVER_VERSION "0.1" > > +#define DRIVER_AUTHOR "Tom Lyon <pugs@xxxxxxxxx>" > > +#define DRIVER_DESC "VFIO - User Level PCI meta-driver" > > + > > +/* > > + * Only a very few platforms today (Intel X7500) fully support > > + * both DMA remapping and interrupt remapping in the IOMMU. > > + * Everyone has DMA remapping but interrupt remapping is missing > > + * in some Intel hardware and software, and its missing in the AMD > > + * IOMMU software. Interrupt remapping is needed to really protect the > > + * system from user level driver mischief. Until it is in more > > platforms + * we allow the admin to load the module with > > allow_unsafe_intrs=1 + * which will make this driver useful (but not > > safe) > > + * on those platforms. > > + */ > > +static int allow_unsafe_intrs; > > +module_param(allow_unsafe_intrs, int, 0); > > + > > +static int vfio_major = -1; > > +static DEFINE_IDR(vfio_idr); > > +static int vfio_max_minor; > > +/* Protect idr accesses */ > > +static DEFINE_MUTEX(vfio_minor_lock); > > + > > +/* > > + * Does [a1,b1) overlap [a2,b2) ? > > + */ > > +static inline int overlap(int a1, int b1, int a2, int b2) > > +{ > > + /* > > + * Ranges overlap if they're not disjoint; and they're > > + * disjoint if the end of one is before the start of > > + * the other one. > > + */ > > + return !(b2 <= a1 || b1 <= a2); > > +} > > + > > +static int vfio_open(struct inode *inode, struct file *filep) > > +{ > > + struct vfio_dev *vdev; > > + struct vfio_listener *listener; > > + int ret = 0; > > + > > + mutex_lock(&vfio_minor_lock); > > + vdev = idr_find(&vfio_idr, iminor(inode)); > > + mutex_unlock(&vfio_minor_lock); > > + if (!vdev) { > > + ret = -ENODEV; > > + goto out; > > + } > > + > > + listener = kzalloc(sizeof(*listener), GFP_KERNEL); > > + if (!listener) { > > + ret = -ENOMEM; > > + goto out; > > + } > > + > > + mutex_lock(&vdev->lgate); > > + listener->vdev = vdev; > > + INIT_LIST_HEAD(&listener->dm_list); > > + filep->private_data = listener; > > + if (vdev->listeners == 0) > > + ret = pci_enable_device(vdev->pdev); > > Why would you want to enable device on open? > Doing this later when domain is set would add an extra level of > protection as device would reject reads/writes when not enabled. Unfortunately, pci_enable_device does some black magic with pci_bios_enable which is platform dependent and which I don't really understand. I'm pretty sure this has to be there before an assignment to an iommu. > > > Also, don't you want to do pci_set_master at some point? No, the user code can do it and the rest of the kernel doesn't care once its under the iommu. > > > + if (ret == 0) > > !ret or better if (ret) > goto err; OK. > > > + vdev->listeners++; > > + mutex_unlock(&vdev->lgate); > > + if (ret) > > + kfree(listener); > > this error handling is > > > +out: > > + return ret; > > +} > > + > > +static int vfio_release(struct inode *inode, struct file *filep) > > +{ > > + int ret = 0; > > + struct vfio_listener *listener = filep->private_data; > > + struct vfio_dev *vdev = listener->vdev; > > + > > + vfio_dma_unmapall(listener); > > + if (listener->mm) { > > +#ifdef CONFIG_MMU_NOTIFIER > > + mmu_notifier_unregister(&listener->mmu_notifier, listener->mm); > > +#endif > > + listener->mm = NULL; > > + } > > + > > + mutex_lock(&vdev->lgate); > > + if (--vdev->listeners <= 0) { > > + /* we don't need to hold igate here since there are > > + * no more listeners doing ioctls > > + */ > > + if (vdev->ev_msix) > > + vfio_drop_msix(vdev); > > + if (vdev->ev_msi) > > + vfio_drop_msi(vdev); > > + if (vdev->ev_irq) { > > + eventfd_ctx_put(vdev->ev_irq); > > + vdev->ev_irq = NULL; > > + } > > + kfree(vdev->vconfig); > > + vdev->vconfig = NULL; > > + kfree(vdev->pci_config_map); > > + vdev->pci_config_map = NULL; > > + pci_disable_device(vdev->pdev); > > + vfio_domain_unset(vdev); > > This does not seem to remove bus master before close. > If the userspace driver dies, and device is doing DMA > into userspace, what will prevent DMA after > you unset the domain? Actually, pci_disable_device does little else than disable bus master. > > > + wake_up(&vdev->dev_idle_q); > > + } > > + mutex_unlock(&vdev->lgate); > > + > > + kfree(listener); > > + return ret; > > +} > > + > > +static ssize_t vfio_read(struct file *filep, char __user *buf, > > + size_t count, loff_t *ppos) > > +{ > > + struct vfio_listener *listener = filep->private_data; > > + struct vfio_dev *vdev = listener->vdev; > > + struct pci_dev *pdev = vdev->pdev; > > + int pci_space; > > + > > + pci_space = vfio_offset_to_pci_space(*ppos); > > + > > + /* config reads are OK before iommu domain set */ > > + if (pci_space == VFIO_PCI_CONFIG_RESOURCE) > > + return vfio_config_readwrite(0, vdev, buf, count, ppos); > > + > > + /* no other reads until IOMMU domain set */ > > + if (vdev->udomain == NULL) > > + return -EINVAL; > > + if (pci_space > PCI_ROM_RESOURCE) > > + return -EINVAL; > > + if (pci_resource_flags(pdev, pci_space) & IORESOURCE_IO) > > + return vfio_io_readwrite(0, vdev, buf, count, ppos); > > + if (pci_resource_flags(pdev, pci_space) & IORESOURCE_MEM) > > + return vfio_mem_readwrite(0, vdev, buf, count, ppos); > > + if (pci_space == PCI_ROM_RESOURCE) > > + return vfio_mem_readwrite(0, vdev, buf, count, ppos); > > + return -EINVAL; > > +} > > + > > +static int vfio_msix_check(struct vfio_dev *vdev, u64 start, u32 len) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + u16 pos; > > + u32 table_offset; > > + u16 table_size; > > + u8 bir; > > + u32 lo, hi, startp, endp; > > + > > + pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); > > + if (!pos) > > + return 0; > > + > > + pci_read_config_word(pdev, pos + PCI_MSIX_FLAGS, &table_size); > > + table_size = (table_size & PCI_MSIX_FLAGS_QSIZE) + 1; > > + pci_read_config_dword(pdev, pos + 4, &table_offset); > > + bir = table_offset & PCI_MSIX_FLAGS_BIRMASK; > > + lo = table_offset >> PAGE_SHIFT; > > + hi = (table_offset + PCI_MSIX_ENTRY_SIZE * table_size + PAGE_SIZE - 1) > > + >> PAGE_SHIFT; > > + startp = start >> PAGE_SHIFT; > > + endp = (start + len + PAGE_SIZE - 1) >> PAGE_SHIFT; > > + if (bir == vfio_offset_to_pci_space(start) && > > + overlap(lo, hi, startp, endp)) { > > + printk(KERN_WARNING "%s: cannot write msi-x vectors\n", > > + __func__); > > + return -EINVAL; > > + } > > + return 0; > > +} > > + > > +static ssize_t vfio_write(struct file *filep, const char __user *buf, > > + size_t count, loff_t *ppos) > > +{ > > + struct vfio_listener *listener = filep->private_data; > > + struct vfio_dev *vdev = listener->vdev; > > + struct pci_dev *pdev = vdev->pdev; > > + int pci_space; > > + int ret; > > + > > + /* no writes until IOMMU domain set */ > > + if (vdev->udomain == NULL) > > + return -EINVAL; > > + pci_space = vfio_offset_to_pci_space(*ppos); > > + if (pci_space == VFIO_PCI_CONFIG_RESOURCE) > > + return vfio_config_readwrite(1, vdev, > > + (char __user *)buf, count, ppos); > > + if (pci_space > PCI_ROM_RESOURCE) > > + return -EINVAL; > > + if (pci_resource_flags(pdev, pci_space) & IORESOURCE_IO) > > + return vfio_io_readwrite(1, vdev, > > + (char __user *)buf, count, ppos); > > + if (pci_resource_flags(pdev, pci_space) & IORESOURCE_MEM) { > > + if (allow_unsafe_intrs) { > > + /* don't allow writes to msi-x vectors */ > > + ret = vfio_msix_check(vdev, *ppos, count); > > + if (ret) > > + return ret; > > + } > > + return vfio_mem_readwrite(1, vdev, > > + (char __user *)buf, count, ppos); > > + } > > + return -EINVAL; > > +} > > + > > +static int vfio_mmap(struct file *filep, struct vm_area_struct *vma) > > +{ > > + struct vfio_listener *listener = filep->private_data; > > + struct vfio_dev *vdev = listener->vdev; > > + struct pci_dev *pdev = vdev->pdev; > > + unsigned long requested, actual; > > + int pci_space; > > + u64 start; > > + u32 len; > > + unsigned long phys; > > + int ret; > > + > > + /* no reads or writes until IOMMU domain set */ > > + if (vdev->udomain == NULL) > > + return -EINVAL; > > What happens if user creates a mapping when domain is > set, and then removes it with DOMAIN_UNSET ioctl? > Can't userdpace access an unprotected device now? > we should just drop DOMAIN_UNSET, and document > that iommu can not be changed once set. Unset returns EBUSY if mappings are still in place. But I don't expect anyone to bother with unsets. > > > + > > + if (vma->vm_end < vma->vm_start) > > + return -EINVAL; > > + if ((vma->vm_flags & VM_SHARED) == 0) > > + return -EINVAL; > > + > > + > > + pci_space = vfio_offset_to_pci_space((u64)vma->vm_pgoff << PAGE_SHIFT); > > + if (pci_space > PCI_ROM_RESOURCE) > > + return -EINVAL; > > + switch (pci_space) { > > + case PCI_ROM_RESOURCE: > > + if (vma->vm_flags & VM_WRITE) > > + return -EINVAL; > > + if (pci_resource_flags(pdev, PCI_ROM_RESOURCE) == 0) > > + return -EINVAL; > > + actual = pci_resource_len(pdev, PCI_ROM_RESOURCE) >> PAGE_SHIFT; > > + break; > > + default: > > + if ((pci_resource_flags(pdev, pci_space) & IORESOURCE_MEM) == 0) > > + return -EINVAL; > > + actual = pci_resource_len(pdev, pci_space) >> PAGE_SHIFT; > > + break; > > + } > > + > > + requested = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; > > + if (requested > actual || actual == 0) > > + return -EINVAL; > > + > > + start = vma->vm_pgoff << PAGE_SHIFT; > > + len = vma->vm_end - vma->vm_start; > > + if (allow_unsafe_intrs && (vma->vm_flags & VM_WRITE)) { > > + /* > > + * Deter users from screwing up MSI-X intrs > > + */ > > + ret = vfio_msix_check(vdev, start, len); > > + if (ret) > > + return ret; > > + } > > + > > + vma->vm_private_data = vdev; > > + vma->vm_flags |= VM_IO | VM_RESERVED; > > + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); > > + phys = pci_resource_start(pdev, pci_space) >> PAGE_SHIFT; > > + > > + return remap_pfn_range(vma, vma->vm_start, phys, > > + vma->vm_end - vma->vm_start, > > + vma->vm_page_prot); > > +} > > + > > +static long vfio_unl_ioctl(struct file *filep, > > + unsigned int cmd, > > + unsigned long arg) > > +{ > > + struct vfio_listener *listener = filep->private_data; > > + struct vfio_dev *vdev = listener->vdev; > > + void __user *uarg = (void __user *)arg; > > + struct pci_dev *pdev = vdev->pdev; > > + struct vfio_dma_map *dm; > > + int ret = 0; > > + int fd, nfd; > > + int bar; > > + > > + if (vdev == NULL) > > + return -EINVAL; > > + > > + switch (cmd) { > > + > > + case VFIO_DMA_MAP_IOVA: > > + dm = kmalloc(sizeof *dm, GFP_KERNEL); > > Why bother allocating on heap? It's a small structure ... Vestigial nonsense; removed. > > > + if (dm == NULL) > > + return -ENOMEM; > > + if (copy_from_user(dm, uarg, sizeof *dm)) { > > + kfree(dm); > > + return -EFAULT; > > + } > > + ret = vfio_dma_map_common(listener, cmd, dm); > > + if (!ret && copy_to_user(uarg, dm, sizeof *dm)) > > + ret = -EFAULT; > > + kfree(dm); > > + break; > > + > > + case VFIO_DMA_UNMAP: > > + dm = kmalloc(sizeof *dm, GFP_KERNEL); > > same here > > > + if (dm == NULL) > > + return -ENOMEM; > > + if (copy_from_user(dm, uarg, sizeof *dm)) { > > + kfree(dm); > > + return -EFAULT; > > + } > > + ret = vfio_dma_unmap_dm(listener, dm); > > + kfree(dm); > > + break; > > + > > + case VFIO_EVENTFD_IRQ: > > + if (copy_from_user(&fd, uarg, sizeof fd)) > > + return -EFAULT; > > + mutex_lock(&vdev->igate); > > + if (vdev->ev_irq) > > + eventfd_ctx_put(vdev->ev_irq); > > + if (fd >= 0) { > > + vdev->ev_irq = eventfd_ctx_fdget(fd); > > + if (vdev->ev_irq == NULL) > > + ret = -EINVAL; > > + } > > + mutex_unlock(&vdev->igate); > > + break; > > + > > + case VFIO_EVENTFDS_MSI: > > + if (copy_from_user(&nfd, uarg, sizeof nfd)) > > + return -EFAULT; > > + uarg += sizeof nfd; > > + mutex_lock(&vdev->igate); > > + if (nfd > 0 && vdev->ev_msi == NULL) > > == NULL -> ! here and elsewhere > > > + ret = vfio_setup_msi(vdev, nfd, uarg); > > + else if (nfd == 0 && vdev->ev_msi) > > + vfio_drop_msi(vdev); > > + else > > + ret = -EINVAL; > > + mutex_unlock(&vdev->igate); > > + break; > > + > > + case VFIO_EVENTFDS_MSIX: > > + if (copy_from_user(&nfd, uarg, sizeof nfd)) > > + return -EFAULT; > > + uarg += sizeof nfd; > > Maybe cast to int __user *. > Then use simple + 1 for access instead of sizeof, > and get_user instead of copy_from_user. Done. > > > + mutex_lock(&vdev->igate); > > + if (nfd > 0 && vdev->ev_msix == NULL) > > + ret = vfio_setup_msix(vdev, nfd, uarg); > > + else if (nfd == 0 && vdev->ev_msix) > > + vfio_drop_msix(vdev); > > + else > > + ret = -EINVAL; > > + mutex_unlock(&vdev->igate); > > + break; > > + > > + case VFIO_BAR_LEN: > > + if (copy_from_user(&bar, uarg, sizeof bar)) > > + return -EFAULT; > > + if (bar < 0 || bar > PCI_ROM_RESOURCE) > > + return -EINVAL; > > + if (pci_resource_start(pdev, bar)) > > + bar = pci_resource_len(pdev, bar); > > + else > > + bar = 0; > > + if (copy_to_user(uarg, &bar, sizeof bar)) > > + return -EFAULT; > > + break; > > + > > + case VFIO_DOMAIN_SET: > > + if (copy_from_user(&fd, uarg, sizeof fd)) > > + return -EFAULT; > > + ret = vfio_domain_set(vdev, fd, allow_unsafe_intrs); > > + break; > > + > > + case VFIO_DOMAIN_UNSET: > > + ret = vfio_domain_unset(vdev); > > + break; > > + > > + default: > > + return -EINVAL; > > + } > > + return ret; > > +} > > + > > +static const struct file_operations vfio_fops = { > > + .owner = THIS_MODULE, > > + .open = vfio_open, > > + .release = vfio_release, > > + .read = vfio_read, > > + .write = vfio_write, > > + .unlocked_ioctl = vfio_unl_ioctl, > > + .mmap = vfio_mmap, > > +}; > > + > > +static int vfio_get_devnum(struct vfio_dev *vdev) > > +{ > > + int retval = -ENOMEM; > > + int id; > > + > > + mutex_lock(&vfio_minor_lock); > > + if (idr_pre_get(&vfio_idr, GFP_KERNEL) == 0) > > + goto exit; > > + > > + retval = idr_get_new(&vfio_idr, vdev, &id); > > + if (retval < 0) { > > + if (retval == -EAGAIN) > > + retval = -ENOMEM; > > + goto exit; > > + } > > + if (id > MINORMASK) { > > + idr_remove(&vfio_idr, id); > > + retval = -ENOMEM; > > + } > > + if (id > vfio_max_minor) > > + vfio_max_minor = id; > > + if (vfio_major < 0) { > > + retval = register_chrdev(0, "vfio", &vfio_fops); > > + if (retval < 0) > > + goto exit; > > + vfio_major = retval; > > + } > > + > > + retval = MKDEV(vfio_major, id); > > +exit: > > + mutex_unlock(&vfio_minor_lock); > > + return retval; > > +} > > + > > +int vfio_validate(struct vfio_dev *vdev) > > +{ > > + int rc = 0; > > + int id; > > + > > + mutex_lock(&vfio_minor_lock); > > + for (id = 0; id <= vfio_max_minor; id++) > > + if (vdev == idr_find(&vfio_idr, id)) > > + goto out; > > + rc = 1; > > +out: > > + mutex_unlock(&vfio_minor_lock); > > + return rc; > > +} > > + > > +static void vfio_free_minor(struct vfio_dev *vdev) > > +{ > > + mutex_lock(&vfio_minor_lock); > > + idr_remove(&vfio_idr, MINOR(vdev->devnum)); > > + mutex_unlock(&vfio_minor_lock); > > +} > > + > > +/* > > + * Verify that the device supports Interrupt Disable bit in command > > register, + * per PCI 2.3, by flipping this bit and reading it back: > > this bit was readonly + * in PCI 2.2. (from uio_pci_generic) > > + */ > > +static int verify_pci_2_3(struct pci_dev *pdev) > > +{ > > + u16 orig, new; > > + u8 pin; > > + > > + pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &pin); > > + if (pin == 0) /* irqs not needed */ > > + return 0; > > + > > + pci_read_config_word(pdev, PCI_COMMAND, &orig); > > + pci_write_config_word(pdev, PCI_COMMAND, > > + orig ^ PCI_COMMAND_INTX_DISABLE); > > + pci_read_config_word(pdev, PCI_COMMAND, &new); > > + /* There's no way to protect against > > + * hardware bugs or detect them reliably, but as long as we know > > + * what the value should be, let's go ahead and check it. */ > > + if ((new ^ orig) & ~PCI_COMMAND_INTX_DISABLE) { > > + dev_err(&pdev->dev, "Command changed from 0x%x to 0x%x: " > > + "driver or HW bug?\n", orig, new); > > + return -EBUSY; > > + } > > + if (!((new ^ orig) & PCI_COMMAND_INTX_DISABLE)) { > > + dev_warn(&pdev->dev, "Device does not support " > > + "disabling interrupts: unable to bind.\n"); > > + return -ENODEV; > > + } > > + /* Now restore the original value. */ > > + pci_write_config_word(pdev, PCI_COMMAND, orig); > > + return 0; > > +} > > + > > +static int vfio_probe(struct pci_dev *pdev, const struct pci_device_id > > *id) +{ > > + struct vfio_dev *vdev; > > + int err; > > + u8 type; > > + > > + if (!iommu_found()) > > + return -EINVAL; > > + > > + pci_read_config_byte(pdev, PCI_HEADER_TYPE, &type); > > + if ((type & 0x7F) != PCI_HEADER_TYPE_NORMAL) > > + return -EINVAL; > > + > > + err = verify_pci_2_3(pdev); > > + if (err) > > + return err; > > + > > + vdev = kzalloc(sizeof(struct vfio_dev), GFP_KERNEL); > > + if (!vdev) > > + return -ENOMEM; > > + vdev->pdev = pdev; > > + > > + mutex_init(&vdev->lgate); > > + mutex_init(&vdev->dgate); > > + mutex_init(&vdev->igate); > > + mutex_init(&vdev->ngate); > > + INIT_LIST_HEAD(&vdev->nlc_list); > > + init_waitqueue_head(&vdev->dev_idle_q); > > + init_waitqueue_head(&vdev->nl_wait_q); > > + > > + err = vfio_get_devnum(vdev); > > + if (err < 0) > > + goto err_get_devnum; > > + vdev->devnum = err; > > + err = 0; > > + > > + sprintf(vdev->name, "vfio%d", MINOR(vdev->devnum)); > > + pci_set_drvdata(pdev, vdev); > > + vdev->dev = device_create(vfio_class->class, &pdev->dev, > > + vdev->devnum, vdev, vdev->name); > > + if (IS_ERR(vdev->dev)) { > > + printk(KERN_ERR "VFIO: device register failed\n"); > > + err = PTR_ERR(vdev->dev); > > + goto err_device_create; > > + } > > + > > + err = vfio_dev_add_attributes(vdev); > > + if (err) > > + goto err_vfio_dev_add_attributes; > > + > > + > > + if (pdev->irq > 0) { > > + err = request_irq(pdev->irq, vfio_interrupt, > > + IRQF_SHARED, vdev->name, vdev); > > + if (err) > > + goto err_request_irq; > > Since this is a sahred interrupt, you will get called > even if MSI in device is enabled, which will confuse > users. How about requesting irq upon an ioctl? OK, now requested at ioctl and freed on release. > > > + } > > + > > + return 0; > > + > > +err_request_irq: > > +err_vfio_dev_add_attributes: > > + device_destroy(vfio_class->class, vdev->devnum); > > +err_device_create: > > + vfio_free_minor(vdev); > > +err_get_devnum: > > + kfree(vdev); > > + return err; > > +} > > + > > +static void vfio_remove(struct pci_dev *pdev) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + int ret; > > + > > + /* prevent further opens */ > > + vfio_free_minor(vdev); > > + > > + /* notify users */ > > + ret = vfio_nl_remove(vdev); > > + > > + /* wait for all closed */ > > + wait_event(vdev->dev_idle_q, vdev->listeners == 0); > > + > > + pci_disable_device(pdev); > > + if (pdev->irq > 0) > > + free_irq(pdev->irq, vdev); > > + > > + vfio_nl_freeclients(vdev); > > + device_destroy(vfio_class->class, vdev->devnum); > > + pci_set_drvdata(pdev, NULL); > > + kfree(vdev); > > +} > > + > > +static struct pci_error_handlers vfio_error_handlers = { > > + .error_detected = vfio_error_detected, > > + .mmio_enabled = vfio_mmio_enabled, > > + .link_reset = vfio_link_reset, > > + .slot_reset = vfio_slot_reset, > > + .resume = vfio_error_resume, > > +}; > > + > > +static struct pci_driver driver = { > > + .name = "vfio", > > + .id_table = NULL, /* only dynamic id's */ > > + .probe = vfio_probe, > > + .remove = vfio_remove, > > + .err_handler = &vfio_error_handlers, > > +}; > > + > > +static atomic_t vfio_pm_suspend_count; > > +static int vfio_pm_suspend_result; > > +static DECLARE_WAIT_QUEUE_HEAD(vfio_pm_wait_q); > > + > > +/* > > + * Notify user level drivers of hibernation/suspend request > > + * Send all the notifies in parallel, collect all the replies > > + * If one ULD can't suspend, none can > > + */ > > +static int vfio_pm_suspend(void) > > +{ > > + struct vfio_dev *vdev; > > + int id, alive = 0; > > + int ret; > > + > > + mutex_lock(&vfio_minor_lock); > > + atomic_set(&vfio_pm_suspend_count, 0); > > + vfio_pm_suspend_result = NOTIFY_DONE; > > + for (id = 0; id <= vfio_max_minor; id++) { > > + vdev = idr_find(&vfio_idr, id); > > + if (vdev == NULL) > > + continue; > > + if (vdev->listeners == 0) > > + continue; > > + alive++; > > + ret = vfio_nl_upcall(vdev, VFIO_MSG_PM_SUSPEND, 0, 0); > > + if (ret == 0) > > + atomic_inc(&vfio_pm_suspend_count); > > + } > > + mutex_unlock(&vfio_minor_lock); > > + if (alive > atomic_read(&vfio_pm_suspend_count)) > > + return NOTIFY_BAD; > > + > > + /* sleep for reply */ > > + if (wait_event_interruptible_timeout(vfio_pm_wait_q, > > + (atomic_read(&vfio_pm_suspend_count) == 0), > > + VFIO_SUSPEND_REPLY_TIMEOUT) <= 0) { > > + printk(KERN_ERR "vfio upcall suspend reply timeout\n"); > > + return NOTIFY_BAD; > > + } > > + return vfio_pm_suspend_result; > > +} > > + > > +static int vfio_pm_resume(void) > > +{ > > + struct vfio_dev *vdev; > > + int id; > > + > > + mutex_lock(&vfio_minor_lock); > > + for (id = 0; id <= vfio_max_minor; id++) { > > + vdev = idr_find(&vfio_idr, id); > > + if (vdev == NULL) > > + continue; > > + if (vdev->listeners == 0) > > + continue; > > + (void) vfio_nl_upcall(vdev, VFIO_MSG_PM_RESUME, 0, 0); > > + } > > + mutex_unlock(&vfio_minor_lock); > > + return NOTIFY_DONE; > > +} > > + > > + > > +void vfio_pm_process_reply(int reply) > > +{ > > + if (vfio_pm_suspend_result == NOTIFY_DONE) { > > + if (reply != NOTIFY_DONE) > > + vfio_pm_suspend_result = NOTIFY_BAD; > > + } > > + if (atomic_dec_and_test(&vfio_pm_suspend_count)) > > + wake_up(&vfio_pm_wait_q); > > +} > > + > > +static int vfio_pm_notify(struct notifier_block *this, unsigned long > > event, + void *notused) > > +{ > > + switch (event) { > > + case PM_HIBERNATION_PREPARE: > > + case PM_SUSPEND_PREPARE: > > + return vfio_pm_suspend(); > > + break; > > + case PM_POST_HIBERNATION: > > + case PM_POST_SUSPEND: > > + return vfio_pm_resume(); > > + break; > > + default: > > + return NOTIFY_DONE; > > + } > > +} > > + > > +struct notifier_block vfio_pm_nb = { > > + .notifier_call = vfio_pm_notify, > > +}; > > + > > +static int __init init(void) > > +{ > > + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); > > + vfio_class_init(); > > + vfio_nl_init(); > > + register_pm_notifier(&vfio_pm_nb); > > + return pci_register_driver(&driver); > > +} > > + > > +static void __exit cleanup(void) > > +{ > > + if (vfio_major >= 0) > > + unregister_chrdev(vfio_major, "vfio"); > > + pci_unregister_driver(&driver); > > + unregister_pm_notifier(&vfio_pm_nb); > > + unregister_pm_notifier(&vfio_pm_nb); > > + vfio_nl_exit(); > > + vfio_class_destroy(); > > +} > > + > > +module_init(init); > > +module_exit(cleanup); > > + > > +MODULE_VERSION(DRIVER_VERSION); > > +MODULE_LICENSE("GPL v2"); > > +MODULE_AUTHOR(DRIVER_AUTHOR); > > +MODULE_DESCRIPTION(DRIVER_DESC); > > diff --git a/drivers/vfio/vfio_netlink.c b/drivers/vfio/vfio_netlink.c > > new file mode 100644 > > index 0000000..bc9a7d3 > > --- /dev/null > > +++ b/drivers/vfio/vfio_netlink.c > > @@ -0,0 +1,459 @@ > > +/* > > + * Netlink inteface for VFIO > > + * Author: Tom Lyon (pugs@xxxxxxxxx) > > + * > > + * Copyright 2010, Cisco Systems, Inc. > > + * Copyright 2007, 2008 Siemens AG > > + * > > + * 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, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA > > 02110-1301 USA. > > + * > > + * Derived from net/ieee802154/netlink.c Written by: > > + * Sergey Lapin <slapin@xxxxxxxxxxx> > > + * Dmitry Eremin-Solenikov <dbaryshkov@xxxxxxxxx> > > + * Maxim Osipov <maxim.osipov@xxxxxxxxxxx> > > + */ > > + > > +/* > > + * This code handles the signaling of various system events > > + * to the user level driver, using the generic netlink facilities. > > + * In many cases, we wait for replies from the user driver as well. > > + */ > > + > > +#include <linux/kernel.h> > > +#include <linux/gfp.h> > > +#include <linux/pci.h> > > +#include <linux/sched.h> > > +#include <net/genetlink.h> > > +#include <linux/mmu_notifier.h> > > +#include <linux/vfio.h> > > + > > +static u32 vfio_seq_num; > > +static DEFINE_SPINLOCK(vfio_seq_lock); > > + > > +struct genl_family vfio_nl_family = { > > + .id = GENL_ID_GENERATE, > > + .hdrsize = 0, > > + .name = VFIO_GENL_NAME, > > + .version = 1, > > + .maxattr = VFIO_NL_ATTR_MAX, > > +}; > > + > > +/* Requests to userspace */ > > +struct sk_buff *vfio_nl_create(u8 req) > > +{ > > + void *hdr; > > + struct sk_buff *msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); > > + unsigned long f; > > + > > + if (!msg) > > + return NULL; > > + > > + spin_lock_irqsave(&vfio_seq_lock, f); > > + hdr = genlmsg_put(msg, 0, ++vfio_seq_num, > > + &vfio_nl_family, 0, req); > > + spin_unlock_irqrestore(&vfio_seq_lock, f); > > + if (!hdr) { > > + nlmsg_free(msg); > > + return NULL; > > + } > > + > > + return msg; > > +} > > + > > +/* > > + * We would have liked to use NL multicast, but > > + * (a) multicast sockets are only for root > > + * (b) there's no multicast user level api in libnl > > + * (c) we need to know what net namespaces are involved > > + * Sigh. > > + */ > > +int vfio_nl_mcast(struct vfio_dev *vdev, struct sk_buff *msg, u8 type) > > +{ > > + struct list_head *pos; > > + struct vfio_nl_client *nlc; > > + struct sk_buff *skb; > > + /* XXX: nlh is right at the start of msg */ > > + void *hdr = genlmsg_data(NLMSG_DATA(msg->data)); > > + int good = 0; > > + int rc; > > + > > + if (genlmsg_end(msg, hdr) < 0) { > > + nlmsg_free(msg); > > + return -ENOBUFS; > > + } > > + > > + mutex_lock(&vdev->ngate); > > + list_for_each(pos, &vdev->nlc_list) { > > + nlc = list_entry(pos, struct vfio_nl_client, list); > > + if (nlc->msgcap & (1LL << type)) { > > + skb = skb_copy(msg, GFP_KERNEL); > > + if (skb == NULL) { > > + rc = -ENOBUFS; > > + goto out; > > + } > > + rc = genlmsg_unicast(nlc->net, skb, nlc->pid); > > + if (rc == 0) > > + good++; > > + } > > + } > > + rc = 0; > > +out: > > + mutex_unlock(&vdev->ngate); > > + nlmsg_free(msg); > > + if (good) > > + return good; > > + return rc; > > +} > > + > > +#ifdef notdef > > +struct sk_buff *vfio_nl_new_reply(struct genl_info *info, > > + int flags, u8 req) > > +{ > > + void *hdr; > > + struct sk_buff *msg = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); > > + > > + if (!msg) > > + return NULL; > > + > > + hdr = genlmsg_put_reply(msg, info, > > + &vfio_nl_family, flags, req); > > + if (!hdr) { > > + nlmsg_free(msg); > > + return NULL; > > + } > > + > > + return msg; > > +} > > + > > +int vfio_nl_reply(struct sk_buff *msg, struct genl_info *info) > > +{ > > + /* XXX: nlh is right at the start of msg */ > > + void *hdr = genlmsg_data(NLMSG_DATA(msg->data)); > > + > > + if (genlmsg_end(msg, hdr) < 0) > > + goto out; > > + > > + return genlmsg_reply(msg, info); > > +out: > > + nlmsg_free(msg); > > + return -ENOBUFS; > > +} > > +#endif > > + > > + > > +static const struct nla_policy vfio_nl_reg_policy[VFIO_NL_ATTR_MAX+1] = > > { + [VFIO_ATTR_MSGCAP] = { .type = NLA_U64 }, > > + [VFIO_ATTR_PCI_DOMAIN] = { .type = NLA_U32 }, > > + [VFIO_ATTR_PCI_BUS] = { .type = NLA_U16 }, > > + [VFIO_ATTR_PCI_SLOT] = { .type = NLA_U8 }, > > + [VFIO_ATTR_PCI_FUNC] = { .type = NLA_U8 }, > > +}; > > + > > +struct vfio_dev *vfio_nl_get_vdev(struct genl_info *info) > > +{ > > + u32 domain; > > + u16 bus; > > + u8 slot, func; > > + u16 devfn; > > + struct pci_dev *pdev; > > + struct vfio_dev *vdev; > > + > > + domain = nla_get_u32(info->attrs[VFIO_ATTR_PCI_DOMAIN]); > > + bus = nla_get_u16(info->attrs[VFIO_ATTR_PCI_BUS]); > > + slot = nla_get_u8(info->attrs[VFIO_ATTR_PCI_SLOT]); > > + func = nla_get_u8(info->attrs[VFIO_ATTR_PCI_FUNC]); > > + devfn = PCI_DEVFN(slot, func); > > + pdev = pci_get_domain_bus_and_slot(domain, bus, devfn); > > + if (pdev == NULL) > > + return NULL; > > + vdev = pci_get_drvdata(pdev); > > + if (vdev == NULL) > > + return NULL; > > + if (vfio_validate(vdev)) > > + return NULL; > > + if (vdev->pdev != pdev || strncmp(vdev->name, "vfio", 4)) > > + return NULL; > > + return vdev; > > +} > > + > > +/* > > + * The user driver must register here with a bitmask of which > > + * events it is interested in receiving > > + */ > > +static int vfio_nl_user_register(struct sk_buff *skb, struct genl_info > > *info) +{ > > + u64 msgcap; > > + struct list_head *pos; > > + struct vfio_nl_client *nlc; > > + int rc = 0; > > + struct vfio_dev *vdev; > > + > > + msgcap = nla_get_u64(info->attrs[VFIO_ATTR_MSGCAP]); > > + if (msgcap == 0) > > + return -EINVAL; > > + vdev = vfio_nl_get_vdev(info); > > + if (vdev == NULL) > > + return -EINVAL; > > + > > + mutex_lock(&vdev->ngate); > > + list_for_each(pos, &vdev->nlc_list) { > > + nlc = list_entry(pos, struct vfio_nl_client, list); > > + if (nlc->pid == info->snd_pid && > > + nlc->net == info->_net) /* already here */ > > + goto update; > > + } > > + nlc = kzalloc(sizeof(struct vfio_nl_client), GFP_KERNEL); > > + if (nlc == NULL) { > > + rc = -ENOMEM; > > + goto out; > > + } > > + nlc->pid = info->snd_pid; > > + nlc->net = info->_net; > > + list_add(&nlc->list, &vdev->nlc_list); > > +update: > > + nlc->msgcap = msgcap; > > +out: > > + mutex_unlock(&vdev->ngate); > > + return rc; > > +} > > + > > +static const struct nla_policy vfio_nl_err_policy[VFIO_NL_ATTR_MAX+1] = > > { + [VFIO_ATTR_ERROR_HANDLING_REPLY] = { .type = NLA_U32 }, > > + [VFIO_ATTR_PCI_DOMAIN] = { .type = NLA_U32 }, > > + [VFIO_ATTR_PCI_BUS] = { .type = NLA_U16 }, > > + [VFIO_ATTR_PCI_SLOT] = { .type = NLA_U8 }, > > + [VFIO_ATTR_PCI_FUNC] = { .type = NLA_U8 }, > > +}; > > + > > +static int vfio_nl_error_handling_reply(struct sk_buff *skb, > > + struct genl_info *info) > > +{ > > + u32 value, seq; > > + struct vfio_dev *vdev; > > + > > + value = nla_get_u32(info->attrs[VFIO_ATTR_ERROR_HANDLING_REPLY]); > > + vdev = vfio_nl_get_vdev(info); > > + if (vdev == NULL) > > + return -EINVAL; > > + seq = nlmsg_hdr(skb)->nlmsg_seq; > > + if (seq > vdev->nl_reply_seq) { > > + vdev->nl_reply_value = value; > > + vdev->nl_reply_seq = seq; > > + wake_up(&vdev->nl_wait_q); > > + } > > + return 0; > > +} > > + > > +static const struct nla_policy vfio_nl_pm_policy[VFIO_NL_ATTR_MAX+1] = { > > + [VFIO_ATTR_PM_SUSPEND_REPLY] = { .type = NLA_U32 }, > > + [VFIO_ATTR_PCI_DOMAIN] = { .type = NLA_U32 }, > > + [VFIO_ATTR_PCI_BUS] = { .type = NLA_U16 }, > > + [VFIO_ATTR_PCI_SLOT] = { .type = NLA_U8 }, > > + [VFIO_ATTR_PCI_FUNC] = { .type = NLA_U8 }, > > +}; > > + > > +static int vfio_nl_pm_suspend_reply(struct sk_buff *skb, struct > > genl_info *info) +{ > > + u32 value; > > + struct vfio_dev *vdev; > > + > > + value = nla_get_u32(info->attrs[VFIO_ATTR_PM_SUSPEND_REPLY]); > > + vdev = vfio_nl_get_vdev(info); > > + if (vdev == NULL) > > + return -EINVAL; > > + if (vdev->listeners == 0) > > + return -EINVAL; > > + vfio_pm_process_reply(value); > > + return 0; > > +} > > + > > +void vfio_nl_freeclients(struct vfio_dev *vdev) > > +{ > > + struct list_head *pos, *pos2; > > + struct vfio_nl_client *nlc; > > + > > + mutex_lock(&vdev->ngate); > > + list_for_each_safe(pos, pos2, &vdev->nlc_list) { > > + nlc = list_entry(pos, struct vfio_nl_client, list); > > + list_del(&nlc->list); > > + kfree(nlc); > > + } > > + mutex_unlock(&vdev->ngate); > > +} > > + > > +static struct genl_ops vfio_nl_reg_ops = { > > + .cmd = VFIO_MSG_REGISTER, > > + .doit = vfio_nl_user_register, > > + .policy = vfio_nl_reg_policy, > > +}; > > + > > +static struct genl_ops vfio_nl_err_ops = { > > + .cmd = VFIO_MSG_ERROR_HANDLING_REPLY, > > + .doit = vfio_nl_error_handling_reply, > > + .policy = vfio_nl_err_policy, > > +}; > > + > > +static struct genl_ops vfio_nl_pm_ops = { > > + .cmd = VFIO_MSG_PM_SUSPEND_REPLY, > > + .doit = vfio_nl_pm_suspend_reply, > > + .policy = vfio_nl_pm_policy, > > +}; > > + > > +int vfio_nl_init(void) > > +{ > > + int rc; > > + > > + rc = genl_register_family(&vfio_nl_family); > > + if (rc) > > + goto fail; > > + > > + rc = genl_register_ops(&vfio_nl_family, &vfio_nl_reg_ops); > > + if (rc < 0) > > + goto fail; > > + rc = genl_register_ops(&vfio_nl_family, &vfio_nl_err_ops); > > + if (rc < 0) > > + goto fail; > > + rc = genl_register_ops(&vfio_nl_family, &vfio_nl_pm_ops); > > + if (rc < 0) > > + goto fail; > > + return 0; > > + > > +fail: > > + genl_unregister_family(&vfio_nl_family); > > + return rc; > > +} > > + > > +void vfio_nl_exit(void) > > +{ > > + genl_unregister_family(&vfio_nl_family); > > +} > > + > > +int vfio_nl_remove(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + struct sk_buff *msg; > > + int rc; > > + > > + msg = vfio_nl_create(VFIO_MSG_REMOVE); > > + if (!msg) > > + return -ENOBUFS; > > + > > + NLA_PUT_U32(msg, VFIO_ATTR_PCI_DOMAIN, pci_domain_nr(pdev->bus)); > > + NLA_PUT_U16(msg, VFIO_ATTR_PCI_BUS, pdev->bus->number); > > + NLA_PUT_U8(msg, VFIO_ATTR_PCI_SLOT, PCI_SLOT(pdev->devfn)); > > + NLA_PUT_U8(msg, VFIO_ATTR_PCI_FUNC, PCI_FUNC(pdev->devfn)); > > + > > + rc = vfio_nl_mcast(vdev, msg, VFIO_MSG_REMOVE); > > + if (rc > 0) > > + rc = 0; > > + return rc; > > + > > +nla_put_failure: > > + nlmsg_free(msg); > > + return -ENOBUFS; > > +} > > + > > +int vfio_nl_upcall(struct vfio_dev *vdev, u8 type, int state, int > > waitret) +{ > > + struct pci_dev *pdev = vdev->pdev; > > + struct sk_buff *msg; > > + u32 seq; > > + > > + msg = vfio_nl_create(type); > > + if (!msg) > > + goto null_out; > > + seq = nlmsg_hdr(msg)->nlmsg_seq; > > + > > + NLA_PUT_U32(msg, VFIO_ATTR_PCI_DOMAIN, pci_domain_nr(pdev->bus)); > > + NLA_PUT_U16(msg, VFIO_ATTR_PCI_BUS, pdev->bus->number); > > + NLA_PUT_U8(msg, VFIO_ATTR_PCI_SLOT, PCI_SLOT(pdev->devfn)); > > + NLA_PUT_U8(msg, VFIO_ATTR_PCI_FUNC, PCI_FUNC(pdev->devfn)); > > + > > + if (type == VFIO_MSG_ERROR_DETECTED) > > + NLA_PUT_U32(msg, VFIO_ATTR_CHANNEL_STATE, state); > > + > > + if (vfio_nl_mcast(vdev, msg, type) <= 0) > > + goto null_out; > > + if (!waitret) > > + return 0; > > + > > + /* sleep for reply */ > > + if (wait_event_interruptible_timeout(vdev->nl_wait_q, > > + (vdev->nl_reply_seq >= seq), VFIO_ERROR_REPLY_TIMEOUT) <= 0) { > > + printk(KERN_ERR "vfio upcall timeout\n"); > > + goto null_out; > > + } > > + if (seq != vdev->nl_reply_seq) > > + goto null_out; > > + return vdev->nl_reply_value; > > + > > +nla_put_failure: > > + nlmsg_free(msg); > > +null_out: > > + return -1; > > +} > > + > > +/* the following routines invoked for pci error handling */ > > + > > +pci_ers_result_t vfio_error_detected(struct pci_dev *pdev, > > + pci_channel_state_t state) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + int ret; > > + > > + ret = vfio_nl_upcall(vdev, VFIO_MSG_ERROR_DETECTED, (int)state, 1); > > + if (ret >= 0) > > + return ret; > > + return PCI_ERS_RESULT_NONE; > > +} > > + > > +pci_ers_result_t vfio_mmio_enabled(struct pci_dev *pdev) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + int ret; > > + > > + ret = vfio_nl_upcall(vdev, VFIO_MSG_MMIO_ENABLED, 0, 1); > > + if (ret >= 0) > > + return ret; > > + return PCI_ERS_RESULT_NONE; > > +} > > + > > +pci_ers_result_t vfio_link_reset(struct pci_dev *pdev) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + int ret; > > + > > + ret = vfio_nl_upcall(vdev, VFIO_MSG_LINK_RESET, 0, 1); > > + if (ret >= 0) > > + return ret; > > + return PCI_ERS_RESULT_NONE; > > +} > > + > > +pci_ers_result_t vfio_slot_reset(struct pci_dev *pdev) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + int ret; > > + > > + ret = vfio_nl_upcall(vdev, VFIO_MSG_SLOT_RESET, 0, 1); > > + if (ret >= 0) > > + return ret; > > + return PCI_ERS_RESULT_NONE; > > +} > > + > > +void vfio_error_resume(struct pci_dev *pdev) > > +{ > > + struct vfio_dev *vdev = pci_get_drvdata(pdev); > > + > > + (void) vfio_nl_upcall(vdev, VFIO_MSG_ERROR_RESUME, 0, 0); > > +} > > diff --git a/drivers/vfio/vfio_pci_config.c > > b/drivers/vfio/vfio_pci_config.c new file mode 100644 > > index 0000000..b7de0bf > > --- /dev/null > > +++ b/drivers/vfio/vfio_pci_config.c > > @@ -0,0 +1,698 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > + > > +/* > > + * This code handles reading and writing of PCI configuration registers. > > + * This is hairy because we want to allow a lot of flexibility to the > > + * user driver, but cannot trust it with all of the config fields. > > + * Tables determine which fields can be read and written, as well as > > + * which fields are 'virtualized' - special actions and translations to > > + * make it appear to the user that he has control, when in fact things > > + * must be negotiated with the underlying OS. > > + */ > > + > > +#include <linux/fs.h> > > +#include <linux/pci.h> > > +#include <linux/mmu_notifier.h> > > +#include <linux/uaccess.h> > > +#include <linux/vfio.h> > > + > > +#define PCI_CAP_ID_BASIC 0 > > +#ifndef PCI_CAP_ID_MAX > > +#define PCI_CAP_ID_MAX PCI_CAP_ID_AF > > +#endif > > + > > +/* > > + * Lengths of PCI Config Capabilities > > + * 0 means unknown (but at least 4) > > + * FF means special/variable > > + */ > > +static u8 pci_capability_length[] = { > > + [PCI_CAP_ID_BASIC] = 64, /* pci config header */ > > + [PCI_CAP_ID_PM] = PCI_PM_SIZEOF, > > + [PCI_CAP_ID_AGP] = PCI_AGP_SIZEOF, > > + [PCI_CAP_ID_VPD] = 8, > > + [PCI_CAP_ID_SLOTID] = 4, > > + [PCI_CAP_ID_MSI] = 0xFF, /* 10, 14, 20, or 24 */ > > + [PCI_CAP_ID_CHSWP] = 4, > > + [PCI_CAP_ID_PCIX] = 0xFF, /* 8 or 24 */ > > + [PCI_CAP_ID_HT] = 28, > > + [PCI_CAP_ID_VNDR] = 0xFF, > > + [PCI_CAP_ID_DBG] = 0, > > + [PCI_CAP_ID_CCRC] = 0, > > + [PCI_CAP_ID_SHPC] = 0, > > + [PCI_CAP_ID_SSVID] = 0, /* bridge only - not supp */ > > + [PCI_CAP_ID_AGP3] = 0, > > + [PCI_CAP_ID_EXP] = 36, > > + [PCI_CAP_ID_MSIX] = 12, > > + [PCI_CAP_ID_AF] = 6, > > +}; > > + > > +/* > > + * Read/Write Permission Bits - one bit for each bit in capability > > + * Any field can be read if it exists, > > + * but what is read depends on whether the field > > + * is 'virtualized', or just pass thru to the hardware. > > + * Any virtualized field is also virtualized for writes. > > + * Writes are only permitted if they have a 1 bit here. > > + */ > > +struct perm_bits { > > + u32 rvirt; /* read bits which must be virtualized */ > > + u32 write; /* writeable bits - virt if read virt */ > > +}; > > + > > +static struct perm_bits pci_cap_basic_perm[] = { > > + { 0xFFFFFFFF, 0, }, /* 0x00 vendor & device id - RO */ > > + { 0x00000003, 0xFFFFFFFF, }, /* 0x04 cmd - mem & io bits virt */ > > + { 0, 0, }, /* 0x08 class code & revision id */ > > + { 0, 0xFF00FFFF, }, /* 0x0c bist, htype, lat, cache */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x10 bar */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x14 bar */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x18 bar */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x1c bar */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x20 bar */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x24 bar */ > > + { 0, 0, }, /* 0x28 cardbus - not yet */ > > + { 0, 0, }, /* 0x2c subsys vendor & dev */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x30 rom bar */ > > + { 0, 0, }, /* 0x34 capability ptr & resv */ > > + { 0, 0, }, /* 0x38 resv */ > > + { 0x000000FF, 0x000000FF, }, /* 0x3c max_lat ... irq */ > > +}; > > + > > +static struct perm_bits pci_cap_pm_perm[] = { > > + { 0, 0, }, /* 0x00 PM capabilities */ > > + { 0, 0xFFFFFFFF, }, /* 0x04 PM control/status */ > > +}; > > + > > +static struct perm_bits pci_cap_vpd_perm[] = { > > + { 0, 0xFFFF0000, }, /* 0x00 address */ > > + { 0, 0xFFFFFFFF, }, /* 0x04 data */ > > +}; > > + > > +static struct perm_bits pci_cap_slotid_perm[] = { > > + { 0, 0, }, /* 0x00 all read only */ > > +}; > > + > > +/* 4 different possible layouts of MSI capability */ > > +static struct perm_bits pci_cap_msi_10_perm[] = { > > + { 0x00FF0000, 0x00FF0000, }, /* 0x00 MSI message control */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x04 MSI message address */ > > + { 0x0000FFFF, 0x0000FFFF, }, /* 0x08 MSI message data */ > > +}; > > +static struct perm_bits pci_cap_msi_14_perm[] = { > > + { 0x00FF0000, 0x00FF0000, }, /* 0x00 MSI message control */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x04 MSI message address */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x08 MSI message upper addr */ > > + { 0x0000FFFF, 0x0000FFFF, }, /* 0x0c MSI message data */ > > +}; > > +static struct perm_bits pci_cap_msi_20_perm[] = { > > + { 0x00FF0000, 0x00FF0000, }, /* 0x00 MSI message control */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x04 MSI message address */ > > + { 0x0000FFFF, 0x0000FFFF, }, /* 0x08 MSI message data */ > > + { 0, 0xFFFFFFFF, }, /* 0x0c MSI mask bits */ > > + { 0, 0xFFFFFFFF, }, /* 0x10 MSI pending bits */ > > +}; > > +static struct perm_bits pci_cap_msi_24_perm[] = { > > + { 0x00FF0000, 0x00FF0000, }, /* 0x00 MSI message control */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x04 MSI message address */ > > + { 0xFFFFFFFF, 0xFFFFFFFF, }, /* 0x08 MSI message upper addr */ > > + { 0x0000FFFF, 0x0000FFFF, }, /* 0x0c MSI message data */ > > + { 0, 0xFFFFFFFF, }, /* 0x10 MSI mask bits */ > > + { 0, 0xFFFFFFFF, }, /* 0x14 MSI pending bits */ > > +}; > > + > > +static struct perm_bits pci_cap_pcix_perm[] = { > > + { 0, 0xFFFF0000, }, /* 0x00 PCI_X_CMD */ > > + { 0, 0, }, /* 0x04 PCI_X_STATUS */ > > + { 0, 0xFFFFFFFF, }, /* 0x08 ECC ctlr & status */ > > + { 0, 0, }, /* 0x0c ECC first addr */ > > + { 0, 0, }, /* 0x10 ECC second addr */ > > + { 0, 0, }, /* 0x14 ECC attr */ > > +}; > > + > > +/* pci express capabilities */ > > +static struct perm_bits pci_cap_exp_perm[] = { > > + { 0, 0, }, /* 0x00 PCIe capabilities */ > > + { 0, 0, }, /* 0x04 PCIe device capabilities */ > > + { 0, 0xFFFFFFFF, }, /* 0x08 PCIe device control & status */ > > + { 0, 0, }, /* 0x0c PCIe link capabilities */ > > + { 0, 0x000000FF, }, /* 0x10 PCIe link ctl/stat - SAFE? */ > > + { 0, 0, }, /* 0x14 PCIe slot capabilities */ > > + { 0, 0x00FFFFFF, }, /* 0x18 PCIe link ctl/stat - SAFE? */ > > + { 0, 0, }, /* 0x1c PCIe root port stuff */ > > + { 0, 0, }, /* 0x20 PCIe root port stuff */ > > +}; > > + > > +static struct perm_bits pci_cap_msix_perm[] = { > > + { 0, 0, }, /* 0x00 MSI-X Enable */ > > + { 0, 0, }, /* 0x04 table offset & bir */ > > + { 0, 0, }, /* 0x08 pba offset & bir */ > > +}; > > + > > +static struct perm_bits pci_cap_af_perm[] = { > > + { 0, 0, }, /* 0x00 af capability */ > > + { 0, 0x0001, }, /* 0x04 af flr bit */ > > +}; > > + > > +static struct perm_bits *pci_cap_perms[] = { > > + [PCI_CAP_ID_BASIC] = pci_cap_basic_perm, > > + [PCI_CAP_ID_PM] = pci_cap_pm_perm, > > + [PCI_CAP_ID_VPD] = pci_cap_vpd_perm, > > + [PCI_CAP_ID_SLOTID] = pci_cap_slotid_perm, > > + [PCI_CAP_ID_MSI] = NULL, /* special */ > > + [PCI_CAP_ID_PCIX] = pci_cap_pcix_perm, > > + [PCI_CAP_ID_EXP] = pci_cap_exp_perm, > > + [PCI_CAP_ID_MSIX] = pci_cap_msix_perm, > > + [PCI_CAP_ID_AF] = pci_cap_af_perm, > > +}; > > + > > +static int vfio_msi_cap_len(struct vfio_dev *vdev, u8 pos) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + int len; > > + int ret; > > + u16 flags; > > + > > + ret = pci_read_config_word(pdev, pos + PCI_MSI_FLAGS, &flags); > > + if (ret < 0) > > + return ret; > > + if (flags & PCI_MSI_FLAGS_64BIT) > > + len = 14; > > + else > > + len = 10; > > + if (flags & PCI_MSI_FLAGS_MASKBIT) > > + len += 10; > > + > > + switch (len) { > > + case 10: > > + vdev->msi_perm = pci_cap_msi_10_perm; > > + break; > > + case 14: > > + vdev->msi_perm = pci_cap_msi_14_perm; > > + break; > > + case 20: > > + vdev->msi_perm = pci_cap_msi_20_perm; > > + break; > > + case 24: > > + vdev->msi_perm = pci_cap_msi_24_perm; > > + break; > > + } > > + return len; > > +} > > + > > +/* > > + * We build a map of the config space that tells us where > > + * and what capabilities exist, so that we can map reads and > > + * writes back to capabilities, and thus figure out what to > > + * allow, deny, or virtualize > > + */ > > +int vfio_build_config_map(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + u8 *map; > > + int i, len; > > + u8 pos, cap, tmp; > > + u16 flags; > > + int ret; > > +#ifndef PCI_FIND_CAP_TTL > > +#define PCI_FIND_CAP_TTL 48 > > +#endif > > + int loops = PCI_FIND_CAP_TTL; > > + > > + map = kmalloc(pdev->cfg_size, GFP_KERNEL); > > + if (map == NULL) > > + return -ENOMEM; > > + for (i = 0; i < pdev->cfg_size; i++) > > + map[i] = 0xFF; > > + vdev->pci_config_map = map; > > + > > + /* default config space */ > > + for (i = 0; i < pci_capability_length[0]; i++) > > + map[i] = 0; > > + > > + /* any capabilities? */ > > + ret = pci_read_config_word(pdev, PCI_STATUS, &flags); > > + if (ret < 0) > > + return ret; > > + if ((flags & PCI_STATUS_CAP_LIST) == 0) > > + return 0; > > + > > + ret = pci_read_config_byte(pdev, PCI_CAPABILITY_LIST, &pos); > > + if (ret < 0) > > + return ret; > > + while (pos && --loops > 0) { > > + ret = pci_read_config_byte(pdev, pos, &cap); > > + if (ret < 0) > > + return ret; > > + if (cap == 0) { > > + printk(KERN_WARNING "%s: cap 0\n", __func__); > > + break; > > + } > > + if (cap > PCI_CAP_ID_MAX) { > > + printk(KERN_WARNING "%s: unknown pci capability id %x\n", > > + __func__, cap); > > + len = 0; > > + } else > > + len = pci_capability_length[cap]; > > + if (len == 0) { > > + printk(KERN_WARNING "%s: unknown length for pci cap %x\n", > > + __func__, cap); > > + len = 4; > > + } > > + if (len == 0xFF) { > > + switch (cap) { > > + case PCI_CAP_ID_MSI: > > + len = vfio_msi_cap_len(vdev, pos); > > + if (len < 0) > > + return len; > > + break; > > + case PCI_CAP_ID_PCIX: > > + ret = pci_read_config_word(pdev, pos + 2, > > + &flags); > > + if (ret < 0) > > + return ret; > > + if (flags & 0x3000) > > + len = 24; > > + else > > + len = 8; > > + break; > > + case PCI_CAP_ID_VNDR: > > + /* length follows next field */ > > + ret = pci_read_config_byte(pdev, pos + 2, &tmp); > > + if (ret < 0) > > + return ret; > > + len = tmp; > > + break; > > + default: > > + len = 0; > > + break; > > + } > > + } > > + > > + for (i = 0; i < len; i++) { > > + if (map[pos+i] != 0xFF) > > + printk(KERN_WARNING > > + "%s: pci config conflict at %x, " > > + "caps %x %x\n", > > + __func__, i, map[pos+i], cap); > > + map[pos+i] = cap; > > + } > > + ret = pci_read_config_byte(pdev, pos + PCI_CAP_LIST_NEXT, &pos); > > + if (ret < 0) > > + return ret; > > + } > > + if (loops <= 0) > > + printk(KERN_ERR "%s: config space loop!\n", __func__); > > + return 0; > > +} > > + > > +static int vfio_virt_init(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + u32 *lp; > > + int i; > > + > > + vdev->vconfig = kmalloc(256, GFP_KERNEL); > > + if (vdev->vconfig == NULL) > > + return -ENOMEM; > > + > > + lp = (u32 *)vdev->vconfig; > > + for (i = 0; i < 256/sizeof(u32); i++, lp++) > > + pci_read_config_dword(pdev, i * sizeof(u32), lp); > > + vdev->bardirty = 1; > > + > > + vdev->rbar[0] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_0]; > > + vdev->rbar[1] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_1]; > > + vdev->rbar[2] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_2]; > > + vdev->rbar[3] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_3]; > > + vdev->rbar[4] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_4]; > > + vdev->rbar[5] = *(u32 *)&vdev->vconfig[PCI_BASE_ADDRESS_5]; > > + vdev->rbar[6] = *(u32 *)&vdev->vconfig[PCI_ROM_ADDRESS]; > > + > > + /* for sr-iov devices */ > > + vdev->vconfig[PCI_VENDOR_ID] = pdev->vendor & 0xFF; > > + vdev->vconfig[PCI_VENDOR_ID+1] = pdev->vendor >> 8; > > + vdev->vconfig[PCI_DEVICE_ID] = pdev->device & 0xFF; > > + vdev->vconfig[PCI_DEVICE_ID+1] = pdev->device >> 8; > > + > > + return 0; > > +} > > + > > +/* > > + * Restore the *real* BARs after we detect a backdoor reset. > > + * (backdoor = some device specific technique that we didn't catch) > > + */ > > +static void vfio_bar_restore(struct vfio_dev *vdev) > > +{ > > + printk(KERN_WARNING "%s: restoring real bars\n", __func__); > > + > > +#define do_bar(off, which) \ > > + pci_user_write_config_dword(vdev->pdev, off, vdev->rbar[which]) > > + > > + do_bar(PCI_BASE_ADDRESS_0, 0); > > + do_bar(PCI_BASE_ADDRESS_1, 1); > > + do_bar(PCI_BASE_ADDRESS_2, 2); > > + do_bar(PCI_BASE_ADDRESS_3, 3); > > + do_bar(PCI_BASE_ADDRESS_4, 4); > > + do_bar(PCI_BASE_ADDRESS_5, 5); > > + do_bar(PCI_ROM_ADDRESS, 6); > > +#undef do_bar > > +} > > + > > +/* > > + * Pretend we're hardware and tweak the values > > + * of the *virtual* pci BARs to reflect the hardware > > + * capabilities > > + */ > > +static void vfio_bar_fixup(struct vfio_dev *vdev) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + int bar; > > + u32 *lp; > > + u64 mask; > > + > > + for (bar = 0; bar <= 5; bar++) { > > + if (pci_resource_start(pdev, bar)) > > + mask = ~(pci_resource_len(pdev, bar) - 1); > > + else > > + mask = 0; > > + lp = (u32 *)vdev->vconfig + PCI_BASE_ADDRESS_0 + 4*bar; > > + *lp &= (u32)mask; > > + > > + if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) > > + *lp |= PCI_BASE_ADDRESS_SPACE_IO; > > + else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { > > + *lp |= PCI_BASE_ADDRESS_SPACE_MEMORY; > > + if (pci_resource_flags(pdev, bar) & IORESOURCE_PREFETCH) > > + *lp |= PCI_BASE_ADDRESS_MEM_PREFETCH; > > + if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM_64) { > > + *lp |= PCI_BASE_ADDRESS_MEM_TYPE_64; > > + lp++; > > + *lp &= (u32)(mask >> 32); > > + bar++; > > + } > > + } > > + } > > + > > + if (pci_resource_start(pdev, PCI_ROM_RESOURCE)) > > + mask = ~(pci_resource_len(pdev, PCI_ROM_RESOURCE) - 1); > > + else > > + mask = 0; > > + lp = (u32 *)vdev->vconfig + PCI_ROM_ADDRESS; > > + *lp &= (u32)mask; > > + > > + vdev->bardirty = 0; > > +} > > + > > +static inline int vfio_read_config_byte(struct vfio_dev *vdev, > > + int pos, u8 *valp) > > +{ > > + return pci_user_read_config_byte(vdev->pdev, pos, valp); > > +} > > + > > +static inline int vfio_write_config_byte(struct vfio_dev *vdev, > > + int pos, u8 val) > > +{ > > + vdev->vconfig[pos] = val; > > + return pci_user_write_config_byte(vdev->pdev, pos, val); > > +} > > + > > +static int vfio_config_rwbyte(int write, > > + struct vfio_dev *vdev, > > + int pos, > > + char __user *buf) > > +{ > > + u8 *map = vdev->pci_config_map; > > + u8 cap, val, newval; > > + u16 start, off; > > + int p; > > + struct perm_bits *perm; > > + u8 wr, virt; > > + int ret; > > + > > + cap = map[pos]; > > + if (cap == 0xFF) { /* unknown region */ > > + if (write) > > + return 0; /* silent no-op */ > > + val = 0; > > + if (pos <= pci_capability_length[0]) /* ok to read */ > > + (void) vfio_read_config_byte(vdev, pos, &val); > > + if (copy_to_user(buf, &val, 1)) > > + return -EFAULT; > > + return 0; > > + } > > + > > + /* scan back to start of cap region */ > > + for (p = pos; p >= 0; p--) { > > + if (map[p] != cap) > > + break; > > + start = p; > > + } > > + off = pos - start; /* offset within capability */ > > + > > + if (cap == PCI_CAP_ID_MSI) > > + perm = vdev->msi_perm; > > + else > > + perm = pci_cap_perms[cap]; > > + if (perm == NULL) { > > + wr = 0; > > + virt = 0; > > + } else { > > + perm += (off >> 2); > > + wr = perm->write >> ((off & 3) * 8); > > + virt = perm->rvirt >> ((off & 3) * 8); > > + } > > + if (write && !wr) /* no writeable bits */ > > + return 0; > > + if (!virt) { > > + if (write) { > > + if (copy_from_user(&val, buf, 1)) > > + return -EFAULT; > > + val &= wr; > > + if (wr != 0xFF) { > > + u8 existing; > > + > > + ret = vfio_read_config_byte(vdev, pos, > > + &existing); > > + if (ret < 0) > > + return ret; > > + val |= (existing & ~wr); > > + } > > + vfio_write_config_byte(vdev, pos, val); > > + } else { > > + ret = vfio_read_config_byte(vdev, pos, &val); > > + if (ret < 0) > > + return ret; > > + if (copy_to_user(buf, &val, 1)) > > + return -EFAULT; > > + } > > + return 0; > > + } > > + > > + if (write) { > > + if (copy_from_user(&newval, buf, 1)) > > + return -EFAULT; > > + } > > + /* > > + * We get here if there are some virt bits > > + * handle remaining real bits, if any > > + */ > > + if (~virt) { > > + u8 rbits = (~virt) & wr; > > + > > + ret = vfio_read_config_byte(vdev, pos, &val); > > + if (ret < 0) > > + return ret; > > + if (write && rbits) { > > + val &= ~rbits; > > + val |= (newval & rbits); > > + vfio_write_config_byte(vdev, pos, val); > > + } > > + } > > + /* > > + * Now handle entirely virtual fields > > + */ > > + switch (cap) { > > + case PCI_CAP_ID_BASIC: /* virtualize BARs */ > > + switch (off) { > > + /* > > + * vendor and device are virt because they don't > > + * show up otherwise for sr-iov vfs > > + */ > > + case PCI_VENDOR_ID: > > + case PCI_VENDOR_ID + 1: > > + case PCI_DEVICE_ID: > > + case PCI_DEVICE_ID + 1: > > + /* read only */ > > + val = vdev->vconfig[pos]; > > + break; > > + case PCI_COMMAND: > > + /* > > + * If the real mem or IO enable bits are zero > > + * then there may have been a backdoor reset. > > + * Restore the real BARs before allowing those > > + * bits to re-enable > > + */ > > + if (vdev->pdev->is_virtfn) > > + val |= PCI_COMMAND_MEMORY; > > + if (write) { > > + int upd = 0; > > + > > + upd = (newval & PCI_COMMAND_MEMORY) > > > + (val & PCI_COMMAND_MEMORY); > > + upd += (newval & PCI_COMMAND_IO) > > > + (val & PCI_COMMAND_IO); > > + if (upd) > > + vfio_bar_restore(vdev); > > + vfio_write_config_byte(vdev, pos, newval); > > + } > > + break; > > + case PCI_INTERRUPT_LINE: > > + if (write) > > + vdev->vconfig[pos] = newval; > > + else > > + val = vdev->vconfig[pos]; > > + break; > > + case PCI_BASE_ADDRESS_0: > > + case PCI_BASE_ADDRESS_0+1: > > + case PCI_BASE_ADDRESS_0+2: > > + case PCI_BASE_ADDRESS_0+3: > > + case PCI_BASE_ADDRESS_1: > > + case PCI_BASE_ADDRESS_1+1: > > + case PCI_BASE_ADDRESS_1+2: > > + case PCI_BASE_ADDRESS_1+3: > > + case PCI_BASE_ADDRESS_2: > > + case PCI_BASE_ADDRESS_2+1: > > + case PCI_BASE_ADDRESS_2+2: > > + case PCI_BASE_ADDRESS_2+3: > > + case PCI_BASE_ADDRESS_3: > > + case PCI_BASE_ADDRESS_3+1: > > + case PCI_BASE_ADDRESS_3+2: > > + case PCI_BASE_ADDRESS_3+3: > > + case PCI_BASE_ADDRESS_4: > > + case PCI_BASE_ADDRESS_4+1: > > + case PCI_BASE_ADDRESS_4+2: > > + case PCI_BASE_ADDRESS_4+3: > > + case PCI_BASE_ADDRESS_5: > > + case PCI_BASE_ADDRESS_5+1: > > + case PCI_BASE_ADDRESS_5+2: > > + case PCI_BASE_ADDRESS_5+3: > > + case PCI_ROM_ADDRESS: > > + case PCI_ROM_ADDRESS+1: > > + case PCI_ROM_ADDRESS+2: > > + case PCI_ROM_ADDRESS+3: > > + if (write) { > > + vdev->vconfig[pos] = newval; > > + vdev->bardirty = 1; > > + } else { > > + if (vdev->bardirty) > > + vfio_bar_fixup(vdev); > > + val = vdev->vconfig[pos]; > > + } > > + break; > > + } > > + break; > > + case PCI_CAP_ID_MSI: /* virtualize (parts of) MSI */ > > + if (off == PCI_MSI_FLAGS) { > > + u8 num; > > + > > + if (write) { > > + if (vdev->ev_msi == NULL) > > + newval &= ~PCI_MSI_FLAGS_ENABLE; > > + num = (newval & PCI_MSI_FLAGS_QSIZE) >> 4; > > + if (num > vdev->msi_qmax) > > + num = vdev->msi_qmax; > > + newval &= ~PCI_MSI_FLAGS_QSIZE; > > + newval |= num << 4; > > + vfio_write_config_byte(vdev, pos, newval); > > + } else { > > + ret = vfio_read_config_byte(vdev, pos, &val); > > + if (ret < 0) > > + return ret; > > + val &= ~PCI_MSI_FLAGS_QMASK; > > + val |= vdev->msi_qmax << 1; > > + } > > + } else { > > + if (write) > > + vdev->vconfig[pos] = newval; > > + else > > + val = vdev->vconfig[pos]; > > + } > > + break; > > + } > > + if (!write && copy_to_user(buf, &val, 1)) > > + return -EFAULT; > > + return 0; > > +} > > + > > +ssize_t vfio_config_readwrite(int write, > > + struct vfio_dev *vdev, > > + char __user *buf, > > + size_t count, > > + loff_t *ppos) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + int done = 0; > > + int ret; > > + u16 pos; > > + > > + > > + if (vdev->pci_config_map == NULL) { > > + ret = vfio_build_config_map(vdev); > > + if (ret) > > + goto out; > > + } > > + if (vdev->vconfig == NULL) { > > + ret = vfio_virt_init(vdev); > > + if (ret) > > + goto out; > > + } > > + > > + while (count > 0) { > > + pos = *ppos; > > + if (pos == pdev->cfg_size) > > + break; > > + if (pos > pdev->cfg_size) { > > + ret = -EINVAL; > > + goto out; > > + } > > + > > + ret = vfio_config_rwbyte(write, vdev, pos, buf); > > + > > + if (ret < 0) > > + goto out; > > + buf++; > > + done++; > > + count--; > > + (*ppos)++; > > + } > > + ret = done; > > +out: > > + return ret; > > +} > > diff --git a/drivers/vfio/vfio_rdwr.c b/drivers/vfio/vfio_rdwr.c > > new file mode 100644 > > index 0000000..1fd50a6 > > --- /dev/null > > +++ b/drivers/vfio/vfio_rdwr.c > > @@ -0,0 +1,158 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > + > > +/* > > + * This code handles normal read and write system calls; allowing > > + * access to device memory or I/O registers > > + * without the need for mmap'ing. > > + */ > > + > > +#include <linux/fs.h> > > +#include <linux/mmu_notifier.h> > > +#include <linux/pci.h> > > +#include <linux/uaccess.h> > > +#include <linux/io.h> > > + > > +#include <linux/vfio.h> > > + > > +ssize_t vfio_io_readwrite( > > + int write, > > + struct vfio_dev *vdev, > > + char __user *buf, > > + size_t count, > > + loff_t *ppos) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + size_t done = 0; > > + resource_size_t end; > > + void __iomem *io; > > + loff_t pos; > > + int pci_space; > > + int unit; > > + > > + pci_space = vfio_offset_to_pci_space(*ppos); > > + pos = vfio_offset_to_pci_offset(*ppos); > > + > > + if (!pci_resource_start(pdev, pci_space)) > > + return -EINVAL; > > + end = pci_resource_len(pdev, pci_space); > > + if (pos + count > end) > > + return -EINVAL; > > + if (vdev->barmap[pci_space] == NULL) > > + vdev->barmap[pci_space] = pci_iomap(pdev, pci_space, 0); > > + io = vdev->barmap[pci_space]; > > + > > + while (count > 0) { > > + if ((pos % 4) == 0 && count >= 4) { > > + u32 val; > > + > > + if (write) { > > + if (copy_from_user(&val, buf, 4)) > > + return -EFAULT; > > + iowrite32(val, io + pos); > > + } else { > > + val = ioread32(io + pos); > > + if (copy_to_user(buf, &val, 4)) > > + return -EFAULT; > > + } > > + unit = 4; > > + } else if ((pos % 2) == 0 && count >= 2) { > > + u16 val; > > + > > + if (write) { > > + if (copy_from_user(&val, buf, 2)) > > + return -EFAULT; > > + iowrite16(val, io + pos); > > + } else { > > + val = ioread16(io + pos); > > + if (copy_to_user(buf, &val, 2)) > > + return -EFAULT; > > + } > > + unit = 2; > > + } else { > > + u8 val; > > + > > + if (write) { > > + if (copy_from_user(&val, buf, 1)) > > + return -EFAULT; > > + iowrite8(val, io + pos); > > + } else { > > + val = ioread8(io + pos); > > + if (copy_to_user(buf, &val, 1)) > > + return -EFAULT; > > + } > > + unit = 1; > > + } > > + pos += unit; > > + buf += unit; > > + count -= unit; > > + done += unit; > > + } > > + *ppos += done; > > + return done; > > +} > > Can we export and use pci_write_legacy_io? Same for read. > Drivers don't do unaligned accesses, do they? pci legacy routines only exists for weird platforms, not x86. > > > + > > +ssize_t vfio_mem_readwrite( > > + int write, > > + struct vfio_dev *vdev, > > + char __user *buf, > > + size_t count, > > + loff_t *ppos) > > +{ > > + struct pci_dev *pdev = vdev->pdev; > > + resource_size_t end; > > + void __iomem *io; > > + loff_t pos; > > + int pci_space; > > + > > + pci_space = vfio_offset_to_pci_space(*ppos); > > + pos = vfio_offset_to_pci_offset(*ppos); > > + > > + if (!pci_resource_start(pdev, pci_space)) > > + return -EINVAL; > > + end = pci_resource_len(pdev, pci_space); > > + if (vdev->barmap[pci_space] == NULL) > > + vdev->barmap[pci_space] = pci_iomap(pdev, pci_space, 0); > > + io = vdev->barmap[pci_space]; > > + > > + if (pos > end) > > + return -EINVAL; > > + if (pos == end) > > + return 0; > > + if (pos + count > end) > > + count = end - pos; > > + if (write) { > > + if (copy_from_user(io + pos, buf, count)) > > + return -EFAULT; > > + } else { > > + if (copy_to_user(buf, io + pos, count)) > > + return -EFAULT; > > + } > > + *ppos += count; > > + return count; > > +} > > diff --git a/drivers/vfio/vfio_sysfs.c b/drivers/vfio/vfio_sysfs.c > > new file mode 100644 > > index 0000000..a3ddba1 > > --- /dev/null > > +++ b/drivers/vfio/vfio_sysfs.c > > @@ -0,0 +1,118 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > + > > +/* > > + * This code handles vfio related files in sysfs > > + * (not much useful yet) > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/device.h> > > +#include <linux/kobject.h> > > +#include <linux/sysfs.h> > > +#include <linux/mm.h> > > +#include <linux/fs.h> > > +#include <linux/pci.h> > > +#include <linux/mmu_notifier.h> > > + > > +#include <linux/vfio.h> > > + > > +struct vfio_class *vfio_class; > > + > > +int vfio_class_init(void) > > +{ > > + int ret = 0; > > + > > + if (vfio_class != NULL) { > > + kref_get(&vfio_class->kref); > > + goto exit; > > + } > > + > > + vfio_class = kzalloc(sizeof(*vfio_class), GFP_KERNEL); > > + if (!vfio_class) { > > + ret = -ENOMEM; > > + goto err_kzalloc; > > + } > > + > > + kref_init(&vfio_class->kref); > > + vfio_class->class = class_create(THIS_MODULE, "vfio"); > > + if (IS_ERR(vfio_class->class)) { > > + ret = IS_ERR(vfio_class->class); > > + printk(KERN_ERR "class_create failed for vfio\n"); > > + goto err_class_create; > > + } > > + return 0; > > + > > +err_class_create: > > + kfree(vfio_class); > > + vfio_class = NULL; > > +err_kzalloc: > > +exit: > > + return ret; > > +} > > + > > +static void vfio_class_release(struct kref *kref) > > +{ > > + /* Ok, we cheat as we know we only have one vfio_class */ > > + class_destroy(vfio_class->class); > > + kfree(vfio_class); > > + vfio_class = NULL; > > +} > > + > > +void vfio_class_destroy(void) > > +{ > > + if (vfio_class) > > + kref_put(&vfio_class->kref, vfio_class_release); > > +} > > + > > +static ssize_t show_locked_pages(struct device *dev, > > + struct device_attribute *attr, > > + char *buf) > > +{ > > + struct vfio_dev *vdev = dev_get_drvdata(dev); > > + > > + if (vdev == NULL) > > + return -ENODEV; > > + return sprintf(buf, "%u\n", vdev->locked_pages); > > +} > > + > > +static DEVICE_ATTR(locked_pages, S_IRUGO, show_locked_pages, NULL); > > + > > +static struct attribute *vfio_attrs[] = { > > + &dev_attr_locked_pages.attr, > > + NULL, > > +}; > > + > > +static struct attribute_group vfio_attr_grp = { > > + .attrs = vfio_attrs, > > +}; > > + > > +int vfio_dev_add_attributes(struct vfio_dev *vdev) > > +{ > > + return sysfs_create_group(&vdev->dev->kobj, &vfio_attr_grp); > > +} > > diff --git a/include/linux/Kbuild b/include/linux/Kbuild > > index 2fc8e14..3121529 100644 > > --- a/include/linux/Kbuild > > +++ b/include/linux/Kbuild > > @@ -167,6 +167,7 @@ header-y += ultrasound.h > > > > header-y += un.h > > header-y += utime.h > > header-y += veth.h > > > > +header-y += vfio.h > > > > header-y += videotext.h > > header-y += x25.h > > > > diff --git a/include/linux/vfio.h b/include/linux/vfio.h > > new file mode 100644 > > index 0000000..b7dd524 > > --- /dev/null > > +++ b/include/linux/vfio.h > > @@ -0,0 +1,267 @@ > > +/* > > + * Copyright 2010 Cisco Systems, Inc. All rights reserved. > > + * Author: Tom Lyon, pugs@xxxxxxxxx > > + * > > + * This program is free software; you may redistribute it and/or modify > > + * it under the terms of the GNU General Public License as published by > > + * the Free Software Foundation; version 2 of the License. > > + * > > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > > + * SOFTWARE. > > + * > > + * Portions derived from drivers/uio/uio.c: > > + * Copyright(C) 2005, Benedikt Spranger <b.spranger@xxxxxxxxxxxxx> > > + * Copyright(C) 2005, Thomas Gleixner <tglx@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Hans J. Koch <hjk@xxxxxxxxxxxxx> > > + * Copyright(C) 2006, Greg Kroah-Hartman <greg@xxxxxxxxx> > > + * > > + * Portions derived from drivers/uio/uio_pci_generic.c: > > + * Copyright (C) 2009 Red Hat, Inc. > > + * Author: Michael S. Tsirkin <mst@xxxxxxxxxx> > > + */ > > +#include <linux/types.h> > > + > > +/* > > + * VFIO driver - allow mapping and use of certain PCI devices > > + * in unprivileged user processes. (If IOMMU is present) > > + * Especially useful for Virtual Function parts of SR-IOV devices > > + */ > > + > > +#ifdef __KERNEL__ > > + > > +struct vfio_nl_client { > > + struct list_head list; > > + u64 msgcap; > > + struct net *net; > > + u32 pid; > > +}; > > + > > +struct perm_bits; > > +struct vfio_dev { > > + struct device *dev; > > + struct pci_dev *pdev; > > + char name[8]; > > + u8 *pci_config_map; > > + int pci_config_size; > > + int devnum; > > + void __iomem *barmap[PCI_ROM_RESOURCE+1]; > > + spinlock_t irqlock; /* guards command register accesses */ > > + int listeners; > > + u32 locked_pages; > > + struct mutex lgate; /* listener gate */ > > + struct mutex dgate; /* dma op gate */ > > + struct mutex igate; /* intr op gate */ > > + struct mutex ngate; /* netlink op gate */ > > + struct list_head nlc_list; /* netlink clients */ > > + wait_queue_head_t dev_idle_q; > > + wait_queue_head_t nl_wait_q; > > + u32 nl_reply_seq; > > + u32 nl_reply_value; > > + int mapcount; > > + struct uiommu_domain *udomain; > > + int cachec; > > + struct msix_entry *msix; > > + struct eventfd_ctx *ev_irq; > > + struct eventfd_ctx **ev_msi; > > + struct eventfd_ctx **ev_msix; > > + int msi_nvec; > > + int msix_nvec; > > + u8 *vconfig; > > + u32 rbar[7]; /* copies of real bars */ > > + u8 msi_qmax; > > + u8 bardirty; > > + struct perm_bits *msi_perm; > > +}; > > + > > +struct vfio_listener { > > + struct vfio_dev *vdev; > > + struct list_head dm_list; > > + struct mm_struct *mm; > > + struct mmu_notifier mmu_notifier; > > +}; > > + > > +/* > > + * Structure for keeping track of memory nailed down by the > > + * user for DMA > > + */ > > +struct dma_map_page { > > + struct list_head list; > > + struct page **pages; > > + dma_addr_t daddr; > > + unsigned long vaddr; > > + int npage; > > + int rdwr; > > +}; > > + > > +/* VFIO class infrastructure */ > > +struct vfio_class { > > + struct kref kref; > > + struct class *class; > > +}; > > +extern struct vfio_class *vfio_class; > > + > > +ssize_t vfio_io_readwrite(int, struct vfio_dev *, > > + char __user *, size_t, loff_t *); > > +ssize_t vfio_mem_readwrite(int, struct vfio_dev *, > > + char __user *, size_t, loff_t *); > > +ssize_t vfio_config_readwrite(int, struct vfio_dev *, > > + char __user *, size_t, loff_t *); > > + > > +void vfio_drop_msi(struct vfio_dev *); > > +void vfio_drop_msix(struct vfio_dev *); > > +int vfio_setup_msi(struct vfio_dev *, int, void __user *); > > +int vfio_setup_msix(struct vfio_dev *, int, void __user *); > > + > > +#ifndef PCI_MSIX_ENTRY_SIZE > > +#define PCI_MSIX_ENTRY_SIZE 16 > > +#endif > > +#ifndef PCI_STATUS_INTERRUPT > > +#define PCI_STATUS_INTERRUPT 0x08 > > +#endif > > + > > +struct vfio_dma_map; > > +void vfio_dma_unmapall(struct vfio_listener *); > > +int vfio_dma_unmap_dm(struct vfio_listener *, struct vfio_dma_map *); > > +int vfio_dma_map_common(struct vfio_listener *, unsigned int, > > + struct vfio_dma_map *); > > +int vfio_domain_set(struct vfio_dev *, int, int); > > +int vfio_domain_unset(struct vfio_dev *); > > + > > +int vfio_class_init(void); > > +void vfio_class_destroy(void); > > +int vfio_dev_add_attributes(struct vfio_dev *); > > +int vfio_build_config_map(struct vfio_dev *); > > + > > +int vfio_nl_init(void); > > +void vfio_nl_freeclients(struct vfio_dev *); > > +void vfio_nl_exit(void); > > +int vfio_nl_remove(struct vfio_dev *); > > +int vfio_validate(struct vfio_dev *); > > +int vfio_nl_upcall(struct vfio_dev *, u8, int, int); > > +void vfio_pm_process_reply(int); > > +pci_ers_result_t vfio_error_detected(struct pci_dev *, > > pci_channel_state_t); +pci_ers_result_t vfio_mmio_enabled(struct pci_dev > > *); > > +pci_ers_result_t vfio_link_reset(struct pci_dev *); > > +pci_ers_result_t vfio_slot_reset(struct pci_dev *); > > +void vfio_error_resume(struct pci_dev *); > > +#define VFIO_ERROR_REPLY_TIMEOUT (3*HZ) > > +#define VFIO_SUSPEND_REPLY_TIMEOUT (5*HZ) > > + > > +irqreturn_t vfio_interrupt(int, void *); > > + > > +#endif /* __KERNEL__ */ > > + > > +/* Kernel & User level defines for ioctls */ > > + > > +/* > > + * Structure for DMA mapping of user buffers > > + * vaddr, dmaaddr, and size must all be page aligned > > + * buffer may only be larger than 1 page if (a) there is > > + * an iommu in the system, or (b) buffer is part of a huge page > > + */ > > +struct vfio_dma_map { > > + __u64 vaddr; /* process virtual addr */ > > + __u64 dmaaddr; /* desired and/or returned dma address */ > > + __u64 size; /* size in bytes */ > > + __u64 flags; /* bool: 0 for r/o; 1 for r/w */ > > +#define VFIO_FLAG_WRITE 0x1 /* req writeable DMA mem */ > > +}; > > + > > +/* map user pages at specific dma address */ > > +/* requires previous VFIO_DOMAIN_SET */ > > +#define VFIO_DMA_MAP_IOVA _IOWR(';', 101, struct vfio_dma_map) > > + > > +/* unmap user pages */ > > +#define VFIO_DMA_UNMAP _IOW(';', 102, struct vfio_dma_map) > > + > > +/* request IRQ interrupts; use given eventfd */ > > +#define VFIO_EVENTFD_IRQ _IOW(';', 103, int) > > + > > +/* Request MSI interrupts: arg[0] is #, arg[1-n] are eventfds */ > > +#define VFIO_EVENTFDS_MSI _IOW(';', 104, int) > > + > > +/* Request MSI-X interrupts: arg[0] is #, arg[1-n] are eventfds */ > > +#define VFIO_EVENTFDS_MSIX _IOW(';', 105, int) > > + > > +/* Get length of a BAR */ > > +#define VFIO_BAR_LEN _IOWR(';', 167, __u32) > > + > > +/* Set the IOMMU domain - arg is fd from uiommu driver */ > > +#define VFIO_DOMAIN_SET _IOW(';', 107, int) > > + > > +/* Unset the IOMMU domain */ > > +#define VFIO_DOMAIN_UNSET _IO(';', 108) > > + > > +/* > > + * Reads, writes, and mmaps determine which PCI BAR (or config space) > > + * from the high level bits of the file offset > > + */ > > +#define VFIO_PCI_BAR0_RESOURCE 0x0 > > +#define VFIO_PCI_BAR1_RESOURCE 0x1 > > +#define VFIO_PCI_BAR2_RESOURCE 0x2 > > +#define VFIO_PCI_BAR3_RESOURCE 0x3 > > +#define VFIO_PCI_BAR4_RESOURCE 0x4 > > +#define VFIO_PCI_BAR5_RESOURCE 0x5 > > +#define VFIO_PCI_ROM_RESOURCE 0x6 > > +#define VFIO_PCI_CONFIG_RESOURCE 0xF > > +#define VFIO_PCI_SPACE_SHIFT 32 > > +#define VFIO_PCI_CONFIG_OFF > > vfio_pci_space_to_offset(VFIO_PCI_CONFIG_RESOURCE) + > > +static inline int vfio_offset_to_pci_space(__u64 off) > > +{ > > + return (off >> VFIO_PCI_SPACE_SHIFT) & 0xF; > > +} > > + > > +static inline __u32 vfio_offset_to_pci_offset(__u64 off) > > +{ > > + return off & (__u32)0xFFFFFFFF; > > You don't really need the cast, do you? > > > +} > > + > > +static inline __u64 vfio_pci_space_to_offset(int sp) > > +{ > > + return (__u64)(sp) << VFIO_PCI_SPACE_SHIFT; > > +} > > + > > Is this ever used besides VFIO_PCI_CONFIG_OFF? > If not it's likely an overkill. > If yes note that sp will get sign extended when cast. Can be used when accessing different bar areas. > > > +/* > > + * Netlink defines: > > + */ > > +#define VFIO_GENL_NAME "VFIO" > > + > > +/* message types */ > > +enum { > > + VFIO_MSG_INVAL = 0, > > + /* kernel to user */ > > + VFIO_MSG_REMOVE, /* unbind, module or hotplug remove */ > > + VFIO_MSG_ERROR_DETECTED, /* pci err handling - error detected */ > > + VFIO_MSG_MMIO_ENABLED, /* pci err handling - mmio enabled */ > > + VFIO_MSG_LINK_RESET, /* pci err handling - link reset */ > > + VFIO_MSG_SLOT_RESET, /* pci err handling - slot reset */ > > + VFIO_MSG_ERROR_RESUME, /* pci err handling - resume normal */ > > + VFIO_MSG_PM_SUSPEND, /* suspend or hibernate notification */ > > + VFIO_MSG_PM_RESUME, /* resume after suspend or hibernate */ > > + /* user to kernel */ > > + VFIO_MSG_REGISTER, > > + VFIO_MSG_ERROR_HANDLING_REPLY, /* err handling reply */ > > + VFIO_MSG_PM_SUSPEND_REPLY, /* suspend notify reply */ > > +}; > > + > > +/* attributes */ > > +enum { > > + VFIO_ATTR_UNSPEC, > > + VFIO_ATTR_MSGCAP, /* bitmask of messages desired */ > > + VFIO_ATTR_PCI_DOMAIN, > > + VFIO_ATTR_PCI_BUS, > > + VFIO_ATTR_PCI_SLOT, > > + VFIO_ATTR_PCI_FUNC, > > + VFIO_ATTR_CHANNEL_STATE, > > + VFIO_ATTR_ERROR_HANDLING_REPLY, > > + VFIO_ATTR_PM_SUSPEND_REPLY, > > + __VFIO_NL_ATTR_MAX > > +}; > > +#define VFIO_NL_ATTR_MAX (__VFIO_NL_ATTR_MAX - 1) -- To unsubscribe from this list: send the line "unsubscribe linux-pci" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html