arm linux dma_map_single原理

linux 3.0  arm11

因为需要验证spi dma(IP内部)驱动的原因,使用了linux 3.0的spi子系统,并且实操了dma驱动,详情如下:

spi_master->transfer 的实现中foreach spi_message中每个spi_transfer,判断transfer 的长度超过spi fifo buf的最大支持长度32byte的话就选用dma模式传送数据

1、dma 映射的空间要求物理地址连续,va=kmalloc  GFP_KERNEL申请一段空间

2、dma_addr = dma_map_single(va)   dma_map_error(dma_addr),处理map错误,dma_addr是传给spi hw的dma地址

spi_sync api 的call tree如下(有些inline函数的原因codevise 追踪不到,或者尝试关掉gcc编译选项-finline)

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

	BUG_ON(!valid_dma_direction(dir));

	addr = __dma_map_single(dev, cpu_addr, size, dir);
	debug_dma_map_page(dev, virt_to_page(cpu_addr),
			(unsigned long)cpu_addr & ~PAGE_MASK, size,
			dir, addr, true);
	return addr;
}

static inline dma_addr_t __dma_map_single(struct device *dev, void *cpu_addr,
		size_t size, enum dma_data_direction dir)
{
	__dma_single_cpu_to_dev(cpu_addr, size, dir);
	return virt_to_dma(dev, cpu_addr);
}

void ___dma_single_cpu_to_dev(const void *kaddr, size_t size,
	enum dma_data_direction dir)
{
	unsigned long paddr;

	BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));

	dmac_map_area(kaddr, size, dir);

	paddr = __pa(kaddr);
	if (dir == DMA_FROM_DEVICE) {
		outer_inv_range(paddr, paddr + size);
	} else {
		outer_clean_range(paddr, paddr + size);
	}
	/* FIXME: non-speculating: flush on bidirectional mappings? */
}


void ___dma_single_dev_to_cpu(const void *kaddr, size_t size,
	enum dma_data_direction dir)
{
	BUG_ON(!virt_addr_valid(kaddr) || !virt_addr_valid(kaddr + size - 1));

	/* FIXME: non-speculating: not required */
	/* don't bother invalidating if DMA to device */
	if (dir != DMA_TO_DEVICE) {
		unsigned long paddr = __pa(kaddr);
		outer_inv_range(paddr, paddr + size);
	}

	dmac_unmap_area(kaddr, size, dir);
}

dma_map_single:流式DMA相比一致性dma,这段mem是会被cache的,为了防止cache一致性问题,在调用dma_map_single函数时需要指定DMA的direction,linux会根据direction的值invalid 或者write back cache。 一致性DMA与流式DMA映射分析, 当驱动主要去分配一个DMA缓冲区并且该缓冲区的存在周期与所在的驱动模块 一样长时,就用一致性DMA映射, 这种映射在一开始为DMA操作分配缓冲区时就解决了cache一致性的问题.   如果驱动需要使用从别的模块传进来的地址空间作为DMA缓冲区,那就需要考虑使用流式DMA映射,这种映射对传 入的地址空间要求是,必须位于内核窨的线性映射区中, 驱动的处理主要是确保每次DMA操作前后cache的一致性问题. 

__dma_map_single:该函数中的virt_to_dma 会把虚拟地址转换成bus地址 如果对总线地址的概念有疑问的话可以参考博客 链接如下:https://blog.csdn.net/alada007/article/details/7616434

__dma_single_cpu_to_dev:该函数会根据direction 操作cache

         --> outer_inv_cache:如果dma数据流向是from device即dma把hw 寄存器的值放到dma映射的mem中,说明该段mem已经背着cpu更新了,所以要把这段mem的cache invalid掉

          --> outer_clean_cache:dma控制器从mem中取数据到hw register处理的时候,防止数据只被取到cache中

dma_unmap_single: umap dma mem 调用到___dma_single_dev_to_cpu 也是inv cache

dmac_map_area & dmac_unmap_are :在map和unmap的时候会调用这对函数,追踪到cacheflush.h 中的全局变量cpu_cache.dma_map_area,cpu_cache最终定义在在arm/mm/cache-v6.s

arm/mm/cache-v6.s
ENTRY(v6_cache_fns)
	.long	v6_flush_icache_all
	.long	v6_flush_kern_cache_all
	.long	v6_flush_user_cache_all
	.long	v6_flush_user_cache_range
	.long	v6_coherent_kern_range
	.long	v6_coherent_user_range
	.long	v6_flush_kern_dcache_area
	.long	v6_dma_map_area
	.long	v6_dma_unmap_area
	.long	v6_dma_flush_range
	.size	v6_cache_fns, . - v6_cache_fns

遇到过dma unmap 过后cpu访问这段mem的时候数据不是预期的值有很多0 和0x6b6b6b6b ,可是如果用codeviser stop cpu一段时间再去访问这段mem 拿到的值就没问题,怀疑umap没起作用,在cpu访问之前调用下flush_cache_all,果然问题就不存在了

猜你喜欢

转载自blog.csdn.net/shenhuxi_yu/article/details/81676704