学习目的:
- 熟悉Linux下DMA驱动程序编写
1、DMA基本概念
DMA,全称Direct Memory Access,即直接储存器访问。
它是一种高速的数据传送操作,可用于芯片的外设和存储器或存储器和存储器或外设和外设之间数据的传输。DMA的数据传送过程不需要CPU干预,是通过DMA控制器完成的,因此在DMA数据传送过程中不占用CPU的资源。不过在启动DMA控制器进行数据传输前,需要CPU来告诉DMA控制器数据传输的源地址、目的地址、数据传输长度等信息。
2、硬件相关配置
DMA控制器的配置依赖于硬件平台,这里使用的是S3C2440芯片,它有一个4通道的DMA控制器,每个通道都可以不受限制地在系统总线和/或外围总线中的设备之间执行数据移动。
2.1 请求模式设置
S3C2440芯片的DMA数据传输支持两者请求模式,一种是硬件请求模式,另一种是软件请求模式。请求模式可以通过DMA控制寄存器DCONn的第23位来设置,如果设置成0,该通道的DMA工作在软件请求模式,如果设置成1,则该通道DMA工作在硬件请求模式。
对于工作在硬件请求模式的DMA,各通道的请求源,如下图所示:
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/irq.h>#include <asm/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <plat/gpio-fns.h>#include <mach/gpio-nrs.h>#include <linux/interrupt.h>#include <linux/wait.h>#include <linux/sched.h>#include <linux/device.h>#include <linux/gpio.h>#include <linux/dma-mapping.h>#define COPY_NO_DMA 0#define COPY_BY_DMA 1#define COPY_BUFF_SIZE (1024*512)#define DMA0_BASE_ADDR 0x4B000000#define DMA1_BASE_ADDR 0x4B000040#define DMA2_BASE_ADDR 0x4B000080#define DMA3_BASE_ADDR 0x4B0000C0struct s3c_dma_regs{unsigned long disrc;unsigned long disrcc;unsigned long didst;unsigned long didstc;unsigned long dcon;unsigned long dstat;unsigned long dcsrc;unsigned long dcdst;unsigned long dmasktrig;};static int major = 0;static int trans_ok = 0;static char *src_addr;static dma_addr_t *src_phy_addr;static char *dst_addr;static dma_addr_t *dst_phy_addr;static volatile struct s3c_dma_regs *dma_regs;static DECLARE_WAIT_QUEUE_HEAD(dma_waitq);static struct class *cls;static struct class_device *cls_dev;static irqreturn_t s3c_dma_irq(int irq, void *dev_id){trans_ok = 1;wake_up_interruptible(&dma_waitq);return IRQ_HANDLED;}static int dma_drv_open(struct inode *inode, struct file *file){if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, \"s3c_dma\", NULL)){printk(\"can\'t request_irq for DMA\\n\");return -EBUSY;}src_addr = (char *)dma_alloc_writecombine(NULL, COPY_BUFF_SIZE, &src_phy_addr, GFP_KERNEL);if(src_addr == NULL){free_irq(IRQ_DMA3, NULL);printk(\"can\'t alloc buffer for src\\n\");return -ENOMEM;}dst_addr = (char *)dma_alloc_writecombine(NULL, COPY_BUFF_SIZE, &dst_phy_addr, GFP_KERNEL);if(dst_addr == NULL){free_irq(IRQ_DMA3, 1);dma_free_writecombine(NULL, COPY_BUFF_SIZE, src_addr, src_phy_addr);printk(\"can\'t alloc buffer for dst\\n\");return -ENOMEM;}dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));return 0;}static long dma_drv_ioctl(struct file *file, unsigned int cmd, unsigned long arg){unsigned int n;memset(src_addr, 0x55, COPY_BUFF_SIZE);memset(dst_addr, 0, COPY_BUFF_SIZE);switch(cmd){case COPY_NO_DMA:for(n = 0; n < COPY_BUFF_SIZE; n++)dst_addr = src_addr;if(memcmp(src_addr, dst_addr, COPY_BUFF_SIZE) == 0){printk(\"MEM_CPY_NO_DMA OK\\n\");}else{printk(\"MEM_CPY_DMA ERROR\\n\");}break;case COPY_BY_DMA:trans_ok = 0;/* 把源,目的,长度告诉DMA */dma_regs->disrc = src_phy_addr; /* 源的物理地址 */dma_regs->disrcc = (0<<1) | (0<<0); /* 源位于AHB总线, 源地址递增 */dma_regs->didst = dst_phy_addr; /* 目的的物理地址 */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)|(COPY_BUFF_SIZE<<0); /* 使能中断,单个传输,软件触发, *//* 启动DMA */dma_regs->dmasktrig = (1<<1) | (1<<0);wait_event_interruptible(dma_waitq, trans_ok);if (memcmp(src_addr, dst_addr, COPY_BUFF_SIZE) == 0){printk(\"MEM_CPY_DMA OK\\n\");}else{printk(\"MEM_CPY_DMA ERROR\\n\");}break;default:break;}return 0;}static int dma_drv_close(struct inode *inode, struct file *file){printk(\"dma_drv_close...\\n\");dma_free_writecombine(NULL, COPY_BUFF_SIZE, src_addr, src_phy_addr);dma_free_writecombine(NULL, COPY_BUFF_SIZE, dst_addr, dst_phy_addr);free_irq(IRQ_DMA3, NULL);iounmap(dma_regs);return 0;}static struct file_operations fops = {.owner = THIS_MODULE,.open = dma_drv_open,.unlocked_ioctl = dma_drv_ioctl,.release = dma_drv_close,};static int __init dma_drv_init(void){major = register_chrdev(0, \"dma_dev\", &fops);cls = class_create(THIS_MODULE, \"dma_cls\");cls_dev = device_create(cls, NULL, MKDEV(major, 0), NULL, \"dma_drv\");return 0;}static void __exit dma_drv_exit(void){unregister_chrdev(major, \"dma_dev\");device_unregister(cls_dev);class_destroy(cls);}module_init(dma_drv_init);module_exit(dma_drv_exit);MODULE_LICENSE(\"GPL\");
dma_drv.c完整测试程序
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#define COPY_NO_DMA 0#define COPY_BY_DMA 1#define IOCTL_CNT (100*10000)void print_usage(char *name){printf(\"Usage: %s <no_dma | dma>\\n\", name);}int main(int argc, char **argv){int fd;int n, cmd;if(argc != 2){print_usage(argv[0]);return -1;}fd = open(\"/dev/dma_drv\", O_RDWR);if(fd < 0){printf(\"can\'t open\\n\");return -1;}if(strcmp(argv[1], \"no_dma\") == 0){cmd = COPY_NO_DMA;}else if(strcmp(argv[1], \"dma\") == 0){cmd = COPY_BY_DMA;}else{print_usage(argv[0]);return -1;}while(n < IOCTL_CNT){ioctl(fd, cmd);n++;}close(fd);return 0;}
dma_test.c