使用 ISA 和 LPC 设备的 DMA

作者:

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 控制器处于某种状态,则必须在恢复时恢复这些寄存器。