Linux Kernel之flush_cache_all在ARM平臺下是如何實現的
在驅動程式的設計中,我們可能會用到flush_cache_all將ARM cache的內容重新整理到RAM,這是因為ARM Linux中cache一般會被設定為write back的。而通常象DMA是訪問不了cache,所以如果我們需要啟動DMA將RAM中的內容寫到Flash中或LCD framebuffer,那麼我們就需要呼叫flush_cache_all將cache中最新的內容重新整理到RAM中。如果不這樣做在LCD中可能會出現花屏。本文主要分析在ARM平臺上到底如何實現的。
在include/asm-arm/cacheflush.h中:
#define flush_cache_all()
#define __cpuc_flush_kern_all cpu_cache.flush_kern_all
在setup_processor():
list = lookup_processor_type(processor_id);
//根據processor id找到對應ARM CPU(常見的如ARM926)相關的資訊,存在list中。如果想把事情徹底搞清楚,必然要問processor_id是怎麼來。它是在Linux Kernel啟動時候從ARM chip中讀出來。如果以後有機會大家一起討論ARM Linux的啟動全過程,可以詳細分析。
…
cpu_cache = *list->cache;
而lookup_processor_type定義在arch/arm/kernel/head-comman.S中:相應的assembler code如下:
.type __lookup_processor_type, %function
__lookup_processor_type:
adr r3, 3f
ldmda r3, {r5 - r7}
sub r3, r3, r7 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
/*
* This provides a C-API version of the above function.
*/
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r7, r9, lr}
mov r9, r0
bl __lookup_processor_type
mov r0, r5
ldmfd sp!, {r4 - r7, r9, pc}
/*
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
它其實就是到__proc_info_begin開始的section中去找到對應當前SOC中用的CPU Cache相關的operation list
再由arch/arm/kernel/vmlinux.lds.S可以__proc_info_begin就是section *(.proc.info.init)的開始地址。
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
而我們知道我們所用是ARM926,所以其定義在arch/arm/mm/proc-arm926.S:
.section ".proc.info.init", #alloc, #execinstr
.type __arm926_proc_info,#object
__arm926_proc_info:
.long 0x41069260 @ ARM926EJ-S (v5TEJ)
.long 0xff0ffff0
.long PMD_TYPE_SECT | \
PMD_SECT_BUFFERABLE | \
PMD_SECT_CACHEABLE | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
.long PMD_TYPE_SECT | \
PMD_BIT4 | \
PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ
b __arm926_setup
.long cpu_arch_name
.long cpu_elf_name
.longHWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA
.long cpu_arm926_name
.long arm926_processor_functions
.long v4wbi_tlb_fns
.long v4wb_user_fns
.long arm926_cache_fns
.size __arm926_proc_info, . - __arm926_proc_info
arm926_cache_fns定義在同一個檔案中,如下:
ENTRY(arm926_cache_fns)
.long arm926_flush_kern_cache_all
.long arm926_flush_user_cache_all
.long arm926_flush_user_cache_range
.long arm926_coherent_kern_range
.long arm926_coherent_user_range
.long arm926_flush_kern_dcache_page
.long arm926_dma_inv_range
.long arm926_dma_clean_range
.long arm926_dma_flush_range
它所對應的struct的定義:(include/asm-arm/cacheflush.h)
struct cpu_cache_fns {
void (*flush_kern_all)(void);
void (*flush_user_all)(void);
void (*flush_user_range)(unsigned long, unsigned long, unsigned int);
void (*coherent_kern_range)(unsigned long, unsigned long);
void (*coherent_user_range)(unsigned long, unsigned long);
void (*flush_kern_dcache_page)(void *);
void (*dma_inv_range)(const void *, const void *);
void (*dma_clean_range)(const void *, const void *);
void (*dma_flush_range)(const void *, const void *);
};
所以其實flush_cache_all 在我們的專案中就是arm926_flush_kern_cache_all:其實現在同一個檔案中:
/*
* flush_kern_cache_all()
* Clean and invalidate the entire cache.
*/
ENTRY(arm926_flush_kern_cache_all)
mov r2, #VM_EXEC
mov ip, #0
__flush_whole_cache:
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
mcr p15, 0, ip, c7, c6, 0 @ invalidate D cache
#else
1: mrc p15, 0, r15, c7, c14, 3 @ test,clean,invalidate
bne 1b
#endif
tst r2, #VM_EXEC
mcrne p15, 0, ip, c7, c5, 0 @ invalidate I cache
mcrne p15, 0, ip, c7, c10, 4 @ drain WB
mov pc, lr
最後我們它不僅僅flush 所有的cache(包括ICache和DCache),也flush了Write Buffer。