今天下午閒下來了,想想還是給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了。