DMA与ISA和LPC设备 【ChatGPT】

发布时间 2023-12-09 16:54:07作者: 摩斯电码

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