/arch/arm/kernel/head.S
/*
* Look in <asm/procinfo.h> for information about the __proc_info structure.
*/
.align 2
.type __lookup_processor_type_data, %object
__lookup_processor_type_data:
.long . @ “.”表示当前这行代码编译连接后的虚拟地址
.long __proc_info_begin @ proc_info_list结构的开始地址,这是链接地址,也是虚拟地址
.long __proc_info_end @ @proc_info_list结构的结束地址,这是链接地址,也是虚拟地址
.size __lookup_processor_type_data, . - __lookup_processor_type_data
/arch/arm/kernel/vmlinux.lds.S
#define PROC_INFO \
. = ALIGN(4); \
VMLINUX_SYMBOL(__proc_info_begin) = .; \
*(.proc.info.init) \
VMLINUX_SYMBOL(__proc_info_end) = .;
/arch/arm/mm/Makefile
obj-$(CONFIG_CPU_V7) += proc-v7.o
/arch/arm/mm/proc-v7.S
.section ".proc.info.init", #alloc, #execinstr
/*
* Standard v7 proc info content
*/
.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
W(b) \initfunc
.long cpu_arch_name
.long cpu_elf_name
.long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
HWCAP_EDSP | HWCAP_TLS | \hwcaps
.long cpu_v7_name
.long \proc_fns
.long v7wbi_tlb_fns
.long v6_user_fns
.long v7_cache_fns
.endm
/*
* ARM Ltd. Cortex A9 processor.
*/
.type __v7_ca9mp_proc_info, #object
__v7_ca9mp_proc_info:
.long 0x410fc090
.long 0xff0ffff0
__v7_proc __v7_ca9mp_setup
.size __v7_ca9mp_proc_info, . - __v7_ca9mp_proc_info
/*
* Match any ARMv7 processor core.
*/
.type __v7_proc_info, #object
__v7_proc_info:
.long 0x000f0000 @ Required ID value
.long 0x000f0000 @ Mask for ID
__v7_proc __v7_setup
.size __v7_proc_info, . - __v7_proc_info
通过使用__v7_proc_info宏定义初始化了一些processer的proc info。
该proc_info structure的定义在/arch/arm/include/asm/procinfo.h:
/*
* Note! struct processor is always defined if we're
* using MULTI_CPU, otherwise this entry is unused,
* but still exists.
*
* NOTE! The following structure is defined by assembly
* language, NOT C code. For more information, check:
* arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
*/
struct proc_info_list {
unsigned int cpu_val;
unsigned int cpu_mask;
unsigned long __cpu_mm_mmu_flags; /* used by head.S */
unsigned long __cpu_io_mmu_flags; /* used by head.S */
unsigned long __cpu_flush; /* used by head.S */
const char *arch_name;
const char *elf_name;
unsigned int elf_hwcap;
const char *cpu_name;
struct processor *proc;
struct cpu_tlb_fns *tlb;
struct cpu_user_fns *user;
struct cpu_cache_fns *cache;
};
不同的proc_info_list结构被用来支持不同的CPU,它们都是定义在“.proc.info.init”段中,在连接内核时,这些结构体被组织在一起,开始地址为__proc_info_begin,结束地址为__proc_info_end。这可以从链接脚本文件“arch/arm/kernel/vmlinux.lds.S”中看出来
在使能MMU前使用的都是物理地址,而内核却是以虚拟地址连接的,所以在访问proc_init_list结构前,需要先将它的虚拟地址转换为物理地址。
NOTE:
/arch/arm/include/asm/assembler.h
#ifdef CONFIG_SMP
#define ALT_SMP(instr...) \
9998: instr
/*
* Note: if you get assembler errors from ALT_UP() when building with
* CONFIG_THUMB2_KERNEL, you almost certainly need to use
* ALT_SMP( W(instr) ... )
*/
#define ALT_UP(instr...) \
.pushsection ".alt.smp.init", "a" ;\ //放到".alt.smp.init section
.long 9998b ;\
9997: instr ;\
.if . - 9997b != 4 ;\ //4byte对齐
.error "ALT_UP() content must assemble to exactly 4 bytes";\
.endif ;\
.popsection
#define ALT_UP_B(label) \
.equ up_b_offset, label - 9998b ;\
.pushsection ".alt.smp.init", "a" ;\
.long 9998b ;\
W(b) . + up_b_offset ;\
.popsection
#else
#define ALT_SMP(instr...)
#define ALT_UP(instr...) instr
#define ALT_UP_B(label) b label
#endif
<p> </p>
SMP=y,表示支持SMP;否则,只支持单核。CONFIG_SMP_ON_UP=y,在启动时,在线检测SMP或者单核,并自我修正不能在non-SMP运行的指令。因此,我们可以根据单核和SMP的差异,通过ALT_SMP和ALT_UP把两种版本的代码放在同一函数中。
ALT_SMP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
ALT_UP(.long PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
对于设置了SMP和SMP_ON_UP的情况,属于SMP的情况会被编译在原本执行函数的内容中,而属于单核的情况会被编译到section ".alt.smp.init中,并且在每4bytes地址后,记录对应4bytes单核版本指令集,以便修正时参考。如下例子:
0xc001858c <__smpalt_begin>:
…..
0xc0018634: c0029fd8
0xc0018638: ee080f37
0xc001863c: c0029fec
0xc0018640: ee07cfd5
0xc0018644: c002a00c
0xc0018648: ee080f37
…..
在最後的Link階段,會把Section .alt.smp.init放在Symbol __smpalt_begin與__smpalt_end之中,因此在程式碼執行階段,就可以透過這兩個Symbol取得 Section .alt.smp.init中所包含單核心程式碼的內容與記憶體範圍.
在Linux Kernel啟動後會呼叫函式__fixup_smp,如果判斷目前是在單核心平台上,就會把在__smpalt_begin到__smpalt_end記憶體範圍的單核心程式碼依據其對應的記憶體位址,進行修正動作.
運作概念如下圖所示
__fixup_smp_on_up:
adr r0, 1f @ 标号1处的地址
ldmia r0, {r3 - r5} @ 虚拟地址和物理地址的转换
sub r3, r0, r3
add r4, r4, r3
add r5, r5, r3
b __do_fixup_smp_on_up
ENDPROC(__fixup_smp)
.align
1: .word .
.word __smpalt_begin
.word __smpalt_end
.pushsection .data
.globl smp_on_up
smp_on_up:
ALT_SMP(.long 1)
ALT_UP(.long 0)
.popsection
#endif
.text
__do_fixup_smp_on_up:
cmp r4, r5 @ 如果已经end([r4] > [r5])就跳回
movhs pc, lr
ldmia r4!, {r0, r6} @ r0:需要修正的指令地址,r6单核指令,[r4] = [r4] + 8
ARM( str r6, [r0, r3] ) @ 用单核指令覆盖原指令,[r0+r3] = [r6],r0+r3是虚拟和物理的转换
THUMB( add r0, r0, r3 )
#ifdef __ARMEB__
THUMB( mov r6, r6, ror #16 ) @ Convert word order for big-endian.
#endif
THUMB( strh r6, [r0], #2 ) @ For Thumb-2, store as two halfwords
THUMB( mov r6, r6, lsr #16 ) @ to be robust against misaligned r3.
THUMB( strh r6, [r0] )
b __do_fixup_smp_on_up
ENDPROC(__do_fixup_smp_on_up)