天天看點

mmc讀寫中scatterlist用法

在閱讀核心代碼下mmc子產品時,經常會遇到mmc讀寫函數,一般的方式為建立一個請求隊列,将指令和資料buf添加到請求隊列裡,有mmc塊裝置驅動将請求隊列發下去,但是将資料buf并不是直接帶下去,而是建立了scatterlist結構體,用sg_init_one函數将buf與其綁定,而由這個結構體進行資料的下發或讀取,如下所示,是讀取MMC ext——csd的一個函數

static int
mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host,
		u32 opcode, void *buf, unsigned len)
{
	struct mmc_request mrq;
	struct mmc_command cmd;
	struct mmc_data data;
	struct scatterlist sg;
	void *data_buf;

	/* dma onto stack is unsafe/nonportable, but callers to this
	 * routine normally provide temporary on-stack buffers ...
	 */
	data_buf = kmalloc(len, GFP_KERNEL);
	if (data_buf == NULL)
		return -ENOMEM;

	memset(&mrq, 0, sizeof(struct mmc_request));
	memset(&cmd, 0, sizeof(struct mmc_command));
	memset(&data, 0, sizeof(struct mmc_data));

	mrq.cmd = &cmd;
	mrq.data = &data;

	cmd.opcode = opcode;
	cmd.arg = 0;

	/* NOTE HACK:  the MMC_RSP_SPI_R1 is always correct here, but we
	 * rely on callers to never use this with "native" calls for reading
	 * CSD or CID.  Native versions of those commands use the R2 type,
	 * not R1 plus a data block.
	 */
	cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;

	data.blksz = len;
	data.blocks = 1;
	data.flags = MMC_DATA_READ;
	data.sg = &sg;
	data.sg_len = 1;

	sg_init_one(&sg, data_buf, len);

	if (opcode == MMC_SEND_CSD || opcode == MMC_SEND_CID) {
		/*
		 * The spec states that CSR and CID accesses have a timeout
		 * of 64 clock cycles.
		 */
		data.timeout_ns = 0;
		data.timeout_clks = 64;
	} else
		mmc_set_data_timeout(&data, card);

	mmc_wait_for_req(host, &mrq);

	memcpy(buf, data_buf, len);
	kfree(data_buf);

	if (cmd.error)
		return cmd.error;
	if (data.error)
		return data.error;

	return 0;
}
           
而核心為什麼要這麼做呢而不是直接将一個buf指針傳下去呢
           

接下來介紹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; 

 struct scatterlist *sg; 

}

        其中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;
};
           

  下面以dw_mmc.c裡面的scatter操作來分析,其實pxamci.c裡面也有,不過pxamci.c裡面隻使用了DMA模式,相對要簡單一點,dw_mmc.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 = dw_dma_enqueue(host->dma, host,

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

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

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

 }

        for循環其實就是周遊記憶體塊了,不過據下面這個網址上說的,這種用for的方式已經out了,現在要用for_each_sg, 其實是一樣的:

    for_each_sg(i, list, sgentry, nentries) {

             program_hw(device, sg_dma_address(sgentry), sg_dma_len(sgentry));

    }

2.CPU方式

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

        static inline int get_data_buffer(struct dw

_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);

        在dw_mmc.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了

繼續閱讀