分析内核顶层Makefile,以及arch/arm/Makefile可知,从u-boot跳到内核后,第一个启动的文件是arch/arm/kernel/head.S。接下来就围绕这个文件来分析Linux架构/开发板相关的引导过程。
/******************************以下仅分析重要部分********************************/
#include "head-common.S" //下面会调用到这个文件里面的函数
......
/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
* ........
*/
.section ".text.head", "ax" //".text.head"段,在vmlinux.lds链接文件中定义
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE //确保进入管理模式并禁止中断
mrc p15, 0, r9, c0, c0 // r9 = CPU ID
bl __lookup_processor_type // r5=procinfo r9=cpuid
movs r10, r5 //如果不支持该CPU则r5=0
beq __error_p //如果r5=0,则打印错误信息
bl __lookup_machine_type // r5=machinfo
movs r8, r5 // 如果不支持该开发板r5=0
beq __error_a //如果r5=0,则打印错误信息
bl __vet_atags
bl __create_page_tables
.......
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
ENDPROC(stext)
.......
架构/开发板相关的引导过程就是由以上蓝色部分的函数来完成的,都是汇编子程序,下面逐个分析。
__lookup_processor_type子程序在arch/arm/kernel/head-common.S文件中定义
/*
* r9 = cpuid
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__lookup_processor_type:
adr r3, 3f //将下面3标号的物理地址存到r3寄存器
/* r5 = __proc_info_begin,r6 = __proc_info_end 的虚拟地址,r7 = 3标号的虚拟地址 */
ldmda r3, {r5 - r7}
sub r3, r3, r7 // r3 = r3 - r7,物理地址与虚拟地址之差
add r5, r5, r3 // r5 = __ proc_info_begin 对应的物理地址
add r6, r6, r3 // r6 = __proc_info_end 对应的物理地址
1: ldmia r5, {r3, r4} // r3 = cpu_val,r4 = cpu_mask 这两个成员在proc_info_list中定义
and r4, r4, r9 // r4 = r4&r9 = cpu_mask & 最开始从CP15传入的cpu id
teq r3, r4
beq 2f //相等则返回
add r5, r5, #PROC_INFO_SZ //下一个pro_info_list ,sizeof(proc_info_list)
cmp r5, r6 //判断是否已经比较完所有的proc_info_list
blo 1b //否则继续比较
mov r5, #0 //比较完后还是没有匹配的proc_info_list结构,r5 = 0
2: mov pc, lr // 返回
ENDPROC(__lookup_processor_type)
.......
/*
* Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin //proc_info_list结构体的开始地址(虚拟地址)
.long __proc_info_end //proc_info_list结构体的结束地址(虚拟地址)
3: .long . // 3标号在内存中的的物理地址
.long __arch_info_begin
.long __arch_info_end
proc_info_list结构体在arch/arm/include/asm中定义
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;
};
对于S3C2440开发板的匹配,它的CPU ID是0x41129200,可查询协处理器的CP15获知。
而在arch/arm/mm/proc_arm920.S中定义的__arm920_proc_info结构如下
__arm920_proc_info:
.long 0x41009200
.long 0xff00fff0
........
即r3 = cpu_val = 0x41009200,r4 = cpu_mask = 0xff00ff00,刚好匹配。
__lookup_machine_type子程序也在arch/arm/kernel/head-common.S文件中定义
/*
* ....
* r1 = machine architecture number
* Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
__lookup_machine_type:
adr r3, 3b //获得3标号的物理地址
/* r4 = __arch_info_begin,r6 = __arch_info_end 的虚拟地址,r7 = 3标号的虚拟地址 */
ldmia r3, {r4, r5, r6}
sub r3, r3, r4 // r3 = r3 - r4 物理地址与虚拟地址之差
add r5, r5, r3 // r5 = __arch_info_begin 的物理地址
add r6, r6, r3 // r6 = __arch_info_end 的物理地址
1: ldr r3, [r5, #MACHINFO_TYPE] // r5 是machine_desc结构体的地址
teq r3, r1 //判断从r1传入的机器ID是否匹配
beq 2f //匹配则返回
add r5, r5, #SIZEOF_MACHINE_DESC //否则,指向下一个 machine_desc
cmp r5, r6 //是否已经比较完所有的machine_desc结构
blo 1b //没有则继续比较
mov r5, #0 //比较完毕之后都没有找到匹配的,r5 = 0
2: mov pc, lr //返回
ENDPROC(__lookup_machine_type)
machine_desc结构在 arch/arm/include/asm/mach/arch.h中定义
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
MACHINE_START(S3C2440, "SMDK2440")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
由上面二个定义得到S3C2440 的machine_desc结构体
static const struct machine_desc __mach_desc_S3C2440
__used
__attribute__((__section__(".arch.info.init"))) =
{
.nr = MACH_TYPE_S3C2440,
.name = "SMDK2440",
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
};
到此,__lookup_processor_type、__lookup_machine_type函数如果都匹配成功,则将继续执行
__vet_atags、(处理u-boot传入的ATAGS)
__create_page_tables创建一级页表,在arch/arm/kernel/head.S原文定义
__switch_data在arch/arm/kernel/head_common.S 定义
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long _data @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp
......
__mmap_switched:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 //复制数据段
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 //清除BSS段
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, r7, sp}
str r9, [r4] //保存CPU ID到 r9 寄存器
str r1, [r5] //保存机器ID到 r1 寄存器
str r2, [r6] // 保存ATAGS指针到 r2 寄存器
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel //跳到第二阶段的C函数去执行,在init/main.c中定义
ENDPROC(__mmap_switched)
__enable_mmu 使能MMU
__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
ENDPROC(__enable_mmu)
.align 5
__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
ENDPROC(__turn_mmu_on)