在閱讀核心代碼下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了