linux  DMA

DMA是一种无需要CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制,使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率

DMA方式的数据传输由DMA控制器控制,在传输期间,CPU可以并发地执行其他任务,当DMA结束后,DMA控制器通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后序处理.DMA可以用做内存与外设之间传输数据的方式,这种传输方式之下,数据并不需要经过CPU中转。

1、  DMA与Cache一致性

假设DMA针对内存的目的地址与Cache缓存的对象没有重叠区域,DMA和Cache之间没有问题,但是,如果DMA的目的地址与Cache所缓存的内存地址访问有重叠,经过DMA操作,Cache缓存对应的内存的数据已经被修改,而CPU本身并不知道,它仍然认为Cache中的数据就是内存中的数据,以后访问Cache映射的内存时,它仍然使用陈旧的Cache数据,这就发生了Cache与内存之间数据不一致性的问题。

解决由于DMA导致的Cache一致性问题的最简单方法是直接禁止DMA目标地址范围内内存的Cache功能

 

DMA只是外设与内存交互数据的一种方式,内存中用于与外设交互数据的一块区域被称作DMA缓冲区。

 

一般的DMA操作只能在16MB以下的内存中进行,因此,在使用kmalloc()和__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存位于DMA_ZONE,是具备DMA能力的

 

在使用DMA区域时需要注意虚拟地址、物理地址和总线地址

基于DMA的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU MMU控制器外围角度看到的内存地址(从CPU核角度看到的是虚拟地址)。虽然在PC上,对于ISA和PCI而言,总线地址即为物理地址,但并非每个平台都是如此。和DMA相关的有IOMMU,它类似于MMU,只不过IOMMU针对的是外设总线地址和内存地址之间的转化

 

一致性DMA缓冲区

DMA映射包括两方面工作:分配一片DMA缓冲区,为这片缓冲区产生设备可访问的地址。同时,DMA映射页必须考虑Cache一致性问题,内核中提供了以下函数用于分配一个DMA一致性的内存区域:

Void* dma_alloc_coherent(struct device* dev,size_tsize,dma_addr_t* handle,gfp_y gfp);

此函数的返回值为申请到的DMA缓冲区的虚拟地址,此外,该函数还通过参数handle返回DMA缓冲区的总线地址,handle的类型为dma_addr_t,代表的是总线地址

Dma_alloc_coherent()申请一片DMA缓冲区,进行地址映射并保证该缓冲区的Cache一致性

Void  dma_free_coherent(struct device* dec,size_tsize,void* cpu_addr,dam_addr_t handle);

内核还提供了PCI设备设备申请DMA缓冲区的函数pci_alloc_consitent(),原型为:

Void* pci_alloc_consistent(struct pci_dev*pdev,size_t size,dma_addr_t* dam_addrp);

 

流式DMA缓冲区

并非所有的DMA缓冲区都是驱动申请的,如果驱动申请的,用一致性DMA缓冲区自然最方便,直接考虑了Cache一致性问题,但是,缓冲区来自内核的较上层,上层很可能用的是普通的kmalloc() _get_free_pages()等方法,这时候就要使用流式DMA映射。流式DMA缓冲区使用的一般步骤如下:

         进行流式DMA映射

         执行DMA操作

         进行流式DMA去映射

内核提供了和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_tsize, 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_tbus_addr, size_t size, enum dma_data_directction direction);

应该在处理器访问流式DMA缓冲区前调用该函数。一旦调用了该函数,处理器将“拥有”DMA缓冲区,并可根据需要对它进行访问。然后在设备访问缓冲区前,应该调用下面的函数将所有权交还给设备:

void dma_sync_single_for_device(struct device *dev, dma_handle_tbus_addr, size_t size, enum dma_data_direction direction);

再次强调,处理器在调用该函数后,不能再访问DMA缓冲区了。

 

 

申请和释放DMA通道

和中断一样,在使用DMA之前,设备驱动程序需首先向系统申请DMA通道,申请DMA通道函数:

Int request_dma(unsigned int dmanr,const char*device_id);

设备结构体指针可作为传入device_id的最佳参数

使用完DMA通道后,需要释放

Void free_dma(unsigned int dmanr);

Linux设备驱动中DMA相关代码流程:

1、request_dma()并初始化DMAC    申请DMA缓冲区 (在设备驱动模块加载或open()函数中进行)

2、进行DMA传输  (在write() read() ioctl()等功能函数中进行)

3、若使能了对应中断,进行DMA传输后的中断处理 (在中断处理程序中进行)

4、释放DMA缓冲区 free_dma() (在设备驱动模块卸载或relese()函数中进行 )

 

外设和内存之间的数据传送控制方式

程序直接控制方式就是CPU告诉外设“我要读了”,然后外设开始准备。CPU这时候停下手中的工作,不停的问外设“好了没有”,一旦外设说“好了”,CPU就从外设读一个字,再往内存写一个字,然后再告诉外设“我要继续读”,就这么重复直到完成。所以在读的过程中CPU别的什么也干不了。 中断方式就是CPU告诉外设“我要读了”,然后就不管了,继续干自己的事。这时候外设开始准备,等准备好了就主动告诉CPU“我好了”,CPU这时候停下手里的工作开始从外设读一个字,然后往内存写一个字。写完了就告诉外设“我要继续读”,说完了就继续干刚才被打断前的事,等外设再准备好以后再通知CPU,直到完成。中断方式的好处就是CPU在外设准备的时候不用不停地问,可以做自己的事,提高了利用率。 DMA方式就是CPU告诉外设“你把XXX个字的内容写到内存的XXX位置去吧”,然后就不管了,等外设准备好了就通知CPU“我准备好了”,然后CPU让出一个节拍的总线使用权,外设就可以直接把数据送入内存,然后再通知CPU说“我传完了”,然后CPU进行一些清理工作就可以了。DMA比中断方式又有提高,因为中断方式中,数据从外设输入内存依然要CPU参与,而DMA在大量数据读入的时候不需要CPU参与,这就节约了CPU的时间。 通道就是相当于有一个处理器专门来管数据传送,只要CPU把指令告诉通道,通道就可以完成一切数据传输任务,所以CPU的利用率更高。