DMA与ISA和LPC设备
作者
Pierre Ossman drzeus@drzeus.cx
本文档描述了如何使用旧的ISA DMA控制器进行DMA传输。尽管ISA在今天已经基本淘汰,但LPC总线使用相同的DMA系统,因此它将在相当长的时间内存在。
头文件和依赖项
要进行ISA风格的DMA,您需要包括两个头文件:
#include <linux/dma-mapping.h> #include <asm/dma.h>
第一个是用于将虚拟地址转换为总线地址的通用DMA API(有关详细信息,请参见使用通用设备进行动态DMA映射)。
第二个包含特定于ISA DMA传输的例程。由于这在所有平台上都不一定存在,请确保您构建的Kconfig依赖于ISA_DMA_API(而不是ISA),以便没有人尝试在不受支持的平台上构建您的驱动程序。
缓冲区分配
ISA DMA控制器对其可以访问的内存有一些非常严格的要求,因此在分配缓冲区时必须格外小心。
(通常,您需要为DMA传输分配一个特殊的缓冲区,而不是直接传输到您的常规数据结构中。)
可进行DMA的地址空间是“物理”内存的最低16 MB。此外,传输块不得跨页边界(这取决于您使用的通道,页边界为64或128 KiB)。
为了分配满足所有这些要求的内存块,您需要将标志GFP_DMA传递给kmalloc。
不幸的是,用于ISA DMA的内存是稀缺的,因此除非您在启动期间分配内存,否则最好也传递__GFP_RETRY_MAYFAIL和__GFP_NOWARN,以使分配器尝试更加努力。
(这种稀缺性也意味着您应尽可能早地分配缓冲区,并在驱动程序卸载之前不要释放它。)
地址转换
要将虚拟地址转换为总线地址,请使用通用DMA API。即使它执行相同的操作,请不要使用isa_virt_to_bus()。原因是函数isa_virt_to_bus()将需要对ISA的Kconfig依赖性,而不仅仅是ISA_DMA_API,这才是您真正需要的。请记住,即使DMA控制器起源于ISA,它也在其他地方使用。
注意:x86_64在涉及ISA时存在破损的DMA API,但已经修复。如果您的架构存在问题,请修复DMA API,而不是恢复到ISA函数。
通道
普通的ISA DMA控制器有8个通道。低四个用于8位传输,而高四个用于16位传输。
(实际上,DMA控制器实际上是两个独立的控制器,其中通道4用于为第二个控制器(0-3)提供DMA访问。这意味着在四个16位通道中,只有三个是可用的。)
您可以以与所有基本资源相似的方式分配这些通道:
extern int request_dma(unsigned int dmanr, const char * device_id); extern void free_dma(unsigned int dmanr);
使用16位或8位传输的能力不取决于驱动程序作者,而取决于硬件支持。请检查您的规格或测试不同的通道。
传输数据
现在是有趣的部分,实际的DMA传输。 😃
在使用任何ISA DMA例程之前,您需要使用claim_dma_lock()来获取DMA锁。原因是一些DMA操作不是原子的,因此一次只能有一个驱动程序可以操作寄存器。
第一次使用DMA控制器时,您应该调用clear_dma_ff()。这会清除DMA控制器中用于非原子操作的内部寄存器。只要您(和其他人)使用锁定函数,您只需要重置一次。
接下来,使用set_dma_mode()告诉控制器您打算进行传输的方向。目前您可以选择DMA_MODE_READ和DMA_MODE_WRITE。
设置传输应该从哪个地址开始(对于16位传输,这需要是16位对齐的),以及要传输多少字节。请注意,这是“字节”。DMA例程将执行所有必需的转换,以使DMA控制器理解这些值。
最后一步是启用DMA通道并释放DMA锁。
一旦DMA传输完成(或超时),您应该再次禁用通道。您还应该检查get_dma_residue()以确保所有数据已传输。
示例:
int flags, residue; flags = claim_dma_lock(); clear_dma_ff(); set_dma_mode(channel, DMA_MODE_WRITE); set_dma_addr(channel, phys_addr); set_dma_count(channel, num_bytes); dma_enable(channel); release_dma_lock(flags); while (!device_done()); flags = claim_dma_lock(); dma_disable(channel); residue = dma_get_residue(channel); if (residue != 0) printk(KERN_ERR "driver: Incomplete DMA transfer!" " %d bytes left!\n", residue); release_dma_lock(flags);
挂起/恢复
确保在进行DMA传输时不会挂起计算机是驱动程序的责任。此外,当系统挂起时,所有DMA设置都会丢失,因此如果您的驱动程序依赖于DMA控制器处于某种状态,则必须在恢复时恢复这些寄存器。