Linux DMA Engine framework(4) - DMA mapping

  • 了解DMA mapping framework

1.CPU and DMA addresses

  DMA mapping is a conversion from virtual addressed memory to a memory which is DMA-able on physical addresses (actually bus addresses).

  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.

在这里插入图片描述
  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.What memory is DMA’able?

  • 通过伙伴系统的接口(例如__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>

3.DMA寻址限制

  设备对于DMA地址空间一般都有一定的限制,比如说我们的设备的寻址能力只有24bit,那么我们一定要将限制通知到内核。

  默认情况下,内核认为设备的寻址空间可以达到32bit。对于有64bit寻址能力的设备来讲,我们需要告知内核调大这个能力。而对于不足32bit寻址能力的设备来讲,需要告诉内核降低这个能力。

  需要特别注意的一点是:PCI-X规范要求PCI-X设备要能够支持64-bit的寻址(DAC)的数据传输。并且某些平台(SGI SN2)也要求当IO总线是PCI-X模式时,必须要支持64bit的consistent分配。

  正确的操作应该是:必须在设备的probe函数中向内核查询机器的DMA控制器能否正常支持当前设备的DMA寻址限制。即使设备支持默认的设置,最好也在probe函数中这么做。

  调用dma_set_mask_and_coherent()完成这种能力通知:

int dma_set_mask_and_coherent(struct device *dev, u64 mask);
  It will cause the kernel to check all the components in the path from the device to memory for addressing restrictions.

  这个函数可以同时通知streaming和coherent DMA的寻址能力。如果有特殊的需求的话,也可以使用下面两个单独的查询函数:

  • 设置streaming DMA的能力:

int dma_set_mask(struct device *dev, u64 mask);

  • 设置consistent DMA的能力:

int dma_set_coherent_mask(struct device *dev, u64 mask);

4.Types of DMA mappings
在这里插入图片描述
两者区别:

  一致性 DMA 映射,采用的是系统预留的一段 DMA 内存用于 DMA 操作,这一段内核在系统启动阶段就已经预留完毕,比如 arm64 平台会在 dts 文件中写明系统预留的 DMA 内存段位于何处,并且会被标志为用于 dma 一致性内存申请,如果有关注 DMA 的一致性映射操作 API 就会发现,一致性 DMA 不会去使用别的地方申请的内存,都是通过dma_alloc_coherent自我申请内存,然后驱动自己填充数据最后被提交给 DMA 控制器。

  流式 DMA 中,可以是随意的内存交给 DMA 进行处理,不需要从系统预留的 DMA 位置进行内存申请,任何普通的 kmalloc 申请的内存都能交给 DMA 控制器进行操作。

3.1.struct dma_map_ops

include/linux/dma-mapping.h:
struct dma_map_ops {
    void* (*alloc)(struct device *dev, size_t size,
                dma_addr_t *dma_handle, gfp_t gfp,
                struct dma_attrs *attrs);
    void (*free)(struct device *dev, size_t size,
             void *vaddr, dma_addr_t dma_handle,
             struct dma_attrs *attrs);
    int (*mmap)(struct device *, struct vm_area_struct *,
             void *, dma_addr_t, size_t, struct dma_attrs *attrs);

    int (*get_sgtable)(struct device *dev, struct sg_table *sgt, void *,
             dma_addr_t, size_t, struct dma_attrs *attrs);

    dma_addr_t (*map_page)(struct device *dev, struct page *page,
             unsigned long offset, size_t size,
             enum dma_data_direction dir,
             struct dma_attrs *attrs);
    void (*unmap_page)(struct device *dev, dma_addr_t dma_handle,
             size_t size, enum dma_data_direction dir,
             struct dma_attrs *attrs);
    int (*map_sg)(struct device *dev, struct scatterlist *sg,
         int nents, enum dma_data_direction dir,
         struct dma_attrs *attrs);
    void (*unmap_sg)(struct device *dev,
             struct scatterlist *sg, int nents,
             enum dma_data_direction dir,
             struct dma_attrs *attrs);
    void (*sync_single_for_cpu)(struct device *dev,
                 dma_addr_t dma_handle, size_t size,
                 enum dma_data_direction dir);
    void (*sync_single_for_device)(struct device *dev,
                 dma_addr_t dma_handle, size_t size,
                 enum dma_data_direction dir);
    void (*sync_sg_for_cpu)(struct device *dev,
                struct scatterlist *sg, int nents,
                enum dma_data_direction dir);
    void (*sync_sg_for_device)(struct device *dev,
                 struct scatterlist *sg, int nents,
                 enum dma_data_direction dir);
    int (*mapping_error)(struct device *dev, dma_addr_t dma_addr);
    int (*dma_supported)(struct device *dev, u64 mask);
    int (*set_dma_mask)(struct device *dev, u64 mask);
#ifdef ARCH_HAS_DMA_GET_REQUIRED_MASK
    u64 (*get_required_mask)(struct device *dev);
#endif
    int is_phys;
}

  按照内核对dma层的架构设计,各平台dma缓冲区映射之间的差异由内核定义的一个dma操作集来统一屏蔽实现的差异。不同差异主要来来自cache的问题。
在这里插入图片描述
  如上所示,在使用dma的api时候,例如dma_alloc_coherent等,这些API中都会调用const struct dma_map_ops *ops = get_dma_ops(dev); 得到ops。代码参考Linux DMA Engine framework(一) - DMA概述:2.2.内核DMA初始化

3.1.Consistent DMA mappings

  Consistent DMA mappings are synchronous and coherent. These mappings are usually mapped by the driver at initialization time and unmapped only when the device is no longer running. Consistent DMA mappings guarantee that the device and the CPU can access the data in parallel and can see each other’s updates without explicit software flushing.

  Consistent DMA mappings always return a mapped DMA address that is 32-bit single address cycle (SAC) addressable, regardless of the DMA capability of the device.

3.1.1.特点

  • 持续使用该DMA buffer(不是一次性的),因此Consistent DMA总是在初始化的时候进行map,在shutdown的时候unmap。

  • CPU和DMA controller在发起对DMA buffer的并行访问的时候不需要考虑cache的影响,也就是说不需要软件进行cache操作,CPU和DMA controller都可以看到对方对DMA buffer的更新。实际上一致性DMA映射中的那个Consistent实际上可以称为coherent,即cache coherent。

3.1.2.Consistent DMA mapping使用场景

  • 网卡驱动和网卡DMA控制器往往是通过一些内存中的描述符(形成环或者链)进行交互,这些保存描述符的memory一般采用Consistent DMA mapping。

  • SCSI硬件适配器上的DMA可以与主存中的一些数据结构(mailbox command)进行交互,这些保存mailbox command的memory一般采用Consistent DMA mapping。

  • 有些外设有能力执行主存上的固件代码(microcode),这些保存microcode的主存一般采用Consistent DMA mapping。

3.1.3.DMA 的内核编程 API

  • 使用大块 DMA 一致性缓冲区
void * dma_alloc_coherent(struct device *dev, size_t size,
                dma_addr_t *dma_handle, gfp_t flag)

  一致性内存:设备对这一块内存进行读写操作,而无需担心处理器一级二级等高速缓存的影响。此函数申请一段大小为 Size 字节的一致性内存,返回两个参数,一个是 dma_handle,它可以用作这段内存的物理地址,另外一个就是指向被分配的内存的指针(处理器的虚拟地址)。

  注意:由于某些平台上一致性内存的代价很高,比如最小的分配长度为一个页,因此你应该尽可能的合并申请一段一致性内存,最简单的方法就是使用 dma_pool 系列函数。

  参数 flag 与一般 kmalloc 的内存申请标志一致。

void
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
                       dma_addr_t dma_handle)

  释放之前申请的一致性内存,dev、size、dma_handle 都是与申请的时候一致的参数,cpu_addr 就是申请函数返回的 cpu 端的虚拟地址。

  注意:和其他的内存分配函数不同,这些函数必须要在中断使能的情况下使用。

  • 使用小块 DMA 一致性缓冲区
    如果需要用到大量的小块的 DMA 缓冲区,最佳的方式就是使用 DMA 内存池的方式申请而不是以页的单位申请内存,这种机制有点类似于 struct kmem_cache,只是它利用了 DMA 的一致性内存分配器,而不是调用 __get_free_pages() 函数。同样的,DMA 内存池还需要知道通用硬件的对齐限制,比如队列头需要 N 字节对齐等。
struct dma_pool *
dma_pool_create(const char *name, struct device *dev,
        size_t size, size_t align, size_t alloc);

  dma_pool_create 函数为设备初始化 DMA 一致性内存的内存池,他必须要在可睡眠上下文调用。name 为内存池的名字(就像 struct kmem_cache .name 一样)。dev 及 size 就如 dma_alloc_coherent 函数的参数一样。align 为设备硬件需要的对齐大小(单位为字节,必须为 2 的幂次方)。如果设备没有边界限制,可以设置参数为 0,如果设置为 4096,则表示从内存池分配的内存不能超过 4k 字节的边界。

void *dma_pool_free(struct dma_pool *pool, void *addr,
        dma_addr_t addr);

  返回内存给内存池,参数 pool 为传递给 dma_pool_alloc 的 pool,参数 vaddr 及 addr 为 dma_pool_alloc 的返回值,也就是从内存池申请到的虚拟地址。

void dma_pool_destroy(struct dma_pool *pool);

  内存池析构函数用于释放整个内存池的资源,这个函数可以在睡眠上下文调用,请确认调用此函数时,所有从该内存池申请的内存都已归还内存池。

3.2.streaming DMA mapping (推荐)

  Streaming DMA mappings are asynchronous and can be buffered (prefetched) by various hardware components along the DMA path.

  Streaming DMA mappings are usually mapped for one DMA transfer and unmapped directly after the transfer. The unmap operation usually guarantees that the DMA data is coherent, but not on SGI Altix systems.

  流式DMA映射是一次性的,一般是需要进行DMA传输的时候才进行mapping,一旦DMA传输完成,就立刻ummap(除非你使用dma_sync_*的接口,下面会描述)。并且硬件可以为顺序化访问进行优化。

  使用streaming DMA mapping的场景:

  • 网卡进行数据传输使用的DMA buffer
  • 文件系统中的各种数据buffer,这些buffer中的数据最终到读写到SCSI设备上去,一般而言,驱动会接受这些buffer,然后进行streaming DMA mapping,之后和SCSI设备上的DMA进行交互。

  无论哪种类型的DMA映射都有对齐的限制,这些限制来自底层的总线,当然也有可能是某些总线上的设备有这样的限制。此外,如果系统中的cache并不是DMA coherent的,而且底层的DMA buffer不和其他数据共享cacheline,这样的系统将工作的更好。

3.2.1.DMA操作方向

  DMA操作方向定义如下:

  • DMA_TO_DEVICE:数据“从内存(dma buffer)到设备”;
  • DMA_FROM_DEVICE:“从设备到内存(dma buffer)”。
  • DMA_BIDIRECTIONAL:强烈要求驱动在知道DMA传输方向的适合,精确的指明是DMA_TO_DEVICE或者DMA_FROM_DEVICE,然而,如果确实不知道具体的操作方向,那么设定为DMA_BIDIRECTIONAL也是可以的,表示DMA操作可以执行任何一个方向的的数据搬移。
  • DMA_NONE:主要是用于调试。

  只有streaming mappings才会指明DMA操作方向,一致性DMA映射隐含的DMA操作方向是DMA_BIDIRECTIONAL。

3.2.2.streaming DMA mapping function

  streaming DMA mapping的接口函数可以在中断上下文中调用。streaming DMA mapping有两个版本的接口函数:

  • 用来map/umap单个的dma buffer;
  • 用来map/umap形成scatterlist的多个dma buffer。

3.2.2.1.map/umap单个的dma buffer

map单个的dma buffer的示例如下:

struct device *dev = &my_dev->dev; 
dma_addr_t dma_handle; 
void *addr = buffer->ptr; 
size_t size = buffer->len;

dma_handle = dma_map_single(dev, addr, size, direction); 
if (dma_mapping_error(dev, dma_handle)) { 
    goto map_error_handling; 
}

umap单个的dma buffer可以使用下面的接口:

dma_unmap_single(dev, dma_handle, size, direction);

3.2.2.2.map/umap多个形成scatterlist的dma buffer

  在scatterlist的情况下,要映射的对象是分散的若干段DMA buffer,示例代码如下:

int i, count = dma_map_sg(dev, sglist, nents, direction); 
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) { 
    hw_address[i] = sg_dma_address(sg); 
    hw_len[i] = sg_dma_len(sg); 
}

umap多个形成scatterlist的dma buffer是通过下面的接口实现的:

dma_unmap_sg(dev, sglist, nents, direction);

4.DTS setting

  • dma-coherent; (of_dma_is_coherent)
  • dma-ranges;

refer to

  • Documentation/DMA-API-HOWTO.txt
  • http://www.mysixue.com/?p=123
  • https://chrtech.com/2017/08/03/17-DMA-summary/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值