一共有两种DMA memory,
a. Consistent/coherent
b. non-consistent/no-coherent
第一类由底层硬件保证了内存的一致性,这样的内存当进行DMA时,不需要相关的invalidate函数操作。
第二类内存在进行DMA之前,必须做flush/invalidate等操作。
底层硬件如何实现,或者处理器通过内存映射,或者有额外的硬件支持,这些对于driver来说是透明的。
注意 coherent和no-coherent和cache是不同的概念,coherent也许是cached,也许是uncached,这是由底层硬件来实现。
DMA-API有两类,
第一类是dma_ API, 它必须包含#include
第二类是pci_ API, 它必须包含#include
第一类是向处理器直接分配内存,第二类是通过pci的接口分配内存。对于mips和arm来说,这两类API是一样的,pci_ API在mips和arm处理器中最后会走到dma_ API
所以这里只提及DMA-API
dma_alloc_coherent
分配coherent内存。
一般来说,coherent内存是比较昂贵,或者代价比较高一些,所以只能小块的内存使用coherent,比如典型的descriptor。
dma_pool_create
这个用来分配一个小块coherent的内存池子。dma_alloc_coherent是以页为单位分配。
dma_sync_single_for_device
dma_sync_single_for_cpu
并不太确定这两个sync是啥意思,很重要的两个函数,在mips上的实现也不同,一个是做了flush的动作,一个是没做
dma_map_single
flush这段内存,并返回物理地址,当分配buffer的时候,通常会使用这个函数返回物理地址。
3.1 对于DMA_ API来说,从
#ifdef CONFIG_HAS_DMA
#include
#else
#include
#endif
一般我们会定义CONFIG_HAS_DMA,所以会包含体系相关的dma-mapping.h。
对于mips来说,是arch/mips/include/asm/dma-mapping.h
对于arm来说,是arch/arm/include/asm/dma-mapping.h
在这两个头文件中,可以直接调用到各个arch中的dma mapping的实现,也可以在封装一层,
再包含#include dma-maping-common.h,然后这里通过dma_map_ops函数指针调到各个arch的dma mapping实现。
体系相关的dma-mapping.h中声明了其它DMA_API。它在哪里实现了,
对于mips来说,它在arch/mips/mm/dma-default.c
对于arm来说,它在arch/arm/mm/dma-mapping.c
对于PCI_ API来说,从
这个头文件会包含体系相关的
对于mips来说,是arch/mips/include/asm/pci.h
对于arm来说,是arch/arm/include/asm/pci.h
对于mips和arm来说,pci.h又会包含如下语句
/* implement the pci_ DMA API in terms of the generic device dma_ one */
#include
然后在pci-dma-compat.h里,开始包含#include
在dma-default.c中实现了mips的DMA函数
dma_alloc_coherent
void *ret;
gfp = massage_gfp_flags(dev, gfp);
ret = (void *) __get_free_pages(gfp, get_order(size));
if (ret) {
memset(ret, 0, size);
*dma_handle = plat_map_dma_mem(dev, ret, size);
if (!plat_device_is_coherent(dev)) {
dma_cache_wback_inv((unsigned long) ret, size);
ret = UNCAC_ADDR(ret);
}
}
return ret;
}
从这个函数可以看出,mips分配一个页面,清空,返回物理地址,然后判断这个device是否是coherent,因为我们前面说过可以有额外的硬件来支持coherent。
plat_device_is_coherent
{
#ifdef CONFIG_DMA_COHERENT
return 1;
#endif
#ifdef CONFIG_DMA_NONCOHERENT
return 0;
#endif
}
对于mips来讲,它依据CONFIG_DMA_COHERENT和CONFIG_DMA_NONCOHERENT
定义。它只是提供了这样一个函数给所有的平台。
一般我们会定义CONFIG_DMA_NONCOHERENT。
所以这里,mips是如何操作这coherent函数的呢?它把这一页先write-inv, 然后返回SEG1地址,其实就是uncache,这样确实保证了coherent,但效率会变低。
dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size,
enum dma_data_direction direction)
{
unsigned long addr = (unsigned long) ptr;
if (!plat_device_is_coherent(dev))
__dma_sync(addr, size, direction);
return plat_map_dma_mem(dev, ptr, size);
}
Dma_map_single有sync的作用,并且返回物理地址。
static inline void __dma_sync(unsigned long addr, size_t size,
enum dma_data_direction direction)
{
switch (direction) {
case DMA_TO_DEVICE:
dma_cache_wback(addr, size);
break;
case DMA_FROM_DEVICE:
dma_cache_inv(addr, size);
break;
case DMA_BIDIRECTIONAL:
dma_cache_wback_inv(addr, size);
break;
default:
BUG();
}
}
对于写device,我们需要wback,对于从device读,我们需要invalidate,对于双向,我们需要invalidate+wback。
这个涉及到cache的操作了,具体如何操作,与cache的类型相关。
在arch/mips/include/asm/io.h中
#ifdef CONFIG_DMA_NONCOHERENT
#define dma_cache_wback_inv(start, size) _dma_cache_wback_inv(start, size)
#define dma_cache_wback(start, size) _dma_cache_wback(start, size)
#define dma_cache_inv(start, size) _dma_cache_inv(start, size)
而_dma_cache_wback_inv这些底层函数的赋值则是在系统起来,初始化cache时赋值的,mips有多种类型,对于24K,74K等,它会使用原先的R4k cache,这里我们要选择CONFIG_CSRC_R4K=y。
在r4k_cache_init函数中
_dma_cache_wback_inv = r4k_dma_cache_wback_inv;
_dma_cache_wback = r4k_dma_cache_wback_inv;
_dma_cache_inv = r4k_dma_cache_inv;
所以最后DMA的操作到了r4k的cache操作上来。
因为不太懂ARM,这里只说一下arm cache的层次结构。
一般如果要sync一个buffer,arm提供到外面的接口有
___dma_single_cpu_to_dev
___dma_single_dev_to_cpu
我不知道这两个的区别,这个要留到以后分析。
___dma_single_dev_to_cpu
{
BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));
/* FIXME: non-speculating: not required */
/* don't bother invalidating if DMA to device */
if (dir != DMA_TO_DEVICE) {
unsigned long paddr = __pa(kaddr);
outer_inv_range(paddr, paddr + size);
}
dmac_unmap_area(kaddr, size, dir);
}
define dmac_unmap_area cpu_cache.dma_unmap_area
如果有需要还要使用outer_inv_range来invalidate L2的cache。
然后需要dmac_unmap_area
这个函数在cacheflush.h中有定义,表示为cpu_cache.dma_unmap_area,这个文件中还定义 了很多其它cache的操作,因为arm有不同的型号,所以每一种 cache都不一样。
在arch/arm/mm下面,cpu_cache在如下函数中定义
如果CPU是ARMV7的话,则在cache-v7.S最后两句,定义了这个结构
@ define struct cpu_cache_fns (see
define_cache_functions v7
而define_cache_functions v7则是在proc-macros.S中定义
ENTRY(\name\()_cache_fns)
.long \name\()_flush_icache_all
其实就是 v7_flush_icache_all等。
而v7_flush_icache_all这些函数的定义 则是在cache-v7.S中
如
ENTRY(v7_dma_unmap_area)
add r1, r1, r0
teq r2, #DMA_TO_DEVICE
bne v7_dma_inv_range
mov pc, lr
ENDPROC(v7_dma_unmap_area)