DMA驱动
DMA驱动
一 DMA简介
DMA:Direct Memory Access 直接内存存取
根据2440的芯片手册,DMA分为四种工作模式:
- 数据传输源地址在系统总线,数据传输目的地址在外部总线
- 数据传输源地址在系统总线,数据传输目的地址在系统总线
- 数据传输源地址在外部总线,数据传输目的地址在外部总线
- 数据传输源地址在外部总线,数据传输目的地址在系统总线
简而言之,DMA的作用就是用来进行数据的传输,而传输的过程不需要CPU的参与,可以为
系统减负。
对DMA的设置分为以下几个步骤:
- 把源告诉DMA
- 把目的告诉DMA
- 把size告诉DMA
- 设置DMA的参数
- 启动DMA
二 DMA驱动代码分析
驱动入口函数
int dma_init(void)
{
if(request_irq(IRQ_DMA3,s3c_dma_irq,0,"s3c_dma",1))
{
printk("can not request irq for dma \n");
return -EBUSY;
}
src = dma_alloc_writecombine(NULL, BUFF_SIZE, &src_phy, GFP_KERNEL);
if(src == NULL)
{
free_irq(IRQ_DMA3,1);
printk(" can not alloc buffer for src \n");
return -ENOMEM;
}
des = dma_alloc_writecombine(NULL, BUFF_SIZE, &des_phy, GFP_KERNEL);
if(des == NULL)
{
free_irq(IRQ_DMA3,1);
printk(" can not alloc buffer for des \n");
dma_free_writecombine(NULL, BUFF_SIZE, &src_phy, GFP_KERNEL);
return -ENOMEM;
}
major = register_chrdev(0, "s3c_dma", &dma_fops);
cls = class_create(THIS_MODULE,"s3c_dma");
class_device_create(cls, NULL, MKDEV(major,0),NULL,"dma"); /* /dev/dma */
p_s3c_regs = ioremap(S3C_DMA3_BASE, sizeof(struct s3c_dma_regs));
return 0;
}
驱动入口函数分析:
- 注册DMA中断函数,在ioctl函数中,启动DMA之后进入休眠,此中断函数是用于在DMA
传输完成之后唤醒ioctl. - dma_alloc_writecombine : 用来分配源和目的的buffer,此处不能用kmalloc,原因在于kmalloc分配到的内存地址不一定连续,而DMA传输需要源和目的的地址都是连续的
- register_chrdev:注册一个字符设备驱动,把dma_fops注册进内核,dma的操作一般使用的是ioctl,而不是rend/write
- class_create:创建一个类
- class_device_create:在这个类下面创建一个字符设备节点
- ioremap:分配DMA寄存器的虚拟地址
ioctl函数
int s3c_dma_ioctl (struct inode * node, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src,0X55,BUFF_SIZE);
memset(des,0XAA,BUFF_SIZE);
switch(cmd)
{
case NO_USE_DMA :
{
for(i=0;i<BUFF_SIZE;i++)
{
des[i] = src[i];
}
if(memcmp(des,src,BUFF_SIZE) == 0)
{
printk("NO_USE_DMA OK \n");
}
else
{
printk("NO_USE_DMA ERROR \n");
}
break;
}
case USE_DMA:
{
ev_dma = 0;
p_s3c_regs->disrc = src_phy;
/* 把源,目的,长度告诉DMA */
p_s3c_regs->disrc = src_phy; /* 源的物理地址 */
p_s3c_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
p_s3c_regs->didst = des_phy; /* 目的的物理地址 */
p_s3c_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
p_s3c_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUFF_SIZE<<0); /* 使能中断,单个传输,软件触发, */
/* 启动DMA */
p_s3c_regs->dmasktrig = (1<<1) | (1<<0);
/* 如何知道DMA转换完成? */
/* 休眠 */
wait_event_interruptible(dma_waittq, ev_dma);
if(memcmp(des,src,BUFF_SIZE) == 0)
{
printk("USE_DMA OK \n");
}
else
{
printk("USE_DMA ERROR \n");
}
break;
}
}
return 0;
}
ioctl函数分析:
- 函数中通过用户传入的数选择使用DMA还是不使用DMA
- wait_event_interruptible(dma_waittq, ev_dma):函数进入休眠,参数定义如下:
static DECLARE_WAIT_QUEUE_HEAD(dma_waittq);
static volatile int ev_dma = 0;
进入iotcl函数时,把ev_dma设置为0,休眠ioctl
DMA传输完成时,把ev_dma设置为1,唤醒ioctl,唤醒的中断函数如下:
static irqreturn_t s3c_dma_irq(int irq, void *devid)
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waittq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
以上就是DMA的一个简单的试验,在虚拟机上将DMA测试文件放在后台运行,再输入ls等指令,使用DMA和不使用DMA,CPU对指令的反应速度有很大的差距。
小结
原先做电池管理时,在STM32上做ADC采集数据,一次需要16路ADC的数据,所以将ADC配置成了DMA模式,减轻CPU工作量的效果还是很明显的。其实,还有别的外设也是可以配置成DMA模式的。
DMA驱动
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!