11.2.6驱动DMA工作

    所有的PC都包含一个称为直接内存访问控制器或DMAC的辅助处理器,它可以用来控制在RAMI/O设备之间数据的传送。DMAC一旦被CPU激活,就可以自行传送数据;当数据传送完成之后,DMAC发出一个中断请求。当CPUDMAC同时访问同一内存单元时,所产生的冲突由一个称为内存仲裁器的硬件电路来解决。

使用DMAC最多的是磁盘驱动器和其他需要一次传送大量字节的慢速设备。因为DMAC的设置时间相当长,所以在传送数量很少的数据时直接使用CPU效率更高。

原来的ISA总线所使用的第一个DMAC非常复杂,难于对其进行编程。PCISCSI总线所使用的最新DMAC依靠总线中的专用硬件电路,这就使设备驱动程序开发人员的开发工作变得简单。

到现在为止,我们已区分了三类内存地址:逻辑地址、线性地址以及物理地址,前两个在CPU内部使用,最后一个是CPU从物理上驱动数据总线所用的内存地址。但是,还有第四种内存地址,称为总线地址:它是除CPU之外的硬件设备驱动数据总线所用的内存地址。在PC体系结构中,总线地址和物理地址是一致的;但是在其他体系结构中,例如SunSPARCCompaqAlpha体系结构中,这两种地址是不同的。

    从根本上说,内核为什么应该关心总线地址呢?这是因为在DMA操作中数据传送不用CPU的参与:I/O设备和DMAC直接驱动数据总线。因此,在内核开始DMA操作时,必须把所涉及的内存缓冲区总线地址或写入DMAC适当的I/O端口、或写入I/O设备适当的I/O端口。

很多I/O驱动程序都使用直接内存访问控制器(DMAC)来加快操作的速度。DMAC与设备的I/O控制器相互作用共同实现数据传送;后文中我们还会看到,内核中包含一组易用的例程来对DMAC进行编程。当数据传送完成时,I/O控制器通过IRQCPU发出信号。

当设备驱动程序为某个I/O设备建立DMA操作时,必须使用总线地址指定所用的内存缓冲区。内核提供两个宏virt_to_busbus_to_virt,分别把虚拟地址转换成总线地址或把总线地址转换成虚拟地址。

IRQ一样,DMAC也是一种资源,必须把这种资源动态地分配给需要它的设备驱动程序。驱动程序开始和结束DMA操作的方法依赖于总线的类型。

1.ISA总线的DMA

每个ISA DMAC只能控制有限通道。每个通道都包括一组独立的内部寄存器,所以,DMAC就可以同时控制几个数据的传送。

设备驱动程序通常使用下面的方式来申请和释放ISA DMAC。设备驱动程序照样要靠一个引用计数器来检测什么时候任何进程都不再访问设备文件。驱动程序执行以下操作:

(1) 在设备文件的open( )方法中把设备的引用计数器加1。如果原来的值是0,那么,驱动程序执行以下操作:

·      调用request_irq( )来分配ISA DMAC所使用的IRQ中断号

·      调用request_dma( )来分配DMA通道

·      通知硬件设备应该使用DMA并产生中断

·      如果需要,为DMA缓冲区分配一个存储区域

(2) 当必须启动DMA操作时,在设备文件的read()和write()方法执行以下操作:

·      调用set_dma_mode( )把通道设置成读/写模式。

·      调用set_dma_addr( )来设置DMA缓冲区的总线地址。(因为只有最低的24位地址会发给DMAC,所以缓冲区必须在RAM的前16MB中。)

·      调用set_dma_count( )来设置要发送的字节数。

·      调用set_dma_dma( )来启用DMA通道。

·      把当前进程加入该设备的等待队列,并把它挂起。当DMAC完成数据传送操作时,设备的I/O控制器就发出一个中断,相应的中断处理程序会唤醒正在睡眠的进程。

·      进程一旦被唤醒,就立即调用disable_dma( )来禁用这个DMA通道。

·      调用get_dma_residue( )来检查是否所有的数据都已被传送。

(3)     在设备文件的release方法中,减少设备的引用计数器。如果该值变成0,就执行以下操作:

·      禁用DMA和对这个硬件设备上的相应中断

·      调用free_dma( )来释放DMA通道

·      调用free_irq( )来释放DMA所使用的IRQ线

2PCI总线的DMA

PCI总线对于DMA的使用要简单得多,因为DMAC是集成到I/O接口内部的。在open方法中,设备驱动程序照样必须分配一条IRQ线来通知DMA操作的完成。但是,并没有必要分配一个DMA通道,因为每个硬件设备都直接控制PCI总线的电信号。要启动DMA操作,设备驱动程序在硬件设备的某个I/O端口中简单地写入DMA缓冲区的总线地址、传送方向以及数据大小;然后驱动程序就挂起当前进程。在最后一个进程关闭这个文件对象时,release方法负责释放这条IRQ线。