*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列内核代码基于linux-3.0.35-imx。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!
在之前的文章里面已经说过了内核镜像文件的生成流程以及内核的自解压,内核解压之后就跳转到解压的起始地址。其实就是跳转到arch/arm/boot/Image的入口,或者说跳转到vmlinux的入口。
老规矩,先看vmlinux的链接脚本,在arch/arm/kernel/vmlinux.lds:
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0x80000000 + 0x00008000;
…
}
这么这个片段我们可以看出一个是入口地址是stext,另外一个是链接地址是0x80008000。(注意此时运行地址是0x10800000,所以在内核开启MMU之前运行地址和链接地址是不一致的,只有到开启MMU之后的代码运行地址跟链接地址才一致)。
stext的定义在arch/arm/kernel/head.S中,注释都比较详细:
__HEAD
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
#ifndef CONFIG_XIP_KERNEL
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
#else
ldr r8, =PLAT_PHYS_OFFSET
#endif
/*
* 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
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
bl __fixup_pv_table
#endif
bl __create_page_tables
/*
* 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_processor_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, =__mmap_switched @ address to jump to after
@ mmu has been enabled
adr lr, BSYM(1f) @ return (PIC) address
mov r8, r4 @ set TTBR1 to swapper_pg_dir
ARM( add pc, r10, #PROCINFO_INITFUNC )
THUMB( add r12, r10, #PROCINFO_INITFUNC )
THUMB( mov pc, r12 )
1: b __enable_mmu
ENDPROC(stext)
我分析下重点部分:
在进入SVC模式,关闭中断之后,接着:
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
将处理器ID保存在r9寄存器,然后调用__lookup_processor_type。__lookup_processor_type函数定义在同目录的head-common.S中,主要的作用就是根据r9寄存器里面保存的处理器类型判断该处理器是否被支持。
定义为:
/*
* Read processor ID register (CP#15, CR0), and look up in the linker-built
* supported processor list. Note that we can't use the absolute addresses
* for the __proc_info lists since we aren't running with the MMU on
* (and therefore, we are not in the correct address space). We have to
* calculate the offset.
*
* r9 = cpuid
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__CPUINIT
__lookup_processor_type:
adr r3, __lookup_processor_type_data
ldmia r3, {r4 - r6}
sub r3, r3, r4 @ 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
ENDPROC(__lookup_processor_type)
.long . ;当前位置,被加载到r4寄存器
.long __proc_info_begin ;被加载到r5寄存器
.long __proc_info_end ;被加载到r6寄存器
.size __lookup_processor_type_data, . - __lookup_processor_type_data
__proc_info_begin和__proc_info_end定义在vmlinux.lds中,后续讲到它的具体作用。
由于__proc_info_begin和__proc_info_end是链接时候赋值的,而链接地址与运行地址不一致,所以我们需要重新计算它的运行地址。
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
之前的代码有出现过计算运行地址和链接地址的偏移量但是我没有详细分析,在这里统一分析。
首先是计算实际地址:
adr r3, __lookup_processor_type_data
adr指令是一条与位置无关的指令,相当于add r3,pc,offset;
其中offset是当前指令与__lookup_processor_type_data之间的偏移量,所以无论程序被加载到哪个位置,总能计算出__lookup_processor_type_data的实际位置。
然后将__lookup_processor_type_data处的位置加载到r4~r6寄存器中,其中加载到r4寄存器的数据是
.long .
即是一个当前位置,这个地址是链接地址,在链接的时候已经固定,而r3寄存器里面保存的是这个位置的实际地址,所以两者相减,就是运行地址和链接地址的偏移量。
2. __proc_info_begin和__proc_info_end之中保存的数据是(.proc.info.init)段,(.proc.info.init)段是处理器信息的一个段,在arch/arm/mm/proc-**.S中。
例如在arch/arm/mm/proc-v7.S中的定义:
.section ".proc.info.init", #alloc, #execinstr
.type __v7_ca9mp_proc_info, #object
__v7_ca9mp_proc_info:
.long 0x410fc090 @ Required ID value
.long 0xff0ffff0 @ Mask for ID
...
实际上,段.proc.info.init有个C语言的数据结构定义在arch/arm/include/asm/procinfo.h:
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;
};
可以直观地看出它的定义。特别注意的是该数据结构的前两个成员cpu_val和cpu_mask,判断CPU是否支持就是根据这两个成员来判断的。
3. 接下来就是遍历支持的CPU类型数据结构表中,判断是否支持本CPU类型:
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
此时r5寄存器中保存的是.proc.info.init的起始地址,r6寄存器保存的是.proc.info.init段的终止地址。
首先将r5地址处的两个数据加载到r3,r4寄存器内,根据定义我们可以知道此时加载的是cpu_val和cpu_mask,然后将r9寄存器与cpu_mask与操作再与cpu_val比较。
如果相等则说明支持该CPU,跳转到
2: mov pc,lr
即返回。
不相等则
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
跳到下一个struct proc_info_list再对比。遍历所有的struct proc_info_list结构体。
如果有找到CPU类型,则r5里面保存的是对应的struct proc_info_list地址,否则为0.
返回到linux的入口处,接着就判断r5寄存器的值是否为0,如果为0则说明不支持该CPU,跳转到__error_p(不分析),否则继续执行。
入口地址做的第一件事就是判断内核是否支持该CPU,如果支持,同时获取到该CPU的struct proc_info_list数据结构的地址保存到r0寄存器内。
暂无