Linux动态DMA映射

1. 几种地址类型

虚拟地址

  Linux内核使用的地址是虚拟地址,数据类型为void *。例如,kmalloc()和vmalloc()函数返回值就是虚拟地址。

物理地址

  处理器真实地址总线上的地址,数据类型为phys_addr_t。

  对I/O设备寄存器和内存统一编址的处理器,如ARM/PowerPC,参考手册一般会给出memory map,也就是各种I/O设备的寄存器在物理地址空间的分布。对I/O设备寄存器独立编址的处理器。如X86,访问I/O设备寄存器或内存时,向地址总线发送地址,并通过控制信号来实现对I/O设备寄存器和内存的不同寻址。这些I/O设备寄存器的地址可以在/proc/iomem中查看,必须使用ioremap()映射到虚拟地址空间才可以使用。

总线地址

  从I/O设备的角度看,I/O设备使用的地址是总线地址。DMA使用地址也是总线地址,数据类型为dma_addr_t。对一些简单的系统,设备通过DMA可以直接访问物理地址,但大多数系统都有IOMMU将总线地址转换为物理地址。

2. DMA寻址能力

  默认情况下Linux认为设备DMA可以进行32位寻址。必须对DMA mask进行设置,将设备的DMA寻址能力通知内核。

int dma_set_mask_and_coherent(struct device *dev, u64 mask)

   该函数也可以分为如下两个函数,如果有需要,可以分别对流式映射设置DMA mask,对一致性分配设置DMA mask。

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

3. DMA映射的类型

 3.1 一致性DMA映射

  一致性DMA映射通常在驱动初始化阶段分配buffer,并且保持cache的一致性。分配和释放一致性DMA buffer通常使用下面方法。

dma_addr_t dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);

dma_free_coherent(dev, size, cpu_addr, dma_handle)

  当分配的buffer较小,小于一个页的时候,通常使用dma_pool的APIs。

struct dma_pool *pool;
pool = dma_pool_create(name, dev, size, align, boundary);
cpu_addr = dma_pool_alloc(pool, flags, &dma_handle);

dma_pool_free(pool, cpu_addr, dma_handle);
dma_pool_destroy(pool)

3.2 流式DMA映射

  流式DMA映射必须声明DMA数据流的方向。

  •  DMA_BIDIRECTIONAL
  •  DMA_TO_DEVICE
  •  DMA_FROM_DEVICE
  •  DMA_NONE

  对单个内存区域的映射和取消映射使用如下方法。

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

dma_unmap_single(dev, dma_handle, size, direction);

  该方法直接使用虚拟地址addr的缺点就是不能对HIGHMEM内存进行映射。下面的函数提供对page映射和取消映射的方法。

struct device *dev = &my_dev->dev;
dma_addr_t dma_handle;
struct page *page = buffer->page;
unsigned long offset = buffer->offset;
size_t size = buffer->len;

dma_handle = dma_map_page(dev, page, offset, size, direction);

dma_unmap_page(dev, dma_handle, size, direction);

  对散列表的映射和取消映射如下,nents是sglist中entry的数目。通过散列表,将多个不连续的内存区域进行映射。返回的count的数值可能比nents小,因为有些scatterlist在内存区域连续可能进行了合并。

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);
}

dma_unmap_sg(dev, sglist, nents, direction)

  在流式DMA映射取消映射之前,CPU不应该访问DMA buffer,如果需要访问,则必须在DMA传输后相应地调用如下函数。

dma_sync_single_for_cpu(dev, dma_handle, size, direction);

dma_sync_sg_for_cpu(dev, sglist, nents, direction);

  CPU访问结束后,将buffer还给设备DMA使用时,需要相应调用如下函数。

dma_sync_single_for_device(dev, dma_handle, size, direction);

dma_sync_sg_for_device(dev, sglist, nents, direction);

 

参考

https://elixir.bootlin.com/linux/v5.4/source/Documentation/DMA-API-HOWTO.txt

posted @ 2020-01-16 08:27  yanceylu  阅读(2811)  评论(0编辑  收藏  举报