您的位置:首页 > 运维架构 > 网站架构

ARM架构内核启动分析-head.S(1.2、stext分析之准备阶段)

2013-07-22 20:31 489 查看

1.1、
stext分析:

1.2.1、运行环境参数:

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'

1.2.2、__lookup_processor_type分析:

本子程序的用途是: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;

1.2.3、__lookup_machine_type分析:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: