一. 前言
head.S在__create_page_tables中创建了页表,接下来的工作就是开启D-CACHE,开启MMU了。
二. __enable_mmu铺垫代码部分分析
__enable_mmu铺垫相关代码如下:
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
ldr r13, __switch_data :
__switch_data定义在head_common.S中,如下:
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
__mmap_switched是函数__mmap_switched的地址,如下。
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
__data_loc定义在vmlinux.lds中,表示存放数据开始的地址。__data_start定义在vmlinux.lds中,作用后续补充。__bss_start表示bss段的开始地址,_end表示bss段的结束,也表示内核镜像数据的结束地址。
ldr r13, __switch_data是将_switch_data的首地址赋值到r13。
adr lr, __enable_mmu :
将__enable_mmu函数的地址赋值到lr寄存器。
add pc, r10, #PROCINFO_INITFUNC :
PROCINFO_INITFUNC 定义在./include/asm/asm-offsets.h中,如下:
#define PROCINFO_INITFUNC 16 /* offsetof(struct proc_info_list, __cpu_flush) @ */
所以这行代码表示将r10指向的struct proc_info_list结构体的__cpu_flush成员的地址赋值到pc,表示接下来将要执行__cpu_flush成员的指令。
我们从前面的文章中已经知道了r10保存的是__arm920_proc_info函数的地址,所以__cpu_flush成员的表示的指令是b __arm920_setup,所以,接下来要执行__arm920_setup函数,__arm920_setup函数如下。
.type __arm920_setup, #function
__arm920_setup:
mov r0, #0
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4
#ifdef CONFIG_MMU
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4
#endif
adr r5, arm920_crval
ldmia r5, {r5, r6}
mrc p15, 0, r0, c1, c0 @ get control register v4
bic r0, r0, r5
orr r0, r0, r6
mov pc, lr
.size __arm920_setup, . - __arm920_setup
mov r0, #0 :
将r0寄存器赋值为0。
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 :
将r0寄存器的值赋值到cp15处理器的c7寄存器,表示关闭处理器 的指令和数据cache。
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4 :
清空cache的写缓存。
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4 :
关闭指令和数据TLB。
adr r5, arm920_crval :
将arm920_crval的地址赋值到r5,arm920_crval的定义如下:
arm920_crval:
crval clear=0x00003f3f, mmuset=0x00003135, ucset=0x00001130
crval是一个宏,定义在./arch/arm/mm/proc-macros.S,如下:
.macro crval, clear, mmuset, ucset
#ifdef CONFIG_MMU
.word \clear
.word \mmuset
#else
.word \clear
.word \ucset
#endif
.endm
如果开启了CONFIG_MMU,arm920_crval展开为,如下:
.word 0x00003f3f
.word 0x00003135
ldmia r5, {r5, r6} :
该指令是将r5 = 0x000003f3f,r6 = 0x00003135。
mrc p15, 0, r0, c1, c0 @ get control register v4 :
将CP15协处理器的C1寄存器的值赋值到r0寄存器中。
bic r0, r0, r5 :
将r0和r5进行按位异或操作的结果赋值到r0。
orr r0, r0, r6 :
将r0和r6进行或操作的结果保存到r0中。
mov pc, lr :
此时lr的值是__enable_mmu,所以接下来执行__enable_mmu函数。
三. __enable_mmu分析
__enable_mmu如下:
.type __enable_mmu, %function
__enable_mmu:
#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
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
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
orr r0, r0, #CR_A :
从前代码可知,r0是CP15处理器的C1寄存器读出的值,并且与0x00003f3f进行按位异或和与0x00003135进行或操作后的值,CR_A定义在include/asm/system.h中,如下:
#define CR_A (1 << 1) /* Alignment abort enable */
该指令是将r0的第一位置1。
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)) :
domain_val是一个宏,定义在include/asm-arm/domain.h中,如下:
#define domain_val(dom,type) ((type) << (2*(dom)))
DOMAIN_USER,DOMAIN_KERNEL,DOMAIN_TABLE,DOMAIN_IO,DOMAIN_MANAGER,DOMAIN_CLIENT也定义在include/asm-arm/domain.h中,如下:
#define DOMAIN_KERNEL 0
#define DOMAIN_TABLE 0
#define DOMAIN_USER 1
#define DOMAIN_IO 2
#define DOMAIN_NOACCESS 0
#define DOMAIN_CLIENT 1
#define DOMAIN_MANAGER 3
#define domain_val(dom,type) ((type) << (2*(dom)))
该条指令是将ARM处理器的域的配置赋值到r5寄存器中。
mcr p15, 0, r5, c3, c0, 0 @ load domain access register :
将r5寄存器的值赋值到CP15协处理器的C3寄存器中。
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer :
r4是0x30004000,是页表的基地址,该条指令是设置页表的基地址。
b __turn_mmu_on :
跳转到__turn_mmu_on,真正的开启MMU。
四. __turn_mmu_on分析
__turn_mmu_on:
mov r0, r0
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
mov r0, r0 :
这是一句空操作指令,相当于nop操作。
mcr p15, 0, r0, c1, c0, 0 @ write control reg :
r0的值是从CP15协处理器C1寄存器中读出的,并经过了几个位操作。该指令将r0的值写到C1寄存器中,使能MMU
mrc p15, 0, r3, c0, c0, 0 @ read id reg :
读出处理器的标识符。
mov r3, r3
mov r3, r3 :
上面两行是空操作指令。
mov pc, r13 :
r13的内容是__switch_data的地址,__switch_data的第一个4字节的内容是__mmap_switched函数的地址。所以接下来要跳到__mmap_switched函数执行。
五. __mmap_switched分析
__mmap_switched代码如下:
.type __mmap_switched, %function
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r6, {r0, r4} @ Save control register values
b start_kernel
__switch_data代码如下:
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
adr r3, __switch_data + 4 :
__switch_data + 4是__data_start的地址。所以r3 = __data_start。
ldmia r3!, {r4, r5, r6, r7} :
由ldmia功能可知,
r4 = __data_loc,
r5 = __data_start,
r6 = __bss_start,
r7 = _end,
r3 = processor_id 。
cmp r4, r5 @ Copy data segment if needed :
比较r4和r5的值是否相等。
1: cmpne r5, r6 :
如果r4和r5不相等,则比较r5和r6的值。
ldrne fp, [r4], #4 :
如果r5和r6不相等,则将r4地址的值赋值到fp寄存器,r4 = r4 + 4。
strne fp, [r5], #4 :
如果r5不等于r6,将fp赋值到r5寄存器保存的地址,r5 = r5 + 4。
bne 1b :
如果r5不等于r6,则跳转到1: cmpne r5, r6指令。
以上的5行代码的功能是将__data_loc地址开始代码拷贝到__data_start。
mov fp, #0 @ Clear BSS (and zero fp) :
将fp寄存器的值赋值为0。
1: cmp r6, r7 :
比较r6和r7寄存器的值,r6 = __bss_start,r7 = _end。
strcc fp, [r6],#4 :
如果r6小于r7,将0赋值到r6指向的__bss_start的位置,r6 = r6 + 4。
bcc 1b :
如果r6小于r7,跳转到1: cmp r6, r7。
以上4行代码的功能是清除内核的bss段。
ldmia r3, {r4, r5, r6, sp} :
此时,r3寄存器的值是processor_id,所以该行指令是将
r4 = processor_id,
r5 = __machine_arch_type,
r6 = cr_alignment,
sp = init_thread_union + THREAD_START_SP。
cr_alignment定义在arch/arm/kernel/entry-armv.S,如下:
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
str r9, [r4] @ Save processor ID :
将r9的值保存到processor_id变量中,
str r1, [r5] :
将机器型号的值保存到__machine_arch_type变量中。
bic r4, r0, #CR_A @ Clear 'A' bit :
将r0清除CR_A位清除。CR_A定义在./include/asm/system.h中,如下:
#define CR_A (1 << 1) /* Alignment abort enable */
stmia r6, {r0, r4} @ Save control register values :
已知r6 = cr_alignment,该行指令将r0存储到cr_alignment,r4存储到cr_no_alignment。
b start_kernel :
跳转到start_kernel函数执行。
六. 总结
本文的主要内容介绍的代码主要和设置MMU相关的配置的CP15协处理器的配置,后面直接进入C语言环境,后面继续分析