浅析armlinux2.4.19启动程序[head-armv.s文件]
文章来源:http://gliethttp.cublog.cn
接续《浅析arm-linux中断vector向量表的建立流程》,分析head-armv.s启动文件 硬件环境描述:[gliethttp 2007-07-25] 1>自制at91rm9200开发板一块[gliethttp] 2>32M-sdram总线地址范围0x20000000~0x22000000 3>u-boot将经过u-boot专用压缩程序mkimage,压缩的vmlinux内核文件 解压到0x20008000处,并把0x20008000作为内核入口地址, 跳转到stext第一句mov r12,r0正式执行linux内核程序 u-boot1.1.5/lib_arm/armlinux.c->do_bootm_linux()-> theKernel(0, bd->bi_arch_number, bd->bi_boot_params); 1.先来看看编译链接脚本arch/arm/vmlinux-armv.lds.in OUTPUT_ARCH(arm) //arm体系 ENTRY(stext) //stext程序为入口点 SECTIONS { //TEXTADDR的定义在arch/arm/Makefile中,部分内容如下: //ifeq ($(CONFIG_CPU_32),y) //PROCESSOR = armv //TEXTADDR = 0xC0008000 //LDSCRIPT = arch/arm/vmlinux-armv.lds.in //endif . = TEXTADDR; //编译器使用的偏移地址从TEXTADDR=0xC0008000开始 .init : { _stext = .;//编译起始地址为0xC0008000,然后生成的代码按如下的顺序依次存放 __init_begin = .; *(.text.init) __proc_info_begin = .; *(.proc.info) __proc_info_end = .; __arch_info_begin = .; *(.arch.info) __arch_info_end = .; __tagtable_begin = .; *(.taglist) __tagtable_end = .; *(.data.init) . = ALIGN(16); __setup_start = .; *(.setup.init) __setup_end = .; __initcall_start = .; *(.initcall.init) __initcall_end = .; . = ALIGN(4096); __init_end = .; } ...... } 2.head-armv.s文件分析[gliethttp 2007-07-25] #if (TEXTADDR & 0xffff) != 0x8000//如果定义地址TEXTADDR非0x8000对齐,编译器报错退出
#error TEXTADDR must start at 0xXXXX8000 #endif
.globl SYMBOL_NAME(swapper_pg_dir) .equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000
.macro pgtbl, reg, rambase adr \reg, stext //伪指令adr,读取与pc相对的stext物理地址 sub \reg, \reg, #0x4000//reg存放值为stext物理地址下的16k空间,对于我的板子来说reg=0x20004000 .endm
.macro krnladr, rd, pgtable, rambase bic \rd, \pgtable, #0x000ff000 .endm
.section ".text.init",#alloc,#execinstr .type stext, #function ENTRY(stext) //++++++++++++++ //由u-boot1.1.5传递函数theKernel(0, bd->bi_arch_number, bd->bi_boot_params); //可知 //r0 = 0; //r1 = bd->bi_arch_number //[在uboot-board_init()中初始化得gd->bd->bi_arch_number = MACH_TYPE_AT91RM9200;] //#define MACH_TYPE_AT91RM9200 251和linux中的一样 //r2 = bd->bi_boot_params //[在uboot-board_init()中初始化得gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;] //#define PHYS_SDRAM 0x20000000 所以从0x2000100地址开始存放linux启动参数, //linux的setup_arch()中mdesc->param_offset指定的0x2000100开始的tag list参数串 //因为固定地址已经相同,所以传不传r2参数已经无所谓了[gliethttp] //-------------- mov r12, r0 mov r0, #F_BIT | I_BIT | MODE_SVC msr cpsr_c, r0 //arm9禁止IRQ和FIQ中断,同时进入SVC超级管理模式 bl __lookup_processor_type//匹配由cp15协处理器读出的cpu型号,分析见后面[gliethttp] //执行__lookup_processor_type处理器匹配函数之后 //r8 = 建立4个1M节MMU页表的标志(0x00000c1e) //r9 = 处理器ID //r10 = 处理器结构体的指针 teq r10, #0 moveq r0, #'p' beq __error//没有找到与硬件cpu匹配的内核,halt系统 bl __lookup_architecture_type//查看与r1型号匹配的体系结构,分析见后面[gliethttp] //执行__lookup_architecture_type体系结构匹配函数之后 //r5 = 体系结构指针下对应的machine_desc->phys_ram物理内存地址 //r6 = 体系结构指针下对应的machine_desc->phys_io物理寄存器地址 //r7 = 体系结构指针下对应的machine_desc->io_pg_offst物理寄存器地址对应的虚拟地址对应的节基址 teq r7, #0 moveq r0, #'a' beq __error//没有找到与r1匹配的体系结构,halt系统 bl __create_page_tables//创建内核启动临时使用的4M页表,但并不启动MMU,分析见后面[gliethttp] adr lr, __ret//伪指令操作,将__ret的物理地址作为lr返回地址 add pc, r10, #12//pc装入b __arm920_setup对应的机器码,执行__arm920_setup函数 //++++++++++++++ //arch/arm/mm/proc-arm9200.S[gliethttp 2007-07-25] //MCR/MRC{cond} p15, opcode_1, Rd, CRn, CRm, opcode_2 //__arm920_setup://小端模式 //mov r0, #0 //mcr p15, 0, r0, c7, c7 //令ICache、DCache缓冲无效 //mcr p15, 0, r0, c7, c10, 4 //耗写缓冲区 //mcr p15, 0, r0, c8, c7 //使指令与数据TLB //mcr p15, 0, r4, c2, c0 //将一级页表的指针r4送入,TTB转换表基寄存器 //mov r0, #0x1f //Domains 0,1为管理域,Domains 2为客户域 //mcr p15, 0, r0, c3, c0 //将r0值置入cp15的c3 //mrc p15, 0, r0, c1, c0 //获取当前c1的控制数据到r0中 //bic r0, r0, #0x0e00 //修改MMU的保护系统:1.系统保护2.ROM 保护 //bic r0, r0, #0x0002 //禁用故障校验 //bic r0, r0, #0x000c //DCache禁用 //bic r0, r0, #0x1000 //ICache禁用 //orr r0, r0, #0x0031 //MMU使能 //orr r0, r0, #0x2100 //异常寄存器基地址定义为:0xFFFF0000;修改系统保护 //#ifndef CONFIG_CPU_DCACHE_DISABLE //orr r0, r0, #0x0004 //DCache使能 //#endif //#ifndef CONFIG_CPU_ICACHE_DISABLE //orr r0, r0, #0x1000 //ICache使能 //#endif //mov pc, lr //返回到__ret继续执行[gliethttp] //-------------- .type __switch_data, %object __switch_data:.long __mmap_switched .long SYMBOL_NAME(__bss_start) .long SYMBOL_NAME(_end) .long SYMBOL_NAME(processor_id) .long SYMBOL_NAME(__machine_arch_type) .long SYMBOL_NAME(cr_alignment) .long SYMBOL_NAME(init_task_union)+8192
.type __ret, %function __ret:ldr lr, __switch_data//__switch_data为返回地址值,起始是__mmap_switched函数地址 mcr p15, 0, r0, c1, c0//将控制值r0置入cp15协处理器的c1控制寄存器,使r0中的控制值生效 mrc p15, 0, r0, c1, c0, 0//1.nop mov r0, r0//2.nop mov r0, r0//3.nop mov pc, lr//经过上面的3个nop无用执行指令之后,arm数据指令体系的三级流水线已经填充完毕 //跳转到__mmap_switched函数继续执行[gliethttp 2007-07-25] .align 5 __mmap_switched: adr r3, __switch_data + 4//adr伪指令获取相对pc的跳转地址 ldmia r3, {r4, r5, r6, r7, r8, sp} //r4 = __bss_start //r5 = _end //r6 = processor_id //r7 = __machine_arch_type //r8 = cr_alignment //sp = (init_task_union)+8192//对于sp的使用,可以参见《浅析armlinux-sp进程切换栈结构和切换函数__switch_to()》 //以及《 浅析armlinux-sp的孵化流程,1号内核线程init的创建》[http://gliethttp.cublog.cn] mov fp, #0 1: cmp r4, r5//清0未初始化变量单元等 strcc fp, [r4],#4 bcc 1b//cc表示如果r4 str r9, [r6]//将r9处理器ID值转存入变量processor_id中,供c程序调用 str r1, [r7]//将r7体系结构指针的machine_desc转存入变量__machine_arch_type中,供c程序调用 #ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #2//r0为cp15协处理器c1中控制值 #endif bic r2, r0, #2//取消掉故障校验后的数值存入r2 stmia r8, {r0, r2}//cr_alignment = r0;其后存储(init_task_union)+8192数值的空间存入r2 b SYMBOL_NAME(start_kernel) //好了,我的分析到此结束,由u-boot1.1.5完成解压的启动部分,转入c代码start_kernel,作更复杂的启动[gliethttp 2007-07-25] __create_page_tables: //通过前面的分析已知r5 = 体系结构指针下对应的machine_desc->phys_ram物理内存地址 //但pgtbl宏并没有使用到r5,而是直接使用stext, pgtbl r4, r5 //所以此时r4=物理地址=0x2000 mov r0, r4 mov r3, #0 add r2, r0, #0x4000//r2=0x20008000 1: str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4//将0x20004000~0x20008000这16k空间清0 teq r0, r2 bne 1b //ok,清0结束[gliethttp] krnladr r2, r4, r5//bic r2,r4,#0x000ff000;将ff处对应的1数据清0 //所以r2 = 0x20000000 //通过前面的分析r8=0x00000c1e //在进行下面的分析之前,最好先看看另一篇文章 //《linux2.4.19下__ioremap函数中remap_area_pages虚拟地址映射建立函数的代码分析》[http://gliethttp.cublog.cn] //熟悉一下MMU的表结构 //MMU对4种大小进行映射管理, //1)微页:构成1k的存储区 //2)小页:构成4k的存储区 //3)大页:构成64k的存储区 //4)节:构成1M的存储区 add r3, r8, r2 //r2=0x20000000 //r3=0x20000000+0x00000c1e=0x20000c1e //r4=0x20004000 //r2>>18剩下的高14位,即16K地址,4k个4字节组地址单元,用于一级表映射bit31~bit20节基址匹配 str r3, [r4, r2, lsr #18]//r4为转换表基 //>>18位,剩余14位,而不是12位,是高12位节基址*4字节存储地址的结果,是bit31~bit20节基址索引×4字节的结果 //表示虚拟地址节0x20000000~0x200fffff对应物理地址节0x20000000~0x200fffff add r0, r4, #(TEXTADDR & 0xff000000) >> 18 bic r2, r3, #0x00f00000 //r2 = 0x20000c1e str r2, [r0] //表示虚拟地址节0xc0000000~0xc00fffff对应物理地址节0x20000000~0x200fffff add r0, r0, #(TEXTADDR & 0x00f00000) >> 18 //接下来开始依次线性节映射[gliethttp 2007-07-25] str r3, [r0], #4 //表示虚拟地址节0xc0000000~0xc00fffff对应物理地址节0x20000000~0x200fffff //r3=0x20000c1e表项值,r0=r0+4,r0指向下一个连续的虚拟地址1M节基址 add r3, r3, #1 << 20 //r3=r3+1M指向下一物理地址节基址 str r3, [r0], #4 //表示虚拟地址节0xc0100000~0xc01fffff对应物理地址节0x20100000~0x201fffff //r0指向下一个连续的虚拟地址1M节基址 add r3, r3, #1 << 20 //r3=r3+1M指向下一物理地址节基址 str r3, [r0], #4 //表示虚拟地址节0xc0200000~0xc02fffff对应物理地址节0x20200000~0x202fffff //r0指向下一个连续的虚拟地址1M节基址 add r3, r3, #1 << 20 //r3=r3+1M指向下一物理地址节基址 str r3, [r0], #4 //表示虚拟地址节0xc0300000~0xc03fffff对应物理地址节0x20300000~0x203fffff //r0指向下一个连续的虚拟地址1M节基址 //这样0xc0000000~0xc03fffff 4M虚拟地址空间和0x20000000~0x200fffff 1M虚拟地址空间 //都线性的映射到了0x20000000~0x203fffff 4M物理地址空间 bic r0, r0, #0x01f00000 >> 18//0x1f=32M空间对齐 and r2, r5, #0xfe000000//对于传递的参数r5-物理内存地址空间 add r3, r8, r2 str r3, [r0]//最后还是0x20000000~0x200fffff虚拟地址到0x20000000~0x200fffff物理地址映射 //如果r5的值和内核拷贝到的内存中的位置值不符,真不知会是什么想象,不过还好,他们相等. bic r8, r8, #0x0c #ifdef CONFIG_DEBUG_LL add r0, r4, r7//r7为debug串口显示物理寄存器对应的虚拟地址对应的节基址 rsb r3, r7, #0x4000//r3=0x4000-r3=距离0x3fff还有多少个地址空间 cmp r3, #0x0800//0xE0000000空间 addge r2, r0, #0x0800//r7对应的虚拟空间<=0xE0000000,加上0x20000000, //调整到0xE0000000~0xFFFFFFFF虚拟空间 addlt r2, r0, r3//r7对应的虚拟空间>0xE0000000,r2=计算出的io虚拟地址映射的大小 orr r3, r6, r8//r6对应串口物理寄存器的物理地址|| r8这个节标志 1: str r3, [r0], #4//将虚拟地址r0所在的1M空间映射到物理地址r3所在的1M空间中,之后r0虚拟地址1M节索引r0+4指向下一节 add r3, r3, #1 << 20//进入物理地址空间的下一个1M节 teq r0, r2//是否映射完毕 bne 1b// #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS) teq r1, #MACH_TYPE_NETWINDER//非该类型,是MACH_TYPE_AT91RM9200类型 teqne r1, #MACH_TYPE_CATS//也非该类型 bne 1f//跳到下一个标号1:处 add r0, r4, #0x3fc0 mov r3, #0x7c000000 orr r3, r3, r8 str r3, [r0], #4 add r3, r3, #1 << 20 str r3, [r0], #4 1: #endif #endif #ifdef CONFIG_ARCH_RPC//未定义 add r0, r4, #0x80 mov r3, #0x02000000 orr r3, r3, r8 str r3, [r0] add r0, r4, #0x3600 str r3, [r0] #endif mov pc, lr//返回 __error: #ifdef CONFIG_DEBUG_LL//实现了串口输出,那么将错误信息打印到串口终端 mov r8, r0 adr r0, err_str bl printascii//打印"\nError: "字符串 mov r0, r8 bl printch//打印 #endif #ifdef CONFIG_ARCH_RPC mov r0, #0x02000000 mov r3, #0x11 orr r3, r3, r3, lsl #8 orr r3, r3, r3, lsl #16 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 str r3, [r0], #4 #endif 1: mov r0, r0//系统因为错误halt在此处 b 1b #ifdef CONFIG_DEBUG_LL err_str: .asciz "\nError: " .align #endif __lookup_processor_type: adr r5, 2f//伪指令adr,读取下面第一个2:标号处的和pc相对的物理地址 ldmia r5, {r7, r9, r10} //r7=虚拟地址__proc_info_end //r9=虚拟地址__proc_info_begin //r10=虚拟地址2b=标号2处的地址值[虚拟地址-反汇编得0xc00081b8] //所以r5=0x200081b8 sub r5, r5, r10//计算标号2物理地址和虚拟地址的间距r5 = 0x200081b8-0xc00081b8; add r7, r7, r5 //将r7中的线性虚拟地址转换成与之线性对应的物理地址,结果存入r7 add r10, r9, r5//同样计算r9中存放的虚拟地址,对应的物理地址,结果存入r10 mrc p15, 0, r9, c0, c0//通过协处理器cp15,获取cpu的型号,对于at91rm9200,r9=0x41129200 //r7 = __proc_info_end结束的物理地址 //r9 = 处理器ID //r10 = 处理器结构体的指针 1: ldmia r10, {r5, r6, r8}//读取编译进r10为起始物理地址的__proc_info_begin结构体信息 //++++++++++++++ //arch/arm/mm/proc-arm9200.S中定义了该结构体信息 // .section ".proc.info", #alloc, #execinstr // .type __arm920_proc_info,#object //__arm920_proc_info: // .long 0x41009200 // .long 0xff00fff0 // .long 0x00000c1e @ mmuflags // b __arm920_setup // .long cpu_arch_name // .long cpu_elf_name // .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB // .long cpu_arm920_info // .long arm920_processor_functions // .size __arm920_proc_info, . - __arm920_proc_info //-------------- //所以r5 = 0x41009200 //r6 = 0xff00fff0 //r8 = 0x00000c1e and r6, r6, r9//r6 = r6 & r9 = 0x41009200 teq r5, r6 moveq pc, lr//因为处理器类型匹配,返回 add r10, r10, #36//如果多处理器,那sizeof(__arm920_proc_info)=0x24=36,r10指向下一个处理器单元 cmp r10, r7 blt 1b//如果没有遍历所有处理器类型,那么跳到标号1:处,继续匹配 mov r10, #0//没有找到匹配的处理器 mov pc, lr 2: .long __proc_info_end .long __proc_info_begin .long 2b .long __arch_info_begin .long __arch_info_end __lookup_architecture_type: adr r4, 2b//伪指令,读取上面第一个2:标号处的和pc相对的物理地址 ldmia r4, {r2, r3, r5, r6, r7} //r2 = 虚拟地址__proc_info_end //r3 = 虚拟地址__proc_info_begin //r5 = 虚拟地址标号2: //r6 = 虚拟地址__arch_info_begin //r7 = 虚拟地址__arch_info_end sub r5, r4, r5//和__lookup_processor_type一样,计算虚拟地址和物理地址的线性偏移量 add r4, r6, r5//r4=__arch_info_begin的物理地址 add r7, r7, r5//r7=__arch_info_end的物理地址 //++++++++++++++ //arch/arm/mach-at91rm9200/Core.c //MACHINE_START(AT91RM9200, "ATMEL AT91RM9200")//.nr = MACH_TYPE_##_type=MACH_TYPE_AT91RM9200=251 //与上面从u-boot传到r1中的一样[gliethttp] //MAINTAINER("SAN People / ATMEL") //BOOT_MEM(AT91_SDRAM_BASE, AT91C_BASE_SYS, AT91C_VA_BASE_SYS) //BOOT_PARAMS(AT91_SDRAM_BASE + 0x100)//tag list存放的物理地址0x20000100[gliethttp] //FIXUP(at91rm9200_fixup) //MAPIO(at91rm9200_map_io) //INITIRQ(at91rm9200_init_irq) //MACHINE_END //-------------- 1: ldr r5, [r4] teq r5, r1//与r1传入的MACH_TYPE_AT91RM9200=251数值匹配 beq 2f//匹配上,则跳到下一个标号2:处,退出 add r4, r4, #SIZEOF_MACHINE_DESC//r4+sizeof(machine_desc),指向下一个体系结构体的物理地址 cmp r4, r7 blt 1b//.arch.info体系结构是否已经遍历结束 mov r7, #0//没有找到与r1类型匹配的体系结构 mov pc, lr 2: ldmib r4, {r5, r6, r7}//先加后装载 //所以r5 = machine_desc->phys_ram //r6 = machine_desc->phys_io //r7 = machine_desc->io_pg_offst mov pc, lr
|