translated by JHJ(jianghuijun211@gmail.com)本文通过伪代码指导驱动开发者如何正确使用DMA API。关于API更精确的描述,请参考DMA-API.txt。大多是64位平台有一些特殊硬件可以将总线地址(DMA地址)转换为物理地址。这个和CPU如何利用页表或TLB将虚拟地址转换成物理地址有点像。这种地址转换是有必要的,就像PCI设备可以在单个寻址周期里在64位物理地址空间寻址到任何一个页面。以前linux上的64位平台需要人为设置系统的最大内存大小,这样virt_to_bus()就可以正在工作了(DMA地址转换页表在系统启动时简单初始化,通过__pa(bus_to_virt()可以将DMA地址转换为物理页地址)。为了使linux可以使用DMA动态映射,它需要得到驱动的一些协助,也就是说需要考虑DMA地址只有在使用时才被映射,DMA传输后,需要取消映射。当然下面这些API可以在没有这些硬件限制的情况下正常工作。请注意这些DMA API可以在任何总线上工作,和体系结构无关。你应该是用DMA API而不是特定总线的DMA API(比pci_dma_*)。首先,你需要确定在你的驱动程序中#include <linux/dma-mapping.h>该文件定义了类型dma_addr_t(),它作为一个从DMA 映射函数返回的(总线)地址,到处都会使用到。
什么样的内存是DMA可用的?
第一件你要知道的事情是什么样的内核内存可以用作DMA映射。关于此有一些非书面的准则,本文试图将它们以文字的方式整理出来。如果你通过页分配器(比如__get_free_page*())或者通用内存分配器(比如kmalloc() or kmem_cache_alloc())分配内存,那么你可以使用由这些函数返回的内存地址用作DMA传输。这意味着你不能使用vmalloc()返回的内存地址用作DMA。DMA使用由vmalloc申请的内存是有可能的,但是需要遍历页表来获取物理地址,然后将这些页通过类似__va()这样的函数转换成内核虚拟地址。这条规则意味着你不能将内核镜像地址(在data/text/bss段),或者模块镜像地址,或者栈地址用于DMA。即使这些物理内存可以用于DMA,你也要确保I/O缓冲区是缓存行对齐的。如果不是这样,你将会看到由于DMA不一致性缓存导致的缓存行共享问题(数据丢失)。比如处理器可能写一个字,而DMA在同一个缓存行写另一个字,他们两中的一个将被修改。同样的,这意味着你不能使用由kmap()调用返回的地址,理由与vmalloc()一样。可阻塞I/O或者网络缓冲区又会怎么样呢?可阻塞I/O及网络子系统可以确保它们使用的缓冲区可以用于DMA传输。