--- v9
+++ v4
@@ -1,82 +1,599 @@
-Normally a bitmap from the iommu_table is used to track what TCE entry
-is in use. Since we are going to use iommu_table without its locks and
-do xchg() instead, it becomes essential not to put bits which are not
-implied in the direction flag as the old TCE value (more precisely -
-the permission bits) will be used to decide whether to put the page or not.
+This adds a iommu_table_ops struct and puts pointer to it into
+the iommu_table struct. This moves tce_build/tce_free/tce_get/tce_flush
+callbacks from ppc_md to the new struct where they really belong to.
-This adds iommu_direction_to_tce_perm() (its counterpart is there already)
-and uses it for powernv's pnv_tce_build().
+This adds the requirement for @it_ops to be initialized before calling
+iommu_init_table() to make sure that we do not leave any IOMMU table
+with iommu_table_ops uninitialized. This is not a parameter of
+iommu_init_table() though as there will be cases when iommu_init_table()
+will not be called on TCE tables used by VFIO.
+
+This does s/tce_build/set/, s/tce_free/clear/ and removes "tce_"
+redundand prefixes.
+
+This removes tce_xxx_rm handlers from ppc_md but does not add
+them to iommu_table_ops as this will be done later if we decide to
+support TCE hypercalls in real mode.
+
+For pSeries, this always uses tce_buildmulti_pSeriesLP/
+tce_buildmulti_pSeriesLP. This changes multi callback to fall back to
+tce_build_pSeriesLP/tce_free_pSeriesLP if FW_FEATURE_MULTITCE is not
+present. The reason for this is we still have to support "multitce=off"
+boot parameter in disable_multitce() and we do not want to walk through
+all IOMMU tables in the system and replace "multi" callbacks with single
+ones.
Signed-off-by: Alexey Kardashevskiy <aik@ozlabs.ru>
-Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
---
-Changes:
-v9:
-* added comment why we must put only valid permission bits
----
- arch/powerpc/include/asm/iommu.h | 1 +
- arch/powerpc/kernel/iommu.c | 15 +++++++++++++++
- arch/powerpc/platforms/powernv/pci.c | 7 +------
- 3 files changed, 17 insertions(+), 6 deletions(-)
+ arch/powerpc/include/asm/iommu.h | 17 +++++++++++
+ arch/powerpc/include/asm/machdep.h | 25 ----------------
+ arch/powerpc/kernel/iommu.c | 46 +++++++++++++++--------------
+ arch/powerpc/kernel/vio.c | 5 ++++
+ arch/powerpc/platforms/cell/iommu.c | 8 +++--
+ arch/powerpc/platforms/pasemi/iommu.c | 7 +++--
+ arch/powerpc/platforms/powernv/pci-ioda.c | 2 ++
+ arch/powerpc/platforms/powernv/pci-p5ioc2.c | 1 +
+ arch/powerpc/platforms/powernv/pci.c | 23 ++++-----------
+ arch/powerpc/platforms/powernv/pci.h | 1 +
+ arch/powerpc/platforms/pseries/iommu.c | 34 +++++++++++----------
+ arch/powerpc/sysdev/dart_iommu.c | 12 ++++----
+ 12 files changed, 93 insertions(+), 88 deletions(-)
diff --git a/arch/powerpc/include/asm/iommu.h b/arch/powerpc/include/asm/iommu.h
-index 4955233..5eb6e76 100644
+index 45b07f6..eb5822d 100644
--- a/arch/powerpc/include/asm/iommu.h
+++ b/arch/powerpc/include/asm/iommu.h
-@@ -205,6 +205,7 @@ extern int iommu_take_ownership(struct iommu_table *tbl);
- extern void iommu_release_ownership(struct iommu_table *tbl);
-
- extern enum dma_data_direction iommu_tce_direction(unsigned long tce);
-+extern unsigned long iommu_direction_to_tce_perm(enum dma_data_direction dir);
-
- #endif /* __KERNEL__ */
- #endif /* _ASM_IOMMU_H */
+@@ -43,6 +43,22 @@
+ extern int iommu_is_off;
+ extern int iommu_force_on;
+
++struct iommu_table_ops {
++ int (*set)(struct iommu_table *tbl,
++ long index, long npages,
++ unsigned long uaddr,
++ enum dma_data_direction direction,
++ struct dma_attrs *attrs);
++ void (*clear)(struct iommu_table *tbl,
++ long index, long npages);
++ unsigned long (*get)(struct iommu_table *tbl, long index);
++ void (*flush)(struct iommu_table *tbl);
++};
++
++/* These are used by VIO */
++extern struct iommu_table_ops iommu_table_lpar_multi_ops;
++extern struct iommu_table_ops iommu_table_pseries_ops;
++
+ /*
+ * IOMAP_MAX_ORDER defines the largest contiguous block
+ * of dma space we can get. IOMAP_MAX_ORDER = 13
+@@ -77,6 +93,7 @@ struct iommu_table {
+ #ifdef CONFIG_IOMMU_API
+ struct iommu_group *it_group;
+ #endif
++ struct iommu_table_ops *it_ops;
+ void (*set_bypass)(struct iommu_table *tbl, bool enable);
+ };
+
+diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h
+index c8175a3..2abe744 100644
+--- a/arch/powerpc/include/asm/machdep.h
++++ b/arch/powerpc/include/asm/machdep.h
+@@ -65,31 +65,6 @@ struct machdep_calls {
+ * destroyed as well */
+ void (*hpte_clear_all)(void);
+
+- int (*tce_build)(struct iommu_table *tbl,
+- long index,
+- long npages,
+- unsigned long uaddr,
+- enum dma_data_direction direction,
+- struct dma_attrs *attrs);
+- void (*tce_free)(struct iommu_table *tbl,
+- long index,
+- long npages);
+- unsigned long (*tce_get)(struct iommu_table *tbl,
+- long index);
+- void (*tce_flush)(struct iommu_table *tbl);
+-
+- /* _rm versions are for real mode use only */
+- int (*tce_build_rm)(struct iommu_table *tbl,
+- long index,
+- long npages,
+- unsigned long uaddr,
+- enum dma_data_direction direction,
+- struct dma_attrs *attrs);
+- void (*tce_free_rm)(struct iommu_table *tbl,
+- long index,
+- long npages);
+- void (*tce_flush_rm)(struct iommu_table *tbl);
+-
+ void __iomem * (*ioremap)(phys_addr_t addr, unsigned long size,
+ unsigned long flags, void *caller);
+ void (*iounmap)(volatile void __iomem *token);
diff --git a/arch/powerpc/kernel/iommu.c b/arch/powerpc/kernel/iommu.c
-index fc8b253..e0e94c7 100644
+index 456acb1..c51ad3e 100644
--- a/arch/powerpc/kernel/iommu.c
+++ b/arch/powerpc/kernel/iommu.c
-@@ -881,6 +881,21 @@ void iommu_free_coherent(struct iommu_table *tbl, size_t size,
- }
- }
-
-+unsigned long iommu_direction_to_tce_perm(enum dma_data_direction dir)
-+{
-+ switch (dir) {
-+ case DMA_BIDIRECTIONAL:
-+ return TCE_PCI_READ | TCE_PCI_WRITE;
-+ case DMA_FROM_DEVICE:
-+ return TCE_PCI_WRITE;
-+ case DMA_TO_DEVICE:
-+ return TCE_PCI_READ;
-+ default:
-+ return 0;
-+ }
-+}
-+EXPORT_SYMBOL_GPL(iommu_direction_to_tce_perm);
-+
- #ifdef CONFIG_IOMMU_API
- /*
- * SPAPR TCE API
+@@ -322,11 +322,11 @@ static dma_addr_t iommu_alloc(struct device *dev, struct iommu_table *tbl,
+ ret = entry << tbl->it_page_shift; /* Set the return dma address */
+
+ /* Put the TCEs in the HW table */
+- build_fail = ppc_md.tce_build(tbl, entry, npages,
++ build_fail = tbl->it_ops->set(tbl, entry, npages,
+ (unsigned long)page &
+ IOMMU_PAGE_MASK(tbl), direction, attrs);
+
+- /* ppc_md.tce_build() only returns non-zero for transient errors.
++ /* tbl->it_ops->set() only returns non-zero for transient errors.
+ * Clean up the table bitmap in this case and return
+ * DMA_ERROR_CODE. For all other errors the functionality is
+ * not altered.
+@@ -337,8 +337,8 @@ static dma_addr_t iommu_alloc(struct device *dev, struct iommu_table *tbl,
+ }
+
+ /* Flush/invalidate TLB caches if necessary */
+- if (ppc_md.tce_flush)
+- ppc_md.tce_flush(tbl);
++ if (tbl->it_ops->flush)
++ tbl->it_ops->flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+@@ -408,7 +408,7 @@ static void __iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
+ if (!iommu_free_check(tbl, dma_addr, npages))
+ return;
+
+- ppc_md.tce_free(tbl, entry, npages);
++ tbl->it_ops->clear(tbl, entry, npages);
+
+ spin_lock_irqsave(&(pool->lock), flags);
+ bitmap_clear(tbl->it_map, free_entry, npages);
+@@ -424,8 +424,8 @@ static void iommu_free(struct iommu_table *tbl, dma_addr_t dma_addr,
+ * not do an mb() here on purpose, it is not needed on any of
+ * the current platforms.
+ */
+- if (ppc_md.tce_flush)
+- ppc_md.tce_flush(tbl);
++ if (tbl->it_ops->flush)
++ tbl->it_ops->flush(tbl);
+ }
+
+ int ppc_iommu_map_sg(struct device *dev, struct iommu_table *tbl,
+@@ -495,7 +495,7 @@ int ppc_iommu_map_sg(struct device *dev, struct iommu_table *tbl,
+ npages, entry, dma_addr);
+
+ /* Insert into HW table */
+- build_fail = ppc_md.tce_build(tbl, entry, npages,
++ build_fail = tbl->it_ops->set(tbl, entry, npages,
+ vaddr & IOMMU_PAGE_MASK(tbl),
+ direction, attrs);
+ if(unlikely(build_fail))
+@@ -534,8 +534,8 @@ int ppc_iommu_map_sg(struct device *dev, struct iommu_table *tbl,
+ }
+
+ /* Flush/invalidate TLB caches if necessary */
+- if (ppc_md.tce_flush)
+- ppc_md.tce_flush(tbl);
++ if (tbl->it_ops->flush)
++ tbl->it_ops->flush(tbl);
+
+ DBG("mapped %d elements:\n", outcount);
+
+@@ -600,8 +600,8 @@ void ppc_iommu_unmap_sg(struct iommu_table *tbl, struct scatterlist *sglist,
+ * do not do an mb() here, the affected platforms do not need it
+ * when freeing.
+ */
+- if (ppc_md.tce_flush)
+- ppc_md.tce_flush(tbl);
++ if (tbl->it_ops->flush)
++ tbl->it_ops->flush(tbl);
+ }
+
+ static void iommu_table_clear(struct iommu_table *tbl)
+@@ -613,17 +613,17 @@ static void iommu_table_clear(struct iommu_table *tbl)
+ */
+ if (!is_kdump_kernel() || is_fadump_active()) {
+ /* Clear the table in case firmware left allocations in it */
+- ppc_md.tce_free(tbl, tbl->it_offset, tbl->it_size);
++ tbl->it_ops->clear(tbl, tbl->it_offset, tbl->it_size);
+ return;
+ }
+
+ #ifdef CONFIG_CRASH_DUMP
+- if (ppc_md.tce_get) {
++ if (tbl->it_ops->get) {
+ unsigned long index, tceval, tcecount = 0;
+
+ /* Reserve the existing mappings left by the first kernel. */
+ for (index = 0; index < tbl->it_size; index++) {
+- tceval = ppc_md.tce_get(tbl, index + tbl->it_offset);
++ tceval = tbl->it_ops->get(tbl, index + tbl->it_offset);
+ /*
+ * Freed TCE entry contains 0x7fffffffffffffff on JS20
+ */
+@@ -657,6 +657,8 @@ struct iommu_table *iommu_init_table(struct iommu_table *tbl, int nid)
+ unsigned int i;
+ struct iommu_pool *p;
+
++ BUG_ON(!tbl->it_ops);
++
+ /* number of bytes needed for the bitmap */
+ sz = BITS_TO_LONGS(tbl->it_size) * sizeof(unsigned long);
+
+@@ -906,8 +908,8 @@ void iommu_register_group(struct iommu_table *tbl,
+ void iommu_flush_tce(struct iommu_table *tbl)
+ {
+ /* Flush/invalidate TLB caches if necessary */
+- if (ppc_md.tce_flush)
+- ppc_md.tce_flush(tbl);
++ if (tbl->it_ops->flush)
++ tbl->it_ops->flush(tbl);
+
+ /* Make sure updates are seen by hardware */
+ mb();
+@@ -918,7 +920,7 @@ int iommu_tce_clear_param_check(struct iommu_table *tbl,
+ unsigned long ioba, unsigned long tce_value,
+ unsigned long npages)
+ {
+- /* ppc_md.tce_free() does not support any value but 0 */
++ /* tbl->it_ops->clear() does not support any value but 0 */
+ if (tce_value)
+ return -EINVAL;
+
+@@ -966,9 +968,9 @@ unsigned long iommu_clear_tce(struct iommu_table *tbl, unsigned long entry)
+
+ spin_lock(&(pool->lock));
+
+- oldtce = ppc_md.tce_get(tbl, entry);
++ oldtce = tbl->it_ops->get(tbl, entry);
+ if (oldtce & (TCE_PCI_WRITE | TCE_PCI_READ))
+- ppc_md.tce_free(tbl, entry, 1);
++ tbl->it_ops->clear(tbl, entry, 1);
+ else
+ oldtce = 0;
+
+@@ -991,10 +993,10 @@ int iommu_tce_build(struct iommu_table *tbl, unsigned long entry,
+
+ spin_lock(&(pool->lock));
+
+- oldtce = ppc_md.tce_get(tbl, entry);
++ oldtce = tbl->it_ops->get(tbl, entry);
+ /* Add new entry if it is not busy */
+ if (!(oldtce & (TCE_PCI_WRITE | TCE_PCI_READ)))
+- ret = ppc_md.tce_build(tbl, entry, 1, hwaddr, direction, NULL);
++ ret = tbl->it_ops->set(tbl, entry, 1, hwaddr, direction, NULL);
+
+ spin_unlock(&(pool->lock));
+
+diff --git a/arch/powerpc/kernel/vio.c b/arch/powerpc/kernel/vio.c
+index 5bfdab9..b41426c 100644
+--- a/arch/powerpc/kernel/vio.c
++++ b/arch/powerpc/kernel/vio.c
+@@ -1196,6 +1196,11 @@ static struct iommu_table *vio_build_iommu_table(struct vio_dev *dev)
+ tbl->it_type = TCE_VB;
+ tbl->it_blocksize = 16;
+
++ if (firmware_has_feature(FW_FEATURE_LPAR))
++ tbl->it_ops = &iommu_table_lpar_multi_ops;
++ else
++ tbl->it_ops = &iommu_table_pseries_ops;
++
+ return iommu_init_table(tbl, -1);
+ }
+
+diff --git a/arch/powerpc/platforms/cell/iommu.c b/arch/powerpc/platforms/cell/iommu.c
+index c7c8720..72763a8 100644
+--- a/arch/powerpc/platforms/cell/iommu.c
++++ b/arch/powerpc/platforms/cell/iommu.c
+@@ -465,6 +465,11 @@ static inline u32 cell_iommu_get_ioid(struct device_node *np)
+ return *ioid;
+ }
+
++static struct iommu_table_ops cell_iommu_ops = {
++ .set = tce_build_cell,
++ .clear = tce_free_cell
++};
++
+ static struct iommu_window * __init
+ cell_iommu_setup_window(struct cbe_iommu *iommu, struct device_node *np,
+ unsigned long offset, unsigned long size,
+@@ -491,6 +496,7 @@ cell_iommu_setup_window(struct cbe_iommu *iommu, struct device_node *np,
+ window->table.it_offset =
+ (offset >> window->table.it_page_shift) + pte_offset;
+ window->table.it_size = size >> window->table.it_page_shift;
++ window->table.it_ops = &cell_iommu_ops;
+
+ iommu_init_table(&window->table, iommu->nid);
+
+@@ -1200,8 +1206,6 @@ static int __init cell_iommu_init(void)
+ /* Setup various ppc_md. callbacks */
+ ppc_md.pci_dma_dev_setup = cell_pci_dma_dev_setup;
+ ppc_md.dma_get_required_mask = cell_dma_get_required_mask;
+- ppc_md.tce_build = tce_build_cell;
+- ppc_md.tce_free = tce_free_cell;
+
+ if (!iommu_fixed_disabled && cell_iommu_fixed_mapping_init() == 0)
+ goto bail;
+diff --git a/arch/powerpc/platforms/pasemi/iommu.c b/arch/powerpc/platforms/pasemi/iommu.c
+index 2e576f2..b7245b2 100644
+--- a/arch/powerpc/platforms/pasemi/iommu.c
++++ b/arch/powerpc/platforms/pasemi/iommu.c
+@@ -132,6 +132,10 @@ static void iobmap_free(struct iommu_table *tbl, long index,
+ }
+ }
+
++static struct iommu_table_ops iommu_table_iobmap_ops = {
++ .set = iobmap_build,
++ .clear = iobmap_free
++};
+
+ static void iommu_table_iobmap_setup(void)
+ {
+@@ -151,6 +155,7 @@ static void iommu_table_iobmap_setup(void)
+ * Should probably be 8 (64 bytes)
+ */
+ iommu_table_iobmap.it_blocksize = 4;
++ iommu_table_iobmap.it_ops = &iommu_table_iobmap_ops;
+ iommu_init_table(&iommu_table_iobmap, 0);
+ pr_debug(" <- %s\n", __func__);
+ }
+@@ -250,8 +255,6 @@ void __init iommu_init_early_pasemi(void)
+
+ ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_pasemi;
+ ppc_md.pci_dma_bus_setup = pci_dma_bus_setup_pasemi;
+- ppc_md.tce_build = iobmap_build;
+- ppc_md.tce_free = iobmap_free;
+ set_pci_dma_ops(&dma_iommu_ops);
+ }
+
+diff --git a/arch/powerpc/platforms/powernv/pci-ioda.c b/arch/powerpc/platforms/powernv/pci-ioda.c
+index 5c74333..af7a689 100644
+--- a/arch/powerpc/platforms/powernv/pci-ioda.c
++++ b/arch/powerpc/platforms/powernv/pci-ioda.c
+@@ -1208,6 +1208,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,
+ TCE_PCI_SWINV_FREE |
+ TCE_PCI_SWINV_PAIR);
+ }
++ tbl->it_ops = &pnv_iommu_ops;
+ iommu_init_table(tbl, phb->hose->node);
+ iommu_register_group(tbl, phb->hose->global_number, pe->pe_number);
+
+@@ -1341,6 +1342,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,
+ 8);
+ tbl->it_type |= (TCE_PCI_SWINV_CREATE | TCE_PCI_SWINV_FREE);
+ }
++ tbl->it_ops = &pnv_iommu_ops;
+ iommu_init_table(tbl, phb->hose->node);
+ iommu_register_group(tbl, phb->hose->global_number, pe->pe_number);
+
+diff --git a/arch/powerpc/platforms/powernv/pci-p5ioc2.c b/arch/powerpc/platforms/powernv/pci-p5ioc2.c
+index 6ef6d4d..0256fcc 100644
+--- a/arch/powerpc/platforms/powernv/pci-p5ioc2.c
++++ b/arch/powerpc/platforms/powernv/pci-p5ioc2.c
+@@ -87,6 +87,7 @@ static void pnv_pci_p5ioc2_dma_dev_setup(struct pnv_phb *phb,
+ struct pci_dev *pdev)
+ {
+ if (phb->p5ioc2.iommu_table.it_map == NULL) {
++ phb->p5ioc2.iommu_table.it_ops = &pnv_iommu_ops;
+ iommu_init_table(&phb->p5ioc2.iommu_table, phb->hose->node);
+ iommu_register_group(&phb->p5ioc2.iommu_table,
+ pci_domain_nr(phb->hose->bus), phb->opal_id);
diff --git a/arch/powerpc/platforms/powernv/pci.c b/arch/powerpc/platforms/powernv/pci.c
-index bca2aeb..b7ea245 100644
+index 9ec7d68..c4782b1 100644
--- a/arch/powerpc/platforms/powernv/pci.c
+++ b/arch/powerpc/platforms/powernv/pci.c
-@@ -576,15 +576,10 @@ static int pnv_tce_build(struct iommu_table *tbl, long index, long npages,
- unsigned long uaddr, enum dma_data_direction direction,
- struct dma_attrs *attrs, bool rm)
- {
-- u64 proto_tce;
-+ u64 proto_tce = iommu_direction_to_tce_perm(direction);
- __be64 *tcep, *tces;
- u64 rpn;
-
-- proto_tce = TCE_PCI_READ; // Read allowed
+@@ -660,18 +660,11 @@ static unsigned long pnv_tce_get(struct iommu_table *tbl, long index)
+ return ((u64 *)tbl->it_base)[index - tbl->it_offset];
+ }
+
+-static int pnv_tce_build_rm(struct iommu_table *tbl, long index, long npages,
+- unsigned long uaddr,
+- enum dma_data_direction direction,
+- struct dma_attrs *attrs)
+-{
+- return pnv_tce_build(tbl, index, npages, uaddr, direction, attrs, true);
+-}
-
-- if (direction != DMA_TO_DEVICE)
-- proto_tce |= TCE_PCI_WRITE;
+-static void pnv_tce_free_rm(struct iommu_table *tbl, long index, long npages)
+-{
+- pnv_tce_free(tbl, index, npages, true);
+-}
++struct iommu_table_ops pnv_iommu_ops = {
++ .set = pnv_tce_build_vm,
++ .clear = pnv_tce_free_vm,
++ .get = pnv_tce_get,
++};
+
+ void pnv_pci_setup_iommu_table(struct iommu_table *tbl,
+ void *tce_mem, u64 tce_size,
+@@ -705,6 +698,7 @@ static struct iommu_table *pnv_pci_setup_bml_iommu(struct pci_controller *hose)
+ return NULL;
+ pnv_pci_setup_iommu_table(tbl, __va(be64_to_cpup(basep)),
+ be32_to_cpup(sizep), 0, IOMMU_PAGE_SHIFT_4K);
++ tbl->it_ops = &pnv_iommu_ops;
+ iommu_init_table(tbl, hose->node);
+ iommu_register_group(tbl, pci_domain_nr(hose->bus), 0);
+
+@@ -859,11 +853,6 @@ 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_vm;
+- ppc_md.tce_free = pnv_tce_free_vm;
+- ppc_md.tce_build_rm = pnv_tce_build_rm;
+- ppc_md.tce_free_rm = pnv_tce_free_rm;
+- ppc_md.tce_get = pnv_tce_get;
+ ppc_md.pci_probe_mode = pnv_pci_probe_mode;
+ set_pci_dma_ops(&dma_iommu_ops);
+
+diff --git a/arch/powerpc/platforms/powernv/pci.h b/arch/powerpc/platforms/powernv/pci.h
+index 6c02ff8..f726700 100644
+--- a/arch/powerpc/platforms/powernv/pci.h
++++ b/arch/powerpc/platforms/powernv/pci.h
+@@ -216,6 +216,7 @@ extern struct pci_ops pnv_pci_ops;
+ #ifdef CONFIG_EEH
+ extern struct pnv_eeh_ops ioda_eeh_ops;
+ #endif
++extern struct iommu_table_ops pnv_iommu_ops;
+
+ void pnv_pci_dump_phb_diag_data(struct pci_controller *hose,
+ unsigned char *log_buff);
+diff --git a/arch/powerpc/platforms/pseries/iommu.c b/arch/powerpc/platforms/pseries/iommu.c
+index 1d3d52d..1aa1815 100644
+--- a/arch/powerpc/platforms/pseries/iommu.c
++++ b/arch/powerpc/platforms/pseries/iommu.c
+@@ -192,7 +192,7 @@ static int tce_buildmulti_pSeriesLP(struct iommu_table *tbl, long tcenum,
+ int ret = 0;
+ unsigned long flags;
+
+- if (npages == 1) {
++ if ((npages == 1) || !firmware_has_feature(FW_FEATURE_MULTITCE)) {
+ return tce_build_pSeriesLP(tbl, tcenum, npages, uaddr,
+ direction, attrs);
+ }
+@@ -284,6 +284,9 @@ static void tce_freemulti_pSeriesLP(struct iommu_table *tbl, long tcenum, long n
+ {
+ u64 rc;
+
++ if (!firmware_has_feature(FW_FEATURE_MULTITCE))
++ return tce_free_pSeriesLP(tbl, tcenum, npages);
++
+ rc = plpar_tce_stuff((u64)tbl->it_index, (u64)tcenum << 12, 0, npages);
+
+ if (rc && printk_ratelimit()) {
+@@ -459,7 +462,6 @@ static int tce_setrange_multi_pSeriesLP_walk(unsigned long start_pfn,
+ return tce_setrange_multi_pSeriesLP(start_pfn, num_pfn, arg);
+ }
+
-
- tces = tcep = ((__be64 *)tbl->it_base) + index - tbl->it_offset;
- rpn = __pa(uaddr) >> tbl->it_page_shift;
-
+ #ifdef CONFIG_PCI
+ static void iommu_table_setparms(struct pci_controller *phb,
+ struct device_node *dn,
+@@ -545,6 +547,12 @@ static void iommu_table_setparms_lpar(struct pci_controller *phb,
+ tbl->it_size = size >> tbl->it_page_shift;
+ }
+
++struct iommu_table_ops iommu_table_pseries_ops = {
++ .set = tce_build_pSeries,
++ .clear = tce_free_pSeries,
++ .get = tce_get_pseries
++};
++
+ static void pci_dma_bus_setup_pSeries(struct pci_bus *bus)
+ {
+ struct device_node *dn;
+@@ -613,6 +621,7 @@ static void pci_dma_bus_setup_pSeries(struct pci_bus *bus)
+ pci->phb->node);
+
+ iommu_table_setparms(pci->phb, dn, tbl);
++ tbl->it_ops = &iommu_table_pseries_ops;
+ pci->iommu_table = iommu_init_table(tbl, pci->phb->node);
+ iommu_register_group(tbl, pci_domain_nr(bus), 0);
+
+@@ -624,6 +633,11 @@ static void pci_dma_bus_setup_pSeries(struct pci_bus *bus)
+ pr_debug("ISA/IDE, window size is 0x%llx\n", pci->phb->dma_window_size);
+ }
+
++struct iommu_table_ops iommu_table_lpar_multi_ops = {
++ .set = tce_buildmulti_pSeriesLP,
++ .clear = tce_freemulti_pSeriesLP,
++ .get = tce_get_pSeriesLP
++};
+
+ static void pci_dma_bus_setup_pSeriesLP(struct pci_bus *bus)
+ {
+@@ -658,6 +672,7 @@ static void pci_dma_bus_setup_pSeriesLP(struct pci_bus *bus)
+ tbl = kzalloc_node(sizeof(struct iommu_table), GFP_KERNEL,
+ ppci->phb->node);
+ iommu_table_setparms_lpar(ppci->phb, pdn, tbl, dma_window);
++ tbl->it_ops = &iommu_table_lpar_multi_ops;
+ ppci->iommu_table = iommu_init_table(tbl, ppci->phb->node);
+ iommu_register_group(tbl, pci_domain_nr(bus), 0);
+ pr_debug(" created table: %p\n", ppci->iommu_table);
+@@ -685,6 +700,7 @@ static void pci_dma_dev_setup_pSeries(struct pci_dev *dev)
+ tbl = kzalloc_node(sizeof(struct iommu_table), GFP_KERNEL,
+ phb->node);
+ iommu_table_setparms(phb, dn, tbl);
++ tbl->it_ops = &iommu_table_pseries_ops;
+ PCI_DN(dn)->iommu_table = iommu_init_table(tbl, phb->node);
+ iommu_register_group(tbl, pci_domain_nr(phb->bus), 0);
+ set_iommu_table_base_and_group(&dev->dev,
+@@ -1107,6 +1123,7 @@ static void pci_dma_dev_setup_pSeriesLP(struct pci_dev *dev)
+ tbl = kzalloc_node(sizeof(struct iommu_table), GFP_KERNEL,
+ pci->phb->node);
+ iommu_table_setparms_lpar(pci->phb, pdn, tbl, dma_window);
++ tbl->it_ops = &iommu_table_lpar_multi_ops;
+ pci->iommu_table = iommu_init_table(tbl, pci->phb->node);
+ iommu_register_group(tbl, pci_domain_nr(pci->phb->bus), 0);
+ pr_debug(" created table: %p\n", pci->iommu_table);
+@@ -1299,22 +1316,11 @@ void iommu_init_early_pSeries(void)
+ return;
+
+ if (firmware_has_feature(FW_FEATURE_LPAR)) {
+- if (firmware_has_feature(FW_FEATURE_MULTITCE)) {
+- ppc_md.tce_build = tce_buildmulti_pSeriesLP;
+- ppc_md.tce_free = tce_freemulti_pSeriesLP;
+- } else {
+- ppc_md.tce_build = tce_build_pSeriesLP;
+- ppc_md.tce_free = tce_free_pSeriesLP;
+- }
+- ppc_md.tce_get = tce_get_pSeriesLP;
+ ppc_md.pci_dma_bus_setup = pci_dma_bus_setup_pSeriesLP;
+ ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_pSeriesLP;
+ ppc_md.dma_set_mask = dma_set_mask_pSeriesLP;
+ ppc_md.dma_get_required_mask = dma_get_required_mask_pSeriesLP;
+ } else {
+- ppc_md.tce_build = tce_build_pSeries;
+- ppc_md.tce_free = tce_free_pSeries;
+- ppc_md.tce_get = tce_get_pseries;
+ ppc_md.pci_dma_bus_setup = pci_dma_bus_setup_pSeries;
+ ppc_md.pci_dma_dev_setup = pci_dma_dev_setup_pSeries;
+ }
+@@ -1332,8 +1338,6 @@ static int __init disable_multitce(char *str)
+ firmware_has_feature(FW_FEATURE_LPAR) &&
+ firmware_has_feature(FW_FEATURE_MULTITCE)) {
+ printk(KERN_INFO "Disabling MULTITCE firmware feature\n");
+- ppc_md.tce_build = tce_build_pSeriesLP;
+- ppc_md.tce_free = tce_free_pSeriesLP;
+ powerpc_firmware_features &= ~FW_FEATURE_MULTITCE;
+ }
+ return 1;
+diff --git a/arch/powerpc/sysdev/dart_iommu.c b/arch/powerpc/sysdev/dart_iommu.c
+index 9e5353f..ab361a3 100644
+--- a/arch/powerpc/sysdev/dart_iommu.c
++++ b/arch/powerpc/sysdev/dart_iommu.c
+@@ -286,6 +286,12 @@ static int __init dart_init(struct device_node *dart_node)
+ return 0;
+ }
+
++static struct iommu_table_ops iommu_dart_ops = {
++ .set = dart_build,
++ .clear = dart_free,
++ .flush = dart_flush,
++};
++
+ static void iommu_table_dart_setup(void)
+ {
+ iommu_table_dart.it_busno = 0;
+@@ -298,6 +304,7 @@ static void iommu_table_dart_setup(void)
+ iommu_table_dart.it_base = (unsigned long)dart_vbase;
+ iommu_table_dart.it_index = 0;
+ iommu_table_dart.it_blocksize = 1;
++ iommu_table_dart.it_ops = &iommu_dart_ops;
+ iommu_init_table(&iommu_table_dart, -1);
+
+ /* Reserve the last page of the DART to avoid possible prefetch
+@@ -386,11 +393,6 @@ void __init iommu_init_early_dart(void)
+ if (dart_init(dn) != 0)
+ goto bail;
+
+- /* Setup low level TCE operations for the core IOMMU code */
+- ppc_md.tce_build = dart_build;
+- ppc_md.tce_free = dart_free;
+- ppc_md.tce_flush = dart_flush;
+-
+ /* Setup bypass if supported */
+ if (dart_is_u4)
+ ppc_md.dma_set_mask = dart_dma_set_mask;
--
2.0.0