linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
@ 2010-10-10  1:53 Kevin Cernekee
  2010-10-13  7:53 ` Ralf Baechle
  0 siblings, 1 reply; 5+ messages in thread
From: Kevin Cernekee @ 2010-10-10  1:53 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: dediao, ddaney, dvomlehn, sshtylyov, linux-mips, linux-kernel

[v3: Patch has been rebased against linux-queue.git, which uses the new
dma-mapping-common.h API.]

The MIPS DMA coherency functions do not work properly (i.e. kernel oops)
when HIGHMEM pages are passed in as arguments.  This patch uses the PPC
approach of calling kmap_atomic() with IRQs disabled to temporarily map
high pages, in order to flush them out to memory.

Signed-off-by: Dezhong Diao <dediao@cisco.com>
Signed-off-by: Kevin Cernekee <cernekee@gmail.com>
---
 arch/mips/mm/dma-default.c |  117 ++++++++++++++++++++++++++-----------------
 1 files changed, 71 insertions(+), 46 deletions(-)

diff --git a/arch/mips/mm/dma-default.c b/arch/mips/mm/dma-default.c
index 4fc1a0f..035c191 100644
--- a/arch/mips/mm/dma-default.c
+++ b/arch/mips/mm/dma-default.c
@@ -15,18 +15,18 @@
 #include <linux/scatterlist.h>
 #include <linux/string.h>
 #include <linux/gfp.h>
+#include <linux/highmem.h>
 
 #include <asm/cache.h>
 #include <asm/io.h>
 
 #include <dma-coherence.h>
 
-static inline unsigned long dma_addr_to_virt(struct device *dev,
+static inline struct page *dma_addr_to_page(struct device *dev,
 	dma_addr_t dma_addr)
 {
-	unsigned long addr = plat_dma_addr_to_phys(dev, dma_addr);
-
-	return (unsigned long)phys_to_virt(addr);
+	return pfn_to_page(
+		plat_dma_addr_to_phys(dev, dma_addr) >> PAGE_SHIFT);
 }
 
 /*
@@ -148,20 +148,20 @@ static void mips_dma_free_coherent(struct device *dev, size_t size, void *vaddr,
 	free_pages(addr, get_order(size));
 }
 
-static inline void __dma_sync(unsigned long addr, size_t size,
+static inline void __dma_sync_virtual(void *addr, size_t size,
 	enum dma_data_direction direction)
 {
 	switch (direction) {
 	case DMA_TO_DEVICE:
-		dma_cache_wback(addr, size);
+		dma_cache_wback((unsigned long)addr, size);
 		break;
 
 	case DMA_FROM_DEVICE:
-		dma_cache_inv(addr, size);
+		dma_cache_inv((unsigned long)addr, size);
 		break;
 
 	case DMA_BIDIRECTIONAL:
-		dma_cache_wback_inv(addr, size);
+		dma_cache_wback_inv((unsigned long)addr, size);
 		break;
 
 	default:
@@ -169,12 +169,52 @@ static inline void __dma_sync(unsigned long addr, size_t size,
 	}
 }
 
+/*
+ * A single sg entry may refer to multiple physically contiguous
+ * pages. But we still need to process highmem pages individually.
+ * If highmem is not configured then the bulk of this loop gets
+ * optimized out.
+ */
+static inline void __dma_sync(struct page *page,
+	unsigned long offset, size_t size, enum dma_data_direction direction)
+{
+	size_t left = size;
+
+	do {
+		size_t len = left;
+
+		if (PageHighMem(page)) {
+			unsigned long flags;
+			void *addr;
+
+			if (offset + len > PAGE_SIZE) {
+				if (offset >= PAGE_SIZE) {
+					page += offset >> PAGE_SHIFT;
+					offset &= ~PAGE_MASK;
+				}
+				len = PAGE_SIZE - offset;
+			}
+
+			local_irq_save(flags);
+			addr = kmap_atomic(page, KM_SYNC_DCACHE);
+			__dma_sync_virtual(addr + offset, len, direction);
+			kunmap_atomic(addr, KM_SYNC_DCACHE);
+			local_irq_restore(flags);
+		} else
+			__dma_sync_virtual(page_address(page) + offset,
+					   size, direction);
+		offset = 0;
+		page++;
+		left -= len;
+	} while (left);
+}
+
 static void mips_dma_unmap_page(struct device *dev, dma_addr_t dma_addr,
 	size_t size, enum dma_data_direction direction, struct dma_attrs *attrs)
 {
 	if (cpu_is_noncoherent_r10000(dev))
-		__dma_sync(dma_addr_to_virt(dev, dma_addr), size,
-		           direction);
+		__dma_sync(dma_addr_to_page(dev, dma_addr),
+			   dma_addr & ~PAGE_MASK, size, direction);
 
 	plat_unmap_dma_mem(dev, dma_addr, size, direction);
 }
@@ -185,13 +225,11 @@ static int mips_dma_map_sg(struct device *dev, struct scatterlist *sg,
 	int i;
 
 	for (i = 0; i < nents; i++, sg++) {
-		unsigned long addr;
-
-		addr = (unsigned long) sg_virt(sg);
-		if (!plat_device_is_coherent(dev) && addr)
-			__dma_sync(addr, sg->length, direction);
-		sg->dma_address = plat_map_dma_mem(dev,
-				                   (void *)addr, sg->length);
+		if (!plat_device_is_coherent(dev))
+			__dma_sync(sg_page(sg), sg->offset, sg->length,
+				   direction);
+		sg->dma_address = plat_map_dma_mem_page(dev, sg_page(sg)) +
+				  sg->offset;
 	}
 
 	return nents;
@@ -201,30 +239,23 @@ static dma_addr_t mips_dma_map_page(struct device *dev, struct page *page,
 	unsigned long offset, size_t size, enum dma_data_direction direction,
 	struct dma_attrs *attrs)
 {
-	unsigned long addr;
-
-	addr = (unsigned long) page_address(page) + offset;
-
 	if (!plat_device_is_coherent(dev))
-		__dma_sync(addr, size, direction);
+		__dma_sync(page, offset, size, direction);
 
-	return plat_map_dma_mem(dev, (void *)addr, size);
+	return plat_map_dma_mem_page(dev, page) + offset;
 }
 
 static void mips_dma_unmap_sg(struct device *dev, struct scatterlist *sg,
 	int nhwentries, enum dma_data_direction direction,
 	struct dma_attrs *attrs)
 {
-	unsigned long addr;
 	int i;
 
 	for (i = 0; i < nhwentries; i++, sg++) {
 		if (!plat_device_is_coherent(dev) &&
-		    direction != DMA_TO_DEVICE) {
-			addr = (unsigned long) sg_virt(sg);
-			if (addr)
-				__dma_sync(addr, sg->length, direction);
-		}
+		    direction != DMA_TO_DEVICE)
+			__dma_sync(sg_page(sg), sg->offset, sg->length,
+				   direction);
 		plat_unmap_dma_mem(dev, sg->dma_address, sg->length, direction);
 	}
 }
@@ -232,24 +263,18 @@ static void mips_dma_unmap_sg(struct device *dev, struct scatterlist *sg,
 static void mips_dma_sync_single_for_cpu(struct device *dev,
 	dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)
 {
-	if (cpu_is_noncoherent_r10000(dev)) {
-		unsigned long addr;
-
-		addr = dma_addr_to_virt(dev, dma_handle);
-		__dma_sync(addr, size, direction);
-	}
+	if (cpu_is_noncoherent_r10000(dev))
+		__dma_sync(dma_addr_to_page(dev, dma_handle),
+			   dma_handle & ~PAGE_MASK, size, direction);
 }
 
 static void mips_dma_sync_single_for_device(struct device *dev,
 	dma_addr_t dma_handle, size_t size, enum dma_data_direction direction)
 {
 	plat_extra_sync_for_device(dev);
-	if (!plat_device_is_coherent(dev)) {
-		unsigned long addr;
-
-		addr = dma_addr_to_virt(dev, dma_handle);
-		__dma_sync(addr, size, direction);
-	}
+	if (!plat_device_is_coherent(dev))
+		__dma_sync(dma_addr_to_page(dev, dma_handle),
+			   dma_handle & ~PAGE_MASK, size, direction);
 }
 
 static void mips_dma_sync_sg_for_cpu(struct device *dev,
@@ -260,8 +285,8 @@ static void mips_dma_sync_sg_for_cpu(struct device *dev,
 	/* Make sure that gcc doesn't leave the empty loop body.  */
 	for (i = 0; i < nelems; i++, sg++) {
 		if (cpu_is_noncoherent_r10000(dev))
-			__dma_sync((unsigned long)page_address(sg_page(sg)),
-			           sg->length, direction);
+			__dma_sync(sg_page(sg), sg->offset, sg->length,
+				   direction);
 	}
 }
 
@@ -273,8 +298,8 @@ static void mips_dma_sync_sg_for_device(struct device *dev,
 	/* Make sure that gcc doesn't leave the empty loop body.  */
 	for (i = 0; i < nelems; i++, sg++) {
 		if (!plat_device_is_coherent(dev))
-			__dma_sync((unsigned long)page_address(sg_page(sg)),
-			           sg->length, direction);
+			__dma_sync(sg_page(sg), sg->offset, sg->length,
+				   direction);
 	}
 }
 
@@ -295,7 +320,7 @@ void mips_dma_cache_sync(struct device *dev, void *vaddr, size_t size,
 
 	plat_extra_sync_for_device(dev);
 	if (!plat_device_is_coherent(dev))
-		__dma_sync((unsigned long)vaddr, size, direction);
+		__dma_sync_virtual(vaddr, size, direction);
 }
 
 static struct dma_map_ops mips_default_dma_map_ops = {
-- 
1.7.0.4


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
  2010-10-10  1:53 [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors Kevin Cernekee
@ 2010-10-13  7:53 ` Ralf Baechle
  2010-10-13 18:01   ` Kevin Cernekee
  2010-11-07 18:27   ` Kevin Cernekee
  0 siblings, 2 replies; 5+ messages in thread
From: Ralf Baechle @ 2010-10-13  7:53 UTC (permalink / raw)
  To: Kevin Cernekee
  Cc: dediao, ddaney, dvomlehn, sshtylyov, linux-mips, linux-kernel

On Sat, Oct 09, 2010 at 06:53:42PM -0700, Kevin Cernekee wrote:

> [v3: Patch has been rebased against linux-queue.git, which uses the new
> dma-mapping-common.h API.]
> 
> The MIPS DMA coherency functions do not work properly (i.e. kernel oops)
> when HIGHMEM pages are passed in as arguments.  This patch uses the PPC
> approach of calling kmap_atomic() with IRQs disabled to temporarily map
> high pages, in order to flush them out to memory.

It's this disabling of interrupts which I don't like.  It's easy to get
around it by having one kmap type for each of process, softirq and
interrupt context.

The good news is that Peter Zijlstra has rewritten kmap to make the need
for manually allocated kmap types go away and his patches are queued to
be merged for 2.6.37.  So I'd like to put this patch on hold until after
his patches are merged.

Does your system have both highmem and cache aliases?

  Ralf

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
  2010-10-13  7:53 ` Ralf Baechle
@ 2010-10-13 18:01   ` Kevin Cernekee
  2010-10-13 20:32     ` Nicolas Pitre
  2010-11-07 18:27   ` Kevin Cernekee
  1 sibling, 1 reply; 5+ messages in thread
From: Kevin Cernekee @ 2010-10-13 18:01 UTC (permalink / raw)
  To: Ralf Baechle, Nicolas Pitre, Gary King
  Cc: dediao, ddaney, dvomlehn, sshtylyov, linux-mips, linux-kernel

On Wed, Oct 13, 2010 at 12:53 AM, Ralf Baechle <ralf@linux-mips.org> wrote:
> It's this disabling of interrupts which I don't like.  It's easy to get
> around it by having one kmap type for each of process, softirq and
> interrupt context.

I am curious as to why ARM opted for the "pte push/pop" strategy
(kmap_high_l1_vipt()) instead of something along these lines?

Is there a reason why using 3 kmap types to solve the "interrupted
flush problem" would work for MIPS, but is not a good solution on ARM?

> The good news is that Peter Zijlstra has rewritten kmap to make the need
> for manually allocated kmap types go away and his patches are queued to
> be merged for 2.6.37.  So I'd like to put this patch on hold until after
> his patches are merged.

OK, I'll take a look at that.  Thanks for the pointer.

> Does your system have both highmem and cache aliases?

This system has HIGHMEM + SMP, no cache aliases.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
  2010-10-13 18:01   ` Kevin Cernekee
@ 2010-10-13 20:32     ` Nicolas Pitre
  0 siblings, 0 replies; 5+ messages in thread
From: Nicolas Pitre @ 2010-10-13 20:32 UTC (permalink / raw)
  To: Kevin Cernekee
  Cc: Ralf Baechle, Gary King, dediao, ddaney, dvomlehn, sshtylyov,
	linux-mips, linux-kernel

[-- Attachment #1: Type: TEXT/PLAIN, Size: 637 bytes --]

On Wed, 13 Oct 2010, Kevin Cernekee wrote:

> On Wed, Oct 13, 2010 at 12:53 AM, Ralf Baechle <ralf@linux-mips.org> wrote:
> > It's this disabling of interrupts which I don't like.  It's easy to get
> > around it by having one kmap type for each of process, softirq and
> > interrupt context.
> 
> I am curious as to why ARM opted for the "pte push/pop" strategy
> (kmap_high_l1_vipt()) instead of something along these lines?
> 
> Is there a reason why using 3 kmap types to solve the "interrupted
> flush problem" would work for MIPS, but is not a good solution on ARM?

It would probably be a good solution for ARM as well.


Nicolas

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
  2010-10-13  7:53 ` Ralf Baechle
  2010-10-13 18:01   ` Kevin Cernekee
@ 2010-11-07 18:27   ` Kevin Cernekee
  1 sibling, 0 replies; 5+ messages in thread
From: Kevin Cernekee @ 2010-11-07 18:27 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: dediao, ddaney, dvomlehn, sshtylyov, linux-mips, linux-kernel

On Wed, Oct 13, 2010 at 12:53 AM, Ralf Baechle <ralf@linux-mips.org> wrote:
> The good news is that Peter Zijlstra has rewritten kmap to make the need
> for manually allocated kmap types go away and his patches are queued to
> be merged for 2.6.37.  So I'd like to put this patch on hold until after
> his patches are merged.

v4 of this patch applies cleanly to 2.6.37-rc1 and tests OK on my hardware:

http://patchwork.linux-mips.org/patch/1695/

What do you think about queuing it for 2.6.37?

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2010-11-07 18:27 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-10-10  1:53 [PATCH v3 2/2] MIPS: HIGHMEM DMA on noncoherent MIPS32 processors Kevin Cernekee
2010-10-13  7:53 ` Ralf Baechle
2010-10-13 18:01   ` Kevin Cernekee
2010-10-13 20:32     ` Nicolas Pitre
2010-11-07 18:27   ` Kevin Cernekee

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).