【轉】scatterlist && DMA

原文: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緩衝區;
  • 爲這片緩衝區產生設備可訪問的地址。

 

內核中提供了一下函數用於分配一個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數組(鏈表)總長。

相關文章
相關標籤/搜索