什麼是DMA(directly memory access)? 簡單的講就直接記憶體存取,或許你還是無法明白,這樣說好了,在一個電腦系統中,通常都是由CPU (中央控制單元) 來對記憶體做存取的動作,也就是在各個電腦週邊元件中,若要對記憶體做存取動作,則必須經由 CPU 來做仲介的角色,而DMA則可以取代CPU這個工作,但僅至於資料而已無法對指令做這個動作,因為記憶體在系統中是唯一的,若要讓CPU及DMA可以很順暢的工作而不會互相干擾,則必需在軟體上做一些協調的動作,另外因為CPU有MMU及Cache的元件,也使得為了要讓DMA取得正確的資料,所以在寫這方面的驅動程式時需要特別注意,以下就來說明這方面的作法及問題。
硬體架構
圖一
問題點
從上圖中我們可以清楚地了解以下幾件事:
- 當CPU要向DRAM或者是週邊元件存取時,所以放出來的位置將會經過cache及MMU的處理。
- 在cache中的處理主要是提高對記憶體的存取速度。
- 當作業啓動時所有程式均使用虛擬記憶體,所以一定會經過MMU的位址轉換,因為當實際要對週邊元件或者記憶體存取時一定會使用實體位址。
- 當DMA要存取記憶體時並不會經過MMU模組的轉換,換句話說就是DMA會直接使用實體位址來存取記憶體。
- 另外一個問題是DMA也沒有經過cache模組,所以存取的資直接從記憶體存取。
解決方案
由以上的問題描述,我們可以看一個很基本的問題,就是CPU和DMA之間對記憶體的存取可能會有不相符的現象產生。首先我們先來看週邊元件要送資料,也就是從CPU寫入資料至記憶體,然後DMA從記憶體取得資料給週邊元件,進而週邊元件送出資料,這裡問題在於CPU寫入資料時,可能不會一定寫入記憶體,有可能保留至cache中,若這時DMA來取資料將會是不對的資料,因此在DMA存取之前,一定要讓CPU的cache單元做一次sync的動作,將若有還有cache中的資料,真的寫入記憶體中,如此一來DMA將可以取得正確資料。換一個簡單的說法,就是週邊元件要送資料時必須對cache做sync的動作,確認最後的資料是有寫入記憶體。
另一個資料方向就是當週邊元件收到資料時,將會由DMA直接寫入記憶體,完作接收後,週邊元件會產生一個中斷通知CPU來讀取資料,這時的問題是在於存放收到資料,有可能是已被cache住,若CPU不做任何動作而直接來讀取資料,一樣會產生資料不正確的情形,這時在CPU讀取時必須對該區塊作一個cache invalid的動作,讓CPU在讀取該區塊資料,會實際至記憶體做讀取的動作,因此重新讓cache元件,針對該區域重新做cache的動作,如此一來就不會有資料不正確的情形產生。所以說週邊元件在收資料時,必需對cache做invalid的動作。
另外有一個簡單的作法,就是在準備這些記憶體時,就取得讓記憶體是不會做cache的動作,如此一來就不需要做資料同步動作。
Kernel API
我們從以上知道問題,也知道該如何解決,現在的問題是有多少的kernel API可供使用,以下我們就來看:
dma_addr_t dma_map_single(struct device *, void *, size_t, enum dma_data_direction);
void dma_unmap_single(struct device *, dma_addr_t, size_t, enum dma_data_direction);
dma_addr_t dma_map_page(struct device *, struct page *,unsigned long, size_t, enum dma_data_direction);
void dma_unmap_page(struct device *, dma_addr_t, size_t,enum dma_data_direction);
void dma_cache_maint(const void *kaddr, size_t size, int rw);
void dma_cache_maint_page(struct page *page, unsigned long offset,size_t size, int rw);
enum dma_data_direction {
DMA_BIDIRECTIONAL = 0,
DMA_TO_DEVICE = 1,
DMA_FROM_DEVICE = 2,
DMA_NONE = 3,
};
以上的kernel API主要是針對將一般取得的記憶體(例如由kmalloc所取得),是會被cache的記憶體,將cache中的資料和實體記憶體資料做同步的動作,其DMA_TO_DEVICE就是做invalidate的動作,而DMA_FROM_DEVICE就是synchronize的動作,這些API必需要在呼叫DMA之前完成。
void *dmam_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t gfp);
void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,dma_addr_t dma_handle);
以上的kernel API是在要allocate記憶體時就取得不會被cache的記憶體,如此一來對這塊記憶體的存取時就不需要前面所提的動作 – invalidate or synchronize,另外也可取得該記憶體的實體記憶體,這個實體記憶體將會給DMA controller所使用。
沒有留言:
張貼留言