VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling.
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan, only POWERNV
platform is supported at the moment.
Also the patch implements an VFIO-IOMMU driver which
manages DMA mapping/unmapping requests coming from
the client (now QEMU). It also returns a DMA window
information to let the guest initialize the device tree
for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform
supporting TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 ++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
8 files changed, 563 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
@@ -64,30 +64,33 @@ struct iommu_pool {}____cacheline_aligned_in_smp;structiommu_table{unsignedlongit_busno;/* Bus number this table belongs to */unsignedlongit_size;/* Size of iommu table in entries */unsignedlongit_offset;/* Offset into global table */unsignedlongit_base;/* mapped address of tce table */unsignedlongit_index;/* which iommu table this is */unsignedlongit_type;/* type: PCI or Virtual Bus */unsignedlongit_blocksize;/* Entries in each block (cacheline) */unsignedlongpoolsize;unsignedlongnr_pools;structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;staticinlinevoidset_iommu_table_base(structdevice*dev,void*base){dev->archdata.dma_data.iommu_table_base=base;}staticinlinevoid*get_iommu_table_base(structdevice*dev){returndev->archdata.dma_data.iommu_table_base;}/* Frees table for an individual device node */
@@ -844,15 +845,154 @@ void *iommu_alloc_coherent(struct device *dev, struct iommu_table *tbl,}voidiommu_free_coherent(structiommu_table*tbl,size_tsize,void*vaddr,dma_addr_tdma_handle){if(tbl){unsignedintnio_pages;size=PAGE_ALIGN(size);nio_pages=size>>IOMMU_PAGE_SHIFT;iommu_free(tbl,dma_handle,nio_pages);size=PAGE_ALIGN(size);free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticstructpage*free_tce(structiommu_table*tbl,unsignedlongentry)+{+structpage*page=NULL;+unsignedlongoldtce;++oldtce=ppc_md.tce_get(tbl,entry);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+returnNULL;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(page&&(oldtce&TCE_PCI_WRITE))+SetPageDirty(page);+ppc_md.tce_free(tbl,entry,1);++returnpage;+}++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret)+ret=-EFAULT;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++return0;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,uint64_ttce,+enumdma_data_directiondirection,unsignedlongpages)+{+inti,ret=0,pages_to_put=0;+structpage*page;+structiommu_pool*pool=get_pool(tbl,entry);+structpage**oldpages;+constintoldpagesnum=PAGE_SIZE/sizeof(*oldpages);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);++/* Handle a single page request without allocation+ofpages-to-releasearray*/+if(pages==1){+spin_lock(&(pool->lock));+page=free_tce(tbl,entry);++if(direction!=DMA_NONE)+ret=put_tce(tbl,entry,tce,direction);++tce_flush(tbl);++if(page)+put_page(page);++spin_unlock(&(pool->lock));+returnret;+}++/* Releasing multiple pages */+/* Allocate an array for pages to be released after TCE table+isupdated*/+oldpages=kmalloc(PAGE_SIZE,GFP_KERNEL);+if(!oldpages)+return-ENOMEM;++spin_lock(&(pool->lock));++for(i=0;(i<pages)&&!ret;++i,++entry,tce+=IOMMU_PAGE_SIZE){+page=free_tce(tbl,entry);+if(page){+oldpages[pages_to_put]=page;+++pages_to_put;+}++if(direction!=DMA_NONE)+ret=put_tce(tbl,entry,tce,direction);++/* Release old pages if we reached the end of oldpages[] or+itisthelastpageorweareabouttoexittheloop*/+if((pages_to_put==oldpagesnum)||(i==pages-1)||ret){+tce_flush(tbl);++/* Release pages after removing them from TCE table */+while(pages_to_put){+--pages_to_put;+put_page(oldpages[pages_to_put]);+}+}+}++spin_unlock(&(pool->lock));+kfree(oldpages);++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -601,15 +602,149 @@ void __init pnv_pci_init(void)/* Configure IOMMU DMA hooks */ppc_md.pci_dma_dev_setup=pnv_pci_dma_dev_setup;ppc_md.tce_build=pnv_tce_build;ppc_md.tce_free=pnv_tce_free;ppc_md.tce_get=pnv_tce_get;ppc_md.pci_probe_mode=pnv_pci_probe_mode;set_pci_dma_ops(&dma_iommu_ops);/* Configure MSIs */#ifdef CONFIG_PCI_MSIppc_md.msi_check_device=pnv_msi_check_device;ppc_md.setup_msi_irqs=pnv_setup_msi_irqs;ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev->kobj.name,+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev->kobj.name);+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev->kobj.name,iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev->kobj.name,ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
@@ -0,0 +1,247 @@+/*+*VFIO:IOMMUDMAmappingsupportforTCEonPOWER+*+*Copyright(C)2012IBMCorp.Allrightsreserved.+*Author:AlexeyKardashevskiy<aik@ozlabs.ru>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*Derivedfromoriginalvfio_iommu_type1.c:+*Copyright(C)2012RedHat,Inc.Allrightsreserved.+*Author:AlexWilliamson<alex.williamson@redhat.com>+*/++#include<linux/module.h>+#include<linux/pci.h>+#include<linux/slab.h>+#include<linux/uaccess.h>+#include<linux/err.h>+#include<linux/vfio.h>+#include<asm/iommu.h>++#define DRIVER_VERSION "0.1"+#define DRIVER_AUTHOR "aik@ozlabs.ru"+#define DRIVER_DESC "VFIO IOMMU SPAPR TCE"++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group);++/*+*VFIOIOMMUfdforSPAPR_TCEIOMMUimplementation+*/++/*+*Thecontainerdescriptorsupportsonlyasinglegrouppercontainer.+*RequiredbytheAPIasthecontainerisnotsuppliedwiththeIOMMUgroup+*atthemomentofinitialization.+*/+structtce_container{+structmutexlock;+structiommu_table*tbl;+};++staticvoid*tce_iommu_open(unsignedlongarg)+{+structtce_container*container;++if(arg!=VFIO_SPAPR_TCE_IOMMU){+printk(KERN_ERR"tce_vfio: Wrong IOMMU type\n");+returnERR_PTR(-EINVAL);+}++container=kzalloc(sizeof(*container),GFP_KERNEL);+if(!container)+returnERR_PTR(-ENOMEM);++mutex_init(&container->lock);++returncontainer;+}++staticvoidtce_iommu_release(void*iommu_data)+{+structtce_container*container=iommu_data;++WARN_ON(container->tbl&&!container->tbl->it_group);+if(container->tbl&&container->tbl->it_group)+tce_iommu_detach_group(iommu_data,container->tbl->it_group);++mutex_destroy(&container->lock);++kfree(container);+}++staticlongtce_iommu_ioctl(void*iommu_data,+unsignedintcmd,unsignedlongarg)+{+structtce_container*container=iommu_data;+unsignedlongminsz;++switch(cmd){+caseVFIO_CHECK_EXTENSION:{+return(arg==VFIO_SPAPR_TCE_IOMMU)?1:0;+}+caseVFIO_IOMMU_SPAPR_TCE_GET_INFO:{+structvfio_iommu_spapr_tce_infoinfo;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(structvfio_iommu_spapr_tce_info,+dma64_window_size);++if(copy_from_user(&info,(void__user*)arg,minsz))+return-EFAULT;++if(info.argsz<minsz)+return-EINVAL;++info.dma32_window_start=tbl->it_offset<<IOMMU_PAGE_SHIFT;+info.dma32_window_size=tbl->it_size<<IOMMU_PAGE_SHIFT;+info.dma64_window_start=0;+info.dma64_window_size=0;+info.flags=0;++if(copy_to_user((void__user*)arg,&info,minsz))+return-EFAULT;++return0;+}+caseVFIO_IOMMU_MAP_DMA:{+vfio_iommu_spapr_tce_dma_mappar;+structiommu_table*tbl=container->tbl;+enumdma_data_directiondirection=DMA_NONE;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_map,size);++if(copy_from_user(&par,(void__user*)arg,minsz))+return-EFAULT;++if(par.argsz<minsz)+return-EINVAL;++if((par.flags&VFIO_DMA_MAP_FLAG_READ)&&+(par.flags&VFIO_DMA_MAP_FLAG_WRITE)){+direction=DMA_BIDIRECTIONAL;+}elseif(par.flags&VFIO_DMA_MAP_FLAG_READ){+direction=DMA_TO_DEVICE;+}elseif(par.flags&VFIO_DMA_MAP_FLAG_WRITE){+direction=DMA_FROM_DEVICE;+}++par.size+=par.iova&~IOMMU_PAGE_MASK;+par.size=_ALIGN_UP(par.size,IOMMU_PAGE_SIZE);++returniommu_put_tces(tbl,par.iova>>IOMMU_PAGE_SHIFT,+par.vaddr&IOMMU_PAGE_MASK,direction,+par.size>>IOMMU_PAGE_SHIFT);+}+caseVFIO_IOMMU_UNMAP_DMA:{+vfio_iommu_spapr_tce_dma_unmappar;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_unmap,size);++if(copy_from_user(&par,(void__user*)arg,minsz))+return-EFAULT;++if(par.argsz<minsz)+return-EINVAL;++par.size+=par.iova&~IOMMU_PAGE_MASK;+par.size=_ALIGN_UP(par.size,IOMMU_PAGE_SIZE);++returniommu_put_tces(tbl,par.iova>>IOMMU_PAGE_SHIFT,+0,DMA_NONE,par.size>>IOMMU_PAGE_SHIFT);+}+default:+printk(KERN_WARNING"tce_vfio: unexpected cmd %x\n",cmd);+}++return-ENOTTY;+}++staticinttce_iommu_attach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+pr_debug("tce_vfio: Attaching group #%u to iommu %p\n",+iommu_group_id(iommu_group),iommu_group);+if(container->tbl){+printk(KERN_WARNING"tce_vfio: Only one group per IOMMU container is allowed, existing id=%d, attaching id=%d\n",+iommu_group_id(container->tbl->it_group),+iommu_group_id(iommu_group));+mutex_unlock(&container->lock);+return-EBUSY;+}++container->tbl=tbl;+mutex_unlock(&container->lock);++return0;+}++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+if(tbl!=container->tbl){+printk(KERN_WARNING"tce_vfio: detaching group #%u, expected group is #%u\n",+iommu_group_id(iommu_group),+iommu_group_id(tbl->it_group));+}else{++pr_debug("tce_vfio: detaching group #%u from iommu %p\n",+iommu_group_id(iommu_group),iommu_group);++iommu_put_tces(tbl,tbl->it_offset,0,DMA_NONE,tbl->it_size);+container->tbl=NULL;+}+mutex_unlock(&container->lock);+}++conststructvfio_iommu_driver_opstce_iommu_driver_ops={+.name="iommu-vfio-powerpc",+.owner=THIS_MODULE,+.open=tce_iommu_open,+.release=tce_iommu_release,+.ioctl=tce_iommu_ioctl,+.attach_group=tce_iommu_attach_group,+.detach_group=tce_iommu_detach_group,+};++staticint__inittce_iommu_init(void)+{+returnvfio_register_iommu_driver(&tce_iommu_driver_ops);+}++staticvoid__exittce_iommu_cleanup(void)+{+vfio_unregister_iommu_driver(&tce_iommu_driver_ops);+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);++MODULE_VERSION(DRIVER_VERSION);+MODULE_LICENSE("GPL v2");+MODULE_AUTHOR(DRIVER_AUTHOR);+MODULE_DESCRIPTION(DRIVER_DESC);+
@@ -430,16 +431,35 @@ struct vfio_iommu_type1_dma_map {/***VFIO_IOMMU_UNMAP_DMA-_IOW(VFIO_TYPE,VFIO_BASE+14,structvfio_dma_unmap)**UnmapIOvirtualaddressesusingtheprovidedstructvfio_dma_unmap.*Callersetsargsz.*/structvfio_iommu_type1_dma_unmap{__u32argsz;__u32flags;__u64iova;/* IO virtual address */__u64size;/* Size of mapping (bytes) */};#define VFIO_IOMMU_UNMAP_DMA _IO(VFIO_TYPE, VFIO_BASE + 14)+/* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */++structvfio_iommu_spapr_tce_info{+__u32argsz;+__u32flags;+__u32dma32_window_start;+__u32dma32_window_size;+__u64dma64_window_start;+__u64dma64_window_size;+};++#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)++/* Reuse type1 map/unmap structs as they are the same at the moment */+typedefstructvfio_iommu_type1_dma_mapvfio_iommu_spapr_tce_dma_map;+typedefstructvfio_iommu_type1_dma_unmapvfio_iommu_spapr_tce_dma_unmap;++/* ***************************************************************** */+#endif /* VFIO_H */
From: Alex Williamson <hidden> Date: 2012-11-20 18:20:00
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted hunk
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling.
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan, only POWERNV
platform is supported at the moment.
Also the patch implements an VFIO-IOMMU driver which
manages DMA mapping/unmapping requests coming from
the client (now QEMU). It also returns a DMA window
information to let the guest initialize the device tree
for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform
supporting TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 ++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
8 files changed, 563 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
@@ -64,30 +64,33 @@ struct iommu_pool {}____cacheline_aligned_in_smp;structiommu_table{unsignedlongit_busno;/* Bus number this table belongs to */unsignedlongit_size;/* Size of iommu table in entries */unsignedlongit_offset;/* Offset into global table */unsignedlongit_base;/* mapped address of tce table */unsignedlongit_index;/* which iommu table this is */unsignedlongit_type;/* type: PCI or Virtual Bus */unsignedlongit_blocksize;/* Entries in each block (cacheline) */unsignedlongpoolsize;unsignedlongnr_pools;structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;staticinlinevoidset_iommu_table_base(structdevice*dev,void*base){dev->archdata.dma_data.iommu_table_base=base;}staticinlinevoid*get_iommu_table_base(structdevice*dev){returndev->archdata.dma_data.iommu_table_base;}/* Frees table for an individual device node */
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry, uint64_t tce,
+ enum dma_data_direction direction, unsigned long pages)
+{
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce += IOMMU_PAGE_SIZE) {
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret) {
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
There's already a notifier in the iommu code if you were to register an
iommu_ops with the add/remove_device entries. That would allow you to
remove the notifier block and notifier function below and the second
loop below. Are you avoiding that to avoid the rest of iommu_ops?
Also, shouldn't this notifier only be registered after the first loop
below? Otherwise ADD_DEVICE could race with setting up groups, which we
assume are present in the add_device() above.
@@ -430,16 +431,35 @@ struct vfio_iommu_type1_dma_map {/***VFIO_IOMMU_UNMAP_DMA-_IOW(VFIO_TYPE,VFIO_BASE+14,structvfio_dma_unmap)**UnmapIOvirtualaddressesusingtheprovidedstructvfio_dma_unmap.*Callersetsargsz.*/structvfio_iommu_type1_dma_unmap{__u32argsz;__u32flags;__u64iova;/* IO virtual address */__u64size;/* Size of mapping (bytes) */};#define VFIO_IOMMU_UNMAP_DMA _IO(VFIO_TYPE, VFIO_BASE + 14)+/* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */++structvfio_iommu_spapr_tce_info{+__u32argsz;+__u32flags;+__u32dma32_window_start;+__u32dma32_window_size;+__u64dma64_window_start;+__u64dma64_window_size;+};++#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)++/* Reuse type1 map/unmap structs as they are the same at the moment */+typedefstructvfio_iommu_type1_dma_mapvfio_iommu_spapr_tce_dma_map;+typedefstructvfio_iommu_type1_dma_unmapvfio_iommu_spapr_tce_dma_unmap;++/* ***************************************************************** */+#endif /* VFIO_H */
I'm glad you were able to reuse these, after this gets merged we can
rename the structure to something more common and typedef for both type1
and spapr_tce so we don't forget it's shared. Thanks,
Alex
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
Yes, we do not want to loose pages if the guest forgot to unmap them.
There's already a notifier in the iommu code if you were to register an
iommu_ops with the add/remove_device entries. That would allow you to
remove the notifier block and notifier function below and the second loop
below. Are you avoiding that to avoid the rest of iommu_ops?
Yes. I need to implement either a small part of iommu_ops (especially the
part which I think should not be there at all) or notifier, cannot how how
the first is simpler.
[Sethi Varun-B16395] Could be one reason, also they are associating the iommu group with the tce table entry and not the device.
Also true.
I would actually allocate IOMMU groups right after we discovered the new
one but this is done during PCI scan which works before
subsys_initcall(iommu_init) is called so I added this first loop.
quoted
Also, shouldn't this notifier only be registered after the first loop
below? Otherwise ADD_DEVICE could race with setting up groups, which we
assume are present in the add_device() above.
[Sethi Varun-B16395] Isn't this similar to how how the notifier is registered in iommu_bus_init? First a notifier is registered and then we check for devices that have already been probed.
Yep. Not very familiar with this stuff but if it is done one way and it is
already upstream, I cannot see why I should go another way :)
--
Alexey
From: Alex Williamson <hidden> Date: 2012-11-26 15:19:02
On Fri, 2012-11-23 at 13:02 +1100, Alexey Kardashevskiy wrote:
On 22/11/12 22:56, Sethi Varun-B16395 wrote:
quoted
quoted
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
Yes, we do not want to loose pages if the guest forgot to unmap them.
Hmm, does that mean we're not actively clearing tce entries or somehow
disabling the iommu window when the iommu is released through vfio?
There's already a notifier in the iommu code if you were to register an
iommu_ops with the add/remove_device entries. That would allow you to
remove the notifier block and notifier function below and the second loop
below. Are you avoiding that to avoid the rest of iommu_ops?
Yes. I need to implement either a small part of iommu_ops (especially the
part which I think should not be there at all) or notifier, cannot how how
the first is simpler.
quoted
[Sethi Varun-B16395] Could be one reason, also they are associating the iommu group with the tce table entry and not the device.
Also true.
I would actually allocate IOMMU groups right after we discovered the new
one but this is done during PCI scan which works before
subsys_initcall(iommu_init) is called so I added this first loop.
quoted
quoted
Also, shouldn't this notifier only be registered after the first loop
below? Otherwise ADD_DEVICE could race with setting up groups, which we
assume are present in the add_device() above.
[Sethi Varun-B16395] Isn't this similar to how how the notifier is registered in iommu_bus_init? First a notifier is registered and then we check for devices that have already been probed.
Yep. Not very familiar with this stuff but if it is done one way and it is
already upstream, I cannot see why I should go another way :)
The existing notifier callback and loop should be able to operate in
parallel... of course they don't because we're not actively adding new
devices at the point where it's setup. IIRC, the notifier callback
blindly uses something that's not setup at the point it's registered.
That's a bit sloppy. Maybe I'm mis-remembering, I'll verify in your new
version. Thanks,
Alex
From: Alex Williamson <hidden> Date: 2012-11-26 18:04:48
On Mon, 2012-11-26 at 08:18 -0700, Alex Williamson wrote:
On Fri, 2012-11-23 at 13:02 +1100, Alexey Kardashevskiy wrote:
quoted
On 22/11/12 22:56, Sethi Varun-B16395 wrote:
quoted
quoted
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
Yes, we do not want to loose pages if the guest forgot to unmap them.
Hmm, does that mean we're not actively clearing tce entries or somehow
disabling the iommu window when the iommu is released through vfio?
Ok, I see tces are put on shutdown via tce_iommu_detach_group, so you're
more concerned about the guest simply mapping over top of it's own
mappings. Is that common? Is it common enough for every multi-page
mapping to assume it will happen? I know this is a performance
sensitive path for you and it seems like a map-only w/ fallback to
unmap, remap would be better in the general case.
On x86 we do exactly that, but we do the unmap, remap from userspace
when we get an EBUSY. Thanks,
Alex
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2012-11-27 00:22:16
On Mon, 2012-11-26 at 11:04 -0700, Alex Williamson wrote:
Ok, I see tces are put on shutdown via tce_iommu_detach_group, so you're
more concerned about the guest simply mapping over top of it's own
mappings. Is that common? Is it common enough for every multi-page
mapping to assume it will happen? I know this is a performance
sensitive path for you and it seems like a map-only w/ fallback to
unmap, remap would be better in the general case.
On x86 we do exactly that, but we do the unmap, remap from userspace
when we get an EBUSY. Thanks,
Right, Linux as guest at least will never map "over" an existing
mapping. It will always unmap first. IE. The only transition we do on
H_PUT_TCE are 0 -> valid and valid -> 0.
So it would be fine to simplify the code and keep the "map over map" as
a slow fallback. I can't tell for other operating systems but we don't
care about those at this point :-)
Cheers,
Ben.
On Mon, 2012-11-26 at 08:18 -0700, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 13:02 +1100, Alexey Kardashevskiy wrote:
quoted
On 22/11/12 22:56, Sethi Varun-B16395 wrote:
quoted
quoted
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
Yes, we do not want to loose pages if the guest forgot to unmap them.
Hmm, does that mean we're not actively clearing tce entries or somehow
disabling the iommu window when the iommu is released through vfio?
Ok, I see tces are put on shutdown via tce_iommu_detach_group, so you're
more concerned about the guest simply mapping over top of it's own
mappings. Is that common? Is it common enough for every multi-page
mapping to assume it will happen? I know this is a performance
sensitive path for you and it seems like a map-only w/ fallback to
unmap, remap would be better in the general case.
I do not get it. Where exactly does the performance suffer?
iommu_put_tces() with non zero "tce" (i.e. "map") has to check if the entry
is not used, at least to return EBUSY when it is, and this check is
performed. If it is zero, there is no overhead at all. And it is going to
be the 99.(9)% case as the guest (un)maps one page per call.
Generally speaking we want to move "put tce" completely to the kernel for
the (much) better performance and vfio won't be dealing with it all.
We already agreed that SPAPR TCE driver uses x86 (aka type1) API but I do
not see why the powerpc implementation should look x86 alike as it still
operates with powerpc machine dependent callbacks so the reader has to have
some powerpc knowledge.
On x86 we do exactly that, but we do the unmap, remap from userspace
when we get an EBUSY. Thanks,
From: Alex Williamson <hidden> Date: 2012-11-27 04:23:25
On Tue, 2012-11-27 at 14:28 +1100, Alexey Kardashevskiy wrote:
On 27/11/12 05:04, Alex Williamson wrote:
quoted
On Mon, 2012-11-26 at 08:18 -0700, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 13:02 +1100, Alexey Kardashevskiy wrote:
quoted
On 22/11/12 22:56, Sethi Varun-B16395 wrote:
quoted
quoted
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
Yes, we do not want to loose pages if the guest forgot to unmap them.
Hmm, does that mean we're not actively clearing tce entries or somehow
disabling the iommu window when the iommu is released through vfio?
Ok, I see tces are put on shutdown via tce_iommu_detach_group, so you're
more concerned about the guest simply mapping over top of it's own
mappings. Is that common? Is it common enough for every multi-page
mapping to assume it will happen? I know this is a performance
sensitive path for you and it seems like a map-only w/ fallback to
unmap, remap would be better in the general case.
I do not get it. Where exactly does the performance suffer?
iommu_put_tces() with non zero "tce" (i.e. "map") has to check if the entry
is not used, at least to return EBUSY when it is, and this check is
performed. If it is zero, there is no overhead at all. And it is going to
be the 99.(9)% case as the guest (un)maps one page per call.
I was mostly concerned about the kmalloc in your mapping path. If you
had a map-only path it could scan the whole range to verify it's clear
and return EBUSY w/o allocating a buffer. A second pass could do the
actual mappings. Maybe it's not much of a win if you expect 99% of the
mappings to be single pages but since you effectively have a pv iommu
interface I wouldn't be surprised if they get batched in the guest.
Generally speaking we want to move "put tce" completely to the kernel for
the (much) better performance and vfio won't be dealing with it all.
Right, but let's not use that as an excuse to be lazy and not ask if we
can do better here.
We already agreed that SPAPR TCE driver uses x86 (aka type1) API but I do
not see why the powerpc implementation should look x86 alike as it still
operates with powerpc machine dependent callbacks so the reader has to have
some powerpc knowledge.
I'm only using x86 as an example because it's the only one we have. I
don't think anything we're talking about here is x86-ish or powerpc-ish.
There's a kmalloc in a performance path and I'm asking if we can get rid
of it. I'm also nervous that we're silently doing fixups on user
parameters to adjust mapping sizes and clear overlaps without any
warning to the user. Thanks,
Alex
From: Alex Williamson <hidden> Date: 2012-11-26 15:08:56
On Thu, 2012-11-22 at 11:56 +0000, Sethi Varun-B16395 wrote:
quoted
-----Original Message-----
From: linux-kernel-owner@vger.kernel.org [mailto:linux-kernel-
owner@vger.kernel.org] On Behalf Of Alex Williamson
Sent: Tuesday, November 20, 2012 11:50 PM
To: Alexey Kardashevskiy
Cc: Benjamin Herrenschmidt; Paul Mackerras; linuxppc-
dev@lists.ozlabs.org; linux-kernel@vger.kernel.org; kvm@vger.kernel.org;
David Gibson
Subject: Re: [PATCH] vfio powerpc: enabled and supported on powernv
platform
On Tue, 2012-11-20 at 11:48 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as a PCI driver, BAR
access (via read/write on a file descriptor or direct mapping when
possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization and
handling.
This patch initializes IOMMU groups based on the IOMMU configuration
discovered during the PCI scan, only POWERNV platform is supported at
the moment.
Also the patch implements an VFIO-IOMMU driver which manages DMA
mapping/unmapping requests coming from the client (now QEMU). It also
returns a DMA window information to let the guest initialize the
device tree for a guest OS properly. Although this driver has been
tested only on POWERNV, it should work on any platform supporting TCE
tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config option.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 +
arch/powerpc/kernel/iommu.c | 140 +++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247
____cacheline_aligned_in_smp;
struct iommu_table {
unsigned long it_busno; /* Bus number this table belongs to */
unsigned long it_size; /* Size of iommu table in entries */
unsigned long it_offset; /* Offset into global table */
unsigned long it_base; /* mapped address of tce table */
unsigned long it_index; /* which iommu table this is */
unsigned long it_type; /* type: PCI or Virtual Bus */
unsigned long it_blocksize; /* Entries in each block (cacheline)
*/
quoted
unsigned long poolsize;
unsigned long nr_pools;
struct iommu_pool large_pool;
struct iommu_pool pools[IOMMU_NR_POOLS];
unsigned long *it_map; /* A simple allocation bitmap for now
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
Missing return ret? Otherwise we've got some bogus uses of page below
and we're setting ret for no reason here.
quoted
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx
ioba=%lx kva=%lx ret=%d\n",
quoted
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl) {
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
uint64_t tce,
quoted
+ enum dma_data_direction direction, unsigned long pages) {
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce +=
IOMMU_PAGE_SIZE) {
quoted
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret)
{
quoted
+ tce_flush(tbl);
Avoiding tce_flush() is the reason for all this extra overhead, right?
I wonder if it'd be cleaner separating map vs unmap, where the map case
can avoid the oldpages array... but that means inserting new mappings on
top of old ones wouldn't put the pages.
There's already a notifier in the iommu code if you were to register an
iommu_ops with the add/remove_device entries. That would allow you to
remove the notifier block and notifier function below and the second loop
below. Are you avoiding that to avoid the rest of iommu_ops?
[Sethi Varun-B16395] Could be one reason, also they are associating
the iommu group with the tce table entry and not the device.
That's fine, the tce is the level they claim to have isolation.
quoted
Also, shouldn't this notifier only be registered after the first loop
below? Otherwise ADD_DEVICE could race with setting up groups, which we
assume are present in the add_device() above.
[Sethi Varun-B16395] Isn't this similar to how how the notifier is
registered in iommu_bus_init? First a notifier is registered and then
we check for devices that have already been probed.
It's not quite the same because the existing notifier callbacks to add
devices also creates groups as necessary. My point here was that we
register a notifier that assumes a group prior to setting up the groups.
In the existing code the order doesn't matter so much because the system
isn't susceptible to hot device adds at that point. That's likely the
case here too, but registering a notifier before setting up the data the
callback references seems unnecessary. Thanks,
Alex
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 ++
arch/powerpc/kernel/iommu.c | 141 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 290 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,143 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticstructpage*free_tce(structiommu_table*tbl,unsignedlongentry)+{+structpage*page;+unsignedlongoldtce;++oldtce=ppc_md.tce_get(tbl,entry);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+returnNULL;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(page&&(oldtce&TCE_PCI_WRITE))+SetPageDirty(page);+ppc_md.tce_free(tbl,entry,1);++returnpage;+}++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret)+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++return0;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,uint64_ttce,+enumdma_data_directiondirection,unsignedlongpages)+{+inti,ret=0,pages_to_put=0;+structpage*page;+structiommu_pool*pool=get_pool(tbl,entry);+structpage**oldpages;+constintoldpagesnum=PAGE_SIZE/sizeof(*oldpages);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);++/* Handle a single page request without allocation+ofpages-to-releasearray*/+if(pages==1){+spin_lock(&(pool->lock));+page=free_tce(tbl,entry);++if(direction!=DMA_NONE)+ret=put_tce(tbl,entry,tce,direction);++tce_flush(tbl);++if(page)+put_page(page);++spin_unlock(&(pool->lock));+returnret;+}++/* Releasing multiple pages */+/* Allocate an array for pages to be released after TCE table+isupdated*/+oldpages=kmalloc(PAGE_SIZE,GFP_KERNEL);+if(!oldpages)+return-ENOMEM;++spin_lock(&(pool->lock));++for(i=0;(i<pages)&&!ret;++i,++entry,tce+=IOMMU_PAGE_SIZE){+page=free_tce(tbl,entry);+if(page){+oldpages[pages_to_put]=page;+++pages_to_put;+}++if(direction!=DMA_NONE)+ret=put_tce(tbl,entry,tce,direction);++/* Release old pages if we reached the end of oldpages[] or+itisthelastpageorweareabouttoexittheloop*/+if((pages_to_put==oldpagesnum)||(i==pages-1)||ret){+tce_flush(tbl);++/* Release pages after removing them from TCE table */+while(pages_to_put){+--pages_to_put;+put_page(oldpages[pages_to_put]);+}+}+}++spin_unlock(&(pool->lock));+kfree(oldpages);++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-11-27 04:41:21
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 6 ++
arch/powerpc/kernel/iommu.c | 141 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 290 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
We're locking memory here on behalf of the user, but I don't see where
rlimit gets checked to verify the user has privileges to lock the pages.
I know you're locking a much smaller set of memory than x86 does, but
are we just foregoing that added security?
+ if (ret < 1) {
+ printk(KERN_ERR "tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
+ return ret;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry, uint64_t tce,
+ enum dma_data_direction direction, unsigned long pages)
+{
+ int i, ret = 0, pages_to_put = 0;
+ struct page *page;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ struct page **oldpages;
+ const int oldpagesnum = PAGE_SIZE/sizeof(*oldpages);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+
+ /* Handle a single page request without allocation
+ of pages-to-release array */
nit, this comment style doesn't seem to match anything existing in this
file. I'd also be tempted to use pr_err/warn in this file, but I'll
leave that for the maintainers. Thanks,
Alex
quoted hunk
+ if (pages == 1) {
+ spin_lock(&(pool->lock));
+ page = free_tce(tbl, entry);
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ tce_flush(tbl);
+
+ if (page)
+ put_page(page);
+
+ spin_unlock(&(pool->lock));
+ return ret;
+ }
+
+ /* Releasing multiple pages */
+ /* Allocate an array for pages to be released after TCE table
+ is updated */
+ oldpages = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!oldpages)
+ return -ENOMEM;
+
+ spin_lock(&(pool->lock));
+
+ for (i = 0; (i < pages) && !ret; ++i, ++entry, tce += IOMMU_PAGE_SIZE) {
+ page = free_tce(tbl, entry);
+ if (page) {
+ oldpages[pages_to_put] = page;
+ ++pages_to_put;
+ }
+
+ if (direction != DMA_NONE)
+ ret = put_tce(tbl, entry, tce, direction);
+
+ /* Release old pages if we reached the end of oldpages[] or
+ it is the last page or we are about to exit the loop */
+ if ((pages_to_put == oldpagesnum) || (i == pages - 1) || ret) {
+ tce_flush(tbl);
+
+ /* Release pages after removing them from TCE table */
+ while (pages_to_put) {
+ --pages_to_put;
+ put_page(oldpages[pages_to_put]);
+ }
+ }
+ }
+
+ spin_unlock(&(pool->lock));
+ kfree(oldpages);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 +++
arch/powerpc/kernel/iommu.c | 147 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 299 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,149 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofpages+*whichitcalledput_page()on.+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,pages_put=0;+unsignedlongoldtce;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++++pages_put;+put_page(page);+}++returnpages_put;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofreleasedpages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);++ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret)+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++return0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactuallylockedpages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+unsignedlongoldtce=ppc_md.tce_get(tbl,entry+i);+if(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)){+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&!ret;++i,tce+=IOMMU_PAGE_SIZE)+ret=put_tce(tbl,entry+i,tce,direction);++/* If failed, release locked pages, otherwise return the number of pages */+if(ret)+clear_tces_nolock(tbl,entry,i);+else+ret=pages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-11-28 21:31:00
On Wed, 2012-11-28 at 18:18 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 +++
arch/powerpc/kernel/iommu.c | 147 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 299 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,149 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofpages+*whichitcalledput_page()on.+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,pages_put=0;+unsignedlongoldtce;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++++pages_put;+put_page(page);+}++returnpages_put;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofreleasedpages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);++ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret)+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++return0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactuallylockedpages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+unsignedlongoldtce=ppc_md.tce_get(tbl,entry+i);+if(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)){+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&!ret;++i,tce+=IOMMU_PAGE_SIZE)+ret=put_tce(tbl,entry+i,tce,direction);++/* If failed, release locked pages, otherwise return the number of pages */+if(ret)+clear_tces_nolock(tbl,entry,i);+else+ret=pages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}
Nice, no more kmalloc! I'm still concerned about the IOMMU page size
mismatch here. If nothing else, the comment is misleading since we're
locking system pages, but returning tce pages. The user would therefore
need to multiply their locked memory limit by (PAGE_SHIFT -
IOMMU_PAGE_SHIFT). Thanks,
Alex
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 159 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 311 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,161 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofpages+*whichitcalledput_page()on.+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++if(!(oldtce&~PAGE_MASK))+++retpages;++put_page(page);+}++returnretpages;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofreleasedpages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);++ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret)+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++return0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactually+*lockedsystempages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0,retpages=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+unsignedlongoldtce=ppc_md.tce_get(tbl,entry+i);+if(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)){+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&!ret;++i,tce+=IOMMU_PAGE_SIZE){+ret=put_tce(tbl,entry+i,tce,direction);+/*+*AsIOMMUpagesizeisalways4K,thesystempagesize+*canbe64KandthereisnospecialtrackingforIOMMUpages,+*weonlydorlimitcheck/updatefortheveryfirst+*4KIOMMUpagewithin64Ksystempage.+*/+if(!(tce&~PAGE_MASK))+++retpages;+}++/* If failed, release locked pages, otherwise return the number of pages */+if(ret)+clear_tces_nolock(tbl,entry,i);+else+ret=retpages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-11-29 04:27:38
On Thu, 2012-11-29 at 14:53 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 159 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 311 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,161 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/+staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofpages+*whichitcalledput_page()on.+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++if(!(oldtce&~PAGE_MASK))+++retpages;
I'm confused, it looks like you're trying to only increment the counter
for tce pages aligned at the start of a page, but don't we need to mask
out the read/write and valid bits? Trickiness like this demands a
comment.
+
+ put_page(page);
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of released pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
+
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret < 1) {
+ printk(KERN_ERR "tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret)
+ ret = -EFAULT;
+ return ret;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ unsigned long oldtce = ppc_md.tce_get(tbl, entry + i);
+ if (oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)) {
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && !ret; ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ /*
+ * As IOMMU page size is always 4K, the system page size
+ * can be 64K and there is no special tracking for IOMMU pages,
+ * we only do rlimit check/update for the very first
+ * 4K IOMMUpage within 64K system page.
+ */
+ if (!(tce & ~PAGE_MASK))
+ ++retpages;
Ah, here's the comment I was looking for, though I'm still not sure
about the read/write bits.
Isn't there an exploit here that a user can lock pages beyond their
limits if they just skip mapping the first 4k of each page? Thanks,
Alex
quoted hunk
+ }
+
+ /* If failed, release locked pages, otherwise return the number of pages */
+ if (ret)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,entry);+__clear_bit(entry,tbl->it_map);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++put_page(page);++/* That was the last IOMMU page within the system page */+if((oldweight==1)&&!syspage_weight(tbl->it_map,entry))+++retpages;+}++returnretpages;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumber+/ofreleasedsystempages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset,oldweight;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret||(ret>1))+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++/* Calculate if new system page has been locked */+oldweight=syspage_weight(tbl->it_map,entry);+__set_bit(entry,tbl->it_map);++return(oldweight==0)?1:0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactually+*lockedsystempages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0,retpages=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+unsignedlongoldtce=ppc_md.tce_get(tbl,entry+i);+if((oldtce&(TCE_PCI_WRITE|TCE_PCI_READ))||+test_bit(entry+i,tbl->it_map)){+WARN_ON(test_bit(entry+i,tbl->it_map));+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&(ret>=0);++i,tce+=IOMMU_PAGE_SIZE){+ret=put_tce(tbl,entry+i,tce,direction);+if(ret==1)+++retpages;+}++/*+*Iffailed,releaselockedpages,otherwisereturnthenumber+*oflockedsystempages+*/+if(ret<0)+clear_tces_nolock(tbl,entry,i);+else+ret=retpages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-11-30 16:48:23
On Fri, 2012-11-30 at 17:14 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;
Ok, entry is the iova page number. So presumably it's relative to the
start of dma32_window_start since you're unlikely to have a bitmap that
covers all of memory. I hadn't realized that previously. Doesn't that
mean that it's actually impossible to create an ioctl based interface to
the dma64_window since we're not going to know which window is the
target? I know you're not planning on one, but it seems limiting. We
at least need some documentation here, but I'm wondering if iova
shouldn't be zero based so we can determine which window it hits. Also,
now that I look at it, I can't find any range checking on the iova.
Thanks,
Alex
quoted hunk
+
+ return ret;
+}
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of system pages
+ * which it called put_page() on
+ */
+static long clear_tces_nolock(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int i, retpages = 0;
+ unsigned long oldtce, oldweight;
+ struct page *page;
+
+ for (i = 0; i < pages; ++i) {
+ oldtce = ppc_md.tce_get(tbl, entry + i);
+ ppc_md.tce_free(tbl, entry + i, 1);
+
+ oldweight = syspage_weight(tbl->it_map, entry);
+ __clear_bit(entry, tbl->it_map);
+
+ if (!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)))
+ continue;
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ WARN_ON(!page);
+ if (!page)
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && !syspage_weight(tbl->it_map, entry))
+ ++retpages;
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ / of released system pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret < 1) {
+ printk(KERN_ERR "tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ if (!ret || (ret > 1))
+ ret = -EFAULT;
+ return ret;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight(tbl->it_map, entry);
+ __set_bit(entry, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ unsigned long oldtce = ppc_md.tce_get(tbl, entry + i);
+ if ((oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)) ||
+ test_bit(entry + i, tbl->it_map)) {
+ WARN_ON(test_bit(entry + i, tbl->it_map));
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
On Fri, 2012-11-30 at 17:14 +1100, Alexey Kardashevskiy wrote:
quoted
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;
Ok, entry is the iova page number. So presumably it's relative to the
start of dma32_window_start since you're unlikely to have a bitmap that
covers all of memory. I hadn't realized that previously.
No, it is zero based. The DMA window is a filter but not offset. But you
are right, the it_map does not cover the whole global table (one per PHB,
roughly), will fix it, thanks for pointing. On my test system IOMMU group
is a whole PHB and DMA window always starts from 0 so tests do not show
everything :)
Doesn't that
mean that it's actually impossible to create an ioctl based interface to
the dma64_window since we're not going to know which window is the
target? I know you're not planning on one, but it seems limiting.
No ,it is not limiting as iova is zero based. Even if it was, there are
flags in map/unmap ioctls which we could use, no?
We
at least need some documentation here, but I'm wondering if iova
shouldn't be zero based so we can determine which window it hits. Also,
now that I look at it, I can't find any range checking on the iova.
True... Have not hit this problem yet :) Good point, will fix, thanks.
--
Alexey
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && !ret; ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ /*
+ * As IOMMU page size is always 4K, the system page size
+ * can be 64K and there is no special tracking for IOMMU pages,
+ * we only do rlimit check/update for the very first
+ * 4K IOMMUpage within 64K system page.
+ */
+ if (!(tce & ~PAGE_MASK))
+ ++retpages;
Ah, here's the comment I was looking for, though I'm still not sure
about the read/write bits.
Isn't there an exploit here that a user can lock pages beyond their
limits if they just skip mapping the first 4k of each page? Thanks,
Heh. True. Posted another patch with 4K pages per system page usage tracking.
--
Alexey
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
@@ -0,0 +1,247 @@+/*+*VFIO:IOMMUDMAmappingsupportforTCEonPOWER+*+*Copyright(C)2012IBMCorp.Allrightsreserved.+*Author:AlexeyKardashevskiy<aik@ozlabs.ru>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*Derivedfromoriginalvfio_iommu_type1.c:+*Copyright(C)2012RedHat,Inc.Allrightsreserved.+*Author:AlexWilliamson<alex.williamson@redhat.com>+*/++#include<linux/module.h>+#include<linux/pci.h>+#include<linux/slab.h>+#include<linux/uaccess.h>+#include<linux/err.h>+#include<linux/vfio.h>+#include<asm/iommu.h>++#define DRIVER_VERSION "0.1"+#define DRIVER_AUTHOR "aik@ozlabs.ru"+#define DRIVER_DESC "VFIO IOMMU SPAPR TCE"++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group);++/*+*VFIOIOMMUfdforSPAPR_TCEIOMMUimplementation+*/++/*+*Thecontainerdescriptorsupportsonlyasinglegrouppercontainer.+*RequiredbytheAPIasthecontainerisnotsuppliedwiththeIOMMUgroup+*atthemomentofinitialization.+*/+structtce_container{+structmutexlock;+structiommu_table*tbl;+};++staticvoid*tce_iommu_open(unsignedlongarg)+{+structtce_container*container;++if(arg!=VFIO_SPAPR_TCE_IOMMU){+printk(KERN_ERR"tce_vfio: Wrong IOMMU type\n");+returnERR_PTR(-EINVAL);+}++container=kzalloc(sizeof(*container),GFP_KERNEL);+if(!container)+returnERR_PTR(-ENOMEM);++mutex_init(&container->lock);++returncontainer;+}++staticvoidtce_iommu_release(void*iommu_data)+{+structtce_container*container=iommu_data;++WARN_ON(container->tbl&&!container->tbl->it_group);+if(container->tbl&&container->tbl->it_group)+tce_iommu_detach_group(iommu_data,container->tbl->it_group);++mutex_destroy(&container->lock);++kfree(container);+}++staticlongtce_iommu_ioctl(void*iommu_data,+unsignedintcmd,unsignedlongarg)+{+structtce_container*container=iommu_data;+unsignedlongminsz;++switch(cmd){+caseVFIO_CHECK_EXTENSION:{+return(arg==VFIO_SPAPR_TCE_IOMMU)?1:0;+}+caseVFIO_IOMMU_SPAPR_TCE_GET_INFO:{+structvfio_iommu_spapr_tce_infoinfo;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(structvfio_iommu_spapr_tce_info,+dma64_window_size);++if(copy_from_user(&info,(void__user*)arg,minsz))+return-EFAULT;++if(info.argsz<minsz)+return-EINVAL;++info.dma32_window_start=tbl->it_offset<<IOMMU_PAGE_SHIFT;+info.dma32_window_size=tbl->it_size<<IOMMU_PAGE_SHIFT;+info.dma64_window_start=0;+info.dma64_window_size=0;+info.flags=0;++if(copy_to_user((void__user*)arg,&info,minsz))+return-EFAULT;++return0;+}+caseVFIO_IOMMU_MAP_DMA:{+vfio_iommu_spapr_tce_dma_mapparam;+structiommu_table*tbl=container->tbl;+enumdma_data_directiondirection=DMA_NONE;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_map,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++if((param.flags&VFIO_DMA_MAP_FLAG_READ)&&+(param.flags&VFIO_DMA_MAP_FLAG_WRITE)){+direction=DMA_BIDIRECTIONAL;+}elseif(param.flags&VFIO_DMA_MAP_FLAG_READ){+direction=DMA_TO_DEVICE;+}elseif(param.flags&VFIO_DMA_MAP_FLAG_WRITE){+direction=DMA_FROM_DEVICE;+}++param.size+=param.iova&~IOMMU_PAGE_MASK;+param.size=_ALIGN_UP(param.size,IOMMU_PAGE_SIZE);++returniommu_put_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+param.vaddr&IOMMU_PAGE_MASK,direction,+param.size>>IOMMU_PAGE_SHIFT);+}+caseVFIO_IOMMU_UNMAP_DMA:{+vfio_iommu_spapr_tce_dma_unmapparam;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_unmap,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++param.size+=param.iova&~IOMMU_PAGE_MASK;+param.size=_ALIGN_UP(param.size,IOMMU_PAGE_SIZE);++returniommu_put_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+0,DMA_NONE,param.size>>IOMMU_PAGE_SHIFT);+}+default:+printk(KERN_WARNING"tce_vfio: unexpected cmd %x\n",cmd);+}++return-ENOTTY;+}++staticinttce_iommu_attach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+pr_debug("tce_vfio: Attaching group #%u to iommu %p\n",+iommu_group_id(iommu_group),iommu_group);+if(container->tbl){+printk(KERN_WARNING"tce_vfio: Only one group per IOMMU container is allowed, existing id=%d, attaching id=%d\n",+iommu_group_id(container->tbl->it_group),+iommu_group_id(iommu_group));+mutex_unlock(&container->lock);+return-EBUSY;+}++container->tbl=tbl;+mutex_unlock(&container->lock);++return0;+}++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+if(tbl!=container->tbl){+printk(KERN_WARNING"tce_vfio: detaching group #%u, expected group is #%u\n",+iommu_group_id(iommu_group),+iommu_group_id(tbl->it_group));+}else{++pr_debug("tce_vfio: detaching group #%u from iommu %p\n",+iommu_group_id(iommu_group),iommu_group);++iommu_put_tces(tbl,tbl->it_offset,0,DMA_NONE,tbl->it_size);+container->tbl=NULL;+}+mutex_unlock(&container->lock);+}++conststructvfio_iommu_driver_opstce_iommu_driver_ops={+.name="iommu-vfio-powerpc",+.owner=THIS_MODULE,+.open=tce_iommu_open,+.release=tce_iommu_release,+.ioctl=tce_iommu_ioctl,+.attach_group=tce_iommu_attach_group,+.detach_group=tce_iommu_detach_group,+};++staticint__inittce_iommu_init(void)+{+returnvfio_register_iommu_driver(&tce_iommu_driver_ops);+}++staticvoid__exittce_iommu_cleanup(void)+{+vfio_unregister_iommu_driver(&tce_iommu_driver_ops);+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);++MODULE_VERSION(DRIVER_VERSION);+MODULE_LICENSE("GPL v2");+MODULE_AUTHOR(DRIVER_AUTHOR);+MODULE_DESCRIPTION(DRIVER_DESC);+
@@ -442,4 +443,23 @@ struct vfio_iommu_type1_dma_unmap {#define VFIO_IOMMU_UNMAP_DMA _IO(VFIO_TYPE, VFIO_BASE + 14)+/* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */++structvfio_iommu_spapr_tce_info{+__u32argsz;+__u32flags;+__u32dma32_window_start;+__u32dma32_window_size;+__u64dma64_window_start;+__u64dma64_window_size;+};++#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)++/* Reuse type1 map/unmap structs as they are the same at the moment */+typedefstructvfio_iommu_type1_dma_mapvfio_iommu_spapr_tce_dma_map;+typedefstructvfio_iommu_type1_dma_unmapvfio_iommu_spapr_tce_dma_unmap;++/* ***************************************************************** */+#endif /* VFIO_H */
From: Alex Williamson <hidden> Date: 2012-11-26 18:20:51
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted hunk
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
Would it be too much paranoia to clear all the tce here as you do below
on detach? ie. is there any risk that there's leftover programming?
x86 allocates a new domain on open of the iommu, so we always start out
clean.
Is there anything we can document about this? It should probably list
that size is in bytes. Is there any need to communicate the IOMMU page
size here?
+
+#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)
+
+/* Reuse type1 map/unmap structs as they are the same at the moment */
+typedef struct vfio_iommu_type1_dma_map vfio_iommu_spapr_tce_dma_map;
+typedef struct vfio_iommu_type1_dma_unmap vfio_iommu_spapr_tce_dma_unmap;
+
+/* ***************************************************************** */
+
#endif /* VFIO_H */
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
I got used to guests which do know what they are doing so I am pretty calm :)
but ok, I'll move alignment to the QEMU, it makes sense.
It should probably list that size is in bytes. Is there any need to communicate the IOMMU page
size here?
It is always 4k. I'll put it to comments.
quoted
+
+#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)
+
+/* Reuse type1 map/unmap structs as they are the same at the moment */
+typedef struct vfio_iommu_type1_dma_map vfio_iommu_spapr_tce_dma_map;
+typedef struct vfio_iommu_type1_dma_unmap vfio_iommu_spapr_tce_dma_unmap;
+
+/* ***************************************************************** */
+
#endif /* VFIO_H */
From: Alex Williamson <hidden> Date: 2012-11-27 05:02:32
On Tue, 2012-11-27 at 15:06 +1100, Alexey Kardashevskiy wrote:
On 27/11/12 05:20, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
I got used to guests which do know what they are doing so I am pretty calm :)
but ok, I'll move alignment to the QEMU, it makes sense.
Would it be too much paranoia to clear all the tce here as you do below
on detach?
Guess so. I do unmap on detach() and the guest calls put_tce(0) (i.e.
unmaps) the whole DMA window at the boot time.
But that's just one user of this interface, we can't assume they'll all
be so agreeable. If any tces were enabled here, a malicious user would
have a window to host memory, right? Thanks,
Alex
On Tue, 2012-11-27 at 15:06 +1100, Alexey Kardashevskiy wrote:
quoted
On 27/11/12 05:20, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
I got used to guests which do know what they are doing so I am pretty calm :)
but ok, I'll move alignment to the QEMU, it makes sense.
Would it be too much paranoia to clear all the tce here as you do below
on detach?
Guess so. I do unmap on detach() and the guest calls put_tce(0) (i.e.
unmaps) the whole DMA window at the boot time.
But that's just one user of this interface, we can't assume they'll all
be so agreeable. If any tces were enabled here, a malicious user would
have a window to host memory, right? Thanks,
But I still release pages on detach(), how can this code be not called on
the guest exit (normal or crashed)?
From: Alex Williamson <hidden> Date: 2012-11-27 05:08:08
On Tue, 2012-11-27 at 15:58 +1100, Alexey Kardashevskiy wrote:
On 27/11/12 15:29, Alex Williamson wrote:
quoted
On Tue, 2012-11-27 at 15:06 +1100, Alexey Kardashevskiy wrote:
quoted
On 27/11/12 05:20, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
I got used to guests which do know what they are doing so I am pretty calm :)
but ok, I'll move alignment to the QEMU, it makes sense.
Would it be too much paranoia to clear all the tce here as you do below
on detach?
Guess so. I do unmap on detach() and the guest calls put_tce(0) (i.e.
unmaps) the whole DMA window at the boot time.
But that's just one user of this interface, we can't assume they'll all
be so agreeable. If any tces were enabled here, a malicious user would
have a window to host memory, right? Thanks,
But I still release pages on detach(), how can this code be not called on
the guest exit (normal or crashed)?
What's the initial state? You leave it clean, but who came before you?
Thanks,
Alex
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 332 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 33 ++++
4 files changed, 372 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
@@ -0,0 +1,332 @@+/*+*VFIO:IOMMUDMAmappingsupportforTCEonPOWER+*+*Copyright(C)2012IBMCorp.Allrightsreserved.+*Author:AlexeyKardashevskiy<aik@ozlabs.ru>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*Derivedfromoriginalvfio_iommu_type1.c:+*Copyright(C)2012RedHat,Inc.Allrightsreserved.+*Author:AlexWilliamson<alex.williamson@redhat.com>+*/++#include<linux/module.h>+#include<linux/pci.h>+#include<linux/slab.h>+#include<linux/uaccess.h>+#include<linux/err.h>+#include<linux/vfio.h>+#include<asm/iommu.h>++#define DRIVER_VERSION "0.1"+#define DRIVER_AUTHOR "aik@ozlabs.ru"+#define DRIVER_DESC "VFIO IOMMU SPAPR TCE"++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group);++/*+*VFIOIOMMUfdforSPAPR_TCEIOMMUimplementation+*/++/*+*Thiscodehandlesmappingandunmappingofuserdatabuffers+*intoDMA'blespaceusingtheIOMMU+*/++#define NPAGE_TO_SIZE(npage) ((size_t)(npage) << PAGE_SHIFT)++structvwork{+structmm_struct*mm;+longnpage;+structwork_structwork;+};++/* delayed decrement/increment for locked_vm */+staticvoidlock_acct_bg(structwork_struct*work)+{+structvwork*vwork=container_of(work,structvwork,work);+structmm_struct*mm;++mm=vwork->mm;+down_write(&mm->mmap_sem);+mm->locked_vm+=vwork->npage;+up_write(&mm->mmap_sem);+mmput(mm);+kfree(vwork);+}++staticvoidlock_acct(longnpage)+{+structvwork*vwork;+structmm_struct*mm;++if(!current->mm)+return;/* process exited */++if(down_write_trylock(¤t->mm->mmap_sem)){+current->mm->locked_vm+=npage;+up_write(¤t->mm->mmap_sem);+return;+}++/*+*Couldn'tgetmmap_semlock,somustsetuptoupdate+*mm->locked_vmlater.Iflocked_vmwereatomic,we+*wouldn'tneedthissilliness+*/+vwork=kmalloc(sizeof(structvwork),GFP_KERNEL);+if(!vwork)+return;+mm=get_task_mm(current);+if(!mm){+kfree(vwork);+return;+}+INIT_WORK(&vwork->work,lock_acct_bg);+vwork->mm=mm;+vwork->npage=npage;+schedule_work(&vwork->work);+}++/*+*Thecontainerdescriptorsupportsonlyasinglegrouppercontainer.+*RequiredbytheAPIasthecontainerisnotsuppliedwiththeIOMMUgroup+*atthemomentofinitialization.+*/+structtce_container{+structmutexlock;+structiommu_table*tbl;+};++staticvoid*tce_iommu_open(unsignedlongarg)+{+structtce_container*container;++if(arg!=VFIO_SPAPR_TCE_IOMMU){+pr_err("tce_vfio: Wrong IOMMU type\n");+returnERR_PTR(-EINVAL);+}++container=kzalloc(sizeof(*container),GFP_KERNEL);+if(!container)+returnERR_PTR(-ENOMEM);++mutex_init(&container->lock);++returncontainer;+}++staticvoidtce_iommu_release(void*iommu_data)+{+structtce_container*container=iommu_data;++WARN_ON(container->tbl&&!container->tbl->it_group);+if(container->tbl&&container->tbl->it_group)+tce_iommu_detach_group(iommu_data,container->tbl->it_group);++mutex_destroy(&container->lock);++kfree(container);+}++staticlongtce_iommu_ioctl(void*iommu_data,+unsignedintcmd,unsignedlongarg)+{+structtce_container*container=iommu_data;+unsignedlongminsz;+longret;++switch(cmd){+caseVFIO_CHECK_EXTENSION:{+return(arg==VFIO_SPAPR_TCE_IOMMU)?1:0;+}+caseVFIO_IOMMU_SPAPR_TCE_GET_INFO:{+structvfio_iommu_spapr_tce_infoinfo;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(structvfio_iommu_spapr_tce_info,+dma64_window_size);++if(copy_from_user(&info,(void__user*)arg,minsz))+return-EFAULT;++if(info.argsz<minsz)+return-EINVAL;++info.dma32_window_start=tbl->it_offset<<IOMMU_PAGE_SHIFT;+info.dma32_window_size=tbl->it_size<<IOMMU_PAGE_SHIFT;+info.dma64_window_start=0;+info.dma64_window_size=0;+info.flags=0;++if(copy_to_user((void__user*)arg,&info,minsz))+return-EFAULT;++return0;+}+caseVFIO_IOMMU_MAP_DMA:{+vfio_iommu_spapr_tce_dma_mapparam;+structiommu_table*tbl=container->tbl;+enumdma_data_directiondirection;+unsignedlonglocked,lock_limit;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_map,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++if((param.flags&VFIO_DMA_MAP_FLAG_READ)&&+(param.flags&VFIO_DMA_MAP_FLAG_WRITE))+direction=DMA_BIDIRECTIONAL;+elseif(param.flags&VFIO_DMA_MAP_FLAG_READ)+direction=DMA_TO_DEVICE;+elseif(param.flags&VFIO_DMA_MAP_FLAG_WRITE)+direction=DMA_FROM_DEVICE;+else+return-EINVAL;++if((param.size&~IOMMU_PAGE_MASK)||+(param.iova&~IOMMU_PAGE_MASK)||+(param.vaddr&~IOMMU_PAGE_MASK))+return-EINVAL;++/* Account for locked pages */+locked=current->mm->locked_vm++(param.size>>IOMMU_PAGE_SHIFT);+lock_limit=rlimit(RLIMIT_MEMLOCK)>>PAGE_SHIFT;+if(locked>lock_limit&&!capable(CAP_IPC_LOCK)){+pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",+rlimit(RLIMIT_MEMLOCK));+return-ENOMEM;+}++ret=iommu_put_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+param.vaddr,direction,+param.size>>IOMMU_PAGE_SHIFT);+if(ret>0)+lock_acct(ret);++returnret;+}+caseVFIO_IOMMU_UNMAP_DMA:{+vfio_iommu_spapr_tce_dma_unmapparam;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_unmap,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++if((param.size&~IOMMU_PAGE_MASK)||+(param.iova&~IOMMU_PAGE_MASK))+return-EINVAL;++ret=iommu_clear_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+param.size>>IOMMU_PAGE_SHIFT);+if(ret>0)+lock_acct(-ret);++returnret;+}+default:+pr_warn("tce_vfio: unexpected cmd %x\n",cmd);+}++return-ENOTTY;+}++staticinttce_iommu_attach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+pr_debug("tce_vfio: Attaching group #%u to iommu %p\n",+iommu_group_id(iommu_group),iommu_group);+if(container->tbl){+pr_warn("tce_vfio: Only one group per IOMMU container is allowed, existing id=%d, attaching id=%d\n",+iommu_group_id(container->tbl->it_group),+iommu_group_id(iommu_group));+mutex_unlock(&container->lock);+return-EBUSY;+}++container->tbl=tbl;+iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);+mutex_unlock(&container->lock);++return0;+}++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+if(tbl!=container->tbl){+pr_warn("tce_vfio: detaching group #%u, expected group is #%u\n",+iommu_group_id(iommu_group),+iommu_group_id(tbl->it_group));+}else{++pr_debug("tce_vfio: detaching group #%u from iommu %p\n",+iommu_group_id(iommu_group),iommu_group);++iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);+container->tbl=NULL;+}+mutex_unlock(&container->lock);+}++conststructvfio_iommu_driver_opstce_iommu_driver_ops={+.name="iommu-vfio-powerpc",+.owner=THIS_MODULE,+.open=tce_iommu_open,+.release=tce_iommu_release,+.ioctl=tce_iommu_ioctl,+.attach_group=tce_iommu_attach_group,+.detach_group=tce_iommu_detach_group,+};++staticint__inittce_iommu_init(void)+{+returnvfio_register_iommu_driver(&tce_iommu_driver_ops);+}++staticvoid__exittce_iommu_cleanup(void)+{+vfio_unregister_iommu_driver(&tce_iommu_driver_ops);+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);++MODULE_VERSION(DRIVER_VERSION);+MODULE_LICENSE("GPL v2");+MODULE_AUTHOR(DRIVER_AUTHOR);+MODULE_DESCRIPTION(DRIVER_DESC);+
@@ -442,4 +443,36 @@ struct vfio_iommu_type1_dma_unmap {#define VFIO_IOMMU_UNMAP_DMA _IO(VFIO_TYPE, VFIO_BASE + 14)+/* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */++/*+*TheSPAPRTCEinfostructprovidestheinformationaboutthePCIbus+*addressrangesavailableforDMA,thesevaluesareprogrammedinto+*thehardwaresotheguesthastoknowthatinformation.+*+*Pageswithin32bitwindowshouldbeexplicitelymapped/unmappedviaioctls.+*64bitwindow(notsupportedatthemomentfortheguest)issupposedto+*bemappedcompletelytotheguestmemorysothedevicescapableof64bit+*DMAwillnothavetousemap/unmapioctls.+*+*TheIOMMUpagesizeisalways4K.+*/++structvfio_iommu_spapr_tce_info{+__u32argsz;+__u32flags;/* reserved for future use */+__u32dma32_window_start;/* 32 bit window start (bytes) */+__u32dma32_window_size;/* 32 bit window size (bytes) */+__u64dma64_window_start;/* 64 bit window start (bytes) */+__u64dma64_window_size;/* 64 bit window size (bytes) */+};++#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)++/* Reuse type1 map/unmap structs as they are the same at the moment */+typedefstructvfio_iommu_type1_dma_mapvfio_iommu_spapr_tce_dma_map;+typedefstructvfio_iommu_type1_dma_unmapvfio_iommu_spapr_tce_dma_unmap;++/* ***************************************************************** */+#endif /* VFIO_H */
From: Alex Williamson <hidden> Date: 2012-11-28 21:02:06
On Wed, 2012-11-28 at 18:21 +1100, Alexey Kardashevskiy wrote:
quoted hunk
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 332 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 33 ++++
4 files changed, 372 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
This looks familiar, should we split it out to a common file instead of
duplicating it?
+
+/*
+ * The container descriptor supports only a single group per container.
+ * Required by the API as the container is not supplied with the IOMMU group
+ * at the moment of initialization.
+ */
+struct tce_container {
+ struct mutex lock;
+ struct iommu_table *tbl;
+};
+
+static void *tce_iommu_open(unsigned long arg)
+{
+ struct tce_container *container;
+
+ if (arg != VFIO_SPAPR_TCE_IOMMU) {
+ pr_err("tce_vfio: Wrong IOMMU type\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ container = kzalloc(sizeof(*container), GFP_KERNEL);
+ if (!container)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&container->lock);
+
+ return container;
+}
+
+static void tce_iommu_release(void *iommu_data)
+{
+ struct tce_container *container = iommu_data;
+
+ WARN_ON(container->tbl && !container->tbl->it_group);
+ if (container->tbl && container->tbl->it_group)
+ tce_iommu_detach_group(iommu_data, container->tbl->it_group);
+
+ mutex_destroy(&container->lock);
+
+ kfree(container);
+}
+
+static long tce_iommu_ioctl(void *iommu_data,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tce_container *container = iommu_data;
+ unsigned long minsz;
+ long ret;
+
+ switch (cmd) {
+ case VFIO_CHECK_EXTENSION: {
+ return (arg == VFIO_SPAPR_TCE_IOMMU) ? 1 : 0;
+ }
+ case VFIO_IOMMU_SPAPR_TCE_GET_INFO: {
+ struct vfio_iommu_spapr_tce_info info;
+ struct iommu_table *tbl = container->tbl;
+
+ if (WARN_ON(!tbl))
+ return -ENXIO;
+
+ minsz = offsetofend(struct vfio_iommu_spapr_tce_info,
+ dma64_window_size);
+
+ if (copy_from_user(&info, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (info.argsz < minsz)
+ return -EINVAL;
+
+ info.dma32_window_start = tbl->it_offset << IOMMU_PAGE_SHIFT;
+ info.dma32_window_size = tbl->it_size << IOMMU_PAGE_SHIFT;
+ info.dma64_window_start = 0;
+ info.dma64_window_size = 0;
+ info.flags = 0;
+
+ if (copy_to_user((void __user *)arg, &info, minsz))
+ return -EFAULT;
+
+ return 0;
+ }
+ case VFIO_IOMMU_MAP_DMA: {
+ vfio_iommu_spapr_tce_dma_map param;
+ struct iommu_table *tbl = container->tbl;
+ enum dma_data_direction direction;
+ unsigned long locked, lock_limit;
+
+ if (WARN_ON(!tbl))
+ return -ENXIO;
+
+ minsz = offsetofend(vfio_iommu_spapr_tce_dma_map, size);
+
+ if (copy_from_user(¶m, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (param.argsz < minsz)
+ return -EINVAL;
+
+ if ((param.flags & VFIO_DMA_MAP_FLAG_READ) &&
+ (param.flags & VFIO_DMA_MAP_FLAG_WRITE))
+ direction = DMA_BIDIRECTIONAL;
+ else if (param.flags & VFIO_DMA_MAP_FLAG_READ)
+ direction = DMA_TO_DEVICE;
+ else if (param.flags & VFIO_DMA_MAP_FLAG_WRITE)
+ direction = DMA_FROM_DEVICE;
+ else
+ return -EINVAL;
+
+ if ((param.size & ~IOMMU_PAGE_MASK) ||
+ (param.iova & ~IOMMU_PAGE_MASK) ||
+ (param.vaddr & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ /* Account for locked pages */
+ locked = current->mm->locked_vm +
+ (param.size >> IOMMU_PAGE_SHIFT);
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
This page accounting doesn't look right. PAGE_SIZE is several orders
bigger than IOMMU_PAGE_SIZE (right?), but we mix them here, which seems
like it will over penalize the user. For example, if a user maps 4x4k
(assume aligned and contiguous) IOMMU pages, isn't that only a single
pinned system page (assuming >=16k pages).
+ * 64 bit window (not supported at the moment for the guest) is supposed to
+ * be mapped completely to the guest memory so the devices capable of 64bit
+ * DMA will not have to use map/unmap ioctls.
+ *
+ * The IOMMU page size is always 4K.
+ */
Thanks,
Alex
+
+struct vfio_iommu_spapr_tce_info {
+ __u32 argsz;
+ __u32 flags; /* reserved for future use */
+ __u32 dma32_window_start; /* 32 bit window start (bytes) */
+ __u32 dma32_window_size; /* 32 bit window size (bytes) */
+ __u64 dma64_window_start; /* 64 bit window start (bytes) */
+ __u64 dma64_window_size; /* 64 bit window size (bytes) */
+};
+
+#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)
+
+/* Reuse type1 map/unmap structs as they are the same at the moment */
+typedef struct vfio_iommu_type1_dma_map vfio_iommu_spapr_tce_dma_map;
+typedef struct vfio_iommu_type1_dma_unmap vfio_iommu_spapr_tce_dma_unmap;
+
+/* ***************************************************************** */
+
#endif /* VFIO_H */
On Wed, 2012-11-28 at 18:21 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 332 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 33 ++++
4 files changed, 372 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
This looks familiar, should we split it out to a common file instead of
duplicating it?
It is simple cut-n-paste from type1 driver :)
Moving it to a separate file is up to you but it is quite small piece of
code to move it somewhere, and I have not fixed rlimit handling yet, so
wait a bit.
quoted
+
+/*
+ * The container descriptor supports only a single group per container.
+ * Required by the API as the container is not supplied with the IOMMU group
+ * at the moment of initialization.
+ */
+struct tce_container {
+ struct mutex lock;
+ struct iommu_table *tbl;
+};
+
+static void *tce_iommu_open(unsigned long arg)
+{
+ struct tce_container *container;
+
+ if (arg != VFIO_SPAPR_TCE_IOMMU) {
+ pr_err("tce_vfio: Wrong IOMMU type\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ container = kzalloc(sizeof(*container), GFP_KERNEL);
+ if (!container)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&container->lock);
+
+ return container;
+}
+
+static void tce_iommu_release(void *iommu_data)
+{
+ struct tce_container *container = iommu_data;
+
+ WARN_ON(container->tbl && !container->tbl->it_group);
+ if (container->tbl && container->tbl->it_group)
+ tce_iommu_detach_group(iommu_data, container->tbl->it_group);
+
+ mutex_destroy(&container->lock);
+
+ kfree(container);
+}
+
+static long tce_iommu_ioctl(void *iommu_data,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tce_container *container = iommu_data;
+ unsigned long minsz;
+ long ret;
+
+ switch (cmd) {
+ case VFIO_CHECK_EXTENSION: {
+ return (arg == VFIO_SPAPR_TCE_IOMMU) ? 1 : 0;
+ }
+ case VFIO_IOMMU_SPAPR_TCE_GET_INFO: {
+ struct vfio_iommu_spapr_tce_info info;
+ struct iommu_table *tbl = container->tbl;
+
+ if (WARN_ON(!tbl))
+ return -ENXIO;
+
+ minsz = offsetofend(struct vfio_iommu_spapr_tce_info,
+ dma64_window_size);
+
+ if (copy_from_user(&info, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (info.argsz < minsz)
+ return -EINVAL;
+
+ info.dma32_window_start = tbl->it_offset << IOMMU_PAGE_SHIFT;
+ info.dma32_window_size = tbl->it_size << IOMMU_PAGE_SHIFT;
+ info.dma64_window_start = 0;
+ info.dma64_window_size = 0;
+ info.flags = 0;
+
+ if (copy_to_user((void __user *)arg, &info, minsz))
+ return -EFAULT;
+
+ return 0;
+ }
+ case VFIO_IOMMU_MAP_DMA: {
+ vfio_iommu_spapr_tce_dma_map param;
+ struct iommu_table *tbl = container->tbl;
+ enum dma_data_direction direction;
+ unsigned long locked, lock_limit;
+
+ if (WARN_ON(!tbl))
+ return -ENXIO;
+
+ minsz = offsetofend(vfio_iommu_spapr_tce_dma_map, size);
+
+ if (copy_from_user(¶m, (void __user *)arg, minsz))
+ return -EFAULT;
+
+ if (param.argsz < minsz)
+ return -EINVAL;
+
+ if ((param.flags & VFIO_DMA_MAP_FLAG_READ) &&
+ (param.flags & VFIO_DMA_MAP_FLAG_WRITE))
+ direction = DMA_BIDIRECTIONAL;
+ else if (param.flags & VFIO_DMA_MAP_FLAG_READ)
+ direction = DMA_TO_DEVICE;
+ else if (param.flags & VFIO_DMA_MAP_FLAG_WRITE)
+ direction = DMA_FROM_DEVICE;
+ else
+ return -EINVAL;
+
+ if ((param.size & ~IOMMU_PAGE_MASK) ||
+ (param.iova & ~IOMMU_PAGE_MASK) ||
+ (param.vaddr & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ /* Account for locked pages */
+ locked = current->mm->locked_vm +
+ (param.size >> IOMMU_PAGE_SHIFT);
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
This page accounting doesn't look right. PAGE_SIZE is several orders
bigger than IOMMU_PAGE_SIZE (right?), but we mix them here, which seems
like it will over penalize the user. For example, if a user maps 4x4k
(assume aligned and contiguous) IOMMU pages, isn't that only a single
pinned system page (assuming >=16k pages).
Oops. My bad. IOMMU_PAGE_SHIFT should be PAGE_SHIFT and should return the
number of system pages.
But we do not track 4K pages so I do not see any easy solution here. Except
fixing iommu_put_tces/iommu_clear_tces (*) to return the number of the very
first 4K IOMMU pages within system 64K pages.
This won't be too accurate but should work, no?
I'll post it as a patch in reply to "vfio powerpc: enabled on powernv
platform".
+ * 64 bit window (not supported at the moment for the guest) is supposed to
+ * be mapped completely to the guest memory so the devices capable of 64bit
+ * DMA will not have to use map/unmap ioctls.
+ *
+ * The IOMMU page size is always 4K.
+ */
Thanks,
Alex
quoted
+
+struct vfio_iommu_spapr_tce_info {
+ __u32 argsz;
+ __u32 flags; /* reserved for future use */
+ __u32 dma32_window_start; /* 32 bit window start (bytes) */
+ __u32 dma32_window_size; /* 32 bit window size (bytes) */
+ __u64 dma64_window_start; /* 64 bit window start (bytes) */
+ __u64 dma64_window_size; /* 64 bit window size (bytes) */
+};
+
+#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)
+
+/* Reuse type1 map/unmap structs as they are the same at the moment */
+typedef struct vfio_iommu_type1_dma_map vfio_iommu_spapr_tce_dma_map;
+typedef struct vfio_iommu_type1_dma_unmap vfio_iommu_spapr_tce_dma_unmap;
+
+/* ***************************************************************** */
+
#endif /* VFIO_H */
From: David Gibson <hidden> Date: 2012-11-27 05:12:25
On Tue, Nov 27, 2012 at 03:58:14PM +1100, Alexey Kardashevskiy wrote:
On 27/11/12 15:29, Alex Williamson wrote:
quoted
On Tue, 2012-11-27 at 15:06 +1100, Alexey Kardashevskiy wrote:
quoted
On 27/11/12 05:20, Alex Williamson wrote:
quoted
On Fri, 2012-11-23 at 20:03 +1100, Alexey Kardashevskiy wrote:
quoted
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 247 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 20 +++
4 files changed, 274 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
I think your patch ordering is backwards here. it_group isn't added
until 2/2. I'd really like to see the arch/powerpc code approved and
merged by the powerpc maintainer before we add the code that makes use
of it into vfio. Otherwise we just get lots of churn if interfaces
change or they disapprove of it altogether.
On x86 we force iova, vaddr, and size to all be aligned to the smallest
page granularity of the iommu and return -EINVAL if it doesn't fit.
What does it imply to the user if they're always aligned to work here?
Won't this interface happily map overlapping entries with no indication
to the user that the previous mapping is no longer valid?
Maybe another reason why a combined unmap/map makes me nervous, we have
to assume the user knows what they're doing.
I got used to guests which do know what they are doing so I am pretty calm :)
but ok, I'll move alignment to the QEMU, it makes sense.
Would it be too much paranoia to clear all the tce here as you do below
on detach?
Guess so. I do unmap on detach() and the guest calls put_tce(0) (i.e.
unmaps) the whole DMA window at the boot time.
But that's just one user of this interface, we can't assume they'll all
be so agreeable. If any tces were enabled here, a malicious user would
have a window to host memory, right? Thanks,
But I still release pages on detach(), how can this code be not
called on the guest exit (normal or crashed)?
I think the concern is about robustness if some bug elsewhere in the
kernel left some TCE entries in place before the table was handed over
to VFIO.
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
The set includes 2 patches.
The first one adds necessary support for VFIO IOMMU support,
the second one adds a SPAPR TCE IOMMU driver to VFIO.
At the moment we have decided to get rid of DMA64 window
properties because we need more API than just 2 properties
(such as dynamic window allocation) but have not decided
about its actual design yet.
Alexey Kardashevskiy (2):
vfio powerpc: enabled on powernv platform
vfio powerpc: implemented IOMMU driver for VFIO
arch/powerpc/include/asm/iommu.h | 9 +
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 +++++++++++++
drivers/iommu/Kconfig | 8 +
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 350 ++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 26 +++
8 files changed, 721 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
--
1.7.10.4
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,entry);+__clear_bit(entry-tbl->it_offset,tbl->it_map);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++WARN_ON(!page);+if(!page)+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++put_page(page);++/* That was the last IOMMU page within the system page */+if((oldweight==1)&&!syspage_weight(tbl->it_map,entry))+++retpages;+}++returnretpages;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumber+/ofreleasedsystempages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset,oldweight;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret<1){+printk(KERN_ERR"tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+if(!ret||(ret>1))+ret=-EFAULT;+returnret;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+entry+=tbl->it_offset;/* Offset into real TCE table */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+printk(KERN_ERR"tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++/* Calculate if new system page has been locked */+oldweight=syspage_weight(tbl->it_map,entry);+__set_bit(entry-tbl->it_offset,tbl->it_map);++return(oldweight==0)?1:0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactually+*lockedsystempages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0,retpages=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+unsignedlongoldtce=ppc_md.tce_get(tbl,entry+i);+if((oldtce&(TCE_PCI_WRITE|TCE_PCI_READ))||+test_bit(entry+i,tbl->it_map)){+WARN_ON(test_bit(entry+i,tbl->it_map));+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&(ret>=0);++i,tce+=IOMMU_PAGE_SIZE){+ret=put_tce(tbl,entry+i,tce,direction);+if(ret==1)+++retpages;+}++/*+*Iffailed,releaselockedpages,otherwisereturnthenumber+*oflockedsystempages+*/+if(ret<0)+clear_tces_nolock(tbl,entry,i);+else+ret=retpages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,137 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+printk(KERN_WARNING"tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+printk(KERN_ERR"tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+printk(KERN_INFO"tce_vfio: cannot create "+"new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-12-03 17:35:34
On Mon, 2012-12-03 at 13:52 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,entry);+__clear_bit(entry-tbl->it_offset,tbl->it_map);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;
Could this happen earlier, above syspage_weight() and __clear_bit()?
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ WARN_ON(!page);
+ if (!page)
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && !syspage_weight(tbl->it_map, entry))
+ ++retpages;
If you used __test_and_clear_bit() above I think you could avoid this
2nd call to syspage_weight. A minor optimization though.
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ / of released system pages
+ */
Something bad happened to your comments here.
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
Maybe the compiler will figure this out, but isn't this the same as tce
& (IOMMU_PAGE_MASK & PAGE_MASK)?
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret < 1) {
Probably (ret != 1) here or else we never get to your >1 case below.
Then (ret >= 0) here. Or return (ret >= 0) ? -EFAULT : ret
+ ret = -EFAULT;
+ return ret;
+ }
You're missing the code from x86 that handles mapping mmap'd ranges.
This is intended to allow peer-to-peer DMA between devices. Is that
intentional?
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
Here's what makes me call the entry "relative" rather than zero-based.
The iova is relative to the start of dma32_window_start, ie. if the
window starts at bus address 512MB and I want to create a translation at
bus address 512MB, I pass in an iova of 0, right? The above adds the
window offset. So you've removed dma64 window, but we really need to
define iova better.
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
Use pr_err
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight(tbl->it_map, entry);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
This doesn't seem BUG worthy, -EINVAL? We can't assume tce_iommu_ioctl
will always be the only caller of this function.
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ unsigned long oldtce = ppc_md.tce_get(tbl, entry + i);
+ if ((oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)) ||
+ test_bit(entry + i, tbl->it_map)) {
+ WARN_ON(test_bit(entry + i, tbl->it_map));
The WARN_ON seems to confirm that these are redundant tests, does that
imply we don't trust it_map? It would be a lot faster if we could rely
on it_map exclusively here.
quoted hunk
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
On Mon, 2012-12-03 at 13:52 +1100, Alexey Kardashevskiy wrote:
quoted
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,entry);+__clear_bit(entry-tbl->it_offset,tbl->it_map);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;
Could this happen earlier, above syspage_weight() and __clear_bit()?
Want to clear it anyway if it is not cleared by some reason. Added WARN_ON.
quoted
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ WARN_ON(!page);
+ if (!page)
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && !syspage_weight(tbl->it_map, entry))
+ ++retpages;
If you used __test_and_clear_bit() above I think you could avoid this
2nd call to syspage_weight. A minor optimization though.
quoted
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ / of released system pages
+ */
Something bad happened to your comments here.
quoted
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
Maybe the compiler will figure this out, but isn't this the same as tce
& (IOMMU_PAGE_MASK & PAGE_MASK)?
it is rather (tce & (IOMMU_PAGE_MASK & ~PAGE_MASK)) but I cannot see how it
is simpler and I doubt that it is faster enough to notice it anyhow :)
quoted
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret < 1) {
Probably (ret != 1) here or else we never get to your >1 case below.
Then (ret >= 0) here. Or return (ret >= 0) ? -EFAULT : ret
quoted
+ ret = -EFAULT;
+ return ret;
+ }
You're missing the code from x86 that handles mapping mmap'd ranges.
This is intended to allow peer-to-peer DMA between devices. Is that
intentional?
I am not following you here. What code exactly are talking about? We do not
track ranges at all and I do not see how it helps with p2p dma.
quoted
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
Here's what makes me call the entry "relative" rather than zero-based.
This is the bug actually, I overlooked it and I removed it now. Thanks for
being so picky :)
The iova is relative to the start of dma32_window_start, ie. if the
window starts at bus address 512MB and I want to create a translation at
bus address 512MB, I pass in an iova of 0, right? The above adds the
window offset. So you've removed dma64 window, but we really need to
define iova better.
quoted
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
Use pr_err
quoted
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight(tbl->it_map, entry);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
This doesn't seem BUG worthy, -EINVAL? We can't assume tce_iommu_ioctl
will always be the only caller of this function.
This is what other function does in this file.
quoted
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ unsigned long oldtce = ppc_md.tce_get(tbl, entry + i);
+ if ((oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)) ||
+ test_bit(entry + i, tbl->it_map)) {
+ WARN_ON(test_bit(entry + i, tbl->it_map));
The WARN_ON seems to confirm that these are redundant tests, does that
imply we don't trust it_map? It would be a lot faster if we could rely
on it_map exclusively here.
As for me, pretty minor optimization. I'm testing it now to see if I do not
miss bits.
quoted
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-12-04 15:51:56
On Tue, 2012-12-04 at 19:12 +1100, Alexey Kardashevskiy wrote:
On 04/12/12 04:35, Alex Williamson wrote:
quoted
On Mon, 2012-12-03 at 13:52 +1100, Alexey Kardashevskiy wrote:
quoted
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 9 ++
arch/powerpc/kernel/iommu.c | 186 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 135 ++++++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 338 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,188 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongentry)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+entry&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits--)+ret+=(test_bit(entry++,map)==0)?0:1;++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,entry);+__clear_bit(entry-tbl->it_offset,tbl->it_map);++if(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ)))+continue;
Could this happen earlier, above syspage_weight() and __clear_bit()?
Want to clear it anyway if it is not cleared by some reason. Added WARN_ON.
The map shouldn't be set unless read/write is enabled, right? It seems
like we don't have a lot of trust in this bitmap.
quoted
quoted
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ WARN_ON(!page);
+ if (!page)
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && !syspage_weight(tbl->it_map, entry))
+ ++retpages;
If you used __test_and_clear_bit() above I think you could avoid this
2nd call to syspage_weight. A minor optimization though.
quoted
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ / of released system pages
+ */
Something bad happened to your comments here.
quoted
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
Maybe the compiler will figure this out, but isn't this the same as tce
& (IOMMU_PAGE_MASK & PAGE_MASK)?
it is rather (tce & (IOMMU_PAGE_MASK & ~PAGE_MASK)) but I cannot see how it
is simpler and I doubt that it is faster enough to notice it anyhow :)
Yes, ~PAGE_MASK. IMHO, it's more intuitive.
quoted
quoted
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret < 1) {
Probably (ret != 1) here or else we never get to your >1 case below.
Then (ret >= 0) here. Or return (ret >= 0) ? -EFAULT : ret
quoted
+ ret = -EFAULT;
+ return ret;
+ }
You're missing the code from x86 that handles mapping mmap'd ranges.
This is intended to allow peer-to-peer DMA between devices. Is that
intentional?
I am not following you here. What code exactly are talking about? We do not
track ranges at all and I do not see how it helps with p2p dma.
The code in type1 that checks PFNMAP and reserved pages that I only
sometimes remember how it works ;) The idea there is to allow p2p dma
by inserting iommu translations for non-page backed memory, ie. the
mmap'd BARs of other devices. It may be that the POWER topology is not
amenable to this since you have a whole PCI bus in your group and
intra-group p2p isn't iommu translated. I'm not sure how useful it is
even on x86, but KVM device assignment does it, so I added it to type1.
quoted
quoted
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ entry += tbl->it_offset; /* Offset into real TCE table */
Here's what makes me call the entry "relative" rather than zero-based.
This is the bug actually, I overlooked it and I removed it now. Thanks for
being so picky :)
Ah, ok. I'll look for it on the next round and make sure I understand
it.
quoted
The iova is relative to the start of dma32_window_start, ie. if the
window starts at bus address 512MB and I want to create a translation at
bus address 512MB, I pass in an iova of 0, right? The above adds the
window offset. So you've removed dma64 window, but we really need to
define iova better.
quoted
quoted
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ printk(KERN_ERR "tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
Use pr_err
quoted
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight(tbl->it_map, entry);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
This doesn't seem BUG worthy, -EINVAL? We can't assume tce_iommu_ioctl
will always be the only caller of this function.
This is what other function does in this file.
Blech, ok.
quoted
quoted
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ unsigned long oldtce = ppc_md.tce_get(tbl, entry + i);
+ if ((oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)) ||
+ test_bit(entry + i, tbl->it_map)) {
+ WARN_ON(test_bit(entry + i, tbl->it_map));
The WARN_ON seems to confirm that these are redundant tests, does that
imply we don't trust it_map? It would be a lot faster if we could rely
on it_map exclusively here.
As for me, pretty minor optimization. I'm testing it now to see if I do not
miss bits.
It would be a lot more re-assuring if we didn't need it ;) Thanks,
Alex
quoted
quoted
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+#endif /* CONFIG_IOMMU_API */
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 10 ++
arch/powerpc/kernel/iommu.c | 214 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 134 +++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 366 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +857,216 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++/*+*iommu_reset_tableiscalledwhenitstarted/stoppedbeingused+*/+voidiommu_reset_table(structiommu_table*tbl,boolrelease)+{+/*+*Pageat0ismarkedasusediniommu_init_table,+*sohereweclearitwhencalledwithrelease=false...+*/+if(!release&&(tbl->it_offset==0))+clear_bit(0,tbl->it_map);++iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);++memset(tbl->it_map,0,(tbl->it_size+7)>>3);++/*+*...orrestorewhenrelease=true+*/+if(release&&(tbl->it_offset==0))+set_bit(0,tbl->it_map);+}+EXPORT_SYMBOL_GPL(iommu_reset_table);++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*bitmap_weightisnotusedasitdoesnotsupportbigendianmaps.+*"offset"isanIOMMUpagenumberrelativetoDMAwindowstart.+*/+staticintsyspage_weight(unsignedlong*map,unsignedlongoffset)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+offset&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits){+if(test_bit(offset,map))+++ret;+--nbits;+++offset;+}++returnret;+}++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0,clr;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i){+if(!test_bit(entry+i-tbl->it_offset,tbl->it_map))+continue;++oldtce=ppc_md.tce_get(tbl,entry+i);+ppc_md.tce_free(tbl,entry+i,1);++oldweight=syspage_weight(tbl->it_map,+entry+i-tbl->it_offset);+clr=__test_and_clear_bit(entry+i-tbl->it_offset,+tbl->it_map);++if(WARN_ON(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ))))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++if(WARN_ON(!page))+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++put_page(page);++/* That was the last IOMMU page within the system page */+if((oldweight==1)&&clr)+++retpages;+}++returnretpages;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumber+*ofreleasedsystempages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+intret;+structiommu_pool*pool=get_pool(tbl,entry);++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,pages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset,oldweight;++/* Map new TCE */+offset=(tce&IOMMU_PAGE_MASK)-(tce&PAGE_MASK);+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret!=1){+pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+return-EFAULT;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++/* Calculate if new system page has been locked */+oldweight=syspage_weight(tbl->it_map,entry-tbl->it_offset);+__set_bit(entry-tbl->it_offset,tbl->it_map);++return(oldweight==0)?1:0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactually+*lockedsystempages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongpages)+{+inti,ret=0,retpages=0;+structiommu_pool*pool=get_pool(tbl,entry);++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<pages;++i){+if(test_bit(entry+i-tbl->it_offset,tbl->it_map)){+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<pages)&&(ret>=0);++i,tce+=IOMMU_PAGE_SIZE){+ret=put_tce(tbl,entry+i,tce,direction);+if(ret==1)+++retpages;+}++/*+*Iffailed,releaselockedpages,otherwisereturnthenumber+*oflockedsystempages+*/+if(ret<0)+clear_tces_nolock(tbl,entry,i);+else+ret=retpages;++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);++#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,136 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+pr_warn("tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+pr_err("tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+pr_info("tce_vfio: cannot create new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Alex Williamson <hidden> Date: 2012-12-07 17:38:55
On Fri, 2012-12-07 at 18:35 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 10 ++
arch/powerpc/kernel/iommu.c | 214 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 134 +++++++++++++++++++++
drivers/iommu/Kconfig | 8 ++
4 files changed, 366 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
"release" to me implies something is freed, maybe this should just be
called "restore".
+}
+EXPORT_SYMBOL_GPL(iommu_reset_table);
+
+/*
+ * Returns the number of used IOMMU pages (4K) within
+ * the same system page (4K or 64K).
+ * bitmap_weight is not used as it does not support bigendian maps.
+ * "offset" is an IOMMU page number relative to DMA window start.
+ */
+static int syspage_weight(unsigned long *map, unsigned long offset)
+{
+ int ret = 0, nbits = PAGE_SIZE/IOMMU_PAGE_SIZE;
+
+ /* Aligns TCE entry number to system page boundary */
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+
+ /* Count used 4K pages */
+ while (nbits) {
+ if (test_bit(offset, map))
+ ++ret;
+ --nbits;
+ ++offset;
+ }
+
+ return ret;
+}
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of system pages
+ * which it called put_page() on
+ */
+static long clear_tces_nolock(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int i, retpages = 0, clr;
+ unsigned long oldtce, oldweight;
+ struct page *page;
+
+ for (i = 0; i < pages; ++i) {
Any reason not to increment "entry" and avoid the 5 cases of "entry + i"
below?
+ if (!test_bit(entry + i - tbl->it_offset, tbl->it_map))
+ continue;
+
+ oldtce = ppc_md.tce_get(tbl, entry + i);
+ ppc_md.tce_free(tbl, entry + i, 1);
+
+ oldweight = syspage_weight(tbl->it_map,
+ entry + i - tbl->it_offset);
+ clr = __test_and_clear_bit(entry + i - tbl->it_offset,
+ tbl->it_map);
+
+ if (WARN_ON(!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))))
+ continue;
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ if (WARN_ON(!page))
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && clr)
+ ++retpages;
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ * of released system pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int ret;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, pages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = (tce & IOMMU_PAGE_MASK) - (tce & PAGE_MASK);
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret != 1) {
+ pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ return -EFAULT;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight(tbl->it_map, entry - tbl->it_offset);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
It seems like there's an optimization for syspage_weight since you only
care about two cases, ie. syspage_weight_one and syspage_weight_zero.
The zero test is easy, just mask and return !! the value. Testing
weight 1 means you don't have to find more than 2 bits set. I won't
hold you to that optimization, just fyi.
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long pages)
+{
+ int i, ret = 0, retpages = 0;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < pages; ++i) {
+ if (test_bit(entry + i - tbl->it_offset, tbl->it_map)) {
Again, seems like there's an optimization here that avoids individually
testing bits since you only care about zero or non-zero for a sequential
run.
quoted hunk
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < pages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0)
+ clear_tces_nolock(tbl, entry, i);
+ else
+ ret = retpages;
+
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_put_tces);
+
+#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,136 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+pr_warn("tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+pr_err("tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+pr_info("tce_vfio: cannot create new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);
BTW, groups have a name property that shows up in sysfs that can be set
with iommu_group_set_name(). IIRC, this was a feature David requested
for PEs. It'd be nice if it was used for PEs... Thanks,
Alex
BTW, groups have a name property that shows up in sysfs that can be set
with iommu_group_set_name(). IIRC, this was a feature David requested
for PEs. It'd be nice if it was used for PEs... Thanks,
But what would I put there?... IOMMU ID is more than enough at the moment
and struct iommu_table does not have anything what would have made sense to
show in the sysfs...
--
Alexey
BTW, groups have a name property that shows up in sysfs that can be set
with iommu_group_set_name(). IIRC, this was a feature David requested
for PEs. It'd be nice if it was used for PEs... Thanks,
But what would I put there?... IOMMU ID is more than enough at the moment
and struct iommu_table does not have anything what would have made sense to
show in the sysfs...
I believe David mentioned that PEs had user visible names. Perhaps they
match an enclosure location or something. Group numbers are rather
arbitrary and really have no guarantee of persistence. Thanks,
Alex
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2012-12-13 02:29:53
On Wed, 2012-12-12 at 07:34 -0700, Alex Williamson wrote:
quoted
But what would I put there?... IOMMU ID is more than enough at the moment
and struct iommu_table does not have anything what would have made sense to
show in the sysfs...
I believe David mentioned that PEs had user visible names. Perhaps they
match an enclosure location or something. Group numbers are rather
arbitrary and really have no guarantee of persistence. Thanks,
I agree. Make up something, for example domain[PE] or something like
that.
Cheers,
Ben.
On Wed, 2012-12-12 at 07:34 -0700, Alex Williamson wrote:
quoted
quoted
But what would I put there?... IOMMU ID is more than enough at the moment
and struct iommu_table does not have anything what would have made sense to
show in the sysfs...
I believe David mentioned that PEs had user visible names. Perhaps they
match an enclosure location or something. Group numbers are rather
arbitrary and really have no guarantee of persistence. Thanks,
I agree. Make up something, for example domain[PE] or something like
that.
To be able to add a PE number, I need to call iommu_group_alloc() in the
correct place where I know this number OR I have to carry it in iommu_table
till the moment the iommu_group_alloc() is called (acceptable but not cool).
I will post a patch which would help as a response to this mail.
--
Alexey
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 10 ++
arch/powerpc/kernel/iommu.c | 329 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 134 ++++++++++++++
drivers/iommu/Kconfig | 8 +
4 files changed, 481 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +858,330 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++structvwork{+structmm_struct*mm;+longnpage;+structwork_structwork;+};++/* delayed decrement/increment for locked_vm */+staticvoidlock_acct_bg(structwork_struct*work)+{+structvwork*vwork=container_of(work,structvwork,work);+structmm_struct*mm;++mm=vwork->mm;+down_write(&mm->mmap_sem);+mm->locked_vm+=vwork->npage;+up_write(&mm->mmap_sem);+mmput(mm);+kfree(vwork);+}++staticvoidlock_acct(longnpage)+{+structvwork*vwork;+structmm_struct*mm;++if(!current->mm)+return;/* process exited */++if(down_write_trylock(¤t->mm->mmap_sem)){+current->mm->locked_vm+=npage;+up_write(¤t->mm->mmap_sem);+return;+}++/*+*Couldn'tgetmmap_semlock,somustsetuptoupdate+*mm->locked_vmlater.Iflocked_vmwereatomic,we+*wouldn'tneedthissilliness+*/+vwork=kmalloc(sizeof(structvwork),GFP_KERNEL);+if(!vwork)+return;+mm=get_task_mm(current);+if(!mm){+kfree(vwork);+return;+}+INIT_WORK(&vwork->work,lock_acct_bg);+vwork->mm=mm;+vwork->npage=npage;+schedule_work(&vwork->work);+}++/*+*iommu_reset_tableiscalledwhenitstarted/stoppedbeingused.+*+*restore==truesaystobringtheiommu_tableintothestateasitwas+*beforebeingusedbyVFIO.+*/+voidiommu_reset_table(structiommu_table*tbl,boolrestore)+{+/* Page#0 is marked as used in iommu_init_table, so we clear it... */+if(!restore&&(tbl->it_offset==0))+clear_bit(0,tbl->it_map);++iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);++/* ... or restore */+if(restore&&(tbl->it_offset==0))+set_bit(0,tbl->it_map);+}+EXPORT_SYMBOL_GPL(iommu_reset_table);++/*+*ReturnsthenumberofusedIOMMUpages(4K)within+*thesamesystempage(4Kor64K).+*+*syspage_weight_zeroisoptimizedforexpectedcase==0+*syspage_weight_oneisoptimizedforexpectedcase>1+*Othercasearenotusedinthisfile.+*/+#if PAGE_SIZE == IOMMU_PAGE_SIZE++#define syspage_weight_zero(map, offset) test_bit((map), (offset))+#define syspage_weight_one(map, offset) test_bit((map), (offset))++#elif PAGE_SIZE/IOMMU_PAGE_SIZE == 16++staticintsyspage_weight_zero(unsignedlong*map,unsignedlongoffset)+{+offset&=PAGE_MASK>>IOMMU_PAGE_SHIFT;+return0xffffUL&(map[BIT_WORD(offset)]>>+(offset&(BITS_PER_LONG-1)));+}++staticintsyspage_weight_one(unsignedlong*map,unsignedlongoffset)+{+intret=0,nbits=PAGE_SIZE/IOMMU_PAGE_SIZE;++/* Aligns TCE entry number to system page boundary */+offset&=PAGE_MASK>>IOMMU_PAGE_SHIFT;++/* Count used 4K pages */+while(nbits&&(ret<2)){+if(test_bit(offset,map))+++ret;++--nbits;+++offset;+}++returnret;+}+#else+#error TODO: support other page size+#endif++staticvoidtce_flush(structiommu_table*tbl)+{+/* Flush/invalidate TLB caches if necessary */+if(ppc_md.tce_flush)+ppc_md.tce_flush(tbl);++/* Make sure updates are seen by hardware */+mb();+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumberofsystempages+*whichitcalledput_page()on+*/+staticlongclear_tces_nolock(structiommu_table*tbl,unsignedlongentry,+unsignedlongpages)+{+inti,retpages=0,clr;+unsignedlongoldtce,oldweight;+structpage*page;++for(i=0;i<pages;++i,++entry){+if(!test_bit(entry-tbl->it_offset,tbl->it_map))+continue;++oldtce=ppc_md.tce_get(tbl,entry);+ppc_md.tce_free(tbl,entry,1);++oldweight=syspage_weight_one(tbl->it_map,+entry-tbl->it_offset);+clr=__test_and_clear_bit(entry-tbl->it_offset,+tbl->it_map);++if(WARN_ON(!(oldtce&(TCE_PCI_WRITE|TCE_PCI_READ))))+continue;++page=pfn_to_page(oldtce>>PAGE_SHIFT);++if(WARN_ON(!page))+continue;++if(oldtce&TCE_PCI_WRITE)+SetPageDirty(page);++put_page(page);++/* That was the last IOMMU page within the system page */+if((oldweight==1)&&clr)+++retpages;+}++returnretpages;+}++/*+*iommu_clear_tcesclearstcesandreturnedthenumber+*ofreleasedsystempages+*/+longiommu_clear_tces(structiommu_table*tbl,unsignedlongioba,+unsignedlongsize)+{+intret;+unsignedlongentry=ioba>>IOMMU_PAGE_SHIFT;+unsignedlongnpages=size>>IOMMU_PAGE_SHIFT;+structiommu_pool*pool=get_pool(tbl,entry);++if((size&~IOMMU_PAGE_MASK)||(ioba&~IOMMU_PAGE_MASK))+return-EINVAL;++if((ioba+size)>((tbl->it_offset+tbl->it_size)+<<IOMMU_PAGE_SHIFT))+return-EINVAL;++if(ioba<(tbl->it_offset<<IOMMU_PAGE_SHIFT))+return-EINVAL;++spin_lock(&(pool->lock));+ret=clear_tces_nolock(tbl,entry,npages);+tce_flush(tbl);+spin_unlock(&(pool->lock));++if(ret>0){+lock_acct(-ret);+return0;+}++returnret;+}+EXPORT_SYMBOL_GPL(iommu_clear_tces);++staticintput_tce(structiommu_table*tbl,unsignedlongentry,+uint64_ttce,enumdma_data_directiondirection)+{+intret;+structpage*page=NULL;+unsignedlongkva,offset,oldweight;++/* Map new TCE */+offset=tce&IOMMU_PAGE_MASK&~PAGE_MASK;+ret=get_user_pages_fast(tce&PAGE_MASK,1,+direction!=DMA_TO_DEVICE,&page);+if(ret!=1){+pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,ret);+return-EFAULT;+}++kva=(unsignedlong)page_address(page);+kva+=offset;++/* tce_build receives a virtual address */+ret=ppc_md.tce_build(tbl,entry,1,kva,direction,NULL);++/* tce_build() only returns non-zero for transient errors */+if(unlikely(ret)){+pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",+tce,entry<<IOMMU_PAGE_SHIFT,kva,ret);+put_page(page);+return-EIO;+}++/* Calculate if new system page has been locked */+oldweight=syspage_weight_zero(tbl->it_map,entry-tbl->it_offset);+__set_bit(entry-tbl->it_offset,tbl->it_map);++return(oldweight==0)?1:0;+}++/*+*iommu_put_tcesbuildstcesandreturnedthenumberofactually+*lockedsystempages+*/+longiommu_put_tces(structiommu_table*tbl,unsignedlongioba,+uint64_ttce,enumdma_data_directiondirection,+unsignedlongsize)+{+inti,ret=0,retpages=0;+unsignedlongentry=ioba>>IOMMU_PAGE_SHIFT;+unsignedlongnpages=size>>IOMMU_PAGE_SHIFT;+structiommu_pool*pool=get_pool(tbl,entry);+unsignedlonglocked,lock_limit;++BUILD_BUG_ON(PAGE_SIZE<IOMMU_PAGE_SIZE);+BUG_ON(direction==DMA_NONE);++if((size&~IOMMU_PAGE_MASK)||+(ioba&~IOMMU_PAGE_MASK)||+(tce&~IOMMU_PAGE_MASK))+return-EINVAL;++if((ioba+size)>((tbl->it_offset+tbl->it_size)+<<IOMMU_PAGE_SHIFT))+return-EINVAL;++if(ioba<(tbl->it_offset<<IOMMU_PAGE_SHIFT))+return-EINVAL;++/* Account for locked pages */+locked=current->mm->locked_vm++(_ALIGN_UP(size,PAGE_SIZE)>>PAGE_SHIFT);+lock_limit=rlimit(RLIMIT_MEMLOCK)>>PAGE_SHIFT;+if(locked>lock_limit&&!capable(CAP_IPC_LOCK)){+pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",+rlimit(RLIMIT_MEMLOCK));+return-ENOMEM;+}++spin_lock(&(pool->lock));++/* Check if any is in use */+for(i=0;i<npages;++i){+if(test_bit(entry+i-tbl->it_offset,tbl->it_map)){+spin_unlock(&(pool->lock));+return-EBUSY;+}+}++/* Put tces to the table */+for(i=0;(i<npages)&&(ret>=0);++i,tce+=IOMMU_PAGE_SIZE){+ret=put_tce(tbl,entry+i,tce,direction);+if(ret==1)+++retpages;+}++/*+*Iffailed,releaselockedpages,otherwisereturnthenumber+*oflockedsystempages+*/+if(ret<0){+clear_tces_nolock(tbl,entry,i);+}else{+if(retpages)+lock_acct(retpages);+ret=0;+}++tce_flush(tbl);+spin_unlock(&(pool->lock));++returnret;+}+EXPORT_SYMBOL_GPL(iommu_put_tces);++#endif /* CONFIG_IOMMU_API */
@@ -613,3 +614,136 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+pr_warn("tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+pr_err("tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+pr_info("tce_vfio: cannot create new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
Hi Alex,
I posted other pair of patches. While debugging and testing my stuff I
implemented some rough hack to support IOMMU mappings without passing those
hypercalls to the QEMU, this is why I moved pieces of code around - want to
support both QEMU-VFIO and kernel optimized H_PUT_TCE handler.
On 12/12/12 23:34, Alexey Kardashevskiy wrote:
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
From: Alex Williamson <hidden> Date: 2012-12-12 23:30:58
On Wed, 2012-12-12 at 23:34 +1100, Alexey Kardashevskiy wrote:
quoted hunk
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 10 ++
arch/powerpc/kernel/iommu.c | 329 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 134 ++++++++++++++
drivers/iommu/Kconfig | 8 +
4 files changed, 481 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +858,330 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++structvwork{+structmm_struct*mm;+longnpage;+structwork_structwork;+};++/* delayed decrement/increment for locked_vm */+staticvoidlock_acct_bg(structwork_struct*work)+{+structvwork*vwork=container_of(work,structvwork,work);+structmm_struct*mm;++mm=vwork->mm;+down_write(&mm->mmap_sem);+mm->locked_vm+=vwork->npage;+up_write(&mm->mmap_sem);+mmput(mm);+kfree(vwork);+}++staticvoidlock_acct(longnpage)+{+structvwork*vwork;+structmm_struct*mm;++if(!current->mm)+return;/* process exited */++if(down_write_trylock(¤t->mm->mmap_sem)){+current->mm->locked_vm+=npage;+up_write(¤t->mm->mmap_sem);+return;+}++/*+*Couldn'tgetmmap_semlock,somustsetuptoupdate+*mm->locked_vmlater.Iflocked_vmwereatomic,we+*wouldn'tneedthissilliness+*/+vwork=kmalloc(sizeof(structvwork),GFP_KERNEL);+if(!vwork)+return;+mm=get_task_mm(current);+if(!mm){+kfree(vwork);+return;+}+INIT_WORK(&vwork->work,lock_acct_bg);+vwork->mm=mm;+vwork->npage=npage;+schedule_work(&vwork->work);+}
Locked page accounting in this version is very, very broken. How do
powerpc folks feel about seemingly generic kernel iommu interfaces
messing with the current task mm? Besides that, more problems below...
+
+/*
+ * iommu_reset_table is called when it started/stopped being used.
+ *
+ * restore==true says to bring the iommu_table into the state as it was
+ * before being used by VFIO.
+ */
+void iommu_reset_table(struct iommu_table *tbl, bool restore)
+{
+ /* Page#0 is marked as used in iommu_init_table, so we clear it... */
+ if (!restore && (tbl->it_offset == 0))
+ clear_bit(0, tbl->it_map);
+
+ iommu_clear_tces(tbl, tbl->it_offset, tbl->it_size);
This does locked page accounting and unpins pages, even on startup when
the pages aren't necessarily pinned or accounted against the current
process.
+
+ /* ... or restore */
+ if (restore && (tbl->it_offset == 0))
+ set_bit(0, tbl->it_map);
+}
+EXPORT_SYMBOL_GPL(iommu_reset_table);
+
+/*
+ * Returns the number of used IOMMU pages (4K) within
+ * the same system page (4K or 64K).
+ *
+ * syspage_weight_zero is optimized for expected case == 0
+ * syspage_weight_one is optimized for expected case > 1
+ * Other case are not used in this file.
+ */
+#if PAGE_SIZE == IOMMU_PAGE_SIZE
+
+#define syspage_weight_zero(map, offset) test_bit((map), (offset))
+#define syspage_weight_one(map, offset) test_bit((map), (offset))
+
+#elif PAGE_SIZE/IOMMU_PAGE_SIZE == 16
+
+static int syspage_weight_zero(unsigned long *map, unsigned long offset)
+{
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+ return 0xffffUL & (map[BIT_WORD(offset)] >>
+ (offset & (BITS_PER_LONG-1)));
+}
I would have expected these to be bools and return true if the weight
matches the value.
If you replaced 0xffff above w/ this, would you need the #error below?
(1UL << (PAGE_SIZE/IOMMU_PAGE_SIZE)) - 1)
+
+static int syspage_weight_one(unsigned long *map, unsigned long offset)
+{
+ int ret = 0, nbits = PAGE_SIZE/IOMMU_PAGE_SIZE;
+
+ /* Aligns TCE entry number to system page boundary */
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+
+ /* Count used 4K pages */
+ while (nbits && (ret < 2)) {
Don't you have a ffs()? Could also be used for _zero. Surely there are
some bitops helpers that could help here even on big endian. hweight
really doesn't work?
+ if (test_bit(offset, map))
+ ++ret;
+
+ --nbits;
+ ++offset;
+ }
+
+ return ret;
+}
+#else
+#error TODO: support other page size
+#endif
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of system pages
+ * which it called put_page() on
+ */
+static long clear_tces_nolock(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int i, retpages = 0, clr;
+ unsigned long oldtce, oldweight;
+ struct page *page;
+
+ for (i = 0; i < pages; ++i, ++entry) {
+ if (!test_bit(entry - tbl->it_offset, tbl->it_map))
+ continue;
+
+ oldtce = ppc_md.tce_get(tbl, entry);
+ ppc_md.tce_free(tbl, entry, 1);
+
+ oldweight = syspage_weight_one(tbl->it_map,
+ entry - tbl->it_offset);
+ clr = __test_and_clear_bit(entry - tbl->it_offset,
+ tbl->it_map);
+
+ if (WARN_ON(!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))))
+ continue;
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ if (WARN_ON(!page))
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && clr)
+ ++retpages;
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ * of released system pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long ioba,
+ unsigned long size)
+{
+ int ret;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ if ((size & ~IOMMU_PAGE_MASK) || (ioba & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, npages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ if (ret > 0) {
+ lock_acct(-ret);
+ return 0;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = tce & IOMMU_PAGE_MASK & ~PAGE_MASK;
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret != 1) {
+ pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ return -EFAULT;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight_zero(tbl->it_map, entry - tbl->it_offset);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long ioba,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long size)
+{
+ int i, ret = 0, retpages = 0;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ unsigned long locked, lock_limit;
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ if ((size & ~IOMMU_PAGE_MASK) ||
+ (ioba & ~IOMMU_PAGE_MASK) ||
+ (tce & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ /* Account for locked pages */
+ locked = current->mm->locked_vm +
+ (_ALIGN_UP(size, PAGE_SIZE) >> PAGE_SHIFT);
Looks like we just over penalize upfront and correct when mapped, that's
better, but not great.
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+ if (locked > lock_limit && !capable(CAP_IPC_LOCK)) {
+ pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",
+ rlimit(RLIMIT_MEMLOCK));
+ return -ENOMEM;
+ }
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < npages; ++i) {
+ if (test_bit(entry + i - tbl->it_offset, tbl->it_map)) {
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < npages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0) {
+ clear_tces_nolock(tbl, entry, i);
+ } else {
+ if (retpages)
+ lock_acct(retpages);
+ ret = 0;
+ }
Bug, if it fails we clear, which decrements our locked pages, but we
haven't incremented them yet. Thanks,
Alex
@@ -613,3 +614,136 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+pr_warn("tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+pr_err("tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+pr_info("tce_vfio: cannot create new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
On Wed, 2012-12-12 at 23:34 +1100, Alexey Kardashevskiy wrote:
quoted
This patch initializes IOMMU groups based on the IOMMU
configuration discovered during the PCI scan on POWERNV
(POWER non virtualized) platform. The IOMMU groups are
to be used later by VFIO driver (PCI pass through).
It also implements an API for mapping/unmapping pages for
guest PCI drivers and providing DMA window properties.
This API is going to be used later by QEMU-VFIO to handle
h_put_tce hypercalls from the KVM guest.
Although this driver has been tested only on the POWERNV
platform, it should work on any platform which supports
TCE tables.
To enable VFIO on POWER, enable SPAPR_TCE_IOMMU config
option and configure VFIO as required.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
arch/powerpc/include/asm/iommu.h | 10 ++
arch/powerpc/kernel/iommu.c | 329 ++++++++++++++++++++++++++++++++++
arch/powerpc/platforms/powernv/pci.c | 134 ++++++++++++++
drivers/iommu/Kconfig | 8 +
4 files changed, 481 insertions(+)
@@ -76,6 +76,9 @@ struct iommu_table {structiommu_poollarge_pool;structiommu_poolpools[IOMMU_NR_POOLS];unsignedlong*it_map;/* A simple allocation bitmap for now */+#ifdef CONFIG_IOMMU_API+structiommu_group*it_group;+#endif};structscatterlist;
@@ -856,3 +858,330 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,free_pages((unsignedlong)vaddr,get_order(size));}}++#ifdef CONFIG_IOMMU_API+/*+*SPAPRTCEAPI+*/++structvwork{+structmm_struct*mm;+longnpage;+structwork_structwork;+};++/* delayed decrement/increment for locked_vm */+staticvoidlock_acct_bg(structwork_struct*work)+{+structvwork*vwork=container_of(work,structvwork,work);+structmm_struct*mm;++mm=vwork->mm;+down_write(&mm->mmap_sem);+mm->locked_vm+=vwork->npage;+up_write(&mm->mmap_sem);+mmput(mm);+kfree(vwork);+}++staticvoidlock_acct(longnpage)+{+structvwork*vwork;+structmm_struct*mm;++if(!current->mm)+return;/* process exited */++if(down_write_trylock(¤t->mm->mmap_sem)){+current->mm->locked_vm+=npage;+up_write(¤t->mm->mmap_sem);+return;+}++/*+*Couldn'tgetmmap_semlock,somustsetuptoupdate+*mm->locked_vmlater.Iflocked_vmwereatomic,we+*wouldn'tneedthissilliness+*/+vwork=kmalloc(sizeof(structvwork),GFP_KERNEL);+if(!vwork)+return;+mm=get_task_mm(current);+if(!mm){+kfree(vwork);+return;+}+INIT_WORK(&vwork->work,lock_acct_bg);+vwork->mm=mm;+vwork->npage=npage;+schedule_work(&vwork->work);+}
Locked page accounting in this version is very, very broken. How do
powerpc folks feel about seemingly generic kernel iommu interfaces
messing with the current task mm? Besides that, more problems below...
quoted
+
+/*
+ * iommu_reset_table is called when it started/stopped being used.
+ *
+ * restore==true says to bring the iommu_table into the state as it was
+ * before being used by VFIO.
+ */
+void iommu_reset_table(struct iommu_table *tbl, bool restore)
+{
+ /* Page#0 is marked as used in iommu_init_table, so we clear it... */
+ if (!restore && (tbl->it_offset == 0))
+ clear_bit(0, tbl->it_map);
+
+ iommu_clear_tces(tbl, tbl->it_offset, tbl->it_size);
This does locked page accounting and unpins pages, even on startup when
the pages aren't necessarily pinned or accounted against the current
process.
>
quoted
+
+ /* ... or restore */
+ if (restore && (tbl->it_offset == 0))
+ set_bit(0, tbl->it_map);
+}
+EXPORT_SYMBOL_GPL(iommu_reset_table);
+
+/*
+ * Returns the number of used IOMMU pages (4K) within
+ * the same system page (4K or 64K).
+ *
+ * syspage_weight_zero is optimized for expected case == 0
+ * syspage_weight_one is optimized for expected case > 1
+ * Other case are not used in this file.
+ */
+#if PAGE_SIZE == IOMMU_PAGE_SIZE
+
+#define syspage_weight_zero(map, offset) test_bit((map), (offset))
+#define syspage_weight_one(map, offset) test_bit((map), (offset))
+
+#elif PAGE_SIZE/IOMMU_PAGE_SIZE == 16
+
+static int syspage_weight_zero(unsigned long *map, unsigned long offset)
+{
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+ return 0xffffUL & (map[BIT_WORD(offset)] >>
+ (offset & (BITS_PER_LONG-1)));
+}
I would have expected these to be bools and return true if the weight
matches the value.
My expectation was different but ok, I'll fix :)
If you replaced 0xffff above w/ this, would you need the #error below?
(1UL << (PAGE_SIZE/IOMMU_PAGE_SIZE)) - 1)
We have 3 pages size on POWER - 4K, 64K and 16MB. We already handle 4K and
64K and the 16MB case will require much different approach and I am not
sure how/when we will add this so I'd keep it as an error.
quoted
+
+static int syspage_weight_one(unsigned long *map, unsigned long offset)
+{
+ int ret = 0, nbits = PAGE_SIZE/IOMMU_PAGE_SIZE;
+
+ /* Aligns TCE entry number to system page boundary */
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+
+ /* Count used 4K pages */
+ while (nbits && (ret < 2)) {
Don't you have a ffs()? Could also be used for _zero. Surely there are
some bitops helpers that could help here even on big endian. hweight
really doesn't work?
quoted
+ if (test_bit(offset, map))
+ ++ret;
+
+ --nbits;
+ ++offset;
+ }
+
+ return ret;
+}
+#else
+#error TODO: support other page size
+#endif
+
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of system pages
+ * which it called put_page() on
+ */
+static long clear_tces_nolock(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int i, retpages = 0, clr;
+ unsigned long oldtce, oldweight;
+ struct page *page;
+
+ for (i = 0; i < pages; ++i, ++entry) {
+ if (!test_bit(entry - tbl->it_offset, tbl->it_map))
+ continue;
+
+ oldtce = ppc_md.tce_get(tbl, entry);
+ ppc_md.tce_free(tbl, entry, 1);
+
+ oldweight = syspage_weight_one(tbl->it_map,
+ entry - tbl->it_offset);
+ clr = __test_and_clear_bit(entry - tbl->it_offset,
+ tbl->it_map);
+
+ if (WARN_ON(!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))))
+ continue;
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ if (WARN_ON(!page))
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && clr)
+ ++retpages;
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ * of released system pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long ioba,
+ unsigned long size)
+{
+ int ret;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ if ((size & ~IOMMU_PAGE_MASK) || (ioba & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, npages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
+
+ if (ret > 0) {
+ lock_acct(-ret);
+ return 0;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = tce & IOMMU_PAGE_MASK & ~PAGE_MASK;
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret != 1) {
+ pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ return -EFAULT;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight_zero(tbl->it_map, entry - tbl->it_offset);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long ioba,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long size)
+{
+ int i, ret = 0, retpages = 0;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ unsigned long locked, lock_limit;
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ if ((size & ~IOMMU_PAGE_MASK) ||
+ (ioba & ~IOMMU_PAGE_MASK) ||
+ (tce & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ /* Account for locked pages */
+ locked = current->mm->locked_vm +
+ (_ALIGN_UP(size, PAGE_SIZE) >> PAGE_SHIFT);
Looks like we just over penalize upfront and correct when mapped, that's
better, but not great.
What would be great? :)
quoted
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+ if (locked > lock_limit && !capable(CAP_IPC_LOCK)) {
+ pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",
+ rlimit(RLIMIT_MEMLOCK));
+ return -ENOMEM;
+ }
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < npages; ++i) {
+ if (test_bit(entry + i - tbl->it_offset, tbl->it_map)) {
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < npages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0) {
+ clear_tces_nolock(tbl, entry, i);
+ } else {
+ if (retpages)
+ lock_acct(retpages);
+ ret = 0;
+ }
Bug, if it fails we clear, which decrements our locked pages, but we
haven't incremented them yet. Thanks,
static clear_tces_nolock does not touch the counter, extern
iommu_clear_tces does or I missed your point.
--
Alexey
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2012-12-13 02:45:55
On Wed, 2012-12-12 at 16:30 -0700, Alex Williamson wrote:
Locked page accounting in this version is very, very broken. How do
powerpc folks feel about seemingly generic kernel iommu interfaces
messing with the current task mm? Besides that, more problems below...
Not good at all :-)
I don't understand tho ... H_PUT_TCE calls should be in the qemu context
(or the guest) as current at the point of the call, so everything should
be accounted fine on the *current* task when those calls occur, what's
the point of the work queue Alexey ?
This code looks horribly complicated ... where does it come from ?
quoted
+/*
+ * iommu_reset_table is called when it started/stopped being used.
+ *
+ * restore==true says to bring the iommu_table into the state as it was
+ * before being used by VFIO.
+ */
+void iommu_reset_table(struct iommu_table *tbl, bool restore)
+{
+ /* Page#0 is marked as used in iommu_init_table, so we clear it... */
+ if (!restore && (tbl->it_offset == 0))
+ clear_bit(0, tbl->it_map);
+
+ iommu_clear_tces(tbl, tbl->it_offset, tbl->it_size);
This does locked page accounting and unpins pages, even on startup when
the pages aren't necessarily pinned or accounted against the current
process.
Not sure what you mean Alex, and not sure either what Alexey
implementation actually does but indeed, pages inside an iommu table
that was used by the host don't have their refcount elevated by the fact
that they are there.
So when taking ownership of an iommu for vfio, you probably need to FAIL
if any page is already mapped. Only once you know the iommu is clear for
use, then you can start populating it and account for anything you put
in it (and de-account anything you remove from it when cleaning things
up).
quoted
+
+ /* ... or restore */
+ if (restore && (tbl->it_offset == 0))
+ set_bit(0, tbl->it_map);
+}
+EXPORT_SYMBOL_GPL(iommu_reset_table);
+
+/*
+ * Returns the number of used IOMMU pages (4K) within
+ * the same system page (4K or 64K).
+ *
+ * syspage_weight_zero is optimized for expected case == 0
+ * syspage_weight_one is optimized for expected case > 1
+ * Other case are not used in this file.
+ */
+#if PAGE_SIZE == IOMMU_PAGE_SIZE
+
+#define syspage_weight_zero(map, offset) test_bit((map), (offset))
+#define syspage_weight_one(map, offset) test_bit((map), (offset))
+
+#elif PAGE_SIZE/IOMMU_PAGE_SIZE == 16
+
+static int syspage_weight_zero(unsigned long *map, unsigned long offset)
+{
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+ return 0xffffUL & (map[BIT_WORD(offset)] >>
+ (offset & (BITS_PER_LONG-1)));
+}
I would have expected these to be bools and return true if the weight
matches the value.
What is that business anyway ? It's very obscure.
If you replaced 0xffff above w/ this, would you need the #error below?
(1UL << (PAGE_SIZE/IOMMU_PAGE_SIZE)) - 1)
quoted
+
+static int syspage_weight_one(unsigned long *map, unsigned long offset)
+{
+ int ret = 0, nbits = PAGE_SIZE/IOMMU_PAGE_SIZE;
+
+ /* Aligns TCE entry number to system page boundary */
+ offset &= PAGE_MASK >> IOMMU_PAGE_SHIFT;
+
+ /* Count used 4K pages */
+ while (nbits && (ret < 2)) {
Don't you have a ffs()? Could also be used for _zero. Surely there are
some bitops helpers that could help here even on big endian. hweight
really doesn't work?
quoted
+ if (test_bit(offset, map))
+ ++ret;
+
+ --nbits;
+ ++offset;
+ }
+
+ return ret;
+}
+#else
+#error TODO: support other page size
+#endif
What combinations do you support ?
quoted
+static void tce_flush(struct iommu_table *tbl)
+{
+ /* Flush/invalidate TLB caches if necessary */
+ if (ppc_md.tce_flush)
+ ppc_md.tce_flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number of system pages
+ * which it called put_page() on
+ */
+static long clear_tces_nolock(struct iommu_table *tbl, unsigned long entry,
+ unsigned long pages)
+{
+ int i, retpages = 0, clr;
+ unsigned long oldtce, oldweight;
+ struct page *page;
+
+ for (i = 0; i < pages; ++i, ++entry) {
+ if (!test_bit(entry - tbl->it_offset, tbl->it_map))
+ continue;
+
+ oldtce = ppc_md.tce_get(tbl, entry);
+ ppc_md.tce_free(tbl, entry, 1);
+
+ oldweight = syspage_weight_one(tbl->it_map,
+ entry - tbl->it_offset);
+ clr = __test_and_clear_bit(entry - tbl->it_offset,
+ tbl->it_map);
+
+ if (WARN_ON(!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))))
+ continue;
+
+ page = pfn_to_page(oldtce >> PAGE_SHIFT);
+
+ if (WARN_ON(!page))
+ continue;
+
+ if (oldtce & TCE_PCI_WRITE)
+ SetPageDirty(page);
+
+ put_page(page);
+
+ /* That was the last IOMMU page within the system page */
+ if ((oldweight == 1) && clr)
+ ++retpages;
+ }
+
+ return retpages;
+}
+
+/*
+ * iommu_clear_tces clears tces and returned the number
+ * of released system pages
+ */
+long iommu_clear_tces(struct iommu_table *tbl, unsigned long ioba,
+ unsigned long size)
+{
+ int ret;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+
+ if ((size & ~IOMMU_PAGE_MASK) || (ioba & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ spin_lock(&(pool->lock));
+ ret = clear_tces_nolock(tbl, entry, npages);
+ tce_flush(tbl);
+ spin_unlock(&(pool->lock));
Why are you messing with the pools and their locks ? These are only
relevant for the in-kernel use of the table. The table should be locked
out of kernel use when given to vfio (we could add a flag to make any
kernel dma mapping attempt to fail).
quoted
+ if (ret > 0) {
+ lock_acct(-ret);
+ return 0;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_clear_tces);
+
+static int put_tce(struct iommu_table *tbl, unsigned long entry,
+ uint64_t tce, enum dma_data_direction direction)
+{
+ int ret;
+ struct page *page = NULL;
+ unsigned long kva, offset, oldweight;
+
+ /* Map new TCE */
+ offset = tce & IOMMU_PAGE_MASK & ~PAGE_MASK;
+ ret = get_user_pages_fast(tce & PAGE_MASK, 1,
+ direction != DMA_TO_DEVICE, &page);
+ if (ret != 1) {
+ pr_err("tce_vfio: get_user_pages_fast failed tce=%llx ioba=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, ret);
+ return -EFAULT;
+ }
+
+ kva = (unsigned long) page_address(page);
+ kva += offset;
+
+ /* tce_build receives a virtual address */
+ ret = ppc_md.tce_build(tbl, entry, 1, kva, direction, NULL);
+
+ /* tce_build() only returns non-zero for transient errors */
+ if (unlikely(ret)) {
+ pr_err("tce_vfio: tce_put failed on tce=%llx ioba=%lx kva=%lx ret=%d\n",
+ tce, entry << IOMMU_PAGE_SHIFT, kva, ret);
+ put_page(page);
+ return -EIO;
+ }
+
+ /* Calculate if new system page has been locked */
+ oldweight = syspage_weight_zero(tbl->it_map, entry - tbl->it_offset);
+ __set_bit(entry - tbl->it_offset, tbl->it_map);
+
+ return (oldweight == 0) ? 1 : 0;
+}
+
+/*
+ * iommu_put_tces builds tces and returned the number of actually
+ * locked system pages
+ */
+long iommu_put_tces(struct iommu_table *tbl, unsigned long ioba,
+ uint64_t tce, enum dma_data_direction direction,
+ unsigned long size)
+{
+ int i, ret = 0, retpages = 0;
+ unsigned long entry = ioba >> IOMMU_PAGE_SHIFT;
+ unsigned long npages = size >> IOMMU_PAGE_SHIFT;
+ struct iommu_pool *pool = get_pool(tbl, entry);
+ unsigned long locked, lock_limit;
+
+ BUILD_BUG_ON(PAGE_SIZE < IOMMU_PAGE_SIZE);
+ BUG_ON(direction == DMA_NONE);
+
+ if ((size & ~IOMMU_PAGE_MASK) ||
+ (ioba & ~IOMMU_PAGE_MASK) ||
+ (tce & ~IOMMU_PAGE_MASK))
+ return -EINVAL;
+
+ if ((ioba + size) > ((tbl->it_offset + tbl->it_size)
+ << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ if (ioba < (tbl->it_offset << IOMMU_PAGE_SHIFT))
+ return -EINVAL;
+
+ /* Account for locked pages */
+ locked = current->mm->locked_vm +
+ (_ALIGN_UP(size, PAGE_SIZE) >> PAGE_SHIFT);
Looks like we just over penalize upfront and correct when mapped, that's
better, but not great.
quoted
+ lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
+ if (locked > lock_limit && !capable(CAP_IPC_LOCK)) {
+ pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",
+ rlimit(RLIMIT_MEMLOCK));
+ return -ENOMEM;
+ }
+
+ spin_lock(&(pool->lock));
+
+ /* Check if any is in use */
+ for (i = 0; i < npages; ++i) {
+ if (test_bit(entry + i - tbl->it_offset, tbl->it_map)) {
+ spin_unlock(&(pool->lock));
+ return -EBUSY;
+ }
+ }
+
+ /* Put tces to the table */
+ for (i = 0; (i < npages) && (ret >= 0); ++i, tce += IOMMU_PAGE_SIZE) {
+ ret = put_tce(tbl, entry + i, tce, direction);
+ if (ret == 1)
+ ++retpages;
+ }
+
+ /*
+ * If failed, release locked pages, otherwise return the number
+ * of locked system pages
+ */
+ if (ret < 0) {
+ clear_tces_nolock(tbl, entry, i);
+ } else {
+ if (retpages)
+ lock_acct(retpages);
+ ret = 0;
+ }
Bug, if it fails we clear, which decrements our locked pages, but we
haven't incremented them yet. Thanks,
Alex
@@ -613,3 +614,136 @@ void __init pnv_pci_init(void)ppc_md.teardown_msi_irqs=pnv_teardown_msi_irqs;#endif}++#ifdef CONFIG_IOMMU_API+/*+*IOMMUgroupssupportrequiredbyVFIO+*/+staticintadd_device(structdevice*dev)+{+structiommu_table*tbl;+intret=0;++if(WARN_ON(dev->iommu_group)){+pr_warn("tce_vfio: device %s is already in iommu group %d, skipping\n",+dev_name(dev),+iommu_group_id(dev->iommu_group));+return-EBUSY;+}++tbl=get_iommu_table_base(dev);+if(!tbl){+pr_debug("tce_vfio: skipping device %s with no tbl\n",+dev_name(dev));+return0;+}++pr_debug("tce_vfio: adding %s to iommu group %d\n",+dev_name(dev),iommu_group_id(tbl->it_group));++ret=iommu_group_add_device(tbl->it_group,dev);+if(ret<0)+pr_err("tce_vfio: %s has not been added, ret=%d\n",+dev_name(dev),ret);++returnret;+}++staticvoiddel_device(structdevice*dev)+{+iommu_group_remove_device(dev);+}++staticintiommu_bus_notifier(structnotifier_block*nb,+unsignedlongaction,void*data)+{+structdevice*dev=data;++switch(action){+caseBUS_NOTIFY_ADD_DEVICE:+returnadd_device(dev);+caseBUS_NOTIFY_DEL_DEVICE:+del_device(dev);+return0;+default:+return0;+}+}++staticstructnotifier_blocktce_iommu_bus_nb={+.notifier_call=iommu_bus_notifier,+};++staticvoidgroup_release(void*iommu_data)+{+structiommu_table*tbl=iommu_data;+tbl->it_group=NULL;+}++staticint__inittce_iommu_init(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp;++/* Allocate and initialize IOMMU groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;++/* Skip already initialized */+if(tbl->it_group)+continue;++grp=iommu_group_alloc();+if(IS_ERR(grp)){+pr_info("tce_vfio: cannot create new IOMMU group, ret=%ld\n",+PTR_ERR(grp));+returnPTR_ERR(grp);+}+tbl->it_group=grp;+iommu_group_set_iommudata(grp,tbl,group_release);+}++bus_register_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Add PCI devices to VFIO groups */+for_each_pci_dev(pdev)+add_device(&pdev->dev);++return0;+}++staticvoid__exittce_iommu_cleanup(void)+{+structpci_dev*pdev=NULL;+structiommu_table*tbl;+structiommu_group*grp=NULL;++bus_unregister_notifier(&pci_bus_type,&tce_iommu_bus_nb);++/* Delete PCI devices from VFIO groups */+for_each_pci_dev(pdev)+del_device(&pdev->dev);++/* Release VFIO groups */+for_each_pci_dev(pdev){+tbl=get_iommu_table_base(&pdev->dev);+if(!tbl)+continue;+grp=tbl->it_group;++/* Skip (already) uninitialized */+if(!grp)+continue;++/* Do actual release, group_release() is expected to work */+iommu_group_put(grp);+BUG_ON(tbl->it_group);+}+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);+#endif /* CONFIG_IOMMU_API */
From: Benjamin Herrenschmidt <benh@kernel.crashing.org> Date: 2012-12-13 02:58:02
On Wed, 2012-12-12 at 16:30 -0700, Alex Williamson wrote:
Locked page accounting in this version is very, very broken. How do
powerpc folks feel about seemingly generic kernel iommu interfaces
messing with the current task mm? Besides that, more problems
below...
After a second look & thought...
This whole accounting business is fucked. First, we simply can't just
randomly return errors from H_PUT_TCE because the process reached some
rlimit. This is not a proper failure mode. That means that the guest
will probably panic() ... possibly right in the middle of some disk
writeback or god knows what. Not good.
Also the overhead of doing all that crap on every TCE map/unmap is
ridiculous.
Finally, it's just not going to work for real mode which we really want,
since we can't take the mmap-sem in real mode anyway, so unless we
convert that counter to an atomic, we can't do it.
I'd suggest just not bothering, or if you want to bother, check once
when creating a TCE table that the rlimit is enough to bolt as many
pages as can be populated in that table and fail to create *that*. The
failure mode is much better, ie, qemu failing to create a PCI bus due to
insufficient rlimits.
Cheers,
Ben.
From: Alex Williamson <hidden> Date: 2012-12-13 03:22:49
On Thu, 2012-12-13 at 13:57 +1100, Benjamin Herrenschmidt wrote:
On Wed, 2012-12-12 at 16:30 -0700, Alex Williamson wrote:
quoted
Locked page accounting in this version is very, very broken. How do
powerpc folks feel about seemingly generic kernel iommu interfaces
messing with the current task mm? Besides that, more problems
below...
After a second look & thought...
This whole accounting business is fucked. First, we simply can't just
randomly return errors from H_PUT_TCE because the process reached some
rlimit. This is not a proper failure mode. That means that the guest
will probably panic() ... possibly right in the middle of some disk
writeback or god knows what. Not good.
Also the overhead of doing all that crap on every TCE map/unmap is
ridiculous.
Finally, it's just not going to work for real mode which we really want,
since we can't take the mmap-sem in real mode anyway, so unless we
convert that counter to an atomic, we can't do it.
I'd suggest just not bothering, or if you want to bother, check once
when creating a TCE table that the rlimit is enough to bolt as many
pages as can be populated in that table and fail to create *that*. The
failure mode is much better, ie, qemu failing to create a PCI bus due to
insufficient rlimits.
I agree, we don't seem to be headed in the right direction. x86 needs
to track rlimits or else a user can exploit the interface to pin all the
memory in the system. On power, only the iova window can be pinned, so
it's a fixed amount. I could see it as granting access to a group
implicitly grants access to pinning the iova window. We can still make
it more explicit by handling the rlimit accounting upfront. Thanks,
Alex
VFIO implements platform independent stuff such as
a PCI driver, BAR access (via read/write on a file descriptor
or direct mapping when possible) and IRQ signaling.
The platform dependent part includes IOMMU initialization
and handling. This patch implements an IOMMU driver for VFIO
which does mapping/unmapping pages for the guest IO and
provides information about DMA window (required by a POWERPC
guest).
The counterpart in QEMU is required to support this functionality.
Cc: David Gibson <redacted>
Signed-off-by: Alexey Kardashevskiy <redacted>
---
drivers/vfio/Kconfig | 6 +
drivers/vfio/Makefile | 1 +
drivers/vfio/vfio_iommu_spapr_tce.c | 350 +++++++++++++++++++++++++++++++++++
include/linux/vfio.h | 26 +++
4 files changed, 383 insertions(+)
create mode 100644 drivers/vfio/vfio_iommu_spapr_tce.c
@@ -0,0 +1,350 @@+/*+*VFIO:IOMMUDMAmappingsupportforTCEonPOWER+*+*Copyright(C)2012IBMCorp.Allrightsreserved.+*Author:AlexeyKardashevskiy<aik@ozlabs.ru>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseversion2as+*publishedbytheFreeSoftwareFoundation.+*+*Derivedfromoriginalvfio_iommu_type1.c:+*Copyright(C)2012RedHat,Inc.Allrightsreserved.+*Author:AlexWilliamson<alex.williamson@redhat.com>+*/++#include<linux/module.h>+#include<linux/pci.h>+#include<linux/slab.h>+#include<linux/uaccess.h>+#include<linux/err.h>+#include<linux/vfio.h>+#include<asm/iommu.h>++#define DRIVER_VERSION "0.1"+#define DRIVER_AUTHOR "aik@ozlabs.ru"+#define DRIVER_DESC "VFIO IOMMU SPAPR TCE"++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group);++/*+*VFIOIOMMUfdforSPAPR_TCEIOMMUimplementation+*/++/*+*Thiscodehandlesmappingandunmappingofuserdatabuffers+*intoDMA'blespaceusingtheIOMMU+*/++#define NPAGE_TO_SIZE(npage) ((size_t)(npage) << PAGE_SHIFT)++structvwork{+structmm_struct*mm;+longnpage;+structwork_structwork;+};++/* delayed decrement/increment for locked_vm */+staticvoidlock_acct_bg(structwork_struct*work)+{+structvwork*vwork=container_of(work,structvwork,work);+structmm_struct*mm;++mm=vwork->mm;+down_write(&mm->mmap_sem);+mm->locked_vm+=vwork->npage;+up_write(&mm->mmap_sem);+mmput(mm);+kfree(vwork);+}++staticvoidlock_acct(longnpage)+{+structvwork*vwork;+structmm_struct*mm;++if(!current->mm)+return;/* process exited */++if(down_write_trylock(¤t->mm->mmap_sem)){+current->mm->locked_vm+=npage;+up_write(¤t->mm->mmap_sem);+return;+}++/*+*Couldn'tgetmmap_semlock,somustsetuptoupdate+*mm->locked_vmlater.Iflocked_vmwereatomic,we+*wouldn'tneedthissilliness+*/+vwork=kmalloc(sizeof(structvwork),GFP_KERNEL);+if(!vwork)+return;+mm=get_task_mm(current);+if(!mm){+kfree(vwork);+return;+}+INIT_WORK(&vwork->work,lock_acct_bg);+vwork->mm=mm;+vwork->npage=npage;+schedule_work(&vwork->work);+}++/*+*Thecontainerdescriptorsupportsonlyasinglegrouppercontainer.+*RequiredbytheAPIasthecontainerisnotsuppliedwiththeIOMMUgroup+*atthemomentofinitialization.+*/+structtce_container{+structmutexlock;+structiommu_table*tbl;+};++staticvoid*tce_iommu_open(unsignedlongarg)+{+structtce_container*container;++if(arg!=VFIO_SPAPR_TCE_IOMMU){+pr_err("tce_vfio: Wrong IOMMU type\n");+returnERR_PTR(-EINVAL);+}++container=kzalloc(sizeof(*container),GFP_KERNEL);+if(!container)+returnERR_PTR(-ENOMEM);++mutex_init(&container->lock);++returncontainer;+}++staticvoidtce_iommu_release(void*iommu_data)+{+structtce_container*container=iommu_data;++WARN_ON(container->tbl&&!container->tbl->it_group);+if(container->tbl&&container->tbl->it_group)+tce_iommu_detach_group(iommu_data,container->tbl->it_group);++mutex_destroy(&container->lock);++kfree(container);+}++staticlongtce_iommu_ioctl(void*iommu_data,+unsignedintcmd,unsignedlongarg)+{+structtce_container*container=iommu_data;+unsignedlongminsz;+longret;++switch(cmd){+caseVFIO_CHECK_EXTENSION:{+return(arg==VFIO_SPAPR_TCE_IOMMU)?1:0;+}+caseVFIO_IOMMU_SPAPR_TCE_GET_INFO:{+structvfio_iommu_spapr_tce_infoinfo;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(structvfio_iommu_spapr_tce_info,+dma32_window_size);++if(copy_from_user(&info,(void__user*)arg,minsz))+return-EFAULT;++if(info.argsz<minsz)+return-EINVAL;++info.dma32_window_start=tbl->it_offset<<IOMMU_PAGE_SHIFT;+info.dma32_window_size=tbl->it_size<<IOMMU_PAGE_SHIFT;+info.flags=0;++if(copy_to_user((void__user*)arg,&info,minsz))+return-EFAULT;++return0;+}+caseVFIO_IOMMU_MAP_DMA:{+vfio_iommu_spapr_tce_dma_mapparam;+structiommu_table*tbl=container->tbl;+enumdma_data_directiondirection;+unsignedlonglocked,lock_limit;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_map,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++if((param.flags&VFIO_DMA_MAP_FLAG_READ)&&+(param.flags&VFIO_DMA_MAP_FLAG_WRITE))+direction=DMA_BIDIRECTIONAL;+elseif(param.flags&VFIO_DMA_MAP_FLAG_READ)+direction=DMA_TO_DEVICE;+elseif(param.flags&VFIO_DMA_MAP_FLAG_WRITE)+direction=DMA_FROM_DEVICE;+else+return-EINVAL;++if((param.size&~IOMMU_PAGE_MASK)||+(param.iova&~IOMMU_PAGE_MASK)||+(param.vaddr&~IOMMU_PAGE_MASK))+return-EINVAL;++if((param.iova+param.size)>+(tbl->it_size<<IOMMU_PAGE_SHIFT))+return-EINVAL;++if(param.iova<(tbl->it_offset<<IOMMU_PAGE_SHIFT))+return-EINVAL;++/* Account for locked pages */+locked=current->mm->locked_vm++(param.size>>PAGE_SHIFT);+lock_limit=rlimit(RLIMIT_MEMLOCK)>>PAGE_SHIFT;+if(locked>lock_limit&&!capable(CAP_IPC_LOCK)){+pr_warn("RLIMIT_MEMLOCK (%ld) exceeded\n",+rlimit(RLIMIT_MEMLOCK));+return-ENOMEM;+}++ret=iommu_put_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+param.vaddr,direction,+param.size>>IOMMU_PAGE_SHIFT);++if(ret>0)+lock_acct(ret);++returnret;+}+caseVFIO_IOMMU_UNMAP_DMA:{+vfio_iommu_spapr_tce_dma_unmapparam;+structiommu_table*tbl=container->tbl;++if(WARN_ON(!tbl))+return-ENXIO;++minsz=offsetofend(vfio_iommu_spapr_tce_dma_unmap,size);++if(copy_from_user(¶m,(void__user*)arg,minsz))+return-EFAULT;++if(param.argsz<minsz)+return-EINVAL;++if((param.size&~IOMMU_PAGE_MASK)||+(param.iova&~IOMMU_PAGE_MASK))+return-EINVAL;++if((param.iova+param.size)>+(tbl->it_size<<IOMMU_PAGE_SHIFT))+return-EINVAL;++if(param.iova<(tbl->it_offset<<IOMMU_PAGE_SHIFT))+return-EINVAL;++ret=iommu_clear_tces(tbl,param.iova>>IOMMU_PAGE_SHIFT,+param.size>>IOMMU_PAGE_SHIFT);++if(ret>0)+lock_acct(-ret);++returnret;+}+default:+pr_warn("tce_vfio: unexpected cmd %x\n",cmd);+}++return-ENOTTY;+}++staticinttce_iommu_attach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+pr_debug("tce_vfio: Attaching group #%u to iommu %p\n",+iommu_group_id(iommu_group),iommu_group);+if(container->tbl){+pr_warn("tce_vfio: Only one group per IOMMU container is allowed, existing id=%d, attaching id=%d\n",+iommu_group_id(container->tbl->it_group),+iommu_group_id(iommu_group));+mutex_unlock(&container->lock);+return-EBUSY;+}++container->tbl=tbl;+iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);+mutex_unlock(&container->lock);++return0;+}++staticvoidtce_iommu_detach_group(void*iommu_data,+structiommu_group*iommu_group)+{+structtce_container*container=iommu_data;+structiommu_table*tbl=iommu_group_get_iommudata(iommu_group);++BUG_ON(!tbl);+mutex_lock(&container->lock);+if(tbl!=container->tbl){+pr_warn("tce_vfio: detaching group #%u, expected group is #%u\n",+iommu_group_id(iommu_group),+iommu_group_id(tbl->it_group));+}else{++pr_debug("tce_vfio: detaching group #%u from iommu %p\n",+iommu_group_id(iommu_group),iommu_group);++iommu_clear_tces(tbl,tbl->it_offset,tbl->it_size);+container->tbl=NULL;+/* Restore reserve for page 0 */+if(tbl->it_offset==0)+set_bit(0,tbl->it_map);++}+mutex_unlock(&container->lock);+}++conststructvfio_iommu_driver_opstce_iommu_driver_ops={+.name="iommu-vfio-powerpc",+.owner=THIS_MODULE,+.open=tce_iommu_open,+.release=tce_iommu_release,+.ioctl=tce_iommu_ioctl,+.attach_group=tce_iommu_attach_group,+.detach_group=tce_iommu_detach_group,+};++staticint__inittce_iommu_init(void)+{+returnvfio_register_iommu_driver(&tce_iommu_driver_ops);+}++staticvoid__exittce_iommu_cleanup(void)+{+vfio_unregister_iommu_driver(&tce_iommu_driver_ops);+}++module_init(tce_iommu_init);+module_exit(tce_iommu_cleanup);++MODULE_VERSION(DRIVER_VERSION);+MODULE_LICENSE("GPL v2");+MODULE_AUTHOR(DRIVER_AUTHOR);+MODULE_DESCRIPTION(DRIVER_DESC);+
@@ -442,4 +443,29 @@ struct vfio_iommu_type1_dma_unmap {#define VFIO_IOMMU_UNMAP_DMA _IO(VFIO_TYPE, VFIO_BASE + 14)+/* -------- Additional API for SPAPR TCE (Server POWERPC) IOMMU -------- */++/*+*TheSPAPRTCEinfostructprovidestheinformationaboutthePCIbus+*addressrangesavailableforDMA,thesevaluesareprogrammedinto+*thehardwaresotheguesthastoknowthatinformation.+*+*TheIOMMUpagesizeisalways4K.+*/++structvfio_iommu_spapr_tce_info{+__u32argsz;+__u32flags;/* reserved for future use */+__u32dma32_window_start;/* 32 bit window start (bytes) */+__u32dma32_window_size;/* 32 bit window size (bytes) */+};++#define VFIO_IOMMU_SPAPR_TCE_GET_INFO _IO(VFIO_TYPE, VFIO_BASE + 12)++/* Reuse type1 map/unmap structs as they are the same at the moment */+typedefstructvfio_iommu_type1_dma_mapvfio_iommu_spapr_tce_dma_map;+typedefstructvfio_iommu_type1_dma_unmapvfio_iommu_spapr_tce_dma_unmap;++/* ***************************************************************** */+#endif /* VFIO_H */