首先,應該先讀 Documentation/ DMA-API.txt和DMA-mapping.txt.
1. DMA memeory分類
一共有兩種DMA memory,
a. Consistent/coherent
b. non-consistent/no-coherent
第一類由底層硬體保證了記憶體的一緻性,這樣的記憶體當進行DMA時,不需要相關的invalidate函數操作。
第二類記憶體在進行DMA之前,必須做flush/invalidate等操作。
底層硬體如何實作,或者處理器通過記憶體映射,或者有額外的硬體支援,這些對于driver來說是透明的。
注意 coherent和no-coherent和cache是不同的概念,coherent也許是cached,也許是uncached,這是由底層硬體來實作。
2. DMA對memory的操作函數
DMA-API有兩類,
第一類是dma_ API, 它必須包含#include <linux/dma-mapping.h>
第二類是pci_ API, 它必須包含#include <linux/pci.h>
第一類是向處理器直接配置設定記憶體,第二類是通過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. DMA的層次結構
3.1 對于DMA_ API來說,從<linux/dma-mapping.h>開始,這裡定義了一些公共的API,然後在這個頭檔案中會
#ifdef CONFIG_HAS_DMA
#include <asm/dma-mapping.h>
#else
#include <asm-generic/dma-mapping-broken.h>
#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來說,從<linux/pci.h>開始。
這個頭檔案會包含體系相關的<asm/pci.h>
對于mips來說,是arch/mips/include/asm/pci.h
對于arm來說,是arch/arm/include/asm/pci.h
對于mips和arm來說,pci.h又會包含如下語句
#include <asm-generic/pci-dma-compat.h>
然後在pci-dma-compat.h裡,開始包含#include <linux/dma-mapping.h>,并且把PCI_向DMA_做了轉換,這就把PCI_和DMA_聯系上了。
4. MIPS上的DMA實作
在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操作上來。
5. ARM上的DMA實作
因為不太懂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));
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 <asm/cacheflush.h> and proc-macros.S)
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)