MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
PATCH v3 2/2 MIPS: HIGHMEM DMA on noncoherent MIPS32 processors
[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.
--- arch/mips/mm/dma-default.c | 117 ++++++++++++++++++++++++++----------------- 1 files changed, 71 insertions(+), 46 deletions(-)
arch/mips/mm/dma-default.c +71 -46
@@ -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 = {
Signature · 1 line
1.7.0.4
Quoted 7 lines · Kevin Cernekee
[v3: Patch has been rebased against linux-queue.git, which uses the new
dma-mapping-common.hAPI.]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
Quoted 3 lines · Ralf Baechle
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?
Quoted 4 lines
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.
Quoted 1 line
Does your system have both highmem and cache aliases?
This system has HIGHMEM + SMP, no cache aliases.
Quoted 10 lines · Kevin Cernekee
Quoted 3 lines · Ralf Baechle
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
Quoted 4 lines · Ralf Baechle
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?