來源:http://xmxohy.blog.163.com/blog/static/53469584201082734630713/
先理解cache的作用
CPU在訪問內存時,首先判斷所要訪問的內容是否在Cache中,如果在,就稱為“命中(hit)”,此時CPU直接從Cache中調用該內容;否則,就 稱為“ 不命中”,CPU只好去內存中調用所需的子程序或指令了。CPU不但可以直接從Cache中讀出內容,也可以直接往其中寫入內容。由於Cache的存取速 率相當快,使得CPU的利用率大大提高,進而使整個系統的性能得以提升。
Cache的一致性就是直Cache中的數據,與對應的內存中的數據是一致的。
DMA是直接操作總線地址的,這里先當作物理地址來看待吧(系統總線地址和物理地址只是觀察內存的角度不同)。如果cache緩存的內存區域不包括DMA分配到的區域,那么就沒有一致性的問題。但是如果cache緩存包括了DMA目的地址的話,會出現什么什么問題呢?
問題出在,經過DMA操作,cache緩存對應的內存數據已經被修改了,而CPU本身不知道(DMA傳輸是不通過CPU的),它仍然認為cache中的數 據就是內存中的數據,以后訪問Cache映射的內存時,它仍然使用舊的Cache數據。這樣就發生Cache與內存的數據“不一致性”錯誤。
題外話:好像2.6.29內核中,6410的總線地址和物理地址是一樣的,因為我在查看vir_to_bus函數的時候,發現在/arch/arm/linux/asm/memory.h中這樣定義:
#ifndef __virt_to_bus
#define __virt_to_bus __virt_to_phys
#define __bus_to_virt __phys_to_virt
#endif
而且用source Insight搜索了一遍,沒有發現6410相關的代碼中,重新定義__vit_to_bus,因此擅自認為2.6內核中,6410的總線地址就是物理地址。希望高手指點。
順便提一下,總線地址是從設備角度上看到的內存,物理地址是CPU的角度看到的未經過轉換的內存(經過轉換的是虛擬地址)
由上面可以看出,DMA如果使用cache,那么一定要考慮cache的一致性。解決DMA導致的一致性的方法最簡單的就是禁止DMA目標地址范圍內的cache功能。但是這樣就會犧牲性能。
因此在DMA是否使用cache的問題上,可以根據DMA緩沖區期望保留的的時間長短來決策。DAM的映射就分為:一致性DMA映射和流式DMA映射。
一致性DMA映射申請的緩存區能夠使用cache,並且保持cache一致性。一致性映射具有很長的生命周期,在這段時間內占用的映射寄存器,即使不使用也不會釋放。生命周期為該驅動的生命周期
流式DMA映射實現比較復雜,因為沒具體了解,就不說明了。只知道種方式的生命周期比較短,而且禁用cache。一些硬件對流式映射有優化。建立流式DMA映射,需要告訴內核數據的流動方向。
因為LCD隨時都在使用,因此在Frame buffer驅動中,使用一致性DMA映射
上面的代碼中用到 dma_alloc_writecombine函數,另外還有一個一致性DMA映射函數dma_alloc_coherent
兩者的區別在於:
查看兩者的源代碼
- /*
- * Allocate DMA-coherent memory space and return both the kernel remapped
- * virtual and bus address for that space.
- */
- void *
- dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
- {
- void *memory;
- if (dma_alloc_from_coherent(dev, size, handle, &memory))
- return memory;
- if (arch_is_coherent()) {
- void *virt;
- virt = kmalloc(size, gfp);
- if (!virt)
- return NULL;
- *handle = virt_to_dma(dev, virt);
- return virt;
- }
- return __dma_alloc(dev, size, handle, gfp,
- pgprot_noncached(pgprot_kernel));
- }
- /*
- * Allocate a writecombining region, in much the same way as
- * dma_alloc_coherent above.
- */
- void *
- dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
- {
- return __dma_alloc(dev, size, handle, gfp,
- pgprot_writecombine(pgprot_kernel));
- }
- #define pgprot_noncached(prot) __pgprot(pgprot_val(prot) &~(L_PTE_CACHEABLE | L_PTE_BUFFERABLE))
- #define pgprot_writecombine(prot) __pgprot(pgprot_val(prot) &~L_PTE_CACHEABLE)
再結合網上的資料(不過我感覺那文章寫的有些問題,我修改了一下),由上面代碼可以看出,兩個函數都調用了__dma_alloc函數,區別只在於最后一個參數。
dma_alloc_coherent 在 arm 平台上會禁止頁表項中的 C (Cacheable) 域以及 B (Bufferable)域。而 dma_alloc_writecombine 只禁止 C (Cacheable) 域.
C 代表是否使用高速緩沖存儲器, 而 B 代表是否使用寫緩沖區。
這樣,dma_alloc_writecombine 分配出來的內存不使用緩存,但是會使用寫緩沖區。而 dma_alloc_coherent 則二者都不使用。
C B 位的具體含義
0 0 無cache,無寫緩沖;任何對memory的讀寫都反映到總線上。對 memory 的操作過程中CPU需要等待。
0 1 無cache,有寫緩沖;讀操作直接反映到總線上;寫操作,CPU將數據寫入到寫緩沖后繼續運行,由寫緩沖進行寫回操作。
1 0 有cache,寫通模式;讀操作首先考慮cache hit;寫操作時直接將數據寫入寫緩沖,如果同時出現cache hit,那么也更新cache。
1 1 有cache,寫回模式;讀操作首先考慮cache hit;寫操作也首先考慮cache hit。
這樣,兩者的區別就很清楚了。
A = dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *handle, gfp_t gfp);
含義:
A : 內存的虛擬起始地址,在內核要用此地址來操作所分配的內存
dev : 可以平台初始化里指定,主要是用到dma_mask之類參數,可參考framebuffer
size : 實際分配大小,傳入dma_map_size即可
handle: 返回的內存物理地址,dma就可以用。
A和hanle是一一對應的,A是虛擬地址,而handle是總線地址。對任意一個操作都將改變寫緩沖區內容。