DMA驱动

DMA驱动

一 DMA简介

DMA:Direct Memory Access 直接内存存取

根据2440的芯片手册,DMA分为四种工作模式:

  • 数据传输源地址在系统总线,数据传输目的地址在外部总线
  • 数据传输源地址在系统总线,数据传输目的地址在系统总线
  • 数据传输源地址在外部总线,数据传输目的地址在外部总线
  • 数据传输源地址在外部总线,数据传输目的地址在系统总线

简而言之,DMA的作用就是用来进行数据的传输,而传输的过程不需要CPU的参与,可以为
系统减负。

对DMA的设置分为以下几个步骤:

  1. 把源告诉DMA
  2. 把目的告诉DMA
  3. 把size告诉DMA
  4. 设置DMA的参数
  5. 启动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;	
}

驱动入口函数分析:

  1. 注册DMA中断函数,在ioctl函数中,启动DMA之后进入休眠,此中断函数是用于在DMA
    传输完成之后唤醒ioctl.
  2. dma_alloc_writecombine : 用来分配源和目的的buffer,此处不能用kmalloc,原因在于kmalloc分配到的内存地址不一定连续,而DMA传输需要源和目的的地址都是连续的
  3. register_chrdev:注册一个字符设备驱动,把dma_fops注册进内核,dma的操作一般使用的是ioctl,而不是rend/write
  4. class_create:创建一个类
  5. class_device_create:在这个类下面创建一个字符设备节点
  6. 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函数分析:

  1. 函数中通过用户传入的数选择使用DMA还是不使用DMA
  2. 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 协议 ,转载请注明出处!

热拔插驱动 上一篇
字符设备驱动 下一篇