1、基本概念
DMA即Direct Memory Access(直接存储器存取),那么为什么要引入这么个东东呢?它的作用又是什么呢?我们通过一个例子来说明:
比 如当我们要往内存里面拷贝一块很大的数据时,由于CPU同一时间只能做一件事情,这样在一段很长的时间里就不能再处理其它事情了,这样就造成了浪费。于是 引入了DMA的概念,所谓DMA就是直接存储器访问,可以不通过CPU而在DMA控制器的控制下,高速地与I/O设备和存储器交换数据。CPU除了在数据 传输开始和结束时做一些处理外,在传输过程中,CPU可以进行其它工作。这样,在大部分的时间里,CPU和输入/输出都处于并行操作状态,大大提高了效 率。
我们需要做的就是将源、目的、长度告诉DMA,然后设置DMA参数,并启动DMA就可以了。
2、DMA工作过程
那么DMA的具体工作过程是怎样的呢?我们也有必要来说一下:
(1)外设向DMA发出请求
(2)DMA通过HOLD向CPU发出总线请求
(3)CPU响应释放三总线,并且发应答HLDA
(4)DMA向外设发DMA应答
(5)DMA发出地址、控制信号,为外设传输数据
(6)传送完规定的数据后,DMA撤销HOLD信号,CPU也撤销HOLD信号,并且恢复对三总线的控制
3、s3c2440的DMA
s3c2440支持4个DMA通道,每个DMA通道支持多个请求源,详见下表:
每个DMA通道能处理下面四种情况的数据传输:
(1)源器件和目的器件都在系统总线
(2)源器件在系统总线,目的器件在外设总线
(3)源器件在外设总线,目的器件在系统总线
(4)源器件和目的器件都在外设总线
1、驱动程序编写
在本驱动程序中,我们打算在内存中开辟两个空间,分别作为源和目的。我们用两个方法将源中的数据写到目的中,一种方法是让cpu去做,另外一种发放是让DMA去做!好的,闲话少说,直接分析代码:
#include #include #include #include #include #include #include #include #include #include #include #include #include #define MEM_CPY_NO_DMA 0 #define MEM_CPY_DMA 1 #define BUF_SIZE (512*1024) #define DMA0_BASE_ADDR 0x4B000000 #define DMA1_BASE_ADDR 0x4B000040 #define DMA2_BASE_ADDR 0x4B000080 #define DMA3_BASE_ADDR 0x4B0000C0 struct 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 char *src; static u32 src_phys; static char *dst; static u32 dst_phys; static struct class *cls; 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) { //这是非DMA模式 case MEM_CPY_NO_DMA : { for (i = 0; i < BUF_SIZE; i++) dst[i] = src[i]; //CPU直接将源拷贝到目的 if (memcmp(src, dst, BUF_SIZE) == 0)//这个函数见注释2 { printk("MEM_CPY_NO_DMA OKn"); } else { printk("MEM_CPY_DMA ERRORn"); } break; } //这是DMA模式 case MEM_CPY_DMA : { ev_dma = 0; /* 把源,目的,长度告诉DMA */ /* 关于下面寄存器的具体情况,我们在注释3里面来详细讲一下 */ 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 OKn"); } else { printk("MEM_CPY_DMA ERRORn"); } 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) { /* 唤醒 */ ev_dma = 1; wake_up_interruptible(&dma_waitq); /* 唤醒休眠的进程 */ return IRQ_HANDLED; } static int s3c_dma_init(void) { /* 这里注册一个中断,当DMA数据传输完毕之后会发生此中断 */ if (request_irq(IRQ_DMA3, s3c_dma_irq, 0, "s3c_dma", 1)) { printk("can't request_irq for DMAn"); return -EBUSY; } /* 分配SRC, DST对应的缓冲区,关于此函数详见注释1 */ src = dma_alloc_writecombine(NULL, BUF_SIZE, &src_phys, GFP_KERNEL);//源 if (NULL == src) { printk("can't alloc buffer for srcn"); 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 dstn"); 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 */ dma_regs = ioremap(DMA3_BASE_ADDR, sizeof(struct s3c_dma_regs));//这边是将DMA控制寄存器映射到内核空间 return 0; } static void s3c_dma_exit(void) { iounmap(dma_regs); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); 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); } module_init(s3c_dma_init); module_exit(s3c_dma_exit); MODULE_LICENSE("GPL"); 注释1: 之前我们知道在内核中开辟空间可以用kmalloc函数,这里却用了dma_alloc_writecombine,这是为什么呢?这是因为kmalloc开辟的空间其逻辑地址虽然是连续的,但是其实际的物理地址可能不是连续的。而DMA传输数据时,要求物理地址是连续的,dma_alloc_writecombine就满足这一点,这个函数的原型是: dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp) 其中size代表开辟的空间的大小,handle代表开辟的空间的物理地址,返回值是开辟的空间的逻辑地址。 注释2: int memcmp(const void *cs, const void *ct, size_t count) { const unsigned char *su1, *su2; int res = 0; for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--) if ((res = *su1 - *su2) != 0) break; return res; } 我们看到这个函数的作用就是将第一个参数和第二个参数一位一位地比较,一旦不相等就返回,此时返回值为非零。比较的位数为第三个参数,如果前两个参数的前count为都是相等的,那么就会返回0 注释3: 我们先来解析一下上面几个寄存器: 这里我们需要注意了,里面有三个东东要分清楚: DSZ :代表数据的大小 TSZ :一次传输多少个数据 TC :一共传输多少次 所以实际传输的数据的大小为:DSZ * TSZ * TC 我们本程序里面由于设置为数据大小为1个字节,一次传输1个数据,所以传输次数直接就是实际数据的大小了。 OK!寄存器分析完毕,具体设置就不在写出来了! 在此我们在来总结一下DMA的操作流程: 我们首先设置DMA的工作方式,然后打开DMA通道,紧接着我们使进程休眠,进入等待队列。与此同时,在DMA控制器的作用下,从源向目的拷贝数据。一旦数据拷贝完成,就会触发中断,在中断函数里面,唤醒进程,从而程序继续运行,打印相关信息。 2、应用程序编写(测试用) #include #include #include #include #include #include /* ./dma_test nodma * ./dma_test dma */ #define MEM_CPY_NO_DMA 0 #define MEM_CPY_DMA 1 void print_usage(char *name) { printf("Usage:n"); printf("%s } int main(int argc, char **argv) { int fd; if (argc != 2) { print_usage(argv[0]); return -1; } fd = open("/dev/dma", O_RDWR); if (fd < 0) { printf("can't open /dev/dman"); return -1; } if (strcmp(argv[1], "nodma") == 0) { while (1) { ioctl(fd, MEM_CPY_NO_DMA); } } else if (strcmp(argv[1], "dma") == 0) { while (1) { ioctl(fd, MEM_CPY_DMA); } } else { print_usage(argv[0]); return -1; } return 0; } 应用程序过于简单,不在分析! 3、测试 # insmod dma.ko //加载驱动 # cat /proc/interrupts //查看中断 CPU0 30: 52318 s3c S3C2410 Timer Tick 33: 0 s3c s3c-mci 34: 0 s3c I2SSDI 35: 0 s3c I2SSDO 36: 0 s3c s3c_dma DISRCn bit Description Initial State S_ADDR [30:0] 源起始地址 0x00000000 DISRCCn bit Description Initial State LOC [1] 用于选择源的位置
0:源在系统总线上
1:源在外设总线上 0 INC [0] 用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst 模式下,传输过程中地址自动增加, 但是一旦传输完这一次数据,地址又变为初值) 0 DIDSTn bit Description Initial State D_ADDR [30:0] 目的起始地址 0x00000000 DIDSTCn Bit Description Initial State CHK_INT [2] 当设置为自动加载时,用来选择中断发生的时间
0:TC为0是产生中断
1:自动加载完成的时候产生中断 0 LOC [1] 用于选择目的设备的位置
0:目的设备在系统总线上
1:目的设备在外设总线上 0 INC [0] 用于选择地址是否自动增加
0:地址自动增加
1:地址固定不变(此时即便是burst模式下,传输过程中地址自动增加,但是一旦传输完这一次数据,地址又重新变为初值) 0 DCONn Bit Description Initial State DMD_HS [31] 选择为Demand模式或者是握手模式
0:选择为Demand模式
1:选择为握手模式
这 两种模式下都是当发生请求时,DMA控制器开始传输数据并且发出 应 答信号,不同点是握手模式下,当DMA控制器收到请求撤销信号,并且自 身发出应答撤销信号之后才能接收下一次请求。而在Demand模式下,并 不需要等待请求撤销信号,他只需要撤销自身的应答信号,然后等待下一 次的请求。 0 SYNC [30] 选择DREQ/DACK的同步
0:DREQ and DACK 与PCLK同步
1:DREQ and DACK 与HCLK同步
因此当设备在AHB系统总线时,这一位必须为1,而当设备在APB系统 时,它应该被设为0。当设备位于外部系统时,应根据具体情况而定。 0 INT [29] 是否使能中断
0:禁止中断,用户需要查看状态寄存器来判断传输是否完成
1:使能中断,所有的传输完成之后产生中断信号 0 TSZ [28] 选择一个原子传输的大小
0:单元传输(一次传输一个单元)
1:突发传输(一次传输四个单元) 0 SERVMODE [27] 选择是单服务模式还是整体服务模式
0:单服务模式,当一次原子传输完成后需要等待下一个DMA请求
1:整体服务模式,进行多次原子传输,知道传输计数值到达0 0 HWSRCSEL [26:24] 为每一个DMA选择DMA请求源
具体参见芯片手册 000 SWHW_SEL [23] 选择DMA源为软件请求模式还是硬件请求模式
0:软件请求模式,需要将寄存器DMASKTRIG的SW_TRIG置位
1:硬件请求模式 0 RELOAD [22] 是否自动重新装载
0:自动重装,当目前的传输计数值变为0时,自动重装
1:不自动重装
RELOAD[1]被设置为0以防无意识地进行新的DMA传输 0 DSZ [21:20] 要被传输的数据的大小
00 = Byte 01 = Half word
10 = Word 11 = reserved 00 TC [19:0] 初始化传输计数 0000 DSTATn Bit Description Initial State STAT [21:20] DMA控制器的状态
00:DMA控制器已经准备好接收下一个DMA请求
01:DMA控制器正在处理DMA请求 00 CURR_TC [19:0] 传输计数的当前值
每个原子传输减1 DCSRCn Bit Description Initial State CURR_SRC [30:0] 当前的源地址 0x00000000 DCDSTn Bit Description Initial State CURR_DST [30:0] 当前的目的地址 0x00000000 DMASKTRIGn Bit Description Initial State STOP [2] 停止DMA操作
1:当前的原子传输完成之后,就停止DMA操作。如果当前没有原子 传输正在进行,就立即结束。 ON_OFF [1] DMA通道的开/闭
0:DMA通道关闭
1:DMA通道打开,并且处理DMA请求 SW_TRIG [0] 1:在软件请求模式时触发DMA通道
上一篇:S3C2440 DMA驱动程序编写及测试(三十二)
下一篇:记录2--s3c2440 DMA的操作
推荐阅读最新更新时间:2024-11-13 06:37
设计资源 培训 开发板 精华推荐
- 具有错误标志的 TS39205 2A 超低压差稳压器的典型应用
- 使用 LTC2900-2 具有按钮复位功能的固定四路应用
- ADR510 用于输出电压微调的低噪声分流基准电压源的典型应用
- DC840A,具有 LTC3448EDD Vin=2.5V-5.5V,Vout= 1.2V/1.5V/1.8V @ 600mA Max 的演示板
- MC34072ADR2G 单位增益缓冲器 TTL 驱动器的典型应用
- 【ART-Pi】【ART-PI】摄像机扩展板
- CH340E转串口和485
- ADR364B 4.096V 低功耗、低噪声电压基准的典型应用,具有灌/拉能力
- 用于简单时钟振荡器的 NCP300HSN45T1 4.5V 电压检测器的典型应用
- LT1766EFE 演示板,1.5A、200kHz 高压降压转换器,Vin = 6V-60V,Vout = 5V @ 1A
- OpenAI呼吁建立“北美人工智能联盟” 好与中国竞争
- 传OpenAI即将推出新款智能体 能为用户自动执行任务
- 尼得科智动率先推出两轮车用电动离合器ECU
- ASML在2024 年投资者日会议上就市场机遇提供最新看法
- AMD将裁员4%,以在人工智能芯片领域争取更强的市场地位
- Arm:以高效计算平台为核心,内外协力共筑可持续未来
- NEC收获新超算订单:英特尔CPU+AMD加速器+英伟达交换机
- 高通推出其首款 RISC-V 架构可编程连接模组 QCC74xM,支持 Wi-Fi 6 等协议
- 消息称内存原厂考虑 HBM4 采用无助焊剂键合,进一步降低层间间隙
- 随时随地享受大屏幕游戏:让便携式 4K 超高清 240Hz 游戏投影仪成为现实