MIPS: HIGHMEM DMA on noncoherent MIPS32 processors

[v4: Patch applies to linux-queue.git with kmap_atomic patches:
 https://patchwork.kernel.org/patch/189932/
 https://patchwork.kernel.org/patch/194552/
 https://patchwork.kernel.org/patch/189912/ ]

The MIPS DMA coherency functions do not work properly (i.e. kernel oops)
when HIGHMEM pages are passed in as arguments.  Use kmap_atomic() to
temporarily map high pages for cache maintenance operations.

Tested on a 2.6.36-rc7 1GB HIGHMEM SMP no-alias system.

Signed-off-by: Dezhong Diao <dediao@cisco.com>
Signed-off-by: Kevin Cernekee <cernekee@gmail.com>
Cc: Dezhong Diao <dediao@cisco.com>
Cc: David Daney <ddaney@caviumnetworks.com>
Cc: David VomLehn <dvomlehn@cisco.com>
Cc: Sergei Shtylyov <sshtylyov@mvista.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/1695/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
This commit is contained in:
Dezhong Diao 2010-10-13 16:57:35 -07:00 committed by Ralf Baechle
parent d0be89f6c2
commit e36863a550
1 changed files with 68 additions and 46 deletions

View File

@ -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,49 @@ 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)) {
void *addr;
if (offset + len > PAGE_SIZE) {
if (offset >= PAGE_SIZE) {
page += offset >> PAGE_SHIFT;
offset &= ~PAGE_MASK;
}
len = PAGE_SIZE - offset;
}
addr = kmap_atomic(page);
__dma_sync_virtual(addr + offset, len, direction);
kunmap_atomic(addr);
} 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 +222,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 +236,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 +260,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 +282,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 +295,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 +317,7 @@ void 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);
}
EXPORT_SYMBOL(dma_cache_sync);