1、首先注意一下,内核代码在进入C代码之前的几个重要文件:
arch/arm/kernel/head.S:贯穿汇编执行阶段的始末,并且定义了最根本的参数;
arch/arm/kernel/head-common.S:包括一些重要汇编子程序;
arch/arm/mm/proc-XXX.S:汇编执行阶段关于内存(临时)页表、CPU缓存、MMU配置相关内容都在这里,非常重要,具体是哪个文件与设备平台相关;
还有一些文件也很重要,只是它们和设备平台定制相关,不完全具有通用性。
2、然后还要注意一下,此时的CPU相关参数,arch/arm/kernel/head.S中有如下注释:
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.
意思就是说:在从bootloader到解压镜像开始执行时,应该的arm寄存器状态,MMU应该关闭,D-cache应该关闭,I-cache无所谓,R0寄存器为0,R1寄存器存储arm CPU的machine nr,R2寄存器存储arm CPU的atags pointer。
3、然后还要再注意一些重要的参数:
TEXT_OFFSET: 在arch/arm/Makefile中定义,值为0x00008000,意为kernel相对于存储空间的偏移;
PAGE_OFFSET: 表面在arch/arm/include/asm/memory.h中定义,实际在include/linux/autoconf.h中定义,值为0xc0000000,内核起始虚拟地址(用户/内核空间分界线);
TEXTADDR: 在本文件中定义,值为上面两个宏相加为0xc0008000,是kernel的内核虚拟起始地址;
KERNEL_RAM_ADDR: 在本文件中定义,值为两个宏相加为0xc0008000,是kernel在RAM中的虚拟地址
PHYS_OFFSET: 在arch/arm/mach-feroceon-kw2/include/mach/memory.h文件中定义(不同CPU不同),值为0,意为RAM的物理地址起始地址
KERNEL_RAM_VADDR: kernel在RAM中的虚拟起始地址,0xc0008000
KERNEL_RAM_PADDR: kernel在RAM中的物理起始地址,0x00008000
下面具体分析stext:
/*stext属于".text.head"段*/
.section ".text.head", "ax"
ENTRY(stext)
/*设置CPU运行模式为SVC,从arm协处理器获取CPU ID(CPU型号,arm7/9/11),并关中断*/
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
/*获取CPU处理器ID,存入R9*/
mrc p15, 0, r9, c0, c0 @ get processor id
/*寻找匹配的CPU ID(__lookup_processor_type这个函数在head-common.S中,已有详细注释),存入R5(最终存入R10),该值不应为0(为0说明没有找到匹配的CPI ID)*/
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
本子程序的用途是:kernel将所有CPU的信息都定义在.proc.info.init段中,可以认为它是一个数组,每个元素都定义一种CPU的信息,本函数就是不断的循环,用每个元素的前两个字段cpuid、mask来匹配当前CPU ID,如果满足则返回,如不满足则写R5为0,意为未知处理器再返回;
在arch/arm/include/asm/procinfo.h文件中,定义了结构体proc_info_list,在/arch/arm/mm/proc-XXX.S文件中有相关的实现(对于这个marvell的设备,是在proc-fereceon.S中定义);
另外注意,标号的f或b是指在后面或前面的意思,如3f指标号3,标号3在下面。
/**************************__lookup_processor_type *************************/
__lookup_processor_type:
adr r3, 3f
adr是相对寻址,这句的意思是当前PC值加上标号3与PC值的偏移(结果其实就是标号3物理地址),并让R3保存;
ldmia r3, {r5 - r7}
结果: R5值为__proc_info_begin(链接地址,在1.1.2节介绍过),R6值为__proc_info_end(链接地址),R7值为标号4的链接地址;
add r3, r3, #8
让R3保存标号4的物理地址;
sub r3, r3, r7 @ get offset between virt&phys
R3为标号4的物理地址,R7为标号4的链接地址,现在把两地址的差(R3-R7)保存在R3;
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
这两句会让R5、R6分别保存数组__proc_info的首、末物理地址;
小节:获取到_proc_info的首末地址,是前面这堆代码的目的;
1: ldmia r5, {r3, r4} @ value, mask
R5已经是数组__proc_info的首地址,它指向的值是第一个数组成员,把该成员前两个值传给R3、R4即CPU ID、mask;
and r4, r4, r9 @ mask wanted bits
在head.S中,R9已保存本CPU的CPU ID,这里用它和mask作与操作,并把结果存入R4;
teq r3, r4
把与操作的结果和R3(数组成员的CPU ID)比较
beq 2f
如果上面的teq r3,r4即比较r3、r4寄存器值是否相等,如果相等说明找到了匹配的CPU ID,则跳到标号2准备返回;
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
如果不相等要把R5增加数组成员大小的值,即让R5保存下一个数组成员的地址,准备比较下一个数组成员;
cmp r5, r6
blo 1b
比较R5和R6,如果不等说明还没有比较完整个数组__proc_info,继续跳回标号1;
mov r5, #0 @ unknown processor
如果找不到匹配的CPU ID,则在返回前要把R5置0
2: mov pc, lr
标号2的意思是: lr寄存器记录下一个指令,把lr给pc将实现从本函数返回
/*在arch/arm/include/asm/procinfo.h文件中,定义了结构体proc_info_list,在/arch/arm/mm/proc-XXX.S文件中有相关的实现,对于这个marvell的设备,是在proc-fereceon.S中定义*/
.align 2
3: .long __proc_info_begin
.long __proc_info_end
4: .long .
.long __arch_info_begin
.long __arch_info_end
/**************************__lookup_processor_type *************************/
下面是proc-fereceon.S中".proc.info.init"定义的节选:
/*这里是段".proc.info.init"*/
.section ".proc.info.init", #alloc, #execinstr
…..(后面是按照type划分的一个个__proc_info数组成员,具体不列。)
总结:__lookup_processor_type函数的意义是,把arm寄存器中的CPUID和内核代码中编译进去的CPUID做匹配,属校验行为,最终由R10寄存器存储CPUID;
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
这个子程序的用途是:寻找匹配的machinfo(__lookup_machine_type这个函数在head-common.S中,已有详细注释),存入R5(最终存入R8),该值不应为0(为0说明没有找到匹配的machinfo);
它和上面的__lookup_processor_type是一样的,只是获取的是machine_desc变量的相关内容,该变量由宏函数MACHINE_START创建的一个静态变量,保存在".arch.info.init"段中,它默认均在arch/arm/include/asm/mach/arch.h文件中定义(该结构体本身也在这里定义),但其具体值的赋值是由不同芯片方案确定,对于我们的marvell,是在arch/arm/mach-feroceon-kw2/core.c文件中定义赋值。
/**************************__lookup_machine_type **************************/
__lookup_machine_type:
adr r3, 4b
相对寻址,让R3保存前面标号4的物理地址;
ldmia r3, {r4, r5, r6}
把R3地址的内容赋给R4/5/6,R4保存标号4的链接地址,R5保存__arch_info_begin链接地址,R6保存__arch_info_end链接地址;
sub r3, r3, r4 @ get offset between virt&phys
R3保存标号4的物理地址和链接地址的差值;
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
让R5、R6保存__arch_info_begin、__arch_info_end的物理地址;
__arch_info_begin、__arch_info_end分别对应machine_type变量的起始和结尾;
1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type
让R3保存R5指向地址处的值(即machine_desc变量第一个成员nr的值,标识架构数量),MACHINFO_TYPE值为0(include/asm-arm/asm-offsets.h中定义);
teq r3, r1 @ matches loader number?
开始比较,R1已保存本CPU的对应值;
beq 2f @ found
如相等则说明匹配,跳到标号2返回;
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
若不等,说明还得继续匹配,让R5保存下一个成员的地址,SIZEOF_MACHINE_DESC值为52;
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
检查是不是最后一个数组成员,如果不是则继续返回标号1去比较,如果是则给R5赋值为0说明没有匹配的;
2: mov pc, lr
从本函数返回;
/**************************__lookup_machine_type ***************************/
这里涉及一个重要的内容,就是创建machine_desc实例,这是每个arm芯片的必需品,它具有极大的重要性;创建方法在arch/arm/include/asm/mach/arch.h文件中定义,以宏函数MACHINE_START形式出现,实际是创建一个静态变量并保存在特定地点(".arch.info.init"),保存在这里是为了方便让汇编函数__lookup_machine_type可以直接操作该静态变量:
#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函数定义,它有两个参数,实际行为是创建一个数据结构为struct machine_desc静态变量__mach_desc_##_type,并规定它链接在".arch.info.init"段中,并且利用宏函数的两个参数,默认配置该结构体变量的成员nr、name的值;
上面只是宏函数的定义,它没有实际创建machine_desc实例,实际创建由用户驱动代码完成,对于这个marvell的arm芯片,在arch/arm/mach-feroceon-kw2/core.c文件中创建:
MACHINE_START(FEROCEON_KW2 ,"Feroceon-KW2")
/* MAINTAINER("MARVELL") */
.phys_io = 0xf1000000,
.io_pg_offst = ((0xf1000000) >> 18) & 0xfffc,
.boot_params = 0x00000100,
.map_io = mv_map_io,
.init_irq = mv_init_irq,
.timer = &mv_timer,
.init_machine = mv_init,
MACHINE_END
展开宏函数MACHINE_START即可很清晰的翻译如下:在这里创建了一个数据结构为struct machine_desc静态变量__mach_desc_##_type,并且规定了一系列成员的值,这个静态变量在内核镜像的链接地址在".arch.info.init"段中。
总结:把arm寄存器R1中的machinfo nr和内核镜像中创建的struct machine_desc静态变量__mach_desc_##_type的成员machinfo nr进行匹配,属校验行为,最终结果存入R8。
接下来是函数__vet_atags,它用于boot告诉kernel关于物理内存的情况,主要是做一些检查,该函数在head-common.S中;
需关注结构体struct tag,定义在arch/arm/include/asm/setup.h文件中,这里是检查atags变量(该变量由boot创建,里边包含ramdisk、memory的一些信息)的有效性,主要就是是否以ATAG_CORE开头、size是否正确等,具体关注需要关注boot的内容,这里不做细致分析。
bl __vet_atags