这里提到的DMA设备是非PCI设备 在framebuffer的prob函数中,用到了这样一个函数,下面分析下它的作用
/* * s3c_fb_map_video_memory(): * 分配DRAM的缓存区给frame buffer。 * 这个缓存区是一个non-cached,non-buffered的。 * 这片内存区域允许调色板和像素在写入时不刷新cache缓存。 * 一旦这片区域重新映射,那么所有用来访问video memory的虚拟内存将会 * 对应另外一片新的区域,即另外一片物理地址 */ int __init s3c_fb_map_video_memory(struct s3c_fb_info *fbi) { ...... fbi->map_size_f1 = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE); fbi->map_cpu_f1 = dma_alloc_writecombine(fbi->dev, fbi->map_size_f1, &fbi->map_dma_f1, GFP_KERNEL); ...... }
首先说说6410的DMA虚拟地址和物理地址的映射。2.6.29中,比2.6.24有一些出入 2.6.29内核中,在arch/arm/mm/dma-mapping.c 中实现了DMA映射的函数。其中
#define CONSISTENT_END (0xffe00000) #define CONSISTENT_BASE (CONSISTENT_END - CONSISTENT_DMA_SIZE)
CONSISTENT_ENT 是DMA虚拟地址的结束地址 CONSISTENT_ENT 是DMA虚拟地址的起始地址 而CONSISTENT_DMA_SIZE定义在/arch/arm/include/asm/memory.h
/* * Size of DMA-consistent memory region. Must be multiple of 2M, * between 2MB and 14MB inclusive. */ #ifndef CONSISTENT_DMA_SIZE #define CONSISTENT_DMA_SIZE (SZ_8M + SZ_4M) //+ chachi - SZ_2M #endif
DMA的大小必须是2M的整数倍。且在2M和14M之间 原来定义的大小是2M,网上看到有些blog说不够用,会报错。考虑到将来如果移植摄像头和音频的话,将会占用不少DMA缓存,因此这里改到12M 由于在编写驱动的时候,出了很多错误,所以走了不少弯路,也学到不少东西。因此在研究
dma_writecombine函数的时,有必要先了解一下cache的一致性问题。 参考书:《Linux设备驱动开发详解》、《Linux设备驱动程序》第三版
cache的一致性
先理解cache的作用 CPU在访问内存时,首先判断所要访问的内容是否在Cache中,如果在,就称为“命中(hit)”,此时CPU直接从Cache中调用该内容;否则,就称为“ 不命中”,CPU只好去内存中调用所需的子程序或指令了。CPU不但可以直接从Cache中读出内容,也可以直接往其中写入内容。由于Cache的存取速 率相当快,使得CPU的利用率大大提高,进而使整个系统的性能得以提升。 Cache的一致性就是直Cache中的数据,与对应的内存中的数据是一致的。 DMA是直接操作总线地址的,这里先当作物理地址来看待吧(系统总线地址和物理地址只是观察内存的角度不同)。如果cache缓存的内存区域不包括DMA分配到的区域,那么就没有一致性的问题。但是如果cache缓存包括了DMA目的地址的话,会出现什么什么问题呢? 问题出在,经过DMA操作,cache缓存对应的内存数据已经被修改了,而CPU本身不知道(DMA传输是不通过CPU的),它仍然认为cache中的数据就是内存中的数据,以后访问Cache映射的内存时,它仍然使用旧的Cache数据。这样就发生Cache与内存的数据“不一致性”错误。 题外话:好像2.6.29内核中,6410的总线地址和物理地址是一样的,因为我在查看vir_to_bus函数的时候,发现在/arch/arm/linux/asm/memory.h中这样定义:
#ifndef __virt_to_bus #define __virt_to_bus __virt_to_phys #define __bus_to_virt __phys_to_virt #endif
而且用source Insight搜索了一遍,没有发现6410相关的代码中,重新定义__vit_to_bus,因此擅自认为2.6内核中,6410的总线地址就是物理地址。希望高手指点。 顺便提一下,总线地址是从设备角度上看到的内存,物理地址是CPU的角度看到的未经过转换的内存(经过转换的是虚拟地址) 由上面可以看出,DMA如果使用cache,那么一定要考虑cache的一致性。解决DMA导致的一致性的方法最简单的就是禁止DMA目标地址范围内的cache功能。但是这样就会牺牲性能。 因此在DMA是否使用cache的问题上,可以根据DMA缓冲区期望保留的的时间长短来决策。DAM的映射就分为:一致性DMA映射和流式DMA映射。 一致性DMA映射申请的缓存区能够使用cache,并且保持cache一致性。一致性映射具有很长的生命周期,在这段时间内占用的映射寄存器,即使不使用也不会释放。生命周期为该驱动的生命周期 流式DMA映射实现比较复杂,因为没具体了解,就不说明了。只知道种方式的生命周期比较短,而且禁用cache。一些硬件对流式映射有优化。建立流式DMA映射,需要告诉内核数据的流动方向。 因为LCD随时都在使用,因此在Frame buffer驱动中,使用一致性DMA映射 上面的代码中用到
dma_alloc_writecombine函数,另外还有一个一致性DMA映射函数dma_alloc_coherent 两者的区别在于: 查看两者的源代码
/* * Allocate DMA-coherent memory space and return both the kernel remapped * virtual and bus address for that space. */ void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp) { void *memory; if (dma_alloc_from_coherent(dev, size, handle, &memory)) return memory; if (arch_is_coherent()) { void *virt; virt = kmalloc(size, gfp); if (!virt) return NULL; *handle = virt_to_dma(dev, virt); return virt; } return __dma_alloc(dev, size, handle, gfp, pgprot_noncached(pgprot_kernel)); } /* * Allocate a writecombining region, in much the same way as * dma_alloc_coherent above. */ void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp) { return __dma_alloc(dev, size, handle, gfp, pgprot_writecombine(pgprot_kernel)); }
#define pgprot_noncached(prot) __pgprot(pgprot_val(prot) & ~(L_PTE_CACHEABLE | L_PTE_BUFFERABLE)) #define pgprot_writecombine(prot) __pgprot(pgprot_val(prot) & ~L_PTE_CACHEABLE)
再结合网上的资料(不过我感觉那文章写的有些问题,我修改了一下),由上面代码可以看出,两个函数都调用了__dma_alloc函数,区别只在于最后一个参数。
dma_alloc_coherent 在 arm 平台上会禁止页表项中的 C (Cacheable) 域以及 B (Bufferable)域。而 dma_alloc_writecombine 只禁止 C (Cacheable) 域.
C 代表是否使用高速缓冲存储器, 而 B 代表是否使用写缓冲区。
这样,dma_alloc_writecombine 分配出来的内存不使用缓存,但是会使用写缓冲区。而 dma_alloc_coherent 则二者都不使用。
C B 位的具体含义 0 0 无cache,无写缓冲;任何对memory的读写都反映到总线上。对 memory 的操作过程中CPU需要等待。 0 1 无cache,有写缓冲;读操作直接反映到总线上;写操作,CPU将数据写入到写缓冲后继续运行,由写缓冲进行写回操作。 1 0 有cache,写通模式;读操作首先考虑cache hit;写操作时直接将数据写入写缓冲,如果同时出现cache hit,那么也更新cache。 1 1 有cache,写回模式;读操作首先考虑cache hit;写操作也首先考虑cache hit。 这样,两者的区别就很清楚了。
A
= dma_alloc_writecombine(struct device *
dev
, size_t
size
,dma_addr_t *
handle
, gfp_t
gfp
);
含义: A : 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存 dev : 可以平台初始化里指定,主要是用到dma_mask之类参数,可参考framebuffer size : 实际分配大小,传入dma_map_size即可 handle: 返回的内存物理地址,dma就可以用。 A和hanle是一一对应的,A是虚拟地址,而handle是总线地址。对任意一个操作都将改变写缓冲区内容。
| 转载处链接: http://www.360doc.com/content/11/1014/12/1317564_156096743.shtml |