接下来,调用__enable_mmu来打开MMU,在该函数的最后会使用这里保存在R13中的__switch_data函数地址并调用它,函数__switch_data定义在head-common.S中,它的函数指针__mmap_switched最终会调用第一个C函数start_kernel!
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
/*然后将lr设为__enable_mmu的地址*/
adr lr, BSYM(__enable_mmu) @ return (PIC) address
/*前面代码已经让R10作为struct proc_info_list变量procinfo的基地址,宏PROCINFO_INITFUNC值为16(在arch/arm/kernel/asm-offset.c中107行定义),所以pc最终指向了procinfo的成员__cpu_flush函数的地址(参考数据结构proc_info_list),这将导致调用该函数,要寻找该函数的实现,需要找到procinfo变量的值,由前面代码已知在文件proc-feroceon.S中(我们的marvell芯片是这个文件,和平台相关),可以找到".proc.info.init"段的实现;
从顶层的.config文件中,注意没有定义宏CONFIG_CPU_FEROCEON_OLD_ID,其__cpu_flush成员都是函数__feroceon_setup,所以将要执行函数__feroceon_setup(也在该文件定义);
调用该函数主要是清除I/D-cache、write buffer、I/D-TLB,并初始设置一下c1控制寄存器*/
ARM( add pc, r10, #PROCINFO_INITFUNC )
__cpu_flush函数,主要是清除I/D-cache、write buffer、I/D-TLB,并初始设置一下c1控制寄存器,在marvell里的实现是函数__feroceon_setup(在arch/arm/mm/proc-feroceon.S),这里涉及的arm寄存器的具体含义及用途用法,是下一阶段重点;
.type __feroceon_setup, #function
__feroceon_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
清空(invalidate)I-cache和D-cache
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
清空(drain)write buffer
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
清空(invalidate)I-TLB和D-TLB
#endif
adr r5, feroceon_crval
获取函数feroceon_crval相对地址到R5
ldmia r5, {r5, r6}
将clear和mmuset的值分别存到了r5, r6中(这些东西都在proc-marco.S文件中定义)
mrc p15, 0, r0, c1, c0 @ get control register v4
获取控制寄存器c1的值
bic r0, r0, r5
将r0中的clear(r5) 对应的位都清除掉
orr r0, r0, r6
设置r0中mmuset(r6) 对应的位
mov pc, lr
返回
这时要真正去开启MMU了!首先注意下这时的arm寄存器状态:
R4: 页表起始处物理地址
R8: machine info,struct machine_desc的基地址
R9: cpu id
R10: procinfo,struct proc_info_list的基地址
R0: c1 parameters,用来配置控制寄存器的参数,MMU分析的重点
__enable_mmu:
/*定义了该宏,置位该bit(CR_A,到底是哪位是何作用,可见文件arch/arm/include/asm/system.h,具体用途还需深入分析)*/
#ifdef CONFIG_ALIGNMENT_TRAP
orr r0, r0, #CR_A
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE @@未定义该宏,无需关注
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE @@未定义该宏,无需关注
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE @@未定义该宏,无需关注
bic r0, r0, #CR_I
#endif
/*下面的指令的意思是用于设置domain参数的值给R5,domain_val和里面各个宏的定义均在文件arch/arm/include/asm/domain.h*/
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
配置 domain(详细需要看arm寄存器手册)
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
配置临时页表在存储器中的位置(set ttb).临时页表的基地址是R4,通过写cp15的c2寄存器来设置临时页表基地址,这里就是把临时页表的内容写到MMU了!
b __turn_mmu_on
即将真正打开mmu!
首先是一个.algin 5,这句意思是cache line对齐,之所以这么做的原因是: 下面我们要进行真正的打开mmu操作了, 我们要把打开mmu的操作放到一个单独的cache line上, 而在之前已经说了,I Cache是无所谓的,即可以打开也可以关闭,这里这么做的原因是要保证在I- Cache打开的时候,打开mmu的操作也能正常执行;
__turn_mmu_on:
mov r0, r0
上面是这是一个空操作,相当于nop,之所以这么做可能和arm9的流水线有关
mcr p15, 0, r0, c1, c0, 0 @ write control reg
上面是写cp15的控制寄存器c1,这里是打开mmu的操作,同时会打开cache等(根据r0相应的配置),终于打开MMU了!
mrc p15, 0, r3, c0, c0, 0 @ read id reg
上面是读取id寄存器
mov r3, r3
上面是nop操作
mov r3, r13
mov pc, r3
上面是,R13在之前存储的是__switch_data,这里最终将它赋值给PC,将跳到__switch_data
在开启MMU后,将最终跳到此处执行;
.align 2
.type __switch_data, %object
/*1、首先定义了一些地址*/
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4 @@--->R3
.long __machine_arch_type @ r5 @@--->R4
.long __atags_pointer @ r6 @@--->R5 @@注意,很多arm设备的实现,是没有__atags_pointer的(而是由内核machine_desc变量直接指定),这个东西是用于bootloader把参数传递给内核,
@@在stext运行之前,由R2保存这个物理地址值,这里定义变量__atags_pointer就是为了让它等于那个物理地址值,
@@后续C代码页可以用这个变量,这就让C代码获取到了bootloader传递的参数 .long cr_alignment @ r7 @@--->R6
.long init_thread_union + THREAD_START_SP @ sp @@--->R7,sp
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags pointer
* r9 = processor ID
*/
上面的当前arm寄存器的注释还是应该注意下的。
/*2、进入函数__mmap_switched*/
__mmap_switched:
adr r3, __switch_data + 4
取__data_loc的地址赋值给R3
ldmia r3!, {r4, r5, r6, r7}
执行后结果: R3: processor_id,R4: __data_loc(数据段存放处),
R5: __data_start(数据段起始处),R6: __bss_start(bss段起始处),
R7: _end(bss段结束处同时也是内核镜像结束处)
这几个符号都是在 arch/arm/kernel/vmlinux.lds.S 中定义的变量
cmp r4, r5 @ Copy data segment if needed
@@比较__data_loc和__data_start
1: cmpne r5, r6
判断数据存储的位置和数据的开始的位置是否相等,如果不相等,则需要搬运数据,从__data_loc将数据搬到__data_start;
ldrne fp, [r4], #4
其中__bss_start是bss段的开始的位置,也标志了数据段结束的位置,因而用其作为判断数据是否搬运完成
strne fp, [r5], #4
bne 1b
/*小节:地址变量__data_start 从此可以标识数据段*/
mov fp, #0 @ Clear BSS (and zero fp)
清除 bss 段的内容,将其都置成0. 这里使用 _end 来判断 bss 的结束位置
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
/*小节:初始化清零bss段,地址变量_end从此可以标识bss段结尾*/
ARM( ldmia r3, {r4, r5, r6, r7, sp})
R3已经在processor_id处,执行后,R3: processor_id,R4: __machine_arch_type
THUMB( ldmia r3, {r4, r5, r6, r7} )
R5: __atags_pointer,R6: cr_alignment,
THUMB( ldr sp, [r3, #16] )
R7 = SP: init_thread_union + THREAD_START_SP
/*小节:核心内容放入arm的寄存器中,包括R3、R4、R5、R6、R7、SP*/
str r9, [r4] @ Save processor ID
将r9中存放的processor id赋值给变量 processor_id
str r1, [r5] @ Save machine type
将r1中存放的machine id赋值给变量__machine_arch_type
str r2, [r6] @ Save atags pointer
将r2中存放的atags pointer赋值给变量cr_alignment
bic r4, r0, #CR_A @ Clear 'A' bit
清除r0中的 CR_A位并将值存到r4中(前面曾经置位过该位)
stmia r7, {r0, r4} @ Save control register values
存储控制寄存器的值,将r0存储到了cr_alignment中,将r4存储到了cr_no_alignment中
/*小节:核心内容存入内存变量,今后C代码一样可以操作*/
b start_kernel
跳到C函数start_kernel!!!!!!!
ENDPROC(__mmap_switched)
ENDPROC(__turn_mmu_on)
ENDPROC(__enable_mmu)
THUMB( add r12, r10, #PROCINFO_INITFUNC)
THUMB( mov pc, r12)
ENDPROC(stext)