*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列内核代码基于linux-3.0.35-imx。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!
接上篇分析完__lookup_processor_type函数,程序继续往下执行:
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atags
#ifdef CONFIG_SMP_ON_UP
bl __fixup_smp
#endif
...
bl __create_page_tables
计算出PAGE_OFFSET的物理地址。
调用__vet_atags、__fixup_smp、__create_page_tables,接下来我们只分析__vet_atags和__create_page_tables。
定义在head-common.S中,主要作用就是检查UBoot传进来的参数是否合法。即是否为ATAG_CORE开头的内核参数或者以OF_DT_MAGIC开头的设备树。
__vet_atags:
tst r2, #0x3 @ aligned?
bne 1f ;判断是否对齐,如果没有对齐直接跳出
ldr r5, [r2, #0] ;加载r2寄存器里的值指向的地址的数据
#ifdef CONFIG_OF_FLATTREE
ldr r6, =OF_DT_MAGIC @ is it a DTB?第二个参数可能是内核参数,也有可能是设备树
cmp r5, r6
beq 2f
#endif
cmp r5, #ATAG_CORE_SIZE @ is first tag ATAG_CORE?ATAG_CORE是固定长度的
cmpne r5, #ATAG_CORE_SIZE_EMPTY
bne 1f ;不是ATAG_CORE或者ATAG_CORE长度不对,跳出
ldr r5, [r2, #4]
ldr r6, =ATAG_CORE ;判断是否为ATAG_CORE
cmp r5, r6
bne 1f
2: mov pc, lr @ atag/dtb pointer is ok
1: mov r2, #0
mov pc, lr
ENDPROC(__vet_atags)
如果不合法,则将r2的寄存器值赋为0。
在开启MMU之前,要先做好一个页表,作为虚拟地址跟物理地址的映射,要将虚拟地址0x000000000~0xFFFFFFFF这4G的地址映射到实际的物理地址上面,由于是临时的,只是为了执行内核(因为内核编译的时候链接地址是PAGE_OFFSET+TEXT_OFFSET),与开启MMU前执行地址(即内核被bootload加载的地方,或者说内核自解压的地方)PHY_OFFSET(本开发板为0x10008000)不一致,涉及到一些变量的存放地址都是虚拟地址,所以要开启MMU做映射,才能正确执行内核。
映射只是将32位虚拟地址中高12位做映射,低20位不做映射,访问一个地址时,先取出它的高12位,然后读取页表偏移量为高12位的值的数据作为映射后的高12位,低20位未改变,如此就是物理地址。
做这样子映射总共需要4096(即2^12)个页表项,每个页表项要用4个字节保存,所以总共需要16K的空间(即0x10004000到0x10008000的物理地址空间),然后对这16K的空间进行赋值。每个页表项里面保存的值高12位是映射值,低20位是MMU flag值。
开启MMU之后,例如要访问一个地址为0x8000FFFF的变量,首先MMU会计算出它在页表项的位置,由于一个页表项代表1M(即2^20),所以0x8000FFFF的位置是0x2000(0x8000FFFF>>20),然后读取该页表项里面指向的物理地址页表,即读取0x10004000+0x2000的位置,假如此时位置的值是0x1000,那么虚拟地址0x8000FFFF对应的物理地址是((0x1000) << 20) | (0x8000FFFF & 0x000FFFFF)。
此时一些寄存器的值如下:
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
定义在head.S中,去掉未定义的宏所包括的代码:
__create_page_tables:
pgtbl r4, r8 @ page table address;pgtbl是一个宏,定义在同文件,计算出r8所保存的内核起始物理地址前16K的地址保存在r4寄存器
/*
* Clear the 16K level 1 swapper page table
*/
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b ;清零这16K数据,作为一级交互页表
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags;加载struct proc_info_list->__cpu_mm_mmu_flags到r7
/*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __enable_mmu_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __enable_mmu,计算__enable_mmu物理地址
add r6, r6, r0 @ phys __enable_mmu_end,计算__enable_mmu_end物理地址
mov r5, r5, lsr #20 ;取高12位
mov r6, r6, lsr #20 ;取高12位
;做__enable_mmu和__enable_mmu_end之间的平等映射
1: orr r3, r7, r5, lsl #20 @ flags + kernel base,映射值或上MMU flag
str r3, [r4, r5, lsl #2] @ identity mapping,将映射值保存在对应的页表项中
teq r5, r6
addne r5, r5, #1 @ next section
bne 1b
/*
* Now setup the pagetables for our kernel direct
* mapped region.
*/
mov r3, pc
mov r3, r3, lsr #20
orr r3, r7, r3, lsl #20 ;r3保存当前PC的映射值
add r0, r4, #(KERNEL_START & 0xff000000) >> 18;KERNEL_START为映射后的内核起始地址,r0保存该地址所对应页表中的位置
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!;保存虚拟内核起始地址的映射值
ldr r6, =(KERNEL_END - 1);KERNEL_END为内核映射终止地址
add r0, r0, #4;跳过一个页表项
add r6, r4, r6, lsr #18;r6保存虚拟内核终止地址所对应页表位置。
;设置映射值
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
mov pc, lr
ENDPROC(__create_page_tables)
.ltorg
.align
__enable_mmu_loc:
.long .
.long __enable_mmu
.long __enable_mmu_end
由于链接地址跟加载地址的不一致,需要开启MMU,使用地址映射方法指之一致,所以需要稍微理解一下映射原理。还有一个是平等映射的关系,理解平等映射之后就不会对开启MMU时执行流程产生疑惑。
暂无