Linux驱动开发--linux下的DMA编程

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()函数中进行 )

时间: 2024-10-02 23:16:04

Linux驱动开发--linux下的DMA编程的相关文章

Linux驱动开发(二)—DMA的使用(一)

1 DMA概念 DMA顾名思义就是指设备和内存之间.内存和外部存储设备之间进行直接的数据读写操作,而不需要CPU的参与. 2 DMA原理 DMA传输需要由DMA控制器DMAC进行,当需要进行DMA传输的时候,DMA控制器会发出占用总线的请求,当CPU响应DMA的请求时,暂时放弃对总线的控制权,当DMA传输结束的时候,DMAC会向I/O接口发出结束命令,并将总线控制权交还给CPU.一个完整的DMA传输过程必须经过DMA请求.DMA响应.DMA传输.DMA结束4个步骤. 3 DMA传输过程 4 DM

驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 不能再扯了,涉及到linux的驱动开发知识面灰常广,再扯文章就会变得灰常长.首先还是回到led驱动的本身上,自从linux被移植到arm上后,做驱动开发的硬件知识要求有所降低,很多都回归到了软件上,这是系统编程的一大特点,当然 ,也不排除有很多

嵌入式linux驱动开发之点亮led未遂(驱动编程思想之初体验)

有了上两篇文章的基础,我们就可以开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的住,不然真像一些人说的,学了一年嵌入式感觉还没找到门. 另外实践很重要,一年多以前就知道了arm,那时整天用单片机的思维去yy着arm,直到前段时间弄来一块arm板,烧上linux系统后才知道,坑呀!根本不是那回事,所以实践是学习计算机类最重要的基本素质,如果整天看书,那基本上

【转】linux驱动开发的经典书籍

原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书为<linux_device_driver 3rd Edition>,这是一本很经典的书,无奈Linux的东东还是过于庞大,这本侧重于实战的书籍也只能停留在基本的接口介绍上,更深入的东东只能靠我们自己摸索了.但万事总有一个开头,没有对Linux驱动整体框架的把握是很难做一个优秀的驱动开发者的.除了

linux 驱动开发-模块的构建

1.模块的含义 linux 是采用模块化的方式构建的,允许内核在运行时动态地向其中插入或从中删除代码,这些代码(包扩函数,数据,模块入口函数,模块出口函数)被一并组合 在一个单独的二进制镜像,就是所谓的可装载内核模块. 模块可以是基本的内核镜像尽可能小,同时可以方便地对新功能进行调试,还可以实现热插拔(后续会学习如何实现设备的热插拔功能,暂时无需深究),和内核的核心子系统不一样,模块文件需要有入口点和出口点. 模块与应用程序的区别: a.模块和库函数类似,一个模块通常包含若干函数和数据,每个函数

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇)

Linux驱动开发之 三 (那些必须要了解的硬件知识 之 存储器篇) 本文重点学习存储器相关的基本知识,网络上对RAM,ROM,FLASH等有非常详细的介绍,老谢将这些知识点摘抄整理并加以注释如下.这个整理的过程也是加深记忆的过程. 1.什么是内存 在计算机的组成结构中,有一个很重要的部分,就是存储器.存储器是用来存储程序和数据的部件,对于计算机来说,有了存储器,才有记忆功能,才能保证正常工作.存储器的种类很多,按其用途可分为主存储器和辅助存储器,主存储器又称内存储器(简称内存),辅助存储器又称

Linux驱动开发学习的一些必要步骤

1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, lsmod, rmmod. 在驱动的init函数里打印hello world, insmod后应该能够通过dmesg看到输出. 5. 写一完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现. 在ioctl里完成从用户空间向内核空间传递结构体的实现. 6. 写一bl

Linux驱动开发之输入子系统

2020-02-15 关键字: Linux 中输入设备大致可分以下几种: 1.按键/键盘(keyboard) 2.鼠标(mouse) 3.触摸屏(touchscreen) 4.游戏杆(joystick) 输入子系统的目的是为了屏蔽众多输入设备在硬件上的差异化,使得在开发输入设备的程序时能更简单统一.输入子系统屏蔽差异的方式就是为各种输入设备与上层应用提供统一的编程接口. Linux 输入子系统是一种编程框架,它可以自上而下分为以下几种层次: 1.应用层 2.input handler层:数据处理

s3c6410 Linux 驱动开发环境搭建

s3c6410 Linux 驱动开发环境搭建 -- 既然你是做Linux开发的,你还用虚拟机? 非常多人都在win下做开发,于是SD_writer.exe之类的烧写工具"大行其道",多是用虚拟机Linux. 全然转到Linux下学习開始蛮不爽的,开发板制作商送的教程都是些讲Win-CE的东东,感觉实质性的东西没什么.对于全然用Linux做开发的技术解说非常少,连烧写SD卡都用的win以下的程序.后来找了些资料,整理在这里,希望留给有心人.共同营造一个更好的共同学习的环境. 当别人遇到困