DMA是一種無須CPU的參與就可以讓外設與系統記憶體之間進行雙向資料轉送的硬體機制。使用DMA可以是系統CPU從實際的IO資料轉送過程中擺脫出來,從而大大提
供系統的吞吐率。DMA方式的資料轉送由DMA控制器(DMAC)控制,在傳輸期間,CPU可以並發地執行其他任務,當DMA結束後,DMAC通過中斷通知CPU資料轉送已經結束,然後由CPU執行相應的中斷服務程式進行後續處理。
在記憶體中用於與外設互動資料的一塊地區被稱作DMA緩衝區,在裝置不支援scatter/gatherCSG,分散/聚集操作的情況下,DMA緩衝區必須是物理上聯絡的。
對於ISA裝置而言,其DMA操作只能在16MB以下的記憶體進行,因此,在使用kmalloc()和__get_free_pages()及其類似函數申請DMA緩衝區時應使用GFP_DMA標誌,這樣能保證獲得的記憶體是具備DMA能力的。
DMA的硬體使用匯流排地址而非物理地址,匯流排地址是從裝置角度上看到的記憶體位址,物理地址是從CPU角度上看到的未經轉換的記憶體位址(經過轉換的那叫虛擬位址)。
在PC上,對於ISA和PCI而言,匯流排即為物理地址,但並非每個平台都是如此。由於有時候介面匯流排是通過橋接電路被串連,橋接電路會將IO地址映射為不同的物理地址。
裝置不一定能在所有的記憶體位址上執行DMA操作,在這種情況下應該通過下列函數執行DMA位址遮罩:
int dma_set_mask(struct device *dev, u64 mask);
DMA映射包括兩個方面的工作:
- 分配一片DMA緩衝區;
- 為這片緩衝區產生裝置可訪問的地址。
核心中提供了一下函數用於分配一個DMA一致性的記憶體地區:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
這個函數的傳回值為申請到的DMA緩衝區的虛擬位址。此外,該函數還通過參數handle返回DMA緩衝區的匯流排地址。與之對應的釋放函數為:
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
以下函數用於分配一個寫合并(writecombinbing)的DMA緩衝區:
void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
與之對應的是釋放函數:dma_free_writecombine(),它其實就是dma_free_conherent,只不過是用了#define重新命名而已。
對於單個已經分配的緩衝區而言,使用dma_map_single()可實現流式DMA映射:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction); 如果映射成功,返回的是匯流排地址,否則返回NULL.最後一個參數DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE;
與之對應的反函數是:
void dma_unmap_single(struct device *dev,dma_addr_t *dma_addrp,size_t size,enum dma_data_direction direction);
MMC的scatter list相關操作
MMC作為塊裝置,它的儲存空間,最小單位由struct bio_vec 描述,它代表一段物理位址範圍。
struct bio_vec {struct page*bv_page;unsigned intbv_len;unsigned intbv_offset;};
一次塊裝置傳輸請求,會涉及到很多個這樣的不連續的物理空間。不連續的物理空間,不能直接使用DMA
這時,可以利用sg操作,讓每個bio_vec結構,對應一個scatterlist結構:
struct scatterlist { unsigned long page_link; unsigned int offset; /* buffer offset */ dma_addr_t dma_address; /* dma address */ unsigned int length; /* length */};
在MMC的請求處理函數中,遍曆每一request中所有bio_vec結構,對應一個scatterlis結構描述:
rq_for_each_segment(bvec, rq, iter) { ... ... sg = sg_next(sg); //指向sg鏈表中的下一個scatterlist sg_set_page(sg, bvec->bv_page, bvec->bv_len, bvec->bv_offset); //使用sg描述一個頁 ... ... } ... ... sg_mark_end(sg); //標誌sg鏈表到此sg節點就結束了
上面sg這個鏈表初始化代碼如下:
sg_init_table(mq->bounce_sg, bouncesz / 512);第一個參數為鏈表頭,第二個為成員數量。
上邊所涉及到的幾個函數:
void sg_init_table(struct scatterlist *sg, unsigned int nents);
sg是sg鏈表(數組)的表頭,nents是要分配的數組的個數。
void sg_set_page(struct scatterlist *sg, struct page *page, unsigned int len, unsigned int offset);void sg_set_buf(struct scatterlist *sg, const void *buf, unsigned int buflen);
使用指定的參數,填充sg結構。第一個以頁為頁地址,位移量,長度為接線;第二個以地址和長度為界限。
struct scatterlist *sg_next(struct scatterlist *sg);
返回下一個sg成員地址。
也有一個宏,來遍曆sg鏈表上的所有sg結構,它的使用方法通常如下:
int i; struct scatterlist *list, *sgentry; /* Fill in list and pass it to dma_map_sg(). Then... */ for_each_sg(i, list, sgentry, nentries) {program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry)); }
sgentry為sg鏈表入口,nentryies是sg數組(鏈表)總長。