| From 7559d3d295f3365ea7ac0c0274c05e633fe4f594 Mon Sep 17 00:00:00 2001 |
| From: Alexey Kardashevskiy <aik@ozlabs.ru> |
| Date: Mon, 16 Dec 2019 15:19:22 +1100 |
| Subject: powerpc/pseries: Allow not having ibm, hypertas-functions::hcall-multi-tce for DDW |
| |
| From: Alexey Kardashevskiy <aik@ozlabs.ru> |
| |
| commit 7559d3d295f3365ea7ac0c0274c05e633fe4f594 upstream. |
| |
| By default a pseries guest supports a H_PUT_TCE hypercall which maps |
| a single IOMMU page in a DMA window. Additionally the hypervisor may |
| support H_PUT_TCE_INDIRECT/H_STUFF_TCE which update multiple TCEs at once; |
| this is advertised via the device tree /rtas/ibm,hypertas-functions |
| property which Linux converts to FW_FEATURE_MULTITCE. |
| |
| FW_FEATURE_MULTITCE is checked when dma_iommu_ops is used; however |
| the code managing the huge DMA window (DDW) ignores it and calls |
| H_PUT_TCE_INDIRECT even if it is explicitly disabled via |
| the "multitce=off" kernel command line parameter. |
| |
| This adds FW_FEATURE_MULTITCE checking to the DDW code path. |
| |
| This changes tce_build_pSeriesLP to take liobn and page size as |
| the huge window does not have iommu_table descriptor which usually |
| the place to store these numbers. |
| |
| Fixes: 4e8b0cf46b25 ("powerpc/pseries: Add support for dynamic dma windows") |
| Signed-off-by: Alexey Kardashevskiy <aik@ozlabs.ru> |
| Reviewed-by: Thiago Jung Bauermann <bauerman@linux.ibm.com> |
| Tested-by: Thiago Jung Bauermann <bauerman@linux.ibm.com> |
| Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> |
| Link: https://lore.kernel.org/r/20191216041924.42318-3-aik@ozlabs.ru |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| arch/powerpc/platforms/pseries/iommu.c | 43 ++++++++++++++++++++++----------- |
| 1 file changed, 29 insertions(+), 14 deletions(-) |
| |
| --- a/arch/powerpc/platforms/pseries/iommu.c |
| +++ b/arch/powerpc/platforms/pseries/iommu.c |
| @@ -132,10 +132,10 @@ static unsigned long tce_get_pseries(str |
| return be64_to_cpu(*tcep); |
| } |
| |
| -static void tce_free_pSeriesLP(struct iommu_table*, long, long); |
| +static void tce_free_pSeriesLP(unsigned long liobn, long, long); |
| static void tce_freemulti_pSeriesLP(struct iommu_table*, long, long); |
| |
| -static int tce_build_pSeriesLP(struct iommu_table *tbl, long tcenum, |
| +static int tce_build_pSeriesLP(unsigned long liobn, long tcenum, long tceshift, |
| long npages, unsigned long uaddr, |
| enum dma_data_direction direction, |
| unsigned long attrs) |
| @@ -146,25 +146,25 @@ static int tce_build_pSeriesLP(struct io |
| int ret = 0; |
| long tcenum_start = tcenum, npages_start = npages; |
| |
| - rpn = __pa(uaddr) >> TCE_SHIFT; |
| + rpn = __pa(uaddr) >> tceshift; |
| proto_tce = TCE_PCI_READ; |
| if (direction != DMA_TO_DEVICE) |
| proto_tce |= TCE_PCI_WRITE; |
| |
| while (npages--) { |
| - tce = proto_tce | (rpn & TCE_RPN_MASK) << TCE_RPN_SHIFT; |
| - rc = plpar_tce_put((u64)tbl->it_index, (u64)tcenum << 12, tce); |
| + tce = proto_tce | (rpn & TCE_RPN_MASK) << tceshift; |
| + rc = plpar_tce_put((u64)liobn, (u64)tcenum << tceshift, tce); |
| |
| if (unlikely(rc == H_NOT_ENOUGH_RESOURCES)) { |
| ret = (int)rc; |
| - tce_free_pSeriesLP(tbl, tcenum_start, |
| + tce_free_pSeriesLP(liobn, tcenum_start, |
| (npages_start - (npages + 1))); |
| break; |
| } |
| |
| if (rc && printk_ratelimit()) { |
| printk("tce_build_pSeriesLP: plpar_tce_put failed. rc=%lld\n", rc); |
| - printk("\tindex = 0x%llx\n", (u64)tbl->it_index); |
| + printk("\tindex = 0x%llx\n", (u64)liobn); |
| printk("\ttcenum = 0x%llx\n", (u64)tcenum); |
| printk("\ttce val = 0x%llx\n", tce ); |
| dump_stack(); |
| @@ -193,7 +193,8 @@ static int tce_buildmulti_pSeriesLP(stru |
| unsigned long flags; |
| |
| if ((npages == 1) || !firmware_has_feature(FW_FEATURE_MULTITCE)) { |
| - return tce_build_pSeriesLP(tbl, tcenum, npages, uaddr, |
| + return tce_build_pSeriesLP(tbl->it_index, tcenum, |
| + tbl->it_page_shift, npages, uaddr, |
| direction, attrs); |
| } |
| |
| @@ -209,8 +210,9 @@ static int tce_buildmulti_pSeriesLP(stru |
| /* If allocation fails, fall back to the loop implementation */ |
| if (!tcep) { |
| local_irq_restore(flags); |
| - return tce_build_pSeriesLP(tbl, tcenum, npages, uaddr, |
| - direction, attrs); |
| + return tce_build_pSeriesLP(tbl->it_index, tcenum, |
| + tbl->it_page_shift, |
| + npages, uaddr, direction, attrs); |
| } |
| __this_cpu_write(tce_page, tcep); |
| } |
| @@ -261,16 +263,16 @@ static int tce_buildmulti_pSeriesLP(stru |
| return ret; |
| } |
| |
| -static void tce_free_pSeriesLP(struct iommu_table *tbl, long tcenum, long npages) |
| +static void tce_free_pSeriesLP(unsigned long liobn, long tcenum, long npages) |
| { |
| u64 rc; |
| |
| while (npages--) { |
| - rc = plpar_tce_put((u64)tbl->it_index, (u64)tcenum << 12, 0); |
| + rc = plpar_tce_put((u64)liobn, (u64)tcenum << 12, 0); |
| |
| if (rc && printk_ratelimit()) { |
| printk("tce_free_pSeriesLP: plpar_tce_put failed. rc=%lld\n", rc); |
| - printk("\tindex = 0x%llx\n", (u64)tbl->it_index); |
| + printk("\tindex = 0x%llx\n", (u64)liobn); |
| printk("\ttcenum = 0x%llx\n", (u64)tcenum); |
| dump_stack(); |
| } |
| @@ -285,7 +287,7 @@ static void tce_freemulti_pSeriesLP(stru |
| u64 rc; |
| |
| if (!firmware_has_feature(FW_FEATURE_MULTITCE)) |
| - return tce_free_pSeriesLP(tbl, tcenum, npages); |
| + return tce_free_pSeriesLP(tbl->it_index, tcenum, npages); |
| |
| rc = plpar_tce_stuff((u64)tbl->it_index, (u64)tcenum << 12, 0, npages); |
| |
| @@ -400,6 +402,19 @@ static int tce_setrange_multi_pSeriesLP( |
| u64 rc = 0; |
| long l, limit; |
| |
| + if (!firmware_has_feature(FW_FEATURE_MULTITCE)) { |
| + unsigned long tceshift = be32_to_cpu(maprange->tce_shift); |
| + unsigned long dmastart = (start_pfn << PAGE_SHIFT) + |
| + be64_to_cpu(maprange->dma_base); |
| + unsigned long tcenum = dmastart >> tceshift; |
| + unsigned long npages = num_pfn << PAGE_SHIFT >> tceshift; |
| + void *uaddr = __va(start_pfn << PAGE_SHIFT); |
| + |
| + return tce_build_pSeriesLP(be32_to_cpu(maprange->liobn), |
| + tcenum, tceshift, npages, (unsigned long) uaddr, |
| + DMA_BIDIRECTIONAL, 0); |
| + } |
| + |
| local_irq_disable(); /* to protect tcep and the page behind it */ |
| tcep = __this_cpu_read(tce_page); |
| |