Linux驱动之DMA
2015-10-14 11:25
507 查看
上代码之前说一点自己的总结:不能用kmalloc,因为 kmalloc分配的物理地址有可能是不连续的,dma不能识别 DMA负责读取数据,读取的过程和CPU无关,当读取完了产生一次中断,让CPU来处理数据这样大大节省了时间,而不是让CPU去负责读取数据。DMA在产品效率开发过程中用的比较多,当你的CPU忙不过来的时候后你可以使用DMA,像读取数据,解析数据这些完全可以让DMA去做,CPU只来处理读出来的指令就行,免得CPU既要去读取
数据还得处理数据,这样效率不是很高。还有代码有可能你看不懂,但是没有关系,你结合你的Linux内核版本,不懂得函数用sourceInsight去查找。
下面有一个测试程序来教大家怎么使用DMA,由于个人能力有限,如果读者发现了那些错误,望请指正。
头文件根据自己内核版本添加,这个问题很好解决,我相信难不倒大家。
static char *src; /*分配的SRC, 对应的缓冲区*/
static u32 src_phys; /*物理地址*/
static char *dst; //分配的dst, 对应的缓冲区
static u32 dst_phys; //物理地址
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);//设置缓冲区的地址
memset(dst, 0x55, BUF_SIZE);//设置缓冲区的地址
switch (cmd)
{
case MEM_CPY_NO_DMA :
{
for (i = 0; i < BUF_SIZE; i++)
dst[i] = src[i];
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_NO_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
case MEM_CPY_DMA :
{
ev_dma = 0;
/* 把源,目的,长度告诉DMA */
dma_regs->disrc = src_phys; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = dst_phys; /* 目的的物理地址 */
dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */
/* 启动DMA */
dma_regs->dmasktrig = (1<<1) | (1<<0);
/* 如何知道DMA什么时候完成? */
/* 休眠 */
wait_event_interruptible(dma_waitq, ev_dma);
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
}
return 0;
}
static struct file_operations dma_fops = { /* 驱动的固定框架,给函数指针赋值,自己构造相应的函数 */
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
static irqreturn_t s3c_dma_irq(int irq, void *devid) //DMA数据传输完成就触发这个中断
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
static int s3c_dma_init(void)
{
if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1))
{
printk("can't request_irq for DMA\n");
return -EBUSY;
}
/* 分配SRC, DST对应的缓冲区 */
src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);
if (NULL == src)
{
printk("can't alloc buffer for src\n");
free_irq(IRQ_DMA3, 1);
return -ENOMEM;
}
dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);
if (NULL == dst)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
printk("can't alloc buffer for dst\n");
return -ENOMEM;
}
dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));
}
static void s3c_dma_exit(void)
{
iounmap(dma_regs);
unregister_chrdev(major, "s3c_dma");
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys);
free_irq(IRQ_DMA3, 1);
}
这个驱动程序不是很完整,但是DMA所使用到的功能应该基本讲解完了,具体在项目中怎么使用还得依靠你的聪明才智了,这篇文章只起到引导的作用。
数据还得处理数据,这样效率不是很高。还有代码有可能你看不懂,但是没有关系,你结合你的Linux内核版本,不懂得函数用sourceInsight去查找。
下面有一个测试程序来教大家怎么使用DMA,由于个人能力有限,如果读者发现了那些错误,望请指正。
头文件根据自己内核版本添加,这个问题很好解决,我相信难不倒大家。
static char *src; /*分配的SRC, 对应的缓冲区*/
static u32 src_phys; /*物理地址*/
static char *dst; //分配的dst, 对应的缓冲区
static u32 dst_phys; //物理地址
static volatile struct s3c_dma_regs *dma_regs;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);
/* 中断事件标志, 中断服务程序将它置1,ioctl将它清0 */
static volatile int ev_dma = 0;
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
memset(src, 0xAA, BUF_SIZE);//设置缓冲区的地址
memset(dst, 0x55, BUF_SIZE);//设置缓冲区的地址
switch (cmd)
{
case MEM_CPY_NO_DMA :
{
for (i = 0; i < BUF_SIZE; i++)
dst[i] = src[i];
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_NO_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
case MEM_CPY_DMA :
{
ev_dma = 0;
/* 把源,目的,长度告诉DMA */
dma_regs->disrc = src_phys; /* 源的物理地址 */
dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */
dma_regs->didst = dst_phys; /* 目的的物理地址 */
dma_regs->didstc = (0<<2) | (0<<1) | (0<<0); /* 目的位于AHB总线, 目的地址递增 */
dma_regs->dcon = (1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(BUF_SIZE<<0); /* 使能中断,单个传输,软件触发, */
/* 启动DMA */
dma_regs->dmasktrig = (1<<1) | (1<<0);
/* 如何知道DMA什么时候完成? */
/* 休眠 */
wait_event_interruptible(dma_waitq, ev_dma);
if (memcmp(src, dst, BUF_SIZE) == 0)
{
printk("MEM_CPY_DMA OK\n");
}
else
{
printk("MEM_CPY_DMA ERROR\n");
}
break;
}
}
return 0;
}
static struct file_operations dma_fops = { /* 驱动的固定框架,给函数指针赋值,自己构造相应的函数 */
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
};
static irqreturn_t s3c_dma_irq(int irq, void *devid) //DMA数据传输完成就触发这个中断
{
/* 唤醒 */
ev_dma = 1;
wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */
return IRQ_HANDLED;
}
static int s3c_dma_init(void)
{
if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1))
{
printk("can't request_irq for DMA\n");
return -EBUSY;
}
/* 分配SRC, DST对应的缓冲区 */
src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);
if (NULL == src)
{
printk("can't alloc buffer for src\n");
free_irq(IRQ_DMA3, 1);
return -ENOMEM;
}
dst = dma_alloc_writecombine(NULL, BUF_SIZE, &dst_phys, GFP_KERNEL);
if (NULL == dst)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
printk("can't alloc buffer for dst\n");
return -ENOMEM;
}
dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));
}
static void s3c_dma_exit(void)
{
iounmap(dma_regs);
unregister_chrdev(major, "s3c_dma");
dma_free_writecombine(NULL, BUF_SIZE, src, src_phys);
dma_free_writecombine(NULL, BUF_SIZE, dst, dst_phys);
free_irq(IRQ_DMA3, 1);
}
这个驱动程序不是很完整,但是DMA所使用到的功能应该基本讲解完了,具体在项目中怎么使用还得依靠你的聪明才智了,这篇文章只起到引导的作用。
相关文章推荐
- linux内核Device Drivers设备驱动程序
- linux下使用vsftpd包按需搭建ftp,留个记录供参考
- 在Centos 5.x或6.x上安装RHEL EPEL Repo
- Linux下的编码和字符集的资料
- Linux入门(一):50个最常用的指令
- Linux如何关闭防火墙
- 20条Linux命令面试问答,Linux命令
- Linux sort 命令的用法
- Linux中的likely()和unlikely()
- Linux学习记录(16)netfilter以及iptables概念及操作
- linux的那些坑爹事
- 学习日志---linux打卡9
- linux 解决非root用户用SSH不能无密码登陆本机问题。
- 在SD卡上安装树莓派的操作系统 - Linux版
- linux下杀死进程(kill)的N种方法
- linux 线程学习之条件变量
- [国嵌笔记][011][Linux密码破解]
- Centos下如何截图
- CentOS用host、dig、nslookup查询DNS命令
- VMware中CentOS设置静态IP