DMA原本不属于CPU体系架构部分的内容,只由于在开发中常常要用到其相关的知识,因此这里就其基本概念、工做原理、常见问题作一个总结。markdown
DMA概述
DMA的英文拼写是“Direct Memory Access”,汉语的意思就是直接内存访问。DMA既能够指内存和外设直接存取数据这种内存访问的计算机技术,又能够指实现该技术的硬件模块(对于通用计算机PC而言,DMA控制逻辑由CPU和DMA控制接口逻辑芯片共同组成,嵌入式系统的DMA控制器内建在处理器芯片内部,通常称为DMA控制器,DMAC)。网络
DMA内存访问技术
使用DMA的好处就是它不须要CPU的干预而直接服务外设,这样CPU就能够去处理别的事务,从而提升系统的效率,对于慢速设备,如UART,其做用只是下降CPU的使用率,但对于高速设备,如硬盘,它不仅是下降CPU的使用率,并且能大大提升硬件设备的吞吐量。由于对于这种设备,CPU直接供应数据的速度过低。 因CPU只能一个总线周期最多存取一次总线,并且对于ARM,它不能把内存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一个寄存器,而后再从这个寄存器搬到B地址。也就是说,对于ARM,要花费两个总线周期才能将A地址的值送到B地址。而DMA就不一样了,通常系统中的DMA都有突发(Burst)传输的能力,在这种模式下,DMA能一次传输几个甚至几十个字节的数据,因此使用DMA能使设备的吞吐能力大为加强。架构
使用DMA时咱们必需要注意以下事实:app
- DMA使用物理地址,程序是使用虚拟地址的,因此配置DMA时必须将虚拟地址转化成物理地址。
- 由于程序使用虚拟地址,并且通常使用cache地址,因此Cache中的内容与其物理地址(内存)的内容不必定一致,因此在启动DMA传输前必定要将该地址的cache刷新,即写入内存。
- OS并不能保证每次分配到的内存空间在物理上是连续的。尤为是在系统使用过一段时间而又分配了一块比较大的内存时。因此每次都须要判断地址是否是连续的,若是不连续就须要把这段内存分红几段让DMA完成传输
DMAC的基本配置
DMA用于无需CPU的介入而直接由专用控制器(DMA控制器)创建源与目的传输的应用,所以,在大量数据传输中解放了CPU。PIC32微控制器中的DMA可用于映射到内存空间中的不一样外设,如从存储区到SPI,UART或I2C等设备。DMA特性详见器件参考手册,这里仅对一些基本原理与功能作一个简析。异步
地址寄存器 |
存放DMA传输时存储单元地址 |
字节计数器 |
存放DMA传输的字节数 |
控制寄存器 |
存放由CPU设定的DMA传输方式,控制命令等 |
状态寄存器 |
存放DMAC当前的状态,包括有无DMA请求,是否结束等 |
独立DMA控制芯片
在课程《微机原理》中,会讲到X86下一片独立的DMA控制芯片8237A。8237A控制芯片各通道在PC机内的任务:
- CH0:用做动态存储器的刷新控制
- CH1:为用户预留
- CH2:软盘驱动器数据传输用的DMA控制
- CH3:硬盘驱动器数据传输用的DMA控制
嵌入式设备中的DMA
直接存储器存取(DMA)控制器是一种在系统内部转移数据的独特外设,能够将其视为一种可以经过一组专用总线将内部和外部存储器与每一个具备DMA能力的外设链接起来的控制器。它之因此属于外设,是由于它是在处理器的编程控制下来执行传输的。值得注意的是,一般只有数据流量较大(kBps或者更高)的外设才须要支持DMA能力,这些应用方面典型的例子包括视频、音频和网络接口。
通常而言,DMA控制器将包括一条地址总线、一条数据总线和控制寄存器。高效率的DMA控制器将具备访问其所须要的任意资源的能力,而无须处理器自己的介入,它必须能产生中断。最后,它必须能在控制器内部计算出地址。
一个处理器能够包含多个DMA控制器。每一个控制器有多个DMA通道,以及多条直接与存储器站(memory bank)和外设链接的总线,如图1所示。在不少高性能处理器中集成了两种类型的DMA控制器。第一类一般称为“系统DMA控制器”,能够实现对任何资源(外设和存储器)的访问,对于这种类型的控制器来讲,信号周期数是以系统时钟(SCLK)来计数的,以ADI的Blackfin处理器为例,频率最高可达133MHz。第二类称为内部存储器DMA控制器(IMDMA),专门用于内部存储器所处位置之间的相互存取操做。由于存取都发生在内部(L1-L一、L1-L2,或者L2-L2),周期数的计数则之内核时钟(CCLK)为基准来进行,该时钟的速度能够超过600MHz。
每一个DMA控制器有一组FIFO,起到DMA子系统和外设或存储器之间的缓冲器的做用。对于MemDMA(Memory DMA)来讲,传输的源端和目标端都有一组FIFO存在。当资源紧张而不能完成数据传输的话,则FIFO能够提供数据的暂存区,从而提升性能。
由于一般会在代码初始化过程当中对DMA控制器进行配置,内核就只须要在数据传输完成后对中断作出响应便可。你能够对DMA控制进行编程,让其与内核并行地移动数据,而同时让内核执行其基本的处理任务―那些应该让它专一完成的工做。

在一个优化的应用中,内核永远不用参与任何数据的移动,而仅仅对L1存储器中的数据进行读写。因而,内核不须要等待数据的到来,由于DMA引擎会在内核准备读取数据以前将数据准备好。图2给出了处理器和DMA控制器间的交互关系。由处理器完成的操做步骤包括:创建传输,启用中断,生成中断时执行代码。返回处处理器的中断输入能够用来指示“数据已经准备好,可进行处理”。

数据除了往来外设以外,还须要从一个存储器空间转移到另外一个空间中。例如,视频源能够从一个 视频端口直接流入L3存储器,由于工做缓冲区规模太大,没法放入到存储器中。咱们并不但愿让处理器在每次须要执行计算时都从外部存储读取像素信息,所以为 了提升存取的效率,能够用一个存储器到存储器的DMA(MemDMA)来将像素转移到L1或者L2存储器中。
到目前为之,咱们还仅专一于数据的移动,可是DMA的传送能力并不老是用来移动数据。
在最简单的MemDMA状况中,咱们须要告诉DMA控制器源端地址、目标端地址和待传送的字的个数。每次传输的字的大小能够是八、16或者12位。 咱们只须要改变数据传输每次的数据大小,就能够简单地增长DMA的灵活性。例如,采用非单一大小的传输方式时,咱们以传输数据块的大小的倍数来做为地址增量。也就是说,若规定32位的传输和4个采样的跨度,则每次传输结束后,地址的增量为16字节(4个32位字)。
DMA的设置
目前有两类主要的DMA传输结构:寄存器模式和描述符模式。不管属于哪一类DMA,表1所描述的几类信息都会在DMA控制器中出现。当DMA以寄存器模式工做时,DMA控制器只是简单地利用寄存器中所存储的参数值。在描述符模式中,DMA控制器在存储器中查找本身的配置参数。
基于寄存器的DMA
在基于寄存器的DMA内部,处理器直接对DMA控制寄存器进行编程,来启动传输。基于寄存器的DMA提供了最佳的DMA控制器性能,由于寄存器并不须要不断地从存储器中的描述符上载入数据,而内核也不须要保持描述符。
基于寄存器的DMA由两种子模式组成:自动缓冲(Autobuffer)模式和中止模式。在自动缓冲DMA中,当一个传输块传输完毕,控制寄存器就自动从新载入其最初的设定值,同一个DMA进程从新启动,开销为零。
正如咱们在图3中所看到的那样,若是将一个自动缓冲DMA设定为从外设传输必定数量的字到 L1数据存储器的缓冲器上,则DMA控制器将会在最后一个字传输完成的时刻就迅速从新载入初始的参数。这构成了一个“循环缓冲器”,由于当一个量值被写入 到缓冲器的最后一个位置上时,下一个值将被写入到缓冲器的第一个位置上。
自动缓冲DMA特别适合于对性能敏感的、存在持续数据流的应用。DMA控制器能够在独立于处理器其余活动的状况下读入数据流,而后在每次传输结束时,向内核发出中断。
中止模式的工做方式与自动缓冲DMA相似,区别在于各寄存器在DMA结束后不会从新载入,因 此整个DMA传输只发生一次。中止模式对于基于某种事件的一次性传输来讲十分有用。例如,非按期地将数据块从一个位置转移到另外一个位置。当你须要对事件进 行同步时,这种模式也很是有用。例如,若是一个任务必须在下一次传输前完成的话,则中止模式能够确保各事件发生的前后顺序。此外,中止模式对于缓冲器的初 始化来讲很是有用。
描述符模型
基于描述符(descriptor)的DMA要求在存储器中存入一组参数,以 启动DMA的系列操做。该描述符所包含的参数与那些一般经过编程写入DMA控制寄存器组的全部参数相同。不过,描述符还能够允许多个DMA操做序列串在一 起。在基于描述符的DMA操做中,咱们能够对一个DMA通道进行编程,在当前的操做序列完成后,自动设置并启动另外一次DMA传输。基于描述符的方式为管理 系统中的DMA传输提供了最大的灵活性。
ADI 的Blackfin处理器上有两种主要的描述符方式―描述符阵列和描述符列表,这两种操做方式所要实现的目标是在灵活性和性能之间实现一种折中平衡。
1. 什么是DMA
直接内存访问是一种硬件机制,它容许外围设备和主内存之间直接传输它们的I/O数据,而不须要系统处理器的参与。使用这种机制能够大大提升与设备通讯的吞吐量。
2. DMA数据传输
有两种方式引起数据传输:
第一种状况:软件对数据的请求
1. 当进程调用read,驱动程序函数分配一个DMA缓冲区,并让硬件将数据传输到这个缓冲区中。进程处于睡眠状态。
2. 硬件将数据写入到DMA缓冲区中,当写入完毕,产生一个中断
3. 中断处理程序获取输入的数据,应答中断,并唤起进程,该进程如今便可读取数据
第二种状况发生在异步使用DMA时。
1. 硬件产生中断,宣告新数据的到来
2. 中断处理程序分配一个缓冲区,而且告诉硬件向哪里传输数据
3. 外围设备将数据写入数据区,完成后,产生另一个中断
4.处理程序分发新数据,唤醒任何相关进程,而后执行清理工做
高效的DMA处理依赖于中断报告。
3. 分配DMA缓冲区
使用DMA缓冲区的主要问题是:当大于一页时,它们必须占据连续的物理页,由于设备使用ISA或PCI系统总线传输数据,而这两种方式使用的都是物理地址。
使用get_free_pasges能够分配多大几M字节的内存(MAX_ORDER是11),可是对于较大数量(即便是远小于128KB)的请求,一般会失败,这是由于系统内存充满了内存碎片。
解决方法之一就是在引导时分配内存,或者为缓冲区保留顶部物理内存。
例子:在系统引导时,向内核传递参数“mem=value”的方法保留顶部的RAM。好比系统有256内存,参数“mem=255M”,使内核不能使用顶部的1M字节。随后,模块可使用下面代码得到该内存的访问权:
dmabuf=ioremap(0XFF00000/**255M/, 0X100000/*1M/*);
解决方法之二是使用GPF_NOFAIL分配标志为缓冲区分配内存,可是该方法为内存管理子系统带来了至关大的压力。
解决方法之三十设备支持分散/汇集I/O,这能够将缓冲区分配成多个小块,设备会很好地处理它们。
4. 通用DMA层
DMA操做最终会分配缓冲区,并将总线地址传递给设备。内核提升了一个与总线——体系结构无关的DMA层。强烈建议在编写驱动程序时,为DMA操做使用该层。使用这些函数的头文件是<linux/dmamapping.h>。
int dma_set_mask(struct device *dev, u64 mask);
该掩码显示该设备能寻址能力对应的位。好比说,设备受限于24位寻址,则mask应该是0x0FFFFFF。
5. DMA映射
IOMMU在设备可访问的地址范围内规划了物理内存,使得物理上分散的缓冲区对设备来讲成连续的。对IOMMU的运用须要使用到通用DMA层,而vir_to_bus函数不能完成这个任务。可是,x86平台没有对IOMMU的支持。
解决之道就是创建回弹缓冲区,而后,必要时会将数据写入或者读出回弹缓冲区。缺点是下降系统性能。
根据DMA缓冲区指望保留的时间长短,PCI代码区分两种类型的DMA映射:
一是一致性DMA映射,存在于驱动程序生命周期中,一致性映射的缓冲区必须可同时被CPU和外围设备访问。一致性映射必须保存在一致性缓存中。创建和使用一致性映射的开销是很大的。
二是流式DMA映射,内核开发者建议尽可能使用流式映射,缘由:一是在支持映射寄存器的系统中,每一个DMA映射使用总线上的一个或多个映射寄存器,而一致性映射生命周期很长,长时间占用这些这些寄存器,甚至在不使用他们的时候也不释放全部权;二是在一些硬件中,流式映射能够被优化,但优化的方法对一致性映射无效。
6. 创建一致性映射
驱动程序可调用pci_alloc_consistent函数创建一致性映射:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int falg);
该函数处理了缓冲区的分配和映射,前两个参数是device结构和所需的缓冲区的大小。函数在两处返回DMA映射的结果:函数的返回值是缓冲区的内核虚拟地址,能够被驱动程序使用;而与其相关的总线地址保存在dma_handle中。
当再也不须要缓冲区时,调用下函数:
void dma_free_conherent(struct device *dev, size_t size, void *vaddr, dma_addr_t *dma_handle);
7. DMA池
DMA池是一个生成小型,一致性DMA映射的机制。调用dma_alloc_coherent函数得到的映射,可能其最小大小为单个页。若是设备须要的DMA区域比这还小,就是用DMA池。在<linux/dmapool.h>中定义了DMA池函数:
struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
void dma_pool_destroy(struct dma_pool *pool);
name是DMA池的名字,dev是device结构,size是从该池中分配的缓冲区的大小,align是该池分配操做所必须遵照的硬件对齐原则(用字节表示),若是allocation不为零,表示内存边界不能超越allocation。好比说传入的allocation是4K,表示从该池分配的缓冲区不能跨越4KB的界限。
在销毁以前必须向DMA池返回全部分配的内存。
void * dma_pool_alloc(sturct dma_pool *pool, int mem_flags, dma_addr_t *handle);
void dma_pool_free(struct dma_pool *pool, void *addr, dma_addr_t addr);
8. 创建流式DMA映射
在某些体系结构中,流式映射也可以拥有多个不连续的页和多个“分散/汇集”缓冲区。创建流式映射时,必须告诉内核数据流动的方向。
DMA_TO_DEVICE
DEVICE_TO_DMA
若是数据被发送到设备,使用DMA_TO_DEVICE;而若是数据被发送到CPU,则使用DEVICE_TO_DMA。
DMA_BIDIRECTTONAL
若是数据可双向移动,则使用该值
DMA_NONE
该符号只是出于调试目的。
当只有一个缓冲区要被传输的时候,使用下函数映射它:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
返回值是总线地址,能够把它传递给设备;若是执行错误,返回NULL。
当传输完毕后,使用下函数删除映射:
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma-data_direction direction);
使用流式DMA的原则:
一是缓冲区只能用于这样的传送,即其传送方向匹配与映射时给定的方向值;
二是一旦缓冲区被映射,它将属于设备,不是处理器。直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。只用当dma_unmap_single函数被调用后,显示刷新处理器缓存中的数据,驱动程序才能安全访问其中的内容。
三是在DMA出于活动期间内,不能撤销对缓冲区的映射,不然会严重破坏系统的稳定性。
若是要映射的缓冲区位于设备不能访问的内存区段(高端内存),怎么办?一些体系结构只产生一个错误,可是其余一些系统结构件建立一个回弹缓冲区。回弹缓冲区就是内存中的独立区域,它可被设备访问。若是使用DMA_TO_DEVICE标志映射缓冲区,而且须要使用回弹缓冲区,则在最初缓冲区中的内容做为映射操做的一部分被拷贝。很明显,在拷贝后,最初缓冲区内容的改变对设备不可见。一样DEVICE_TO_DMA回弹缓冲区被dma_unmap_single函数拷贝回最初的缓冲区中,也就是说,直到拷贝操做完成,来自设备的数据才可用。
有时候,驱动程序须要不通过撤销映射就访问流式DMA缓冲区的内容,为此内核提供了以下调用:
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_directction direction);
应该在处理器访问流式DMA缓冲区前调用该函数。一旦调用了该函数,处理器将“拥有”DMA缓冲区,并可根据须要对它进行访问。而后在设备访问缓冲区前,应该调用下面的函数将全部权交还给设备:
void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
再次强调,处理器在调用该函数后,不能再访问DMA缓冲区了。