1、为什么要用DMA
DMA是一个片内控制模块,作用是将从指定源地址开始的指定长度的字符拷贝到指定目的地址处,并且DMA在执行拷贝操作的时候并不会消耗CPU的资源,等于说是字符串传输不经过CPU,而是直接经由DMA模块进行传输。这在一些需要大量数据传输并且又不想要耗费CPU资源的场合是十分有用的,比如摄像头数据的传输,这个就可使用DMA进行传输,不耗费CPU的资源。
2、S3C2440的DMA简述
- S3C2440有4个DMA通道,每个DMA通道可以有多个中断源,也可是软件触发的DMA中断。
- 支持每次单个字符串传输和突发传输(每次4字符)
- 有握手模式(传输一个单位就重新进行握手操作)和请求传输(一直传输至指定长度的数据)
3、驱动程序编写
#define WITH_DMA 1<span style="white-space:pre"> </span>//IO命令
#define STR_SIZE 256
static char *src; //数据的源地址
static u32 srcphy; //源地址的物理地址
static char *des; //数据的目的地址
static u32 desphy; //目的地址的物理地址
struct dma_regs
{
unsigned int disrc0;
unsigned int disrcc0;
unsigned int didst0;
unsigned int didstc0;
unsigned int dcon0;
unsigned int dstat0;
unsigned int dcsrc0;
unsigned int dcdst0;
unsigned int dmasktring0;
};
static struct dma_regs *dma_regs_begin;
static DECLARE_WAIT_QUEUE_HEAD(dma_waitq); //构建队列的头部
static int dma_ev;
static irqreturn_t dma_irq_handler(int irq,void *dev_id)
{
dma_ev = 1;
wake_up_interruptible(&dma_waitq); //唤醒队列
dma_regs_begin->dmasktring0 = (0 << 1) | (1 << 0); //关闭DMA传输
return IRQ_HANDLED;
}
/* IO控制,由测试函数来进行命令的传递,类似于 ioctl(fb, WITH_DMA); */
static int dma_ioctl_handler(struct inode *node, struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd){
case WITH_DMA : {
/* io重映射 */
dma_regs_begin = ioremap(0x4B0000C0, sizeof(struct dma_regs));
dma_regs_begin->disrc0 = srcphy; //要是物理地址
/* system bus; increment */
dma_regs_begin->disrcc0 = (0 << 1) | (0 << 0);
dma_regs_begin->didst0 = desphy; //目的的物理地址
dma_regs_begin->didstc0 = (0 << 2) | (0 << 1) | (0 << 0);
dma_regs_begin->dcon0 = (0 << 31) | (1 << 30) | (1 << 29) | \
(0 << 28) | (1 << 27) | (1 << 22) | (STR_SIZE << 0);
//如果在DMA传输完成的时候立马关闭会导致DMA中断不能够发生,所以要在中断完成之后关闭DMA
//上面的27位1,下面的2位0
dma_regs_begin->dmasktring0 = (0 << 2) | (1 << 1) | (1 << 0);
dma_ev = 0;
wait_event_interruptible(dma_waitq, dma_ev);
if(0 == memcmp(des, src, STR_SIZE)){
printk("With-dma copy success\n");
}else{
printk("With-dma copy error\n");
}
break;
}
default:
break;
}
return 0;
}
static struct file_operations dma_ops = {
.owner = THIS_MODULE,
.ioctl = dma_ioctl_handler,
};
static int major;
static struct cdev dma_chrdev; //如定义为指针类型的变量需要进行初始化
static struct class *cls; //设备类
static int dma_init(void)
{
dev_t idnod;
if(major){
idnod = MKDEV(major, 0); //根据主设备号进行次设备节点的获取
if(register_chrdev_region(idnod, 1, "dma"))//注册一个字符设备区域,长度为1
{
unregister_chrdev_region(idnod, 1); //注册失败返回
printk("register_chrdev_region error\n");
return -1;
}
}else{
if(alloc_chrdev_region(&idnod, 0, 1, "dma") == 0) //分配设备节点
major = MAJOR(idnod); //获取主设备号
else{
printk("alloc_chrdev_region error\n");
return -1; //未成功分配设备号,返回错误
}
}
//没有使用此函数导致打开设备的时候出现段错误
cdev_init(&dma_chrdev, &dma_ops);
cdev_add(&dma_chrdev, idnod, 1);
cls = class_create(THIS_MODULE, "dma");
class_device_create(cls, 0, MKDEV(major, 0), 0, "DMA_DRV"); //在类上面创建设备节点
/* 分配源地址与目的地址 */
src = dma_alloc_writecombine(NULL, STR_SIZE, &srcphy, GFP_KERNEL);
if(NULL == src){
printk("dma_alloc_writecombine src error\n");
dma_free_writecombine(NULL, STR_SIZE, src, srcphy);
return -1;
}
des = dma_alloc_writecombine(0, STR_SIZE, &desphy, GFP_KERNEL);
if(NULL == des){
printk("dma_alloc_writecombine des error\n");
dma_free_writecombine(NULL, STR_SIZE, des, desphy);
return -1;
}
/* DMA中断初始化 */
if(request_irq(IRQ_DMA3, dma_irq_handler, 0, "s3c_dma", 1)){
printk("request_irq error \n");
free_irq(IRQ_DMA3, 1);
return -1;
}
return 0;
}
static void dma_exit(void)
{
free_irq(IRQ_DMA3, 1);
dma_free_writecombine(NULL, STR_SIZE, src, srcphy);
dma_free_writecombine(NULL, STR_SIZE, des, desphy);
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
cdev_del(&dma_chrdev);
unregister_chrdev_region(MKDEV(major, 0), 1);
iounmap(dma_regs_begin);
}
module_init(dma_init);
module_exit(dma_exit);
MODULE_LICENSE("GPL");
4、遇到的问题以及原因
1、在open设备的时候出现段错误
原因:在cdev初始化的时候使用的是
cdev->ops = fops; cdev->owner = THIS_MODULE;并没有对cdev结构体进行整体的内存初始化,改用
cdev_init(&dma_chrdev, &dma_ops);函数即可,函数内部进行了下面的操作
memset(cdev, 0, sizeof *cdev); //初始化内存为0
INIT_LIST_HEAD(&cdev->list); //将cdev的list结构体初始化为一个list_head的头部
cdev->kobj.ktype = &ktype_cdev_default; //设置默认函数
kobject_init(&cdev->kobj); //内核基础结构之一,用作内核与用户的交互,此处不做深入理解
cdev->ops = fops; //初始化file_operations结构体
2、DMA中断在传输完成之后没有触发
原因:
dma_regs_begin->dcon0 = (0 << 31) | (1 << 30) | (1 << 29) | \
(0 << 28) | (1 << 27) | (1 << 22) | (STR_SIZE << 0);
//如果在DMA传输完成的时候立马关闭会导致DMA中断不能够发生,所以要在中断完成之后关闭DMA
//上面的第27位置1,下面的第2位置0
dma_regs_begin->dmasktring0 = (0 << 2) | (1 << 1) | (1 << 0);