在嵌入式系统中,cache位于CPU与DDR之间,是一段SRAM,读写性能远高于DDR,利用cache line提供了预取功能,平衡CPU与DDR之间的性能差异,提高系统的性能。
据我了解,ARM/PPC/MIPS三款主流嵌入式处理器都是软件管理cache,即有专门的指令来进行cache操作,如PPC的iccci icbi,ARM的CP15协处理器也提供对cache的操作。
cache的操作有2种:写回和无效。写回操作是将cache中数据写回到DDR中,无效操作是无效掉cache中原有数据,下次读取cache中数据时,需要从DDR中重新读取。这两种操作其实都是为了保证cache数据一致性。3.4.55内核为例,在arch/arm/mm/cache-v7.S中就封装了cache的操作函数,其中v7_dma_flush_range刷新函数是完成了写回和无效2种操作。
其他平台(ARM MIPS)中cache操作函数也类似。
首先想明白一点,为什么要进行cache操作,只能说cache是天使也是魔鬼。
cache在提高了系统性能同时却导致了数据的不一致性。嵌入式处理器软件管理cache的初衷就是保证数据一致性。
那什么地方需要保证数据一致性呢?
对于由CPU完全操作的数据,数据是完全一致的。也就是该数据完全由CPU写读操作,没有对CPU不透明的操作。这种情况下CPU读写的数据都是来自于cache,我们代码(代码由处理器执行,我们就应该站在处理器视角来看这个问题)完全不用考虑cache一致性的问题。
想来想去,我觉得kernel中有2种情况是需要保证数据一致性的:首先来看一致性映射如何保证cache数据一致性的。直接看下dma_alloc_coherent实现。
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; return __dma_alloc(dev, size, handle, gfp, pgprot_dmacoherent(pgprot_kernel), __builtin_return_address(0)); } 关键点在于pgprot_dmacoherent,实现如下: #ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE | L_PTE_XN) #define __HAVE_PHYS_MEM_ACCESS_PROT struct file; extern pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, unsigned long size, pgprot_t vma_prot); #else #define pgprot_dmacoherent(prot) \ __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED | L_PTE_XN) #endif其实就是修改page property为uncached,这里需要了解,page property是kernel页管理的页面属性,在缺页异常填TLB时,该属性就会写到TLB的存储属性域中。保证了dma_alloc_coherent映射的地址空间是uncached的。
/* * Ensure that the allocated pages are zeroed, and that any data * lurking in the kernel direct-mapped region is invalidated. */ ptr = page_address(page); memset(ptr, 0, size); dmac_flush_range(ptr, ptr + size); outer_flush_range(__pa(ptr), __pa(ptr) + size);
LDD3中说一致性映射缓冲区可同时被CPU与DMA访问,就是通过uncached的TLB映射来保证的。
再来看流式DMA映射,以dma_map_single为例,因代码调用太深,这里就只是列出调用关系,跟cache有关的调用如下:
dma_map_single ==> __dma_map_page ==> __dma_page_cpu_to_dev ==> ___dma_page_cpu_to_dev ___dma_page_cpu_to_dev实现如下: void ___dma_page_cpu_to_dev(struct page *page, unsigned long off, size_t size, enum dma_data_direction dir) { unsigned long paddr; dma_cache_maint_page(page, off, size, dir, dmac_map_area); paddr = page_to_phys(page) + off; 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? */ } dma_cache_maint_page会对映射地址空间调用dmac_map_area,该函数最终会调用到arch/arm/mm/cache-v7.S中v7处理器的cache处理函数v7_dma_map_area,如下: /* * dma_map_area(start, size, dir) * - start - kernel virtual start address * - size - size of region * - dir - DMA direction */ ENTRY(v7_dma_map_area) add r1, r1, r0 teq r2, #DMA_FROM_DEVICE beq v7_dma_inv_range b v7_dma_clean_range ENDPROC(v7_dma_map_area)指定方向为DMA_FROM_DEVICE,则v7_dma_inv_range无效掉该段地址cache。方向为DMA_TO_DEVICE,则v7_dma_clean_range写回该段地址cache。保证了cache数据一致性。
dma_unmap_single ==> __dma_unmap_page ==> __dma_page_dev_to_cpu ==> ___dma_page_dev_to_cpu ==> dmac_unmap_area ==》v7_dmac_unmap_area dmac_unmap_area实现如下: /* * dma_unmap_area(start, size, dir) * - start - kernel virtual start address * - size - size of region * - dir - DMA direction */ ENTRY(v7_dma_unmap_area) add r1, r1, r0 teq r2, #DMA_TO_DEVICE bne v7_dma_inv_range mov pc, lr ENDPROC(v7_dma_unmap_area)