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)。

为了分配一块满足所有这些要求的内存,你需要向 kmalloc 传递标志 GFP_DMA。

不幸的是,可用于 ISA DMA 的内存稀缺,因此除非你在启动时分配内存,否则最好同时传递 __GFP_RETRY_MAYFAIL 和 __GFP_NOWARN,以使分配器更努力地尝试。

(这种稀缺性也意味着你应该尽早分配缓冲区,并且在驱动程序卸载之前不要释放它。)

地址转换

要将虚拟地址转换为总线地址,请使用正常的 DMA API。不要使用 isa_virt_to_bus(),即使它做同样的事情。原因是 isa_virt_to_bus() 函数将需要 Kconfig 依赖于 ISA,而你真正需要的只是 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 控制器处于某个特定状态,那么你必须在恢复时恢复这些寄存器。