linuxDMA编程

  1. DMA与Cache一致性
  2. Linux下的DMA编程
    1. 申请DMA内存
    2. 虚拟地址、物理地址和总线地址
    3. DMA地址掩码
    4. 一致性DMA缓冲区
    5. 流式DMA映射
    6. DMA Engine标准API

DMA是一种无需CPU参与就可以让外设和系统内存进行双向数据传输的硬件机制。使用DMA可以使得系统CPU从实际的I/O数据传输过程中解脱出来,从而大大提高系统的吞吐率。

DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间CPU可以并发执行其它任务。当DMA传输结束后,DMAC通过中断通知CPU数据传输已结束,然后由CPU执行相应的中断服务程序进行后处理。

DMA与Cache一致性

Cache和DMA本身似乎是两个毫不相关的事物。但当DMA针对的内存目的地址与Cache缓存的对象由重叠关系时,经过DMA操作后,与Cache缓存对应的内存中的数据已经被修改,而CPU本身并不知道,这样就会引发Cache与内存之间数据“不一致性”的错误。

所谓Cache与内存数据之间的不一致性,是指采用Cache的系统中,同样一个数据可能既存在于Cache中,也存在于主存中,Cache与主存中的数据一样则具有一致性,不一样则具有不一致性。

如果Cache与内存出现不一致性错误,驱动将无法正常运行,这将导致一系列难以定位的bug。

另外Cache的不一致性问题还可能发生在Cache使能与关闭的时刻,这就是为什么带MMU功能的ARM处理器,在开启MMU之前,需要先置Cache无效的原因。

Linux下的DMA编程

DMA不属于我们常见的字符设备、块设备和网络设备这样的外设,它只是一种外设与内存的交互数据的方式。

内存中用于与外设交互数据的一块区域叫做DMA缓冲区,在设备不支持SG(scatter/gather)操作的情况下,DMA缓冲区必须时连续的。

申请DMA内存

不同的设备对于DMA操作的内存要求不同,如对于x86系统下的ISA设备而言,其DMA操作只能在16MB以下的内存中进行。

因此若直接使用kmalloc、__get_free_pages等接口时必须使用GFP_DMA标志。

1
kmalloc(size, GFP_DMA)

与此同时,如果不想使用\log_{2} size$(即order)为参数申请DMA内存,还可以使用dma_mem_alloc()

1
2
3
4
static unsigned long dma_mem_alloc(unsigned long size)
{
return __get_dma_pages(GFP_KERNEL|__GFP_NORETRY, get_order(size));
}

虚拟地址、物理地址和总线地址

基于DMA的硬件使用的是总线地址而不是物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU MMU控制器外围角度看到的内存地址,虚拟地址是从CPU核角度看到的内存地址。虽然在PC上,对于ISA和PCI而言,总线地址即为物理地址,但并不是所有平台都一样。因为有时候接口总线通过桥接电路连接,桥接电路会将I/O地址映射为不同的物理地址。如在PReP系统中,物理地址0在设备端看来是0x80000000,而0通常又被映射为虚拟地址0xC0000000,所有同一个地址就具有了三重身份:物理地址0、总线地址0x80000000和虚拟地址0xC0000000。

基于此,我们看到了三种地址的定义,因此需要掌握它们的区别,在没有IOMMU的情况下,内核提供了虚拟地址/总线地址的简单转换函数:

1
2
unsigned long virt_to_bug(volatile void *address);
void *bus_to_virt(unsigned long address);

但在由IOMMU的情况下,就不需要如上函数了,因为IOMMU可以使得外设DMA引擎看到“虚拟地址”。

DMA地址掩码

设备并不一定能在所有的内存地址上执行DMA操作,因此必要时应该使用如下函数执行DMA地址掩码:

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

一致性DMA缓冲区

DMA映射过程如下:

  1. 分配一片DMA缓冲区
  2. 为这片缓冲区产生设备可访问的地址

其中对于有一致性要求的场合,在分配DMA缓冲区时可以使用一致性缓冲API,如(更多参考linux/dma-mapping.h)

1
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp);

流式DMA映射

并不是所有的DMA缓冲区都是驱动申请的,如果是驱动申请的,用一致性DMA缓冲区自然最方便。但是,许多情况下,缓冲区来自内核的较上层,上层可能用的是普通的kmalloc或__get_free_pages等方法申请的,这时候就要使用流式DMA映射了。

流式DM缓冲区使用的步骤如下:

  1. 进行流式DMA映射
  2. 执行DMA操作
  3. 进行流式DMA去映射

流式DMA映射操作本质大多是Cache的使无效或清除操作,以解决Cache一致性问题,相对于一致性DMA映射而言,流式DMA映射的接口较为复杂(为dma_map_*系列函数,参考linux/dma-mapping.h),如下所示:

1
2
3
4
5
6
7
8
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)
#define dma_unmap_single(d, a, s, r) dma_unmap_single_attrs(d, a, s, r, 0)
#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
#define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)
#define dma_map_page(d, p, o, s, r) dma_map_page_attrs(d, p, o, s, r, 0)
#define dma_unmap_page(d, a, s, r) dma_unmap_page_attrs(d, a, s, r, 0)
#define dma_get_sgtable(d, t, v, h, s) dma_get_sgtable_attrs(d, t, v, h, s, 0)
#define dma_mmap_coherent(d, v, c, h, s) dma_mmap_attrs(d, v, c, h, s, 0)

DMA Engine标准API

linux内核目前推荐使用dmaengine的驱动架构来编写DMA控制器的驱动,同时外设的驱动使用标准的dmaengine API进行DMA的准备、发起和完成时的回调工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct dma_async_tx_descriptor *dmaengine_prep_slave_single(
struct dma_chan *chan, dma_addr_t buf, size_t len,
enum dma_transfer_direction dir, unsigned long flags);
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len,
enum dma_transfer_direction dir, unsigned long flags);
struct dma_async_tx_descriptor *dmaengine_prep_rio_sg(
struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len,
enum dma_transfer_direction dir, unsigned long flags,
struct rio_dma_ext *rio_ext);
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction dir,
unsigned long flags);
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);


int dmaengine_terminate_all(struct dma_chan *chan);
int dmaengine_terminate_async(struct dma_chan *chan);

void dmaengine_synchronize(struct dma_chan *chan);
int dmaengine_terminate_sync(struct dma_chan *chan);

int dmaengine_pause(struct dma_chan *chan);
int dmaengine_resume(struct dma_chan *chan);

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);

void dma_async_issue_pending(struct dma_chan *chan);

enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,
dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used);

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:linuxDMA编程

本文作者:红尘追风

发布时间:2016-08-28, 01:32:43

原始链接:http://www.micernel.com/2016/08/28/linuxDMA%E7%BC%96%E7%A8%8B/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录