• 了解DMA Buffer mapping framework

1.Address

  The kernel normally uses virtual addresses. Any address returned by kmalloc(), vmalloc(), and similar interfaces is a virtual address and can be stored in a void *.

  The virtual memory system (TLB, page tables, etc.) translates virtual
addresses to CPU physical addresses, which are stored as “phys_addr_t” or
“resource_size_t”. The kernel manages device resources like registers as
physical addresses. These are the addresses in /proc/iomem. The physical
address is not directly useful to a driver; it must use ioremap() to map
the space and produce a virtual address.

  I/O devices use a third kind of address: a “bus address”. If a device has registers at an MMIO address, or if it performs DMA to read or write system memory, the addresses used by the device are bus addresses. In some
systems, bus addresses are identical to CPU physical addresses, but in
general they are not. IOMMUs and host bridges can produce arbitrary
mappings between physical and bus addresses.

  From a device’s point of view, DMA uses the bus address space, but it may be restricted to a subset of that space. For example, even if a system supports 64-bit addresses for main memory and PCI BARs, it may use an IOMMU so devices only need to use 32-bit DMA addresses.

Linux DMA Engine framework(4)-DMA Buffer mapping framework
  If device supports DMA , the driver sets up buffer using kmalloc or similar interface which returns virtual address (X). The virtual memory system maps X to a physical address (Y) in system RAM. The driver can use virtual address X to access the buffer, but the device itself cannot because DMA doesn’t go through the CPU virtual memory system. In some system only Device can directly do DMA to physical address. In some system IOMMU hardware is used to translate DMA address to physical address.Look at the figure above It translate Z to Y.

  • 中间的一列代表物理地址空间, 其中MMIO Space是其子集, 典型的如ARM处理器中的“外设寄存器”. RAM则是另一子集, 如板载的DDRAM.

  • 左侧一列是虚拟地址空间, Linux内核代码使用的就是虚拟地址. 虚拟地址到物理地址之间必须用MMU进行映射: 例如ioremap可以将MMIO空间的地址B映射到CPU空间的地址C, 这样CPU代码访问地址C即可; kmalloc()、vmalloc()等类似接口可以将地址Y映射到地址X, CPU代码访问地址X即是访问DDRAM.

  • 右侧一列是总线地址空间, “半智能外设”通过总线地址访问挂载其上的Device, CPU无法直接访问总线地址A, 它能访问“半智能外设”的MMIO地址, 然后由这些外设代替它访问地址A.

  • CPU需要问DDRAM, “半智能外设”同样需要访问DDRAM。

  这些“半智能外设”有的可以直接访问DDRAM地址, 例如寄存器地址位于物理地址空间的DMA控制器。

  有的则不能直接访问DDRAM, 例如总线上挂载的”Device”, 它内部的DMA控制器就不能直接访问DDRAM. CPU访问RAM时需要通过MMU映射, ”Device”在访问DDRAM时同样可由IOMMU进行映射(例如把地址Y映射到地址Z). MMU可以把非连续的物理地址映射到连续的虚拟地址, IOMMU同样可以把非连续的物理地址映射到连续的总线地址.

  需要注意的是, DMA控制器只是众多”半智能外设”其中的一个, 因此它所使用的DMA地址只能算总线地址的一个子集.

2.什么样的系统内存可以被DMA控制器访问到?

哪些内存可作为DMA地址?

  • 通过伙伴系统的接口(例如__get_free_page*())或者类似kmalloc() or kmem_cache_alloc()这样的通用内存分配的接口分配的物理地址连续的内存.

  • 使用vmalloc() 分配的DMA buffer可以直接使用吗?最好不要这样,虽然强行使用可能没有问题,但是终究是比较麻烦。

    • 首先,vmalloc分配的page frame是不连续的,如果底层硬件需要物理内存连续,那么vmalloc分配的内存不能满足硬件要求。

    • 即便是底层DMA硬件支持scatter-gather,vmalloc分配出来的内存仍然存在其他问题。我们设置给DMA控制器的必须是硬件地址, vmalloc分配的虚拟地址和对应的物理地址没有线性关系(kmalloc或者__get_free_page*这样的接口,其返回的虚拟地址和物理地址有一个固定偏移的关系),若要获取其物理地址,我们需要遍历页表才可以找到。

  • 在驱动中定义的全局变量可以用于DMA吗?如果编译到内核,那么全局变量位于内核的数据段或者bss段。在内核初始化的时候,会建立kernel image mapping,因此全局变量所占据的内存都是连续的,并且VA和PA是有固定偏移的线性关系,因此可以用于DMA操作。不过,在定义这些全局变量的DMA buffer的时候,我们要小心的进行cacheline的对齐,并且要处理CPU和DMA controller之间的操作同步,以避免cache coherence问题。

  • 如果驱动编译成模块会怎么样呢?这时候,驱动中的全局定义的DMA buffer不在内核的线性映射区域,其虚拟地址是在模块加载的时候,通过vmalloc分配,因此这时候如果DMA buffer如果大于一个page frame,那么实际上我们也是无法保证其底层物理地址的连续性,也无法保证VA和PA的线性关系,这一点和编译到内核是不同的。

  • 通过kmap接口返回的内存可以做DMA buffer吗?也不行,其原理类似vmalloc,。

  • 块设备使用的I/O buffer和网络设备收发数据的buffer是如何确保其内存是可以进行DMA操作的呢?块设备I/O子系统和网络子系统在分配buffer的时候会确保这一点的。

  驱动想要使用DMA mapping framework的API,需要首先包含相关头文件:

#include <linux/dma-mapping.h>

refer to

  • Documentation/DMA-API-HOWTO.txt
  • http://www.mysixue.com/?p=123

相关文章: