前言
什麼是DMA(directly memory access)?
簡單的講就直接記憶體存取,或許你還是無法明白。
這樣說好了,在一個電腦系統中,通常都是由CPU(中央控制單元)來對記憶體做存取的動作,
也就是在各個電腦週邊元件中,若要對記憶體做存取動作,則必須經由 CPU 來做仲介的角色。
而DMA則可以取代CPU這個工作,但僅至於資料而已無法對指令做這個動作,因為記憶體在系統中是唯一的。
若要讓CPU及DMA可以很順暢的工作而不會互相干擾,則必需在軟體上做一些協調的動作。
另外因為CPU有MMU及Cache的元件,也使得為了要讓DMA取得正確的資料。
所以在寫這方面的驅動程式時需要特別注意,以下就來說明這方面的作法及問題。

硬體架構
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所使用。
arrow
arrow
    全站熱搜

    BB 發表在 痞客邦 留言(2) 人氣()