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了。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页