当前位置:Linux教程 - Linux资讯 - Linux对ISA总线DMA的实现

Linux对ISA总线DMA的实现

  作者:詹荣开    摘要    本文主要从内核实现的角度分析了Linux 2.4.0内核对ISA总线接口的外设进行DMA传输的实现。本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。    申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。       你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给:The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA      欢迎各位指出文档中的错误与疑问。    ----------------------------------------------------      DMA是一种无需CPU的参与就可以让外设与系统RAM之间进行双向(to device 或 from device)数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率(throughput)。      由于DMA是一种硬件机制,因此它通常与硬件体系结构是相关的,尤其是依赖于外设的总线技术。比如:ISA卡的DMA机制就与PCI卡的DMA机制有区别。本站主要讨论ISA总线的DMA技术。    1.DMA概述      DMA是外设与主存之间的一种数据传输机制。一般来说,外设与主存之间存在两种数据传输方法:(1)Pragrammed I/O(PIO)方法,也即由CPU通过内存读写指令或I/O指令来持续地读写外设的内存单元(8位、16位或32位),直到整个数据传输过程完成。(2)DMA,即由DMA控制器(DMA Controller,简称DMAC)来完成整个数据传输过程。在此期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的ISR进行后处理。      DMA技术产生时正是ISA总线在PC中流行的时侯。因此,ISA卡的DMA数据传输是通过ISA总线控制芯片组中的两个级联8237 DMAC来实现的。这种DMA机制也称为“标准DMA”(standard DMA)。标准DMA有时也称为“第三方DMA”(third-party DMA),这是因为:系统DMAC完成实际的传输过程,所以它相对于传输过程的“前两方”(传输的发送者和接收者)来说是“第三方”。      标准DMA技术主要有两个缺点:(1)8237 DMAC的数据传输速度太慢,不能与更高速的总线(如PCI)配合使用。(2)两个8237 DMAC一起只提供了8个DMA通道,这也成为了限制系统I/O吞吐率提升的瓶颈。      鉴于上述两个原因,PCI总线体系结构设计一种成为“第一方DMA”(first-party DMA)的DMA机制,也称为“Bus Mastering”(总线主控)。在这种情况下,进行传输的PCI卡必须取得系统总线的主控权后才能进行数据传输。实际的传输也不借助慢速的ISA DMAC来进行,而是由内嵌在PCI卡中的DMA电路(比传统的ISA DMAC要快)来完成。Bus Mastering方式的DMA可以让PCI外设得到它们想要的传输带宽,因此它比标准DMA功能满足现代高性能外设的要求。      随着计算机外设技术的不断发展,现代能提供更快传输速率的Ultra DMA(UDMA)也已经被广泛使用了。本为随后的篇幅只讨论ISA总线的标准DMA技术在Linux中的实现。记住:ISA卡几乎不使用Bus Mastering模式的DMA;而PCI卡只使用Bus Mastering模式的DMA,它从不使用标准DMA。    2.Intel 8237 DMAC      最初的IBM PC/XT中只有一个8237 DMAC,它提供了4个8位的DMA通道(DMA channel 0-3)。从IBM AT开始,又增加了一个8237 DMAC(提供4个16位的DMA通道,DMA channel 4-7)。两个8237 DMAC一起为系统提供8个DMA通道。与中断控制器8259的级联方式相反,第一个DMAC被级联到第二个DMAC上,通道4被用于DMAC级联,因此它对外设来说是不可用的。第一个DMAC也称为“slave DAMC”,第二个DMAC也称为“Master DMAC”。      下面我们来详细叙述一下Intel 8237这个DMAC的结构。      每个8237 DMAC都提供4个DMA通道,每个DMA通道都有各自的寄存器,而8237本身也有一组控制寄存器,用以控制它所提供的所有DMA通道。      2.1 DMA通道的寄存器      8237 DMAC中的每个DMA通道都有5个寄存器,分别是:当前地址寄存器、当前计数寄存器、地址寄存器(也称为偏移寄存器)、计数寄存器和页寄存器。其中,前两个是8237的内部寄存器,对外部是不可见的。      (1)当前地址寄存器(Current Address Register):每个DMA通道都有一个16位的当前地址寄存器,表示一个DMA传输事务(Transfer Transaction)期间当前DMA传输操作的DMA物理内存地址。在每个DMA传输开始前,8237都会自动地用该通道的Address Register中的值来初始化这个寄存器;在传输事务期间的每次DMA传输操作之后该寄存器的值都会被自动地增加或减小。      (2)当前计数寄存器(Current Count Register):每个每个DMA通道都有一个16位的当前计数寄存器,表示当前DMA传输事务还剩下多少未传输的数据。在每个DMA传输事务开始之前,8237都会自动地用该通道的Count Register中的值来初始化这个寄存器。在传输事务期间的每次DMA传输操作之后该寄存器的值都会被自动地增加或减小(步长为1)。      (3)地址寄存器(Address Register)或偏移寄存器(Offset Register):每个DMA通道都有一个16位的地址寄存器,表示系统RAM中的DMA缓冲区的起始位置在页内的偏移。      (4)计数寄存器(Count Register):每个DMA通道都有一个16位的计数寄存器,表示DMA缓冲区的大小。      (5)页寄存器(Page Register):该寄存器定义了DMA缓冲区的起始位置所在物理页的基地址,即页号。页寄存器有点类似于PC中的段基址寄存器。      2.2 8237 DAMC的控制寄存器      (1)命令寄存器(Command Register)      这个8位的寄存器用来控制8237芯片的操作。其各位的定义如下图所示:      (2)模式寄存器(Mode Register)      用于控制各DMA通道的传输模式,如下所示:      (3)请求寄存器(Request Register)      用于向各DMA通道发出DMA请求。各位的定义如下:      (4)屏蔽寄存器(Mask Register)      用来屏蔽某个DMA通道。当一个DMA通道被屏蔽后,它就不能在服务于DMA请求,直到通道的屏蔽码被清除。各位的定义如下:      上述屏蔽寄存器也称为“单通道屏蔽寄存器”(Single Channel Mask Register),因为它一次只能屏蔽一个通道。此外含有一个屏蔽寄存器,可以实现一次屏蔽所有4个DMA通道,如下:      (5)状态寄存器(Status Register)      一个只读的8位寄存器,表示各DMA通道的当前状态。比如:DMA通道是否正服务于一个DMA请求,或者某个DMA通道上的DMA传输事务已经完成。各位的定义如下:      2.3 8237 DMAC的I/O端口地址      主、从8237 DMAC的各个寄存器都是编址在I/O端口空间的。而且其中有些I/O端口地址对于I/O读、写操作有不同的表示含义。如下表示所示:      Slave DMAC’s I/O port Master DMAC’sI/O port read write  0x000 0x0c0 Channel 0/4 的Address Register  0x001 0x0c1 Channel 0/4的Count Register  0x002 0x0c2 Channel 1/5 的Address Register  0x003 0x0c3 Channel 1/5的Count Register  0x004 0x0c4 Channel 2/6的Address Register  0x005 0x0c5 Channel 2/6的Count Register  0x006 0x0c6 Channel 3/7的Address Register  0x007 0x0c7 Channel 3/7的Count Register  0x008 0x0d0 Status Register Command Register  0x009 0x0d2 Request Register  0x00a 0x0d4 Single Channel Mask Register  0x00b 0x0d6 Mode Register  0x00c 0x0d8 Clear Flip-Flop Register  0x00d 0x0da Temporary Register Reset DMA controller  0x00e 0x0dc Reset all channel masks  0x00f 0x0de all-channels Mask Register           各DMA通道的Page Register在I/O端口空间中的地址如下:      DMA channel Page Register’sI/O port address  0 0x087  1 0x083  2 0x081  3 0x082  4 0x08f  5 0x08b  6 0x089  7 0x08a           注意两点:      1. 各DMA通道的Address Register是一个16位的寄存器,但其对应的I/O端口是8位宽,因此对这个寄存器的读写就需要两次连续的I/O端口读写操作,低8位首先被发送,然后紧接着发送高8位。      2. 各DMA通道的Count Register:这也是一个16位宽的寄存器(无论对于8位DMA还是16位DMA),但相对应的I/O端口也是8位宽,因此读写这个寄存器同样需要两次连续的I/O端口读写操作,而且同样是先发送低8位,再发送高8位。往这个寄存器中写入的值应该是实际要传输的数据长度减1后的值。在DMA传输事务期间,这个寄存器中的值在每次DMA传输操作后都会被减1,因此读取这个寄存器所得到的值将是当前DMA事务所剩余的未传输数据长度减1后的值。当DMA传输事务结束时,该寄存器中的值应该被置为0。      2.4 DMA通道的典型使用      在一个典型的PC机中,某些DMA通道通常被固定地用于一些PC机中的标准外设,如下所示:      Channel Size Usage  0 8-bit Memory Refresh  1 8-bit Free  2 8-bit Floppy Disk Controller  3 8-bit Free  4 16-bit Cascading  5 16-bit Free  6 16-bit Free  7 16-bit Free           2.5 启动一个DMA传输事务的步骤      要启动一个DMA传输事务必须对8237进行编程,其典型步骤如下:      1.通过CLI指令关闭中断。    2.Disable那个将被用于此次DMA传输事务的DMA通道。    3.向Flip-Flop寄存器中写入0值,以重置它。    4.设置Mode Register。    5.设置Page Register。    6.设置Address Register。    7.设置Count Register。    8.Enable那个将被用于此次DMA传输事务的DMA通道。    9.用STI指令开中断。    3 Linux对读写操作8237 DMAC的实现      由于DMAC的各寄存器是在I/O端口空间中编址的,因此读写8237 DMAC是平台相关的。对于x86平台来说,Linux在include/asm-i386/Dma.h头文件中实现了对两个8237 DMAC的读写操作。      3.1 端口地址和寄存器值的宏定义      Linux用宏MAX_DMA_CHANNELS来表示系统当前的DMA通道个数,如下:        #define MAX_DMA_CHANNELS 8           然后,用宏IO_DMA1_BASE和IO_DMA2_BASE来分别表示两个DMAC在I/O端口空间的端口基地址:        #define IO_DMA1_BASE 0x00       /* 8 bit slave DMA, channels 0..3 */    #define IO_DMA2_BASE 0xC0       /* 16 bit master DMA, ch 4(=slave input)..7 */           接下来,Linux定义了DMAC各控制寄存器的端口地址。其中,slave SMAC的各控制寄存器的端口地址定义如下:      #define DMA1_CMD_REG 0x08 /* command register (w) */  #define DMA1_STAT_REG 0x08 /* status register (r) */  #define DMA1_REQ_REG 0x09 /* request register (w) */  #define DMA1_MASK_REG 0x0A /* single-channel mask (w) */  #define DMA1_MODE_REG 0x0B /* mode register (w) */  #define DMA1_CLEAR_FF_REG 0x0C /* clear pointer flip-flop (w) */  #define DMA1_TEMP_REG 0x0D /* Temporary Register (r) */  #define DMA1_RESET_REG 0x0D /* Master Clear (w) */  #define DMA1_CLR_MASK_REG 0x0E /* Clear Mask */  #define DMA1_MASK_ALL_REG 0x0F /* all-channels mask (w) */           Master DMAC的各控制寄存器的端口地址定义如下:      #define DMA2_CMD_REG 0xD0 /* command register (w) */  #define DMA2_STAT_REG 0xD0 /* status register (r) */  #define DMA2_REQ_REG 0xD2 /* request register (w) */  #define DMA2_MASK_REG 0xD4 /* single-channel mask (w) */  #define DMA2_MODE_REG 0xD6 /* mode register (w) */  #define DMA2_CLEAR_FF_REG 0xD8 /* clear pointer flip-flop (w) */  #define DMA2_TEMP_REG 0xDA /* Temporary Register (r) */  #define DMA2_RESET_REG 0xDA /* Master Clear (w) */  #define DMA2_CLR_MASK_REG 0xDC /* Clear Mask */  #define DMA2_MASK_ALL_REG 0xDE /* all-channels mask (w) */           8个DMA通道的Address Register的端口地址定义如下:      #define DMA_ADDR_0 0x00 /* DMA address registers */  #define DMA_ADDR_1 0x02  #define DMA_ADDR_2 0x04  #define DMA_ADDR_3 0x06  #define DMA_ADDR_4 0xC0  #define DMA_ADDR_5 0xC4  #define DMA_ADDR_6 0xC8  #define DMA_ADDR_7 0xCC           8个DMA通道的Count Register的端口地址定义如下:      #define DMA_CNT_0 0x01 /* DMA count registers */  #define DMA_CNT_1 0x03  #define DMA_CNT_2 0x05  #define DMA_CNT_3 0x07  #define DMA_CNT_4 0xC2  #define DMA_CNT_5 0xC6  #define DMA_CNT_6 0xCA  #define DMA_CNT_7 0xCE           8个DMA通道的Page Register的端口地址定义如下:      #define DMA_PAGE_0 0x87 /* DMA page registers */  #define DMA_PAGE_1 0x83  #define DMA_PAGE_2 0x81  #define DMA_PAGE_3 0x82  #define DMA_PAGE_5 0x8B  #define DMA_PAGE_6 0x89  #define DMA_PAGE_7 0x8A           Mode Register的几个常用值的定义如下:        #define DMA_MODE_READ 0x44     /* I/O to memory, no autoinit, increment, single mode */    #define DMA_MODE_WRITE 0x48     /* memory to I/O, no autoinit, increment, single mode */    #define DMA_MODE_CASCADE 0xC0      /* pass thru DREQ->HRQ, DACK<-HLDA only */    #define DMA_AUTOINIT 0x10           3.2 读写DMAC的高层接口函数      (1)使能/禁止一个特定的DMA通道      Single Channel Mask Register中的bit[2]为0表示使能一个DMA通道,为1表示禁止一个DMA通道;而该寄存器中的bit[1:0]则用于表示使能或禁止哪一个DMA通道。      函数enable_dma()实现使能某个特定的DMA通道,传输dmanr指定DMA通道号,其取值范围是0~DMA_MAX_CHANNELS-1。如下:      static __inline__ void enable_dma(unsigned int dmanr)  {   if (dmanr<=3)   dma_outb(dmanr, DMA1_MASK_REG);   else   dma_outb(dmanr & 3, DMA2_MASK_REG);  }           宏dma_outb和dma_inb实际上就是outb(或outb_p)和inb函数。注意,当dmanr取值大于3时,对应的是Master DMAC上的DMA通道0~3,因此在写DMA2_MASK_REG之前,要将dmanr与值3进行与操作,以得到它在master DMAC上的局部通道编号。      函数disable_dma()禁止一个特定的DMA通道,其源码如下:      static __inline__ void disable_dma(unsigned int dmanr)  {   if (dmanr<=3)   dma_outb(dmanr 4, DMA1_MASK_REG);   else   dma_outb((dmanr & 3) 4, DMA2_MASK_REG);  }           为禁止某个DMA通道,Single Channel Mask Register中的bit[2]应被置为1。      (2)清除Flip-Flop寄存器      函数Clear_dma_ff()实现对slave/Master DMAC的Flip-Flop寄存器进行清零操作。如下:      static __inline__ void clear_dma_ff(unsigned int dmanr)  {   if (dmanr<=3)   dma_outb(0, DMA1_CLEAR_FF_REG);   else   dma_outb(0, DMA2_CLEAR_FF_REG);  }           (3)设置某个特定DMA通道的工作模式      函数set_dma_mode()实现设置一个特定DMA通道的工作模式。如下:      static __inline__ void set_dma_mode(unsigned int dmanr, char mode)  {   if (dmanr<=3)   dma_outb(mode dmanr, DMA1_MODE_REG);   else   dma_outb(mode (dmanr&3), DMA2_MODE_REG);  }           DMAC 的Mode Register中的bit[1:0]指定对该DMAC上的哪一个DMA通道进行模式设置。      (4)为DMA通道设置DMA缓冲区的起始物理地址和大小      由于8237中的DMA通道是通过一个8位的Page Register和一个16位的Address Register来寻址位于系统RAM中的DMA缓冲区,因此8237 DMAC最大只能寻址系统RAM中物理地址在0x000000~0xffffff范围内的DMA缓冲区,也即只能寻址物理内存的低16MB(24位物理地址)。反过来讲,Slave/Master 8237 DMAC又是如何寻址低16MB中的物理内存单元的呢?      首先来看Slave 8237 DMAC(即第一个8237 DMAC)。由于Slave 8237 DMAC是一个8位的DMAC,因此DMA通道0~3在一次DMA传输操作(一个DMA传输事务又多次DMA传输操作组成)中只能传输8位数据,即一个字节。Slave 8237 DMAC将低16MB物理内存分成256个64K大小的页(Page),然后用Page Register来表示内存单元物理地址的高8位(bit[23:16]),也即页号;用Address Register来表示内存单元物理地址在一个Page(64KB大小)内的页内偏移量,也即24位物理地址中的低16位(bit[15:0])。由于这种寻址机制,因此DMA通道0~3的DMA缓冲区必须在一个Page之内,也即DMA缓冲区不能跨越64KB页边界。      再来看看Master 8237 DMAC(即第二个8237 DMAC)。这是一个16位宽的DMAC,因此DMA通道5~7在一次DMA传输操作时可以传输16位数据,也即一个字Word。此时DMA通道的Count Register(16位宽)表示以字计的待传输数据块大小,因此数据块最大可达128KB(64K个字),也即系统RAM中的DMA缓冲区最大可达128KB。由于一次可传输一个字,因此Master 8237 DMAC所寻址的内存单元的物理地址肯定是偶数,也即物理地址的bit[0]肯定为0。此时物理内存的低16MB被化分成128个128KB大小的page,Page Register中的bit[7:1]用来表示页号,也即对应内存单元物理地址的bit[23:17],而Page Register的bit[0]总是被设置为0。Address Register用来表示内存单元在128KB大小的Page中的页内偏移,也即对应内存单元物理地址的bit[16:1](由于此时物理地址的bit[0]总是为0,因此不需要表示)。由于Master 8237 DMAC的这种寻址机制,因此DMA通道5~7的DMA缓冲区不能跨越128KB的页边界。      下面我们来看看Linux是如何实现为各DMA通道设置其Page寄存器的。NOTE!DMA通道5~7的Page Register中的bit[0]总是为0。如下所示:      static __inline__ void set_dma_page(unsigned int dmanr, char pagenr)  {   switch(dmanr) {   case 0:   dma_outb(pagenr, DMA_PAGE_0);   break;   case 1:   dma_outb(pagenr, DMA_PAGE_1);   break;   case 2:   dma_outb(pagenr, DMA_PAGE_2);   break;   case 3:   dma_outb(pagenr, DMA_PAGE_3);   break;   case 5:   dma_outb(pagenr & 0xfe, DMA_PAGE_5);   break;   case 6:   dma_outb(pagenr & 0xfe, DMA_PAGE_6);   break;   case 7:   dma_outb(pagenr & 0xfe, DMA_PAGE_7);   break;   }  }           在上述函数的基础上,函数set_dma_addr()用来为特定DMA通道设置DMA缓冲区的基地址,传输dmanr指定DMA通道号,传输a指定位于系统RAM中的DMA缓冲区起始位置的物理地址。如下:      /* Set transfer address & page bits for specific DMA channel.   * Assumes dma flipflop is clear.   */  static __inline__ void set_dma_addr(unsigned int dmanr, unsigned int a)  {   set_dma_page(dmanr, a>>16);   if (dmanr <= 3) {   dma_outb( a & 0xff, ((dmanr&3)<<1) + IO_DMA1_BASE );   dma_outb( (a>>8) & 0xff, ((dmanr&3)<<1) + IO_DMA1_BASE );   } else {   dma_outb( (a>>1) & 0xff, ((dmanr&3)<<2) + IO_DMA2_BASE );   dma_outb( (a>>9) & 0xff, ((dmanr&3)<<2) + IO_DMA2_BASE );   }  }           函数set_dma_count()为特定DMA通道设置其Count Register的值。传输dmanr指定DMA通道,传输count指定待传输的数据块大小(以字节计),实际写到Count Register中的值应该是count-1。如下所示:      static __inline__ void set_dma_count(unsigned int dmanr, unsigned int count)  {   count--;   if (dmanr <= 3) {   dma_outb( count & 0xff, ((dmanr&3)<<1) + 1 + IO_DMA1_BASE );   dma_outb( (count>>8) & 0xff, ((dmanr&3)<<1) + 1 + IO_DMA1_BASE );   } else {   dma_outb( (count>>1) & 0xff, ((dmanr&3)<<2) + 2 + IO_DMA2_BASE );   dma_outb( (count>>9) & 0xff, ((dmanr&3)<<2) + 2 + IO_DMA2_BASE );   }  }           函数get_dma_residue()获取某个DMA通道上当前DMA传输事务的未传输剩余数据块的大小(以字节计)。DMA通道的Count Register的值在当前DMA传输事务进行期间会不断地自动将减小,直到当前DMA传输事务完成,Count Register的值减小为0。如下:      static __inline__ int get_dma_residue(unsigned int dmanr)  {   unsigned int io_port = (dmanr<=3)? ((dmanr&3)<<1) + 1 + IO_DMA1_BASE   : ((dmanr&3)<<2) + 2 + IO_DMA2_BASE;     /* using short to get 16-bit wrap around */   unsigned short count;     count = 1 + dma_inb(io_port);   count += dma_inb(io_port) << 8;      return (dmanr<=3)? count : (count<<1);  }           3.3 对DMAC的保护      DMAC是一种全局的共享资源,为了保证设备驱动程序对它的独占访问,Linux在kernel/dma.c文件中定义了自旋锁dma_spin_lock来保护它(实际上是保护DMAC的I/O端口资源)。任何想要访问DMAC的设备驱动程序都首先必须先持有自旋锁dma_spin_lock。如下:      static __inline__ unsigned long claim_dma_lock(void)  {   unsigned long flags;   spin_lock_irqsave(&dma_spin_lock, flags); /* 关中断,加锁*/   return flags;  }    static __inline__ void release_dma_lock(unsigned long flags)  {   spin_unlock_irqrestore(&dma_spin_lock, flags);/* 开中断,开锁*/  }         4 Linux对ISA DMA通道资源的管理      DMA通道是一种系统全局资源。任何ISA外设想要进行DMA传输,首先都必须取得某个DMA通道资源的使用权,并在传输结束后释放所使用DMA通道资源。从这个角度看,DMA通道资源是一种共享的独占型资源。      Linux在kernel/Dma.c文件中实现了对DMA通道资源的管理。      4.1 对DMA通道资源的描述      Linux在kernel/Dma.c文件中定义了数据结构dma_chan来描述DMA通道资源。该结构类型的定义如下:      strUCt dma_chan {   int lock;   const char *device_id;  };           其中,如果成员lock!=0则表示DMA通道正被某个设备所使用;否则该DMA通道就处于free状态。而成员device_id就指向使用该DMA通道的设备名字字符串。      基于上述结构类型dma_chan,Linux定义了全局数组dma_chan_busy[],以分别描述8个DMA通道资源各自的使用状态。如下:      static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {   { 0, 0 },   { 0, 0 },   { 0, 0 },   { 0, 0 },   { 1, "cascade" },   { 0, 0 },   { 0, 0 },   { 0, 0 }  };           显然,在初始状态时除了DMA通道4外,其余DMA通道皆处于free状态。      4.2 DMA通道资源的申请      任何ISA卡在使用某个DMA通道进行DMA传输之前,其设备驱动程序都必须向内核提出DMA通道资源的申请。只有申请获得成功后才能使用相应的DMA通道。否则就会发生资源冲突。      函数request_dma()实现DMA通道资源的申请。其源码如下:      int request_dma(unsigned int dmanr, const char * device_id)  {   if (dmanr >= MAX_DMA_CHANNELS)   return -EINVAL;     if (xchg(&dma_chan_busy[dmanr].lock, 1) != 0)   return -EBUSY;     dma_chan_busy[dmanr].device_id = device_id;     /* old flag was 0, now contains 1 to indicate busy */   return 0;  }            上述函数的核心实现就是用原子操作xchg()让成员变量dma_chan_busy[dmanr].lock和值1进行交换操作,xchg()将返回lock成员在交换操作之前的值。因此:如果xchg()返回非0值,这说明dmanr所指定的DMA通道已被其他设备所占用,所以request_dma()函数返回错误值-EBUSY表示指定DMA通道正忙;否则,如果xchg()返回0值,说明dmanr所指定的DMA通道正处于free状态,于是xchg()将其lock成员设置为1,取得资源的使用权。      4.3 释放DMA通道资源      DMA传输事务完成后,设备驱动程序一定要记得释放所占用的DMA通道资源。否则别的外设将一直无法使用该DMA通道。      函数free_dma()释放指定的DMA通道资源。如下:      void free_dma(unsigned int dmanr)  {   if (dmanr >= MAX_DMA_CHANNELS) {   printk("Trying to free DMA%d  ", dmanr);   return;   }     if (xchg(&dma_chan_busy[dmanr].lock, 0) == 0) {   printk("Trying to free free DMA%d  ", dmanr);   return;   }     } /* free_dma */           显然,上述函数的核心实现就是用原子操作xchg()将lock成员清零。      4.4 对/proc/dma文件的实现      文件/proc/dma将列出当前8个DMA通道的使用状况。Linux在kernel/Dma.c文件中实现了函数个get_dma_list()函数来至此/proc/dma文件的实现。函数get_dma_list()的实现比较简单。主要就是遍历数组dma_chan_busy[],并将那些lock成员为非零值的数组元素输出到列表中即可。如下:      int get_dma_list(char *buf)  {   int i, len = 0;     for (i = 0 ; i < MAX_DMA_CHANNELS ; i++) {   if (dma_chan_busy[i].lock) {   len += sprintf(buf+len, "%2d: %s  ",   i,   dma_chan_busy[i].device_id);   }   }   return len;  } /* get_dma_list */         5 使用DMA的ISA设备驱动程序      DMA虽然是一种硬件机制,但它离不开软件(尤其是设备驱动程序)的配合。任何使用DMA进行数据传输的ISA设备驱动程序都必须遵循一定的框架。      5.1 DMA通道资源的申请与释放      同I/O端口资源类似,设备驱动程序必须在一开始就调用request_dma()函数来向内核申请DMA通道资源的使用权。而且,最好在设备驱动程序的open()方法中完成这个操作,而不是在模块的初始化例程中调用这个函数。因为这在一定程度上可以让多个设备共享DMA通道资源(只要多个设备不同时使用一个DMA通道)。这种共享有点类似于进程对CPU的分时共享:-)      设备使用完DMA通道后,其驱动程序应该记得调用free_dma()函数来释放所占用的DMA通道资源。通常,最好再驱动程序的release()方法中调用该函数,而不是在模块的卸载例程中进行调用。      还需要注意的一个问题是:资源的申请顺序。为了避免死锁(deadlock),驱动程序一定要在申请了中断号资源后才申请DMA通道资源。释放时则要先释放DMA通道,然后再释放中断号资源。      使用DMA的ISA设备驱动程序的open()方法的如下:      int xxx_open(struct inode * inode, struct file * filp)  {   ┆   if((err = request_irq(irq,xxx_ISR,SA_INTERRUPT,”YourDeviceName”,NULL))   return err;   if((err = request_dma(dmanr, “YourDeviceName”)){   free_irq(irq, NULL);   return err;   }   ┆   return 0;  }           release()方法的范例代码如下:      void xxx_release(struct inode * inode, struct file * filp)  {   ┆   free_dma(dmanr);   free_irq(irq,NULL);   ┆  }           5.2 申请DMA缓冲区      由于8237 DMAC只能寻址系统RAM中低16MB物理内存,因此:ISA设备驱动程序在申请DMA缓冲区时,一定要以GFP_DMA标志来调用kmalloc()函数或get_free_pages()函数,以便在系统内存的DMA区中分配物理内存。      5.3 编程DMAC      设备驱动程序可以在他的read()方法、write()方法或ISR中对DMAC进行编程,以便准备启动一个DMA传输事务。一个DMA传输事务有两种典型的过程:(1)用户请求设备进行DMA传输;(2)硬件异步地将外部数据写道系统中。      用户通过I/O请求触发设备进行DMA传输的步骤如下:      1.用户进程通过系统调用read()/write()来调用设备驱动程序的read()方法或write()方法,然后由设备驱动程序read/write方法负责申请DMA缓冲区,对DMAC进行编程,以准备启动一个DMA传输事务,最后正确地设置设备(setup device),并将用户进程投入睡眠。      2.DMAC负责在DMA缓冲区和I/O外设之间进行数据传输,并在结束后触发一个中断。      3.设备的ISR检查DMA传输事务是否成功地结束,并将数据从DMA缓冲区中拷贝到驱动程序的其他内核缓冲区中(对于I/O device to memory的情况)。然后唤醒睡眠的用户进程。      硬件异步地将外部数据写到系统中的步骤如下:      1.外设触发一个中断通知系统有新数据到达。      2.ISR申请一个DMA缓冲区,并对DMAC进行编程,以准备启动一个DMA传输事务,最后正确地设置好外设。      3.硬件将外部数据写到DMA缓冲区中,DMA传输事务结束后,触发一个中断。      4. ISR检查DMA传输事务是否成功地结束,然后将DMA缓冲区中的数据拷贝驱动程序的其他内核缓冲区中,最后唤醒相关的等待进程。      网卡就是上述过程的一个典型例子。      为准备一个DMA传输事务而对DMAC进行编程的典型代码段如下:        unsigned long flags;    flags = claim_dma_lock();    disable_dma(dmanr);    clear_dma_ff(dmanr);    set_dma_mode(dmanr,mode);    set_dma_addr(dmanr, virt_to_bus(buf));    set_dma_count(dmanr, count);    enable_dma(dmanr);    release_dma_lock(flags);           检查一个DMA传输事务是否成功地结束的代码段如下:       int residue;   unsigned long flags = claim_dma_lock();   residue = get_dma_residue(dmanr);   release_dma_lock(flags);   ASSERT(residue == 0);    注:本节大部分内容来自于ldd2。


[1] [2] 下一页 

 

 

(出处:http://www.sheup.com)


上一页 [1] [2]