一、了解几个地址概念

1、虚拟地址:内核通常使用的地址是虚拟地址。我们调用kmalloc()、vmalloc()或者类似的接口返回的地址都是虚拟地址,保存在 void * 的变量中。
2、物理地址:对于嵌入式开发,物理地址是必须明白的,物理地址是指处理器芯片发出,来进行地址空间寻址的地址,它与处理器地址引脚上发出的电信号相对应。物理地址一般与CPU有关系,是给CPU指令使用的。
3、总线地址:总线地址主要是给设备使用的,是设备中的一些内存资源。如果要CPU的物理地址连接上设备上的总线地址就需要使用mmapi映射,这样就能将物理地址与总线地址链接在一起。

二、为什么有两种类型的DMA映射

1、为什么需要映射?

DMA操作是需要物理地址的,但是linux内核中使用的都是虚拟地址。如果想要用DMA对一段内存进行操作,可以用dma映射的api得到一段内存的物理地址和虚拟地址的映射。

2、为什么需要两种映射?

要从cache和内存中内容不一致说起。。。
cpu写内存有两种方式:
(1) write through:同步写到cache和内存。
(2) write back:CPU只写到cache中。cache的硬件使用LRU算法将cache里面的内容替换到内存。
DMA想要完成的是从内存到外设直接进行数据搬移。并且DMA不能访问CPU的cache。而CPU与内存、cache的关系如下:CPU在读内存的时候,如果cache命中则只是在cache去读,而不是从内存读;写内存的时候,也可能实际上没有写到内存,而只是直接写到了cache。
这样一来,如果DMA将数据从外设写到内存,CPU中cache中的数据(如果有的话)就是旧数据了,这时CPU在读内存的时候命中cache了,就是读到了旧数据;CPU写数据到内存时,如果只是先写到了cache,则内存里的数据就是旧数据了。这两种情况(两个方向)都存在cache一致性问题。例如,网卡发包的时候,CPU将数据写到cache,而网卡的DMA从内存里去读数据,就发送了错误的数据。
这就需要下面所说的两种DMA mapping的api了。

三、两种DMA mapping的api解释

1、一致性DMA映射

dma_alloc_coherent和dma_free_coherent

static inline void *dma_alloc_coherent(struct device *dev, 
									   size_t size, 
									   dma_addr_t *dma_handle, 
									   gfp_t flag)

dma_alloc_coherent(连贯的)属于一致性映射:内核专门申请好一段内存给DMA用,通常在驱动初始化阶段分配内存,并且标记这段内存是不带cache的。
A = dma_alloc_coherent(B,C,D,GFP_KERNEL);
A:内核的虚拟起始地址;
B:struct device指针;
C:实际分配大小;
D:返回的内存总线地址;
A和D是一一对应的,A是虚拟地址,D是总线地址,对任何一个操作都会引起这段缓冲区内容的改变。
该函数实际获得两个地址:函数的返回值void*,代表缓冲区的内核虚拟地址,是从CPU角度看到的地址,CPU需要通过返回的虚拟地址来访问这段内存,才是非cache的;从设备角度看到的总线地址,保存在dma_handle(即参数D中),驱动可以将这个总线地址传递到HW中。

2、流式DMA映射

dma_map_single和dma_unmap_single

static inline dma_addr_t dma_map_single(struct device *dev, 
										void *cpu_addr,
										size_t size, 
										enum dma_data_direction dir)

有时需要直接在上层传下来的内存中做事,例如从协议栈里发下来的一个包,想通过网卡发送出去。这时就需要用到dma_map_single,由于协议栈下来的包的数据有可能还在cache里面,调用dma_map_single()后,CPU就会做一次cache的flush,将cache的数据刷到内存,这样DMA去读内存就读到新的数据了。
注意:调用这个api,必须告诉内核数据流式的方向,来指明数据的方向是从外设到内存还是从内存到外设:
(1) DMA_TO_DEVICE:数据发送到设备,从内存到外设;CPU会做cache的flush操作,将cache中新的数据刷到内存。
(2) DMA_FROM_DEVICE:数据被发送到CPU,从外设到内存;CPU将cache置无效,这样CPU读的时候不命中,就会从内存去读新的数据。

四、DMA寻址能力

dma_set_mask_and_coherent(struct device *dev, u64 mask)

设备有DMA寻址的限制,驱动需要将这个限制通知内核。如果驱动不通知内核,在缺省的情况下,在32bit平台下,内核认为外设的DMA可以访问所有系统总线的32bit。64bit平台下也是如此。
是否有寻址限制是和硬件设计相关。
这个限制(能寻址的物理地址的范围,即设备支持的地址线信息)是通过设置DMA mask这个参数,而这个参数通过调用 int dma_set_mask_and_coherent这个接口,如果成功,就返回0。
使用方法:dma_set_mask_and_coherent(dev,DMA_BIT_MASK(36));

五、参考文档

(1) 地址概念参考:https://blog.csdn/u014379540/article/details/52502470
(2) DMA和一致性缓存参考:https://blog.csdn/jasonchen_gbd/article/details/79462064

更多推荐

Linux 内存管理 --- DMA