title: DMA设计

tags: linux

date: 2019年1月5日 17:27:08

toc: true

DMA设计

DMA框架

一个简单的DMA框图如下DREQ→HOLD→HLDA→DACK

DMAC的一些必备特性:

  • 能发出地址信息,对存储器寻址,并修改地址指针,DMAC内部必须有能自动加1或减1的地址寄存
  • 能决定传送的字节数,并能判断DMA传送是否结束。DMA内部必须有能自动减1的字计数寄存器,计数结束产生终止计数信号;
  • 能发出DMA结束信号,释放总线,使CPU恢复总线控制权;
  • 能发出读、写控制信号,包括存储器访问信号和I/O访问信号。DMAC内部必须有时序和读写控制逻辑。

信号线如下

  • DRQ:DMA请求信号。是外设向DMA控制器提出要求DMA操作的申请信号。
  • DACK:DMA响应信号。是DMA控制器向提出DMA请求的外设表示已收到请求和正进行处理的信号。
  • HOLD:总线请求信号。是DMA控制器向CPU要求让出总线的请求信号。
  • HLDA:总线响应信号,是CPU向DMA控制器表示允许总线请求的应答信号。

流程顺序

  1. 当外设有DMA需求,并且准备就绪,就向DMAC控制器发出DMA请求信号DREQ
  2. DMAC接到DMA请求信号后向CPU发出总线请求信号HRQ。该信号连接到CPU的HOLD信号。
  3. CPU接到总线请求信号以后,如果允许DMA传输,则会在当前总线周期结束后,发出DMA响应信号HLDA。一方面CPU将控制总线、数据总线和地址总线置高阻态,即放弃对总线的控制权;另一方面CPU将有效的HLDA信号送给DMAC,通知DMAC,CPU已经放弃了对总线的控制权。
  4. DMAC获得对总线的控制权,并且向外设送出DMAC的应答信号DACK,通知外设可以开始进行DMA传输了。
  5. DMAC向存储器发送地址信号和向存储器及外设发出读/写控制信号,控制数据按初始化设定的方向传送,实现外设与内存的数据传输。
  6. 数据全部传输结束后,DMAC向CPU发HOLD信号,要求撤销总线请求信号。CPU收到该信号以后,使HLDA无效,同时收回对总线的控制权。

DMA控制器的基本组成

  • 内存地址计数器:用于存放内存中要交换的数据的地址。
  • 字计数器:用于记录传送数据块的长度(多少字数)。
  • 数据缓冲寄存器:用于暂存每次传送的数据(一个字)。
  • "DMA请求"标志:每当设备准备好一个数据字后给出一个控制信号,使"DMA请求"标志置"1"。该标志置位后向"控制/状态"逻辑发出DMA请求,后者又向CPU发出总线使用权的请求(HOLD),CPU响应此请求后发回响应信号HLDA,"控制/状态"逻辑接收此信号后发出DMA响应信号,使"DMA 请求"标志复位,为交换下一个字做好准备。
  • "控制/状态"逻辑:由控制和时序电路以及状态标志等组成,用于修改内存地址计数器和字计数器,指定传送类型(输入或输出),并对"DMA请求"信号和CPU响应信号进行协调和同步。
  • 中断机构:当字计数器溢出时,意味着一组数据交换完毕,由溢出信号触发中断机构,向CPU提出中断报告。

手册请看英文手册

芯片特性

请求来源

  • 软件触发

  • 外设触发

  • 外部引脚触发,这个是STM32所没有的,这个是有具体的时序的,STM32应该是可以用中断引脚触发

2440通道传输类型

  • 源和目标都在系统总线上(比如:两个物理内存地址)
  • 目标在外设总线上时,源在系统总线上(外设指:串口,定时器,I2C,I2S等)
  • 目标在系统总线上时,源在外设总线上
  • 源和目标都在外设总线上----------这个ST的也没有

外部引脚的DMA协议

这个貌似有点复杂,暂时也没用过,暂时不做深入分析了

协议简述

2440里面的DMA传输分为两个层次,一个是REQ/ACK协议,还一个是单模式和全模式,所谓单模式个全模式是指的在一次DMA请求中的传输数量

基本时序

时序参数

  • 信号的有效性: 高电平无效,低电平有效,这里称为assert

  • REQ有效

    REQ的只能在ACK释放(high)的时候才能被asserted(high),也就是说 请求信号只能在ACK为高的时候才能被MCU的DMA识别到

  • 信号生效识别

    nXDREQ请求生效并经过2CLK周期同步后,nXDACK响应并开始生效,但至少还要经过3CLK的周期延迟,DMA控制器才可获得总线的控制权,并开始数据传输。

模式

  • Single service : 当没有原子传输(unit/burst)后,停止传输,等待下一次请求

  • Whole service : 重复原子传输,直到计数器到0.这个模式下,不需要另外的请求.这个是重点

    在全模式下,DMA也会在每个原子传输后释放总线然后去尝试获得总线,以防止总线被占据

也就是说,全模式是我们一般使用的模式,使用计数器,一次请求会传输所有数据.单模式一次传输一个原子操作.

ACK清零:

  • 单服务是完成一个原子操作
  • 全服务是完成所有传输

中断发生:

  • 都在计数器为0的时候

协议

这里的协议,指的是请求应答协议,分为两种.

  • Demand Mode 请求/查询模式

    如果REQ信号有效,则一直保持传输,这个时候的ACK只是告诉你这一次传输完成

    这个模式会霸占总线的,不像全服务中完成一个原子操作释放一下总线

  • Handshake Mode 握手模式

    如果REQ信号释放,这个时候DMA控制器释放ACK两个周期,否则DMA会一直等到REQ的释放

    也就是启动下一次传输前,需要请求端先释放,然后MCU完成后会无效ACK两个周期告诉请求端,请求端再来请求,否则一直等待

在Demond模式下,如果DMA完成一次请求后如果Request仍然有效,那么DMA就认为这是下一次DMA请求,并立即开始下一次的传输;

在Handshake模式下,DMA完成一次请求后等待Request信号无效,如果Request无效,DMA会无效ACK两个时钟周期,再等待下一次Request。

数据大小的描述

数据传输的大小=数据传输次数 * 每次传输的读写次数 * 一次读或者写的大小

  • 每次传输的读写次数可以是1个或者4个 unit/burst
  • 一次读或者写的大小可以是1字节,2字节,4字节

具体完整的实例时序

单服务查询请求模式

单服务握手模式

全服务握手模式

在这里其实无所谓hand了,因为在全模式下只需要一次请求就能完成后续的所有操作

代码设计

这里的代码就是驱动实现一个内存的拷贝,不涉及到上面长篇大论的时序分析,只是需要设置好相关的寄存器配置配置DMA的模式,然后启动DMA后进入休眠,完成后DMA中断唤醒后退出.

测试程序调用字符驱动程序的接口ioctl来测试即可

写程序前需要查看用到的DMA

cat /proc/interrupts

驱动程序

#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/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/dma-mapping.h> #define S3C_DMA_SIZE 512*1024 //DMA传输长度 512KB #define NORMAL_COPY 0 //两个地址之间的正常拷贝
#define DMA_COPY 1 //两个地址之间的DMA拷贝 /*函数声明*/
static DECLARE_WAIT_QUEUE_HEAD(s3c_dma_queue); //声明等待队列
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags); /*
* 定义中断事件标志
* 0:进入等待队列 1:退出等待队列
*/
static int s3c_dma_even=0; static unsigned char *source_virt; //源虚拟地址
static unsigned int source_phys; //源物理地址 static unsigned char *dest_virt; //目的虚拟地址
static unsigned int dest_phys; //目的虚拟地址 /*DMA3寄存器*/
struct S3c_dma3_regs{
unsigned int disrc3 ; //0x4b0000c0
unsigned int disrcc3 ;
unsigned int didst3 ;
unsigned int didstc3 ;
unsigned int dcon3 ;
unsigned int dstat3 ;
unsigned int dcsrc3 ;
unsigned int dcdst3 ;
unsigned int dmasktrig3; //0x4b0000e0
}; static volatile struct S3c_dma3_regs *s3c_dma3_regs; /*字符设备操作*/
static struct file_operations s3c_dma_fops={
.owner = THIS_MODULE,
.ioctl = s3c_dma_ioctl,
}; /*中断服务函数*/
static irqreturn_t s3c_dma_irq (int irq, void *dev_id)
{
s3c_dma_even=1; //退出等待队列
wake_up_interruptible(&s3c_dma_queue); //唤醒 中断
return IRQ_HANDLED;
} /*ioctl函数*/
static int s3c_dma_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long flags)
{
int i;
memset(source_virt, 0xAA, S3C_DMA_SIZE);
memset(dest_virt, 0x55, S3C_DMA_SIZE); switch(cmd)
{
case NORMAL_COPY: //正常拷贝 for(i=0;i<S3C_DMA_SIZE;i++)
dest_virt[i] = source_virt[i]; if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("NORMAL_COPY OK\n");
return 0;
}
else
{
printk("NORMAL_COPY ERROR\n");
return -EAGAIN;
} case DMA_COPY: //DMA拷贝 s3c_dma_even=0; //进入等待队列 /*设置DMA寄存器,启动一次DMA传输 */
/* 源的物理地址 */
s3c_dma3_regs->disrc3 = source_phys;
/* 源位于AHB总线, 源地址递增 */
s3c_dma3_regs->disrcc3 = (0<<1) | (0<<0);
/* 目的的物理地址 */
s3c_dma3_regs->didst3 = dest_phys;
/* 目的位于AHB总线, 目的地址递增 */
s3c_dma3_regs->didstc3 = (0<<2) | (0<<1) | (0<<0);
/* 使能中断,单个传输,软件触发, */
s3c_dma3_regs->dcon3=(1<<30)|(1<<29)|(0<<28)|(1<<27)|(0<<23)|(0<<20)|(S3C_DMA_SIZE<<0);
//启动一次DMA传输
s3c_dma3_regs->dmasktrig3 = (1<<1) | (1<<0); wait_event_interruptible(s3c_dma_queue, s3c_dma_even); //进入睡眠,等待DMA传输中断到来才退出 if(memcmp(dest_virt, source_virt, S3C_DMA_SIZE)==0)
{
printk("DMA_COPY OK\n");
return 0;
}
else
{
printk("DMA_COPY ERROR\n");
return -EAGAIN;
} break;
}
return 0;
} static unsigned int major;
static struct class *cls;
static int s3c_dma_init(void)
{
/*1.1 注册DMA3 中断 */
if(request_irq(IRQ_DMA3, s3c_dma_irq,NULL, "s3c_dma",1))
{
printk("Can't request_irq \"IRQ_DMA3\"!!!\n ");
return -EBUSY;
} /*1.2 分配两个DMA缓冲区(源、目的)*/
source_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &source_phys, GFP_KERNEL);
if(source_virt==NULL)
{
printk("Can't dma_alloc \n ");
return -ENOMEM;
} dest_virt=dma_alloc_writecombine(NULL,S3C_DMA_SIZE, &dest_phys, GFP_KERNEL);
if(dest_virt==NULL)
{
printk("Can't dma_alloc \n ");
return -ENOMEM;
} /*2.注册字符设备,并提供文件操作集合fops*/
major=register_chrdev(0, "s3c_dma",&s3c_dma_fops);
cls= class_create(THIS_MODULE, "s3c_dma");
class_device_create(cls, NULL,MKDEV(major,0), NULL, "s3c_dma"); s3c_dma3_regs=ioremap(0x4b0000c0, sizeof(struct S3c_dma3_regs)); return 0;
} static void s3c_dma_exit(void)
{
iounmap(s3c_dma3_regs); class_device_destroy(cls, MKDEV(major,0));
class_destroy(cls); dma_free_writecombine(NULL, S3C_DMA_SIZE, dest_virt, dest_phys);
dma_free_writecombine(NULL, S3C_DMA_SIZE, source_virt, source_phys); free_irq(IRQ_DMA3, 1); }
module_init(s3c_dma_init);
module_exit(s3c_dma_exit);
MODULE_LICENSE("GPL");

测试程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h> /* ./dma_test NORMAL
* ./dma_test DMA
*/
#define NORMAL_COPY 0 //两个地址之间的正常拷贝
#define DMA_COPY 1 //两个地址之间的DMA拷贝 void print_usage(char *name)
{
printf("Usage:\n");
printf("%s <NORMAL | DMA>\n", name);
} int main(int argc, char **argv)
{
int fd,i=30; if (argc != 2)
{
print_usage(argv[0]);
return -1;
} fd = open("/dev/s3c_dma", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/s3c_dma\n");
return -1;
} if (strcmp(argv[1], "NORMAL") == 0)
{
while (i--)                //调用驱动的ioctl(),30次
{
ioctl(fd, NORMAL_COPY);
}
}
else if (strcmp(argv[1], "DMA") == 0)
{
while (i--)                //调用驱动的ioctl(),30次        
{
ioctl(fd, DMA_COPY);
}
}
else
{
print_usage(argv[0]);
return -1;
}
return 0;
}

测试

  1. ./dma_test NORMAL & 卡住
  2. ./dma_test DMA &,输入命令有反应

参考链接

csdn DMA框架

cnblog DMA请求应答协议

cnblog 笔记

DMA设计的更多相关文章

  1. STM32f103的数电采集电路的DMA设计和使用优化程序

    DMA,全称为:Direct Memory Access,即直接存储器访问.DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直 ...

  2. 基于zedboard的DMA设计笔记

    2.BAR0空间的概念:BAR(Base Address Register ) 该组寄存器简称为BAR寄存器,BAR寄存器保存PCI设备使用的地址空间的基地址,该基地址保存的是该设备在PCI总线域中的 ...

  3. Xilinx中的xapp1052理解

    xapp1052是xilinx官方给出的一个有关DMA数据传输的样例,用于PC端和FPGA端之间的DMA数据传输.首先需要说的是,xapp1052并不是一个完整的DMA数据传输的终端硬件设计,这在下面 ...

  4. Virtex6 PCIe 超简版基础概念学习(二)

    Virtex6 PCIe 超简版基础概念学习(二) 分类:FPGAPCIe (2081)  (0)  举报  收藏 文档版本 开发工具 测试平台 工程名字 日期 作者 备注 V1.0 ise14.7 ...

  5. 转载 IO、文件、NIO【草案四】

    本章目录: 1.IO类相关内容 2.文件和目录 3.文件高级操作  NIO详解[1]——缓冲区(Buffer)[深入理解,总结自<Java-NIO>]: [*:下边的Buffer又指代抽象 ...

  6. TCP Socket通信详细过程

    下面这篇文章是参考"骏马金龙"博客中 不可不知的socket和TCP连接过程 https://www.cnblogs.com/f-ck-need-u/p/7623252.html ...

  7. PCI Express

    1.1课题研究背景 在目前高速发展的计算机平台上,应用软件的开发越来越依赖于硬件平台,尤其是随着大数据.云计算的提出,人们对计算机在各个领域的性能有更高的需求.日常生活中的视频和图像信息包含大量的数据 ...

  8. OpenRisc-31-关于在设计具有DMA功能的ipcore时的虚实地址转换问题的分析与解决

    引言 之前,我们在讨论基于ORPSoC的ipcore设计时提到过DMA的问题,当时我们实现DMA的功能时,访问的是local memory,并没有使用主存(即外部的SDRAM),使用的是本地的一块存储 ...

  9. 电子设计省赛--DMA与ADC

    //2014年4月17日 //2014年6月20日入"未完毕" //2014年6月21日 DMA可实现无需cpu控制中断的传输数据保存. 特别是ADC转换多个通道时要用到. 关键是 ...

随机推荐

  1. mssql sqlserver 指定特定值排在表前面

    转自:http://www.maomao365.com/?p=7141 摘要: 下文讲述sql脚本编写中,将 特定值排在最前面的方法分享, 实验环境:sqlserver 2008 R2 例:将数据表中 ...

  2. Proxmox VE登陆的时候提示没有有效的订阅You do not have a valid subscription for this server. Please visit www.proxmox.com to get a list of available options.

    问题描述: 用的是免费版的,所以每次都提示这个没有有效的订阅挺烦的 解决方法: 修改文件/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib. ...

  3. Android Studio教程08-与其他app通信

    目录 1.向另外一个应用发送用户 1.1. 构建隐含Intent 1.2. 验证是否存在接收Intent的应用 1.3. 启动具有Intent的Activity 2. 获取Activity的结果响应 ...

  4. .NET MVC全局异常处理(一)

    目录 .NET MVC全局异常处理 IIS配置 静态错误页配置 .NET错误页配置 程序设置 全局异常配置 .NET MVC全局异常处理 一直知道有.NET有相关的配置,但没有实际做过,以为改下设定就 ...

  5. git 忽略 .idea文件

    多人开发时,会出现明明在gitignore中忽略了.idea文件夹,但是提交时仍旧会出现.idea内文件变动的情况 原因.idea已经被git跟踪,之后再加入.gitignore后是没有作用的 解决办 ...

  6. html基础和CSS选择器

    一.html简单基础 什么是HTML HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言: HyperText Markup Language HTML 不是一种编程语言,而是一种标 ...

  7. Linux内存管理 (2)页表的映射过程

    专题:Linux内存管理专题 关键词:swapper_pd_dir.ARM PGD/PTE.Linux PGD/PTE.pgd_offset_k. Linux下的页表映射分为两种,一是Linux自身的 ...

  8. Java Lucene入门

    1.lucene版本:7.2.1 pom文件: <?xml version="1.0" encoding="UTF-8"?> <project ...

  9. 家庭记账本小程序之框架设计(java web基础版一)

    1.设计主页 main.jsp <%@ page language="java" contentType="text/html; charset=UTF-8&quo ...

  10. DAY12、装饰器

    一.补充:nonlocal关键字 1.作用:将L与E(E中的名字需要提前定义)的名字统一 2.应用场景:如果想在被嵌套的函数中修改外部函数变量(名字)的值 3.案例: def outer():    ...