一、流式DMA:
1、一般的使用方法是:
dma_buf = (void *)__get_free_pages(GFP_ATOMIC|GFP_DMA, get_order(s->fragsize));
desc->snd_buffer = dma_buf;
desc->snd_dma = dma_map_single(NULL, desc->snd_buffer, s->fragsize, DMA_FROM_DEVICE);
_desc->saddr = desc->snd_dma;
kmalloc 和 _get_free_pages分配内存空间, 返回的是cache段的虚拟地址(在/mm/page_alloc.c中定义);
在文件include/asm-generic/dma-mapping-common.h中有:
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, NULL);
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr, 10 size_t size, 11 enum dma_data_direction dir, 12 struct dma_attrs *attrs) 13{ 14 struct dma_map_ops *ops = get_dma_ops(dev); 15 dma_addr_t addr; 16 17 kmemcheck_mark_initialized(ptr, size); 18 BUG_ON(!valid_dma_direction(dir)); 19 addr = ops->map_page(dev, virt_to_page(ptr), 20 (unsigned long)ptr & ~PAGE_MASK, size, 21 dir, attrs); 22 debug_dma_map_page(dev, virt_to_page(ptr), 23 (unsigned long)ptr & ~PAGE_MASK, size, 24 dir, addr, true); 25 return addr; 26} 先来看一下get_dma_ops()这个函数,在/arch/mips/include/asm/dma-mapping.h定义:static inline struct dma_map_ops *get_dma_ops(struct device *dev) 15{ 16 if (dev && dev->archdata.dma_ops) 17 return dev->archdata.dma_ops; 18 else 19 return mips_dma_map_ops; 20}因为dev为NULL,所以会调用mips_dma_map_ops---->
在arch/mips/mm/dma-default.c中定义:struct dma_map_ops *mips_dma_map_ops = &mips_default_dma_map_ops;static struct dma_map_ops mips_default_dma_map_ops = { 304 .alloc_coherent = mips_dma_alloc_coherent, 305 .free_coherent = mips_dma_free_coherent, 306 .map_page = mips_dma_map_page, 307 .unmap_page = mips_dma_unmap_page, 308 .map_sg = mips_dma_map_sg, 309 .unmap_sg = mips_dma_unmap_sg, 310 .sync_single_for_cpu = mips_dma_sync_single_for_cpu, 311 .sync_single_for_device = mips_dma_sync_single_for_device, 312 .sync_sg_for_cpu = mips_dma_sync_sg_for_cpu, 313 .sync_sg_for_device = mips_dma_sync_sg_for_device, 314 .mapping_error = mips_dma_mapping_error, 315 .dma_supported = mips_dma_supported 316};再回到ops->map_page,也就是会调用mips_dma_map_page:static dma_addr_t mips_dma_map_page(struct device *dev, struct page *page, 201 unsigned long offset, size_t size, enum dma_data_direction direction, 202 struct dma_attrs *attrs) 203{ 204 unsigned long addr; 205 206 addr = (unsigned long) page_address(page) + offset; 207 208 if (!plat_device_is_coherent(dev)) 209 __dma_sync(addr, size, direction); 210 211 return plat_map_dma_mem(dev, (void *)addr, size); 212}主要来看__dma_sync,定义在arch/mips/mm/dma-default.c:static inline void __dma_sync(unsigned long addr, size_t size, 152 enum dma_data_direction direction) 153{ 154 switch (direction) { 155 case DMA_TO_DEVICE: 156 dma_cache_wback(addr, size); 157 break; 158 159 case DMA_FROM_DEVICE: 160 dma_cache_inv(addr, size); 161 break; 162 163 case DMA_BIDIRECTIONAL: 164 dma_cache_wback_inv(addr, size); 165 break; 166 167 default: 168 BUG(); 169 } 170}这里我们看到分别对三种情况进行处理:
a、DMA_TO_DEVICE:把cache的数据刷回内存里,用于使能dma传输到外设之前。因为dma传输只会从内存拿数据,所以必须把cache的数据全部刷回到内存中;
b、DMA_FROM_DEVICE:把cache的数据置无效,用于dma已经传输完毕产生中断之后,准备从内存读取到驱动的buffer中。如果不把cache的数据置无效,
那么cpu就会直接从cache中取出旧的数据,不会到内存中去拿新的数据;
c、DMA_BIDIRECTIONAL跟DMA_TO_DEVICE的效果一样。
在文件中/arch/mips/include/asm/io.h定义:589#define dma_cache_wback_inv(start, size) _dma_cache_wback_inv(start, size) 590#define dma_cache_wback(start, size) _dma_cache_wback(start, size) 591#define dma_cache_inv(start, size) _dma_cache_inv(start, size)
具体的函数在arch/mips/mm/c-r4k.c中定义如下:void __cpuinit r4k_cache_init(void) { ......再来看plat_map_dma_mem,在/arch/mips/include/asm/mach-generic/dma-coherence.h中定义:_dma_cache_wback_inv = r4k_dma_cache_wback_inv; 1416 _dma_cache_wback = r4k_dma_cache_wback_inv; 1417 _dma_cache_inv = r4k_dma_cache_inv; ...... }static inline dma_addr_t plat_map_dma_mem(struct device *dev, void *addr, 15 size_t size) 16{ 17 return virt_to_phys(addr); 18}arch/mips/include/asm/io.h中定义:static inline unsigned long virt_to_phys(volatile const void *address) { return (unsigned long)address - PAGE_OFFSET + PHYS_OFFSET; } 在/arch/mips/include/asm/mach-generic/spaces.h中定义: #define PAGE_OFFSET (CAC_BASE + PHYS_OFFSET)#define CAC_BASE _AC(0x80000000, UL)#define PHYS_OFFSET _AC(0, UL)所以plat_map_dma_mem返回的是物理地址,用于填写到DMA的saddr中。
二、一致性DMA映射:在文件arch/mips/include/asm/dma-mapping.h中定义:desc = dma_alloc_coherent(NULL, sizeof(audio_dmadesc_t), (dma_addr_t *)&dma_phyaddr, GFP_KERNEL);static inline void *dma_alloc_coherent(struct device *dev, size_t size, 61 dma_addr_t *dma_handle, gfp_t gfp) 62{ 63 void *ret; 64 struct dma_map_ops *ops = get_dma_ops(dev); 65 66 ret = ops->alloc_coherent(dev, size, dma_handle, gfp); 67 68 debug_dma_alloc_coherent(dev, size, *dma_handle, ret); 69 70 return ret; 71} 从上面的分析可以知道ops->alloc_coherent会调用mips_dma_alloc_coherent(); 在文件arch/mips/mm/dma-default.c中定义:但是我们在驱动程序里面一般都会用cache段的地址,这样会快很多,所以通过CAC_ADDR来转换得到cache段的虚拟地址:static void *mips_dma_alloc_coherent(struct device *dev, size_t size, 101 dma_addr_t * dma_handle, gfp_t gfp) 102{ 103 void *ret; 104 105 if (dma_alloc_from_coherent(dev, size, dma_handle, &ret)) 106 return ret; 107 108 gfp = massage_gfp_flags(dev, gfp); 109 110 ret = (void *) __get_free_pages(gfp, get_order(size)); 111 112 if (ret) { 113 memset(ret, 0, size); 114 *dma_handle = plat_map_dma_mem(dev, ret, size); 115 116 if (!plat_device_is_coherent(dev)) { 117 dma_cache_wback_inv((unsigned long) ret, size); 118 ret = UNCAC_ADDR(ret); 119 } 120 } 121 122 return ret; 123} 因为dev为NULL,所以dma_alloc_from_coherent直接返回0。接着通过__get_free_pages分配空间得到一个cache段虚拟地址,再通过plat_map_dma_mem得到一个物理地址, 再把这段地址在cache中的数据刷回到内存,最后返回一个uncache段的虚拟地址。
info->drcmr_dat = CAC_ADDR(info->drcmr_dat);
从上述可知,当用一致性DMA映射时,用得到的cache段的虚拟地址进行读写完,准备开始DMA或者DMA传输完成时,还要手动地调用dma_cache_wback、dma_cache_inv对缓冲区进行操作。
而流式DMA映射就可以一直调用dma_map_single(DMA_TO_DEVICE / DMA_FROM_DEVICE)就可以了,因为这个函数里面就带了对cache的操作。