DMA---直接內存訪問
用來在設備內存與主存RAM之間直接進行數據交換,這個過程無需CPU干預,
對於系統中有大量數據交換的設備而言,如果能夠充分利用DMA特性,可以大大提高系統性能。
1.內核中DMA層
--內核爲設備驅動程序提供了統一的DMA接口,這些接口屏蔽了不同平臺之間的差異。
--一致性映射類型的dma_alloc_coherent/流式映射類型的dma_map_single
不同的平臺(X86/ARM)提供各自的struct dma_map_ops對象來實現對應的DMA映射。
2.物理地址和總線地址
物理地址是指cpu地址信號線上產生的地址。
總線地址可以認爲從設備的角度看到的地址,不同類型的總線具有不同類型的總線地址。
DMA地址--用來在設備和主存之間尋址,雖然是總線地址,但是從內核代碼的角度來看,被稱爲DMA地址,
與之相對應的數據結構是dma_addr_t(-->typedef u32 dma_addr_t;)。
3.DMA映射
3.1 基本原理
DMA映射主要爲在設備與主存之間建立DMA數據傳輸通道時,在主存中爲該DMA通道分配內存空間的行爲,該內存空間
也稱爲DMA緩衝區。這個任務原本可以很簡單,但是由於現代處理器cache的存在,使得事情變得複雜。
3.2 RAM與cache內容的一致性問題
1.出現問題原因
現代處理器爲了提升系統性能,在CPU與RAM之間加入了高速緩存cache,
所以當在RAM中爲一個DMA通道建立一段緩衝區時,
必須仔細考慮RAM與cache內容的一致性問題。
/*具體的分析*/
如果RAM與Device之間的一次數據交換改變了RAM中DMA緩衝區的內容,
而cache中緩存了DMA緩衝區對應的RAM中一段內存塊。
如果沒有機制保護cache中的內容被新的DMA緩衝區數據更新(或者無效),
那麼cache和他對應的RAM中的一段內存塊在內容上出現了不一致,
此時如果CPU去讀取device傳到RAM的DMA緩衝區中的數據,
它將直接從cache獲得數據,這些數據顯然不是它所期望的,
因爲cache對應的RAM中的數據已經更新了。
2.解決問題--
就cache一致性問題,不同的體系架構有不同的策略,有些是在硬件層面予以保證(x86平臺)
有些沒有硬件支持而需要軟件的參與(ARM品臺)。
--Linux內核中的通用DMA盡力爲設備驅動程序提供統一的接口來處理cache緩存一致性的問題,
而將大量品臺相關的代碼對設備驅動程序隱藏起來。
3.3 DMA映射三種情況
1. 一致性DMA映射
linux內核DMA層爲一致性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)
函數分配的一致性DMA緩衝區的總線地址(也是DMA地址)由參數handle帶回,
函數返回的則是映射到DMA緩衝區的虛擬地址的起始地址
接着調用->__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp,pgprot_t prot)
{
struct page *page;
void *addr;
*handle = ~0;
size = PAGE_ALIGN(size);
page = __dma_alloc_buffer(dev, size, gfp); /*分配大小爲size的一段連續的物理內存頁,並且對應得虛擬地址範圍已經使cache失效*/
if (!page)
return NULL;
if (!arch_is_coherent())/*arch_is_coherent確定體系結構是否通過硬件來保證cache一致性(arm不是,所以函數返回0)*/
addr = __dma_alloc_remap(page, size, gfp, prot);/*在(?--0xffe0 0000)之間尋找一段虛擬地址段,將其重建新映射到page,
--------------------------------------------------------->由於關閉了cache功能所以保證了DMA操作時不會出現cache一致性問題*/-
else
addr = page_address(page);
if (addr)
*handle = pfn_to_dma(dev, page_to_pfn(page));
return addr;
}
一致性所獲得的DMA緩衝區的大小都是頁面的整數倍,如果驅動程序需要更小的DMA一致性的DMA緩衝區,則應該使用內核提供的DMA池(pool)機制
/*釋放一致性DMA緩衝區*/
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)
/*cpu_addr表示要釋放的DMA緩衝區的起始虛擬地址,參數bus表示DMA緩衝區的總線地址*/
對於一致性DMA映射,在分配DMA緩衝區時各平臺相關代碼已經從根本上解決了cache一致性問題.
但是,一致性映射也會遇到無法克服的困難,主要是指驅動程序中使用的DMA緩衝區並非由驅動程序分配,
而是來自其他模塊(如網絡設備驅動程序中用於數據包傳輸的skb->data所指向的緩衝區),此時需要流式DMA映射。
2. 流式DMA映射
流式DMA映射的特點是DMA傳輸通道使用的緩衝區不是由當前驅動程序自身分配的,
而且往往對每次DMA傳輸都會重新建立一個流式映射的緩衝區,所以使用流式DMA映射時,
設備驅動程序必須小心負責處理可能出現的cache一致性。
linux內核DMA層爲設備驅動提供的建立流式DMA映射的函數---dma_map_single
//dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, NULL)
static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
size_t size, enum dma_data_direction dir)
/*dev-->設備對象指針,cpu_addr-->cpu的虛擬地址,
size-->流式空間的範圍,dir-->表明當前流式映射中DMA傳輸通道中的數據方向*/
函數返回數據類型-->dma_addr_t即表示DMA操作中的源地址和目的地址。
/*下面分析ARM的平臺*/
/*將cpu_addr表示的段虛擬地址映射到DMA緩衝區中,返回該緩衝區的起始地址*/
2.1
static inline dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
size_t size, enum dma_data_direction dir)
{
dma_addr_t addr;
addr = __dma_map_single(dev, cpu_addr, size, dir);////
return addr;
}
2.2
static inline dma_addr_t __dma_map_single(struct device *dev, void *cpu_addr,
size_t size, enum dma_data_direction dir)
{
__dma_single_cpu_to_dev(cpu_addr, size, dir);
return virt_to_dma(dev, cpu_addr);
}
2.3
void ___dma_page_cpu_to_dev(struct page *page, unsigned long off,
size_t size, enum dma_data_direction dir)
{
unsigned long paddr;
dma_cache_maint_page(page, off, size, dir, dmac_map_area);
paddr = page_to_phys(page) + off;
if (dir == DMA_FROM_DEVICE) {
outer_inv_range(paddr, paddr + size); /*保證讀數據使得時候,使得(paddr, paddr + size)對應的cache失效*/
} else {
outer_clean_range(paddr, paddr + size);
}
}
/*
sync_single_for_cpu方法用於數據從設備傳到主存的情況:
爲了避免cache的介入導致CPU讀到的只是cache中舊的數據,
驅動程序需要在CPU讀取之前調用該函數---->使得cache無效,這樣處理器將直接從主存中獲得數據。
sync_single_for_device方法用於數據從主存傳到設備,
在啓動DMA操作之前,CPU需要將數據放在位於主存的DMA緩衝區中,
爲了防止write buffer的介入,導致數據只是臨時寫到write buffer中,
驅動程序需要在CPU往主存寫數據之後啓動DMA操作之前調用該函數。
*/
3. 分散/聚集DMA映射
分散/聚集DMA映射通過將虛擬地址上分散的DMA緩衝區通過一個struct scatterlist的數組或鏈表組織起來,
然後通過一次的DMA傳輸操作在主存RAM與設備之間傳輸數據。
分散/聚集DMA映射本質上是通過一次DMA操作把內存中分散的數據塊在主存與設備之間進行傳輸,對於其中的每個數據塊
內核都會建立對應的一個流式DMA映射。---》需要設備的支持。
3.4 回彈緩衝區
如果CPU側虛擬地址對應的物理地址不適合設備的DMA操作,那麼需要建立回彈緩衝區,相當於一個 中轉站,把數據往設備傳輸時,
驅動程序需要把CPU給的數據拷貝到回彈緩衝區,然後再啓動DMA操作。
3.5 DMA池
由於DMA映射所建立的緩衝區是單個頁面的整數倍,
如果驅動程序需要更小的一致性映射的DMA緩衝區,
可以使用內核提供的DMA池機制(非常類似於Linux內存管理中的slab機制).
struct dma_pool就是內核用來完成該任務的數據結構
struct dma_pool { /* the pool */
struct list_head page_list; /*用來將一致性DMA映射建立的頁面組織成鏈表*/
spinlock_t lock; /*自旋鎖*/
size_t size; /*該DMA池用來分配一致性DMA映射的緩衝區的大小,也稱爲塊大小*/
struct device *dev; /*進行DMA操作的 設備對象指針*/
size_t allocation;
size_t boundary;
char name[32]; /*dma池的名稱*/
wait_queue_head_t waitq; /*等待隊列*/
struct list_head pools; /*用來將當前DMA池對象加入到dev->dma_pools鏈表中*/
};
/*相關操作*/
1--創建dma_pool,並初始化
/* dma_pool_create - Creates a pool of consistent memory blocks, for dma.
* @name: name of pool, for diagnostics
* @dev: device that will be doing the DMA
* @size: size of the blocks in this pool.
* @align: alignment requirement for blocks; must be a power of two
* @boundary: returned blocks won't cross this power of two boundary
* Context: !in_interrupt()
*/
struct dma_pool *dma_pool_create(const char *name, struct device *dev,
size_t size, size_t align, size_t boundary)
2--釋放DMA池中的DMA緩衝塊
/**
* dma_pool_free - put block back into dma pool
* @pool: the dma pool holding the block
* @vaddr: virtual address of block
* @dma: dma address of block
**/
void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t dma)
3--銷燬dma_pool
/* dma_pool_destroy - destroys a pool of dma memory blocks.
* @pool: dma pool that will be destroyed
*/
void dma_pool_destroy(struct dma_pool *pool)
//參考文獻:陳雪松-深入Linux設備驅動程序內核機制