mmc读写中scatterlist用法

来源:互联网 发布:大数据魔镜免费吗 编辑:程序博客网 时间:2024/05/24 23:14
在阅读内核代码下mmc模块时,经常会遇到mmc读写函数,一般的方式为创建一个请求队列,将命令和数据buf添加到请求队列里,有mmc块设备驱动将请求队列发下去,但是将数据buf并不是直接带下去,而是创建了scatterlist结构体,用sg_init_one函数将buf与其绑定,而由这个结构体进行数据的下发或读取,如下所示,是读取MMC ext——csd的一个函数
static intmmc_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;} elsemmc_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的原因就是系统在运行的时候内存会产生很多碎片,比如4k100k的,1M的,有时候对应磁盘碎片,总之就是碎片。而在网络和磁盘操作中很多时候需要传送大块的数据,尤其是使用DMA的时候,因为DMA操作的物理地址必须是连续的。假设要1M内存,此时可以分配一个整的1M内存, 也可以把1010K的和9100K的组成一块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来记录现在使用的是内存链中的第几块,这个值在每次requestprepare_pio的时候被清零,每次调用get_data_buffer就加 一,直到等于sg_len,表示所有的内存块都用过了。对于一个scatterlist指针sg,是如下这样获取它代表的内存块的大小和位置的:

 *bytes = sg->length;/*内存块长度*/
 *pointer = sg_virt(sg);/*内存块起始地址*/

        dw_mmc.c中使用了一点点小技巧来操作scatterlistSD/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_bytes0,此时调用get_data_buffer来获取内存链中的下一块内存数据,在get_data_buhost->pio_bytes会被置为新块的长度:

 *bytes = sg->length

        其中的*bytes就是指向host->pio_bytes的。

        fifo-=fifo&3是因为2410每次必须发四个字节,所以要把零头去掉,EVB也有这个问题。

        其实scatterlist这个东东蛮有意思的,俺们的Nucleus上其实也可以借鉴的,由于内存太少,在解JPEG文件时不一定能分到那么大的一块连续内存,可以通过scatterlist来把文件分块读取,然后解码的时候DMA再一块一块的搬,总比分不到内存就返回失败来的强。不过对于应用来讲如果没有MMU支持,还是有点杯具的,如果有MMU支持,让应用层看到的是一整块的内存,估计要爽的多,甚至在文件系统层也是这样的,只要到DMA搬数之前把scatterlist的内存链分清楚就OK

 



0 0
原创粉丝点击