在嵌入式系统中,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)