在阅读内核代码下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; /* 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;
};
下面以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, 其实是一样的:
/* 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自己产生自己消化。在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了
原文:https://blog.csdn.net/wangteng12345678/article/details/51873875