SD/MMC 中的scatterlist

今天下午閒下來了,想想還是給EVB增加一下SD/MMC的驅動吧。SD/MMC的官方文檔很少,也沒有啥書寫到這個問題,聽說華清遠見的宋寶華老師的新一版《Linux驅動開發》會講這個問題。不過Linux最大的優點就是開源,大家都可以研究,網上也有人寫了2410、2440的相關分析文章了,大家都有轉載,比如以下網址的一篇:

http://blog.chinaunix.net/u3/106866/showart_2203940.html

基本的大家都分析了,我分析的估計還沒人清楚,畢竟那也是要花時間的,我就說說這裏面的一個小的部分吧,就是scatterlist.使用scatterlist的原因就是系統在運行的時候內存會產生很多碎片,比如4k,100k的,1M的,有時候對應磁盤碎片,總之就是碎片。而在網絡和磁盤操作中很多時候需要傳送大塊的數據,尤其是使用DMA的時候,因爲DMA操作的物理地址必須是連續的。假設要1M內存,此時可以分配一個整的1M內存,也可以把10個10K的和9個100K的組成一塊1M的內存,當然這19個塊可能是不連續的,也可能其中某些或全部是連續的,總之情況不定,爲了描述這種情況,就引入了scatterlist,其實看成一個關於內存塊構成的鏈表就OK了。

 

在SD/MMC代碼中,在發起request的時候,都是通過scatterlist來發送數據的,定義在mmc_data裏面,(MMC core就是這麼設計的,跟具體的S3C2410還是PXA就沒有關係了)

struct mmc_data {

。。。。。。。。。。。。。。

 此處省略多行

。。。。。。。。。。。。。。
 unsigned int  sg_len;  /* size of scatter list */
 struct scatterlist *sg;  /* I/O scatter list */
}

其中struct scatterlist *sg;就是指向scatter list的指針,可以理解爲數組的頭指針,這個數組的作用就是保存各個scatterlist結構的地址的,sg_len表示有幾個scatterlist結構,相當於數組元素個數。比如前面提到的那個例子,sg_len就應該是19了,sg的組成內存塊就是sg_mem0--->sg_mem1--->........->sg_mem18這樣的內存鏈。所以通過sg就可以遍歷19塊中的任意一塊內存的情況,比如位置和大小。以下是scatterlist的定義:

struct scatterlist {
#ifdef CONFIG_DEBUG_SG
 unsigned long sg_magic;
#endif
 unsigned long page_link;
 unsigned int offset;
 unsigned int length;
 dma_addr_t dma_address;
 unsigned int dma_length;
};

下面以s3cmci.c裏面的scatter操作來分析,其實pxamci.c裏面也有,不過pxamci.c裏面只使用了DMA模式,相對要簡單一點,s3cmci.c裏面還使用pio模式(其實就是cpu模式),要複雜一些,所以分析起來更有意義。

 

1.DMA模式下的使用

在使用DMA操作這些scatterlist之前,先要對scatterlist進行一下map:

int dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
                          enum dma_data_direction dir)

其中的nents就是scatterlist的塊數,sg是指針數組的首地址。返回值是map以後這些地址塊被合併爲多少個適合DMA搬運的塊的數量,假設其中一塊的結束地址和另一塊的起始地址捱到一起了,這兩塊是會合二爲一的,這就是爲什麼說返回的值可能會小於 nents的原因,,比如上面的例子傳進去的nents=19, 函數的返回值肯定是小於等於19的,當然肯定大於0,同時sg的值也被改寫成了新的塊鏈表。此時就可以把這些塊放入DMA隊列一個一個的進行搬運了。s3cmci.c中的代碼是這樣的。

 dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
        rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

。。。。。。。。。。。。。。。。。。

 for (i = 0; i < dma_len; i++) {

。。。。。。。。。。。。。。。。。。。。。
      sg_dma_address(&data->sg[i]),
      sg_dma_len(&data->sg[i]));

  res = s3c2410_dma_enqueue(host->dma, host,
       sg_dma_address(&data->sg[i]),
       sg_dma_len(&data->sg[i]));

。。。。。。。。。。。。。。。。。。

 }
for循環其實就是遍歷內存塊了,不過據下面這個網址上說的,這種用for的方式已經out了,現在要用for_each_sg, 其實是一樣的:

http://lwn.net/Articles/256368/

/* 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));
    }


2.CPU方式

 CPU方式就不用調用map了,這些list本來就是CPU自己產生自己消化。在s3cmci.c中是通過函數:

static inline int get_data_buffer(struct s3cmci_host *host,
      u32 *bytes, u32 **pointer)
來實現的,它使用了一個計數host->pio_sgptr 來記錄現在使用的是內存鏈中的第幾塊,這個值在每次request後prepare_pio的時候被清零,每次調用get_data_buffer就加一,直到等於sg_len,然後表示所有的內存塊都用過了。對於一個scatterlist 指針sg,是如下這樣獲取它代表的內存塊的大小和位置的:

 *bytes = sg->length;/*內存塊長度*/
 *pointer = sg_virt(sg);/*內存塊起始地址*/

在s3cmci.c中使用了一點點小技巧來操作scatterlist和SD/MMC FIFO發送/接收,比如在do_pio_write中

  while ((fifo = fifo_free(host)) > 3) {
  if (!host->pio_bytes) {
   res = get_data_buffer(host, &host->pio_bytes,
       &host->pio_ptr);
   if (res) {
    dbg(host, dbg_pio,
        "pio_write(): complete (no more data)./n");
    host->pio_active = XFER_NONE;

    return;
   }

。。。。。。。。。。。。。。。。。。。。。。。。

  if (fifo >= host->pio_bytes)
   fifo = host->pio_bytes;
  else
   fifo -= fifo & 3;

  host->pio_bytes -= fifo;
  host->pio_count += fifo;

  fifo = (fifo + 3) >> 2;
  ptr = host->pio_ptr;
  while (fifo--)
   writel(*ptr++, to_ptr);
  host->pio_ptr = ptr;
 }

 它通過host->pio_bytes來記錄當前的內存塊還有多少數據沒有發,如果FIFO裏面的空間夠用,那就直接都發了,如果不夠呢,則先把FIFO填滿,然後等着下一次中斷的時候再發。如果這個內存塊的數據都發完了,則host->pio_bytes爲0,此時調用get_data_buffer來獲取內存鏈中的下一塊內存數據,在get_data_bu中host->pio_bytes會被置爲新塊的長度:

 *bytes = sg->length

其中的*bytes就是指向host->pio_bytes的。

fifo-=fifo&3 是因爲2410每次必須發四個字節,所以要把零頭去掉,EVB也有這個問題。

 

其實scatterlist這個東東蠻有意思的,俺們的Nucleus上其實也可以借鑑的,由於內存太少,在解JPEG文件時不一定能分到那麼大的一塊連續內存,可以通過scatterlist來把文件分塊讀取,然後解碼的時候DMA再一塊一塊的搬,總比分不到內存就返回失敗來的強。不過對於應用來講如果沒有MMU支持,還是有點杯具的,如果有MMU支持,讓應用層看到的是一整塊的內存,估計要爽的多,甚至在文件系統層也是這樣的,只有到DMA搬數之前把scatterlist的內存鏈分清楚就OK了。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章