原文:scatterlist && DMA數組
DMA是一種無須CPU的參與就可讓外設與系統內存之間進行雙向數據傳輸的硬件機制。使用DMA能夠是系統CPU從實際的IO數據傳輸過程當中擺脫出來,從而大大提併發
供系統的吞吐率。DMA方式的數據傳輸由DMA控制器(DMAC)控制,在傳輸期間,CPU能夠併發地執行其餘任務,當DMA結束後,DMAC經過中斷通知CPU數據傳輸已經結束,而後由CPU執行相應的中斷服務程序進行後續處理。函數
在內存中用於與外設交互數據的一塊區域被稱做DMA緩衝區,在設備不支持scatter/gatherCSG,分散/彙集操做的狀況下,DMA緩衝區必須是物理上聯繫的。.net
對於ISA設備而言,其DMA操做只能在16MB如下的內存進行,所以,在使用kmalloc()和__get_free_pages()及其相似函數申請DMA緩衝區時應使用GFP_DMA標誌,這樣能保證得到的內存是具有DMA能力的。code
DMA的硬件使用總線地址而非物理地址,總線地址是從設備角度上看到的內存地址,物理地址是從CPU角度上看到的未經轉換的內存地址(通過轉換的那叫虛擬地址)。blog
在PC上,對於ISA和PCI而言,總線即爲物理地址,但並不是每一個平臺都是如此。因爲有時候接口總線是經過橋接電路被鏈接,橋接電路會將IO地址映射爲不一樣的物理地址。接口
設備不必定能在全部的內存地址上執行DMA操做,在這種狀況下應該經過下列函數執行DMA地址掩碼:內存
int dma_set_mask(struct device *dev, u64 mask);
DMA映射包括兩個方面的工做:get
內核中提供了一下函數用於分配一個DMA一致性的內存區域:it
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 int bv_len; unsigned int bv_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數組(鏈表)總長。