嵌入式Linux驱动笔记(二十八)------DMA的简单使用分析

你好!这里是风筝的博客,欢迎和我一起交流。最近被一个需求折磨,对DMA传输速度有极大要求,被迫对着DMA进行魔改。。。。。简单复习总结一下关于DMA到一些知识:在DMA传输里,最耗时到莫过于map操作了,那么,为什么要map呢?内核通常使用的地址是虚拟地址,对于内存和外设之间使用到地址是总线地址(Busaddresses)。例如一个PCI设备支持DMA,那么在驱动中我们可以通过kmalloc或者其...

嵌入式Linux驱动笔记(二十八)------DMA的简单使用分析
你好!这里是风筝的博客,欢迎和我一起交流。

最近被一个需求折磨,对DMA传输速度有极大要求,被迫对着DMA进行魔改。。。。。
简单复习总结一下关于DMA到一些知识:

在DMA传输里,最耗时到莫过于map操作了,那么,为什么要map呢?
内核通常使用的地址是虚拟地址,对于内存和外设之间使用到地址是总线地址(Bus addresses)。

例如一个PCI 设备支持DMA,那么在驱动中我们可以通过kmalloc或者其他类似接口分配一个DMA buffer,并且返回了虚拟地址X,MMU将X地址映射成了物理地址Y,从而定位了DMA buffer在系统内存中的位置。因此,驱动可以通过访问地址X来操作DMA buffer,但是PCI 设备并不能通过X地址来访问DMA buffer,因为MMU对设备不可见,而且系统内存所在的系统总线和PCI总线属于不同的地址空间。
所以,驱动在调用dma_map_single这样的接口函数的时候会传递一个虚拟地址X,在这个函数中会设定IOMMU的页表,将地址X映射到Z,并且将返回z这个总线地址。驱动可以把Z这个总线地址设定到设备上的DMA相关的寄存器中。这样,当设备发起对地址Z开始的DMA操作的时候,IOMMU可以进行地址映射,并将DMA操作定位到Y地址开始的DMA buffer。

网上说:“根据DMA缓冲区期望保留的时间长短,PCI代码有两种DMA映射:一致性映射和流式映射”。
我觉得说的不太对,对于缓存区保留时间到长短来分区两种映射有失偏见,这只能算是他们表现出来到现象。
当然,我也从网上找到了一些比较靠谱到说法:

CPU写内存的时候有两种方式:

  • write through: CPU直接写内存,不经过cache。
  • write back: CPU只写到cache中。cache的硬件使用LRU算法将cache里面的内容替换到内存。通常是这种方式。
  • DMA可以完成从内存到外设直接进行数据搬移。但DMA不能访问CPU的cache,CPU在读内存的时候,如果cache命中则只是在cache去读,而不是从内存读,写内存的时候,也可能实际上没有写到内存,而只是直接写到了cache。
    这样一来,如果DMA从将数据从外设写到内存,CPU中cache中的数据(如果有的话)就是旧数据了,这时CPU在读内存的时候命中cache了,就是读到了旧数据;CPU写数据到内存时,如果只是先写到了cache,则内存里的数据就是旧数据了。这两种情况(两个方向)都存在cache一致性问题。例如,网卡发包的时候,CPU将数据写到cache,而网卡的DMA从内存里去读数据,就发送了错误的数据。

    对于DMA一致性映射来说,CPU和DMA controller在发起对DMA buffer的并行访问的时候不需要考虑cache的影响,也就是说不需要软件进行cache操作,CPU和DMA controller都可以看到对方对DMA buffer的更新。
    对于DMA流式映射,可以说是属于非一致性的,它是异步的。相较于DMA一致性映射在驱动初始化时map,在驱动退出的时候unmap,DMA流式映射需要进行DMA传输的时候才进行mapping,一旦DMA传输完成,就立刻ummap,这样充分优化硬件的性能。

    DMA一致性映射使用cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp)请一块缓冲区…
    DMA流式映射有两种map/ummap:
    1)对于单个dma buffer:dma_handle = dma_map_single(dev, addr, size, direction);
    1)对于多个形成scatterlist的dma buffer:count = dma_map_sg(dev, sglist, nents, direction)

    int i, count = dma_map_sg(dev, sglist, nents, direction);//其中nents为sglist的条目数量struct scatterlist *sg;for_each_sg(sglist, sg, count, i) {hw_address[i] = sg_dma_address(sg);  hw_len[i] = sg_dma_len(sg); }//这种实现可以很方便将若干连续的sglist条目合并成一个大块且连续的总线地址区域。//然后调用for_each_sg来遍历所有成功映射的mappings(可能会小于nents次)并且使用//sg_dma_address() 和 sg_dma_len() 这两个宏来得到mapping后的dma地址和长度

    DMA流式映射需要定义方向:

    DMA_TO_DEVICE 数据从内存传输到设备。DMA_FROM_DEVICE 数据从设备传输到内存。DMA_BIDIRECTIONAL 不清楚传输方向则可用该类型。DMA_NONE 仅用于调试目的

    一致性内存映射隐性的设置为DMA_BIDIRECTIONAL。

    多次使用DMA流映射,同时在DMA传输过程中访问数据,必须确保缓冲区中所有的数据已经被实际写到内存。可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新(对DMA buffer进行sync操作),需要合适地同步数据缓冲区,这样可以让处理器及外设可以看到最新的更新和正确的DMA缓冲区数据。
    在DMA传输完成后适当地调用:

    dma_sync_single_for_cpu(dev, dma_handle, size, direction);

    或者

    dma_sync_sg_for_cpu(dev, sglist, nents, direction);

    如果,CPU操作了DMA buffer的数据,然后你又想把控制权交给设备上的DMA 控制器,让DMA controller访问DMA buffer,这时候,在真正让HW(指DMA控制器)去访问DMA buffer之前,需要调用:

    dma_sync_single_for_device(dev, dma_handle, size, direction);

    或者:

    dma_sync_sg_for_device(dev, sglist, nents, direction);

    最后,使用DMA分为五个步骤:
    1)申请一个DMA channel。
    2)根据设备(slave)的特性,配置DMA channel的参数。
    3)要进行DMA传输的时候,获取一个用于识别本次传输(transaction)的描述符(descriptor)。
    4)将本次传输(transaction)提交给dma engine并启动传输。
    5)等待传输(transaction)结束。

    放出一个demo参考,tx_buf写数据,rx_buf读数据,还弄了一个tmp_buf用来模拟device作为中转:

    #include <linux/delay.h>#include <linux/dmaengine.h>#include <linux/init.h>#include <linux/kthread.h>#include <linux/module.h>#include <linux/of_dma.h>#include <linux/platform_device.h>#include <linux/random.h>#include <linux/slab.h>#include <linux/wait.h>#include <asm/uaccess.h>#include <linux/dmaengine.h>#include <linux/dma-mapping.h>#include <linux/dma/sunxi-dma.h>#include <linux/mm.h>#include <linux/miscdevice.h>#include <linux/kernel.h>#include <asm/cacheflush.h>#include <asm/io.h>#include <asm/uaccess.h>#define DBG(string, args...)\do { \printk("DMA> %s(%u): ", __FUNCTION__, __LINE__); \printk(string, ##args); \} while(0)#define BUF_SIZE 256struct completion tx_cmp;struct completion rx_cmp;static struct dma_data{u8 *tx_buf;u8 *rx_buf;struct dma_chan *tx_chan;struct dma_chan *rx_chan;};struct dma_data* dma;void dma_tx_callback(void *completion){DBG("dma -write data end!\n");complete(completion);}void dma_rx_callback(void *completion){DBG("dma -read data end!\n");complete(completion);}static ssize_t send_dma(struct file *filp, const char __user *buf,size_t count, loff_t *f_pos){unsigned long time_left;//struct dma_data *dma = filp->private_data;DBG("start send\n");init_completion(&tx_cmp);DBG("wait send\n");//Setup 5dma_async_issue_pending(dma->tx_chan);time_left = wait_for_completion_timeout(&tx_cmp,msecs_to_jiffies(10000));if(time_left == 0){printk("dma tx time  out\n");}else {DBG("Start DMA success\n");}return 0;}static ssize_t recv_dma(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){unsigned long time_left;int i = 0;//struct dma_data *dma = filp->private_data; /*获得设备结构体指针*/DBG("start recv\n");init_completion(&rx_cmp);//Setup 3dma_async_issue_pending(dma->rx_chan);DBG("wait recv\n");time_left = wait_for_completion_timeout(&rx_cmp,msecs_to_jiffies(10000));if(time_left == 0){printk("dma rx time  out\n");}else {printk("recv data is :");for(i=0; i<BUF_SIZE; i  )printk("%d ",dma->rx_buf[i]);}return 0;}static int mmap_dma(struct file*filp, struct vm_area_struct *vma){//struct dma_data *dma = filp->private_data; /*获得设备结构体指针*/vma->vm_flags |= VM_IO;vma->vm_flags |= VM_LOCKED;if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(dma->tx_buf)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))return  -EAGAIN;return 0;}static int dma_transfer(struct dma_data * dma){struct dma_chan *tx_chan = dma->tx_chan;struct dma_chan *rx_chan = dma->rx_chan;struct device *tx_dev = tx_chan->device->dev;struct device *rx_dev = rx_chan->device->dev;dma_addr_t dma_srcs;dma_addr_t dma_dest;struct dma_async_tx_descriptor *dma_desc_tx;struct dma_async_tx_descriptor *dma_desc_rx;//Setup 3dma_srcs = dma_map_single(tx_dev, dma->tx_buf, BUF_SIZE,DMA_MEM_TO_DEV);if (dma_mapping_error(tx_dev, dma_srcs)) {printk("tx:DMA mapping failed\n");goto err_map;}dma_dest = dma_map_single(rx_dev, dma->rx_buf, BUF_SIZE,DMA_DEV_TO_MEM);if (dma_mapping_error(rx_dev, dma_dest)) {printk("rx:DMA mapping failed\n");goto err_map;}dma_desc_tx = dmaengine_prep_slave_single(tx_chan, dma_srcs,BUF_SIZE, DMA_MEM_TO_DEV,DMA_PREP_INTERRUPT | DMA_CTRL_ACK);if (!dma_desc_tx) {printk("Not able to get desc for DMA xfer\n");goto err_desc;}dma_desc_tx->callback = dma_tx_callback;dma_desc_tx->callback_param = &tx_cmp;dma_desc_rx = dmaengine_prep_slave_single(rx_chan, dma_dest,BUF_SIZE, DMA_DEV_TO_MEM,DMA_PREP_INTERRUPT | DMA_CTRL_ACK);if (!dma_desc_rx) {printk("Not able to get desc for DMA xfer\n");goto err_desc;}dma_desc_rx->callback = dma_rx_callback;dma_desc_rx->callback_param = &rx_cmp;//Setup 4if (dma_submit_error(dmaengine_submit(dma_desc_tx))) {printk(" DMA submit failed\n");goto err_submit;}if (dma_submit_error(dmaengine_submit(dma_desc_rx))) {printk(" DMA submit failed\n");goto err_submit;}return 0;err_submit:err_desc:dma_unmap_single(tx_dev, dma_srcs,BUF_SIZE,DMA_MEM_TO_DEV );dma_unmap_single(rx_dev, dma_dest,BUF_SIZE,DMA_DEV_TO_MEM );err_map:return -EINVAL;}static int open_dma(struct inode *inode,struct file *filp){return 0;}static struct file_operations dev_fops = {.owner=THIS_MODULE,.open =open_dma,.write =send_dma,.read =recv_dma,.mmap =mmap_dma,};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR,.name = "dma_transfer_test",.fops = &dev_fops,};static int dma_transfer_init(void){int ret;//struct dma_data* dma;dma_cap_mask_t mask_tx, mask_rx;struct dma_slave_config dma_tx_sconfig;struct dma_slave_config dma_rx_sconfig;u8 * virt_tmp_buf;ret = misc_register(&misc);DBG("DMA register\n");dma = kzalloc(sizeof(struct dma_data), GFP_KERNEL);if (!dma)return -ENOMEM;dma->tx_buf = kmalloc(BUF_SIZE,GFP_KERNEL);//DMA);if (dma->tx_buf == NULL) {printk("tx_buf kmalloc faul!\n");}dma->rx_buf = kmalloc(BUF_SIZE,GFP_KERNEL);//DMA);if (dma->rx_buf == NULL) {printk("rx_buf kmalloc faul!\n");}//Setup 1dma_cap_zero(mask_tx);dma_cap_set(DMA_SLAVE, mask_tx);dma->tx_chan =dma_request_channel(mask_tx, NULL, NULL);//dma_request_slave_channelif(IS_ERR(dma->tx_chan)){printk("request tx chan is fail!\n");return PTR_ERR(dma->tx_chan);}dma_cap_zero(mask_rx);dma_cap_set(DMA_SLAVE, mask_rx);dma->rx_chan =dma_request_channel(mask_rx, NULL, NULL);//dma_request_slave_channelif(IS_ERR(dma->rx_chan)){printk("request rx chan is fail!\n");return PTR_ERR(dma->rx_chan);}virt_tmp_buf = kmalloc(BUF_SIZE, GFP_KERNEL);memset(virt_tmp_buf, 0, BUF_SIZE);memset(dma->tx_buf, 6, BUF_SIZE);//Setup 2dma_tx_sconfig.dst_addr = virt_to_phys(virt_tmp_buf);//dma_tx_sconfig.src_addr = virt_to_phys(dma->tx_buf);dma_tx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;dma_tx_sconfig.src_maxburst = 1;dma_tx_sconfig.dst_maxburst = 1;dma_tx_sconfig.slave_id = sunxi_slave_id(DRQDST_SDRAM,DRQSRC_SDRAM);dma_tx_sconfig.direction = DMA_MEM_TO_DEV;ret = dmaengine_slave_config(dma->tx_chan, &dma_tx_sconfig);if (ret < 0) {printk("can't configure tx channel\n");return -1;}//dma_tx_sconfig.dst_addr = virt_to_phys(dma->tx_buf);dma_rx_sconfig.src_addr = virt_to_phys(virt_tmp_buf);dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;dma_rx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;dma_rx_sconfig.src_maxburst = 1;dma_rx_sconfig.dst_maxburst = 1;dma_rx_sconfig.slave_id = sunxi_slave_id(DRQDST_SDRAM,DRQSRC_SDRAM);dma_rx_sconfig.direction = DMA_DEV_TO_MEM;ret = dmaengine_slave_config(dma->rx_chan, &dma_rx_sconfig);if (ret < 0) {printk("can't configure rx channel\n");return -1;}ret = dma_transfer(dma);if(ret){printk("Unable to init dma transfer\n");}return 0;}static void dma_transfer_exit(void){misc_deregister(&misc);}module_init(dma_transfer_init);module_exit(dma_transfer_exit);MODULE_LICENSE("GPL");

    更多细节详情参考:
    蜗窝科技
    Linux之DMA动态映射指南

    源文地址:https://www.guoxiongfei.cn/csdn/7920.html