Linux 下的DMA浅析

DMA是一种无需CPU的参与就可以让外设和系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。DMA经常与硬件体系结构特别是外设的总线技术密切相关。一、DMA控制器硬件结构       DMA允许外围设备和主内存之间直接传输 I/O 数据, DMA 依赖于系统。每一种体系结构DMA传输不同,编
摘要由CSDN通过智能技术生成

DMA是一种无需CPU的参与就可以让外设和系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。DMA经常与硬件体系结构特别是外设的总线技术密切相关。


一、DMA控制器硬件结构

       DMA允许外围设备和主内存之间直接传输 I/O 数据, DMA 依赖于系统。每一种体系结构DMA传输不同,编程接口也不同。

数据传输可以以两种方式触发:一种软件请求数据,另一种由硬件异步传输

a -- 软件请求数据

     调用的步骤可以概括如下(以read为例):

(1)在进程调用 read 时,驱动程序的方法分配一个 DMA 缓冲区,随后指示硬件传送它的数据。进程进入睡眠。
(2)硬件将数据写入 DMA 缓冲区并在完成时产生一个中断。

(3)中断处理程序获得输入数据,应答中断,最后唤醒进程,该进程现在可以读取数据了。

b -- 由硬件异步传输

      在 DMA 被异步使用时发生的。以数据采集设备为例:

(1)硬件发出中断来通知新的数据已经到达。
(2)中断处理程序分配一个DMA缓冲区。
(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。
(4)处理程序利用DMA分发新的数据,唤醒任何相关进程。

     

      网卡传输也是如此,网卡有一个循环缓冲区(通常叫做 DMA 环形缓冲区)建立在与处理器共享的内存中。每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。然后驱动程序将网络数据包传给内核的其它部分处理,并在环形缓冲区中放置一个新的 DMA 缓冲区。

     驱动程序在初始化时分配DMA缓冲区,并使用它们直到停止运行


二、DMA通道使用的地址

        DMA通道用dma_chan结构数组表示,这个结构在kernel/dma.c中,列出如下:

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. struct dma_chan {  
  2.     int  lock;  
  3.     const char *device_id;  
  4. };  
  5.    
  6. static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {  
  7.     [4] = { 1, "cascade" },  
  8. };  

      如果dma_chan_busy[n].lock != 0表示忙,DMA0保留为DRAM更新用,DMA4用作级联。DMA 缓冲区的主要问题是,当它大于一页时,它必须占据物理内存中的连续页。
由于DMA需要连续的内存,因而在引导时分配内存或者为缓冲区保留物理 RAM 的顶部。在引导时给内核传递一个"mem="参数可以保留 RAM 的顶部。例如,如果系统有 32MB 内存,参数"mem=31M"阻止内核使用最顶部的一兆字节。稍后,模块可以使用下面的代码来访问这些保留的内存:

dmabuf = ioremap( 0x1F00000 /* 31M */, 0x100000 /* 1M */);

    分配 DMA 空间的方法,代码调用 kmalloc(GFP_ATOMIC) 直到失败为止,然后它等待内核释放若干页面,接下来再一次进行分配。最终会发现由连续页面组成的DMA 缓冲区的出现。

    一个使用 DMA 的设备驱动程序通常会与连接到接口总线上的硬件通讯,这些硬件使用物理地址,而程序代码使用虚拟地址。基于 DMA 的硬件使用总线地址而不是物理地址,有时,接口总线是通过将 I/O 地址映射到不同物理地址的桥接电路连接的。甚至某些系统有一个页面映射方案,能够使任意页面在外围总线上表现为连续的。

      当驱动程序需要向一个 I/O 设备(例如扩展板或者DMA控制器)发送地址信息时,必须使用 virt_to_bus 转换,在接受到来自连接到总线上硬件的地址信息时,必须使用 bus_to_virt 了。


三、DMA操作函数

        写一个DMA驱动的主要工作包括:DMA通道申请、DMA中断申请、控制寄存器设置、挂入DMA等待队列、清除DMA中断、释放DMA通道

        因为 DMA 控制器是一个系统级的资源,所以内核协助处理这一资源。内核使用 DMA 注册表为 DMA 通道提供了请求/释放机制,并且提供了一组函数在 DMA 控制器中配置通道信息。

      以下具体分析关键函数(linux/arch/arm/mach-s3c2410/dma.c)

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. int s3c2410_request_dma(const char *device_id, dmach_t channel,  
  2.     dma_callback_t write_cb, dma_callback_t read_cb) (s3c2410_dma_queue_buffer);  
  3. /* 
  4. 函数描述:申请某通道的DMA资源,填充s3c2410_dma_t 数据结构的内容,申请DMA中断。 
  5. 输入参数:device_id DMA 设备名;channel 通道号; 
  6. write_cb DMA写操作完成的回调函数;read_cb DMA读操作完成的回调函数 
  7. 输出参数:若channel通道已使用,出错返回;否则,返回0 
  8. */  
  9.   
  10. int s3c2410_dma_queue_buffer(dmach_t channel, void *buf_id,  
  11. dma_addr_t data, int size, int write) (s3c2410_dma_stop);  
  12. /* 
  13. 函数描述:这是DMA操作最关键的函数,它完成了一系列动作:分配并初始化一个DMA内核缓冲区控制结构,并将它插入DMA等待队列,设置DMA控制寄存器内容,等待DMA操作触发 
  14. 输入参数: channel 通道号;buf_id,缓冲区标识 
  15. dma_addr_t data DMA数据缓冲区起始物理地址;size DMA数据缓冲区大小;write 是写还是读操作 
  16. 输出参数:操作成功,返回0;否则,返回错误号 
  17. */  
  18.   
  19. int s3c2410_dma_stop(dmach_t channel)  
  20. //函数描述:停止DMA操作。  
  21.   
  22. int s3c2410_dma_flush_all(dmach_t channel)  
  23. //函数描述:释放DMA通道所申请的所有内存资源  
  24.   
  25. void s3c2410_free_dma(dmach_t channel)  
  26. //函数描述:释放DMA通道  


四、DMA映射

       一个DMA映射就是分配一个 DMA 缓冲区并为该缓冲区生成一个能够被设备访问的地址的组合操作。一般情况下,简单地调用函数virt_to_bus 就设备总线上的地址,但有些硬件映射寄存器也被设置在总线硬件中。映射寄存器(mapping register)是一个类似于外围设备的虚拟内存等价物。在使用这些寄存器的系统上,外围设备有一个相对较小的、专用的地址区段,可以在此区段执行 DMA。通过映射寄存器,这些地址被重映射到系统 RAM。映射寄存器具有一些好的特性,包括使分散的页面在设备地址空间看起来是连续的。但不是所有的体系结构都有映射寄存器,特别地,PC 平台没有映射寄存器。

     在某些情况下,为设备设置有用的地址也意味着需要构造一个反弹(bounce)缓冲区。例如,当驱动程序试图在一个不能被外围设备访问的地址(一个高端内存地址)上执行 DMA 时,反弹缓冲区被创建。然后,按照需要,数据被复制到反弹缓冲区,或者从反弹缓冲区复制。

     根据 DMA 缓冲区期望保留的时间长短,PCI 代码区分两种类型的 DMA 映射:

a -- 一致 DMA 映射 

       它们存在于驱动程序的生命周期内。一个被一致映射的缓冲区必须同时可被 CPU 和外围设备访问,这个缓冲区被处理器写时,可立即被设备读取而没有cache效应,反之亦然,使用函数pci_alloc_consistent建立一致映射。

b -- 流式 DMA映射

       流式DMA映射是为单个操作进行的设置。它映射处理器虚拟空间的一块地址,以致它能被设备访问。应尽可能使用流式映射,而不是一致映射。这是因为在支持一致映射的系统上,每个 DMA 映射会使用总线上一个或多个映射寄存器。具有较长生命周期的一致映射,会独占这些寄存器很长时间――即使它们没有被使用。使用函数dma_map_single建立流式映射。


1、建立一致 DMA 映射

      函数pci_alloc_consistent处理缓冲区的分配和映射,函数分析如下(在include/asm-generic/pci-dma-compat.h中):

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. static inline void *pci_alloc_consistent(struct pci_dev *hwdev,   
  2.                  size_t size, dma_addr_t *dma_handle)  
  3. {  
  4.     return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev,   
  5.                        size, dma_handle, GFP_ATOMIC);  
  6. }  

    结构dma_coherent_mem定义了DMA一致性映射的内存的地址、大小和标识等。结构dma_coherent_mem列出如下(在arch/i386/kernel/pci-dma.c中):

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. struct dma_coherent_mem {  
  2.     void        *virt_base;  
  3.     u32     device_base;  
  4.     int     size;  
  5.     int     flags;  
  6.     unsigned long   *bitmap;  
  7. };  

     函数dma_alloc_coherent分配size字节的区域的一致内存,得到的dma_handle是指向分配的区域的地址指针,这个地址作为区域的物理基地址。dma_handle是与总线一样的位宽的无符号整数。 函数dma_alloc_coherent分析如下(在arch/i386/kernel/pci-dma.c中):

[cpp]  view plain   copy
  在CODE上查看代码片 派生到我的代码片
  1. void *dma_alloc_coherent(struct device *dev, 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值