一、DMA 介绍(直接存储器访问)

DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方。在这个时间中,CPU 对于其他的工作来说就无法使用。

DMA 传输将数据从一个地址空间复制到另外一个地址空间。当CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。像是这样的操作并没有让处理器工作拖延,反而可以被重新排程去处理其他的工作。DMA 传输对于高效能 嵌入式系统算法和网络是很重要的。

在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

  1. 请求CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。
  2. 响应DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。
  3. 传输DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。
    在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。
  4. 结束

当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。

由此可见,DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,使CPU的效率大为提高。


二、Linux下内核DMA相关函数介绍

2.1 分配一块物理地址连续的内存

static inline void *dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)

  • 功能介绍:分配一块物理地址连续的内存,供后续DMA传输使用。
  • 函数参数:struct device *dev : 传入设备指针,没有填NULL
    size_t size : 分配的空间大小。
    dma_addr_t *dma_handle :存放分配之后的物理地址。
    gfp_t flag :分配的属性。常用: GFP_KERNEL
  • 返回值: 物理地址对应的虚拟地址,内核代码本身只能操作虚拟地址。

2.2 释放分配的内存

static inline void dma_free_writecombine(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_handle)

  • 功能介绍:释放之前申请的内存空间。
  • 函数参数:

struct device *dev : 传入设备指针,没有填NULL

size_t size : 释放空间的大小。

dma_addr_t *dma_handle :空间的虚拟地址(dma_alloc_writecombine返回值)

dma_addr_t dma_handle :空间物理地址

2.3 分配内存示例​

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/fs.h>

#include <linux/uaccess.h>

#include <linux/io.h>

#include <linux/dma-mapping.h>


#define BUF_SIZE (512*1024)

static u32 src_phys; 存放分配的物理起始地址

static unsigned char *src; //存放分配的虚拟起始地址


static u8 buff[]="分配一块连续的物理地址,可供DMA使用";

static int __init tiny4412_dma_dev_init(void)

{

/*分配一块连续的物理地址,可供DMA使用*/

src=dma_alloc_writecombine(NULL,BUF_SIZE,&src_phys,GFP_KERNEL);

if(src==NULL)

{

printk("空间分配失败!\n");

}

else

{

printk("驱动安装成功!\n"); /*提示语句*/

printk("分配的物理地址:0x%X\n",&src_phys);

printk("分配的虚拟地址:0x%X\n",src);


memcpy(src,buff,sizeof(buff));

}


/*

说明: Tiny4412开发板内存条大小为1G

*/

return 0;

}


static void __exit tiny4412_dma_dev_exit(void)

{

if(src)

{

printk("buff=%s\n",buff);

/*释放分配的空间*/

dma_free_writecombine(NULL,BUF_SIZE,src,src_phys);

}

printk("驱动卸载成功!\n");

}


module_init(tiny4412_dma_dev_init);

module_exit(tiny4412_dma_dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("tiny4412 wbyq");