本节总结:
uboot启动流程如下:
- 1)设置CPU为管理模式
- 2)关看门狗
- 3)关中断
- 4)设置时钟频率
- 5)关mmu,初始化各个bank
- 6)进入board_init_f()函数 (初始化定时器,GPIO,串口等,划分内存区域)
- 7)重定位 复制uboot,然后修改SDRAM上的uboot链接地址)
- 8)清bss
- 9)跳转到board_init_r()函数,启动流程结束
1.首先来安装arm-linux-gcc-4.3.2交叉编译器
mkdir arm-linux-gcc-4.3.2 //创建目录 tar -xjf arm-linux-gcc-4.3.2.tar.bz2 -C arm-linux-gcc-4.3.2/ //解压到arm-linux-gcc-4.3.2目录下
然后添加环境变量:
有两种方法,第一种只是临时修改,重启虚拟机便会复位:
export PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ... //将arm-linux-gcc-4.3.2添加到环境变量
第二种,重启不复位:
vi /etc/environment 添加: PATH=/arm-linux-gcc-4.3.2/usr/local/arm/4.3.2/bin:/usr/sbin:/usr/bin... ... //将arm-linux-gcc-4.3.2添加到环境变量
2.然后进入ftp://ftp.denx.de/pub/u-boot/来下载u-boot-2012.04.01
2.1.创建source insight工程,来看代码
1)在board 目录下只添加:
board/samsung/smdk2410/ // (2410单板文件)
2)在arch 目录下只添加:
arch/arm/cpu/arm920t/ //(只添加这个目录下的*.c,*.S公用文件) arch/arm/cpu/arm920t/s3c24x0/ //(24x0架构所有文件) arch/arm/include/asm/ //(只添加这个目录下的*.h公用头文件) arch/arm/include/asm/proc-armv/ //(arm架构的文件) arch/arm/include/asm/arch-s3c24x0/ //(24x0架构头文件) arch/arm/lib/ //(与arm相关的库文件)
3)在include/configs目录下只添加:
include/configs/smdk2410.h // (用来配置2410单板的头文件)
2.2编译烧写:
tar xjf u-boot-2012.04.01.tar.bz2 cd u-boot-2012.04.01 //进入解压后文件目录 make smdk2410_config //由于该uboot不支持2440板卡,所以只有配置2410板卡 make //编译,生成u-boot.bin
3.最后烧写u-boot.bin,发现无法启动,接下来便来分析uboot的启动流程
4.首先查看arch/arm/cpu/u-boot.lds链接脚本
如下图所示,看到uboot最开始会进入_start:
5. _start位于arch/arm/cpu/arm920t/start.S
所以,我们从start.S开始分析uboot启动流程:
.globl _start //声明_start全局符号,这个符号会被lds链接脚本用到 _start: b start_code //跳转到start_code符号处,0x00 ldr pc, _undefined_instruction //0x04 ldr pc, _software_interrupt //0x08 ldr pc, _prefetch_abort //0x0c ldr pc, _data_abort //0x10 ldr pc, _not_used //0x14 ldr pc, _irq //0x18 ldr pc, _fiq //0x20 _undefined_instruction: .word undefined_instruction //定义_undefined_instruction指向undefined_instruction(32位地址) _software_interrupt: .word software_interrupt _prefetch_abort: .word prefetch_abort _data_abort: .word data_abort _not_used: .word not_used _irq: .word irq _fiq: .word fiq .balignl 16,0xdeadbeef //balignl使用,参考http://www.cnblogs.com/lifexy/p/7171507.html
其中符号保存的地址都在顶层目录/system.map中列出来了
6. 从上面看到, _start会跳转到start_code处
start_code: /*设置CPSR寄存器,让CPU进入管理模式*/ mrs r0, cpsr //读出cpsr的值 bic r0, r0, #0x1f //清位 orr r0, r0, #0xd3 //位或 msr cpsr, r0 //写入cpsr #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) /* * relocate exception table */ ldr r0, =_start ldr r1, =0x0 //r1等于异常向量基地址 mov r2, #16 copyex: subs r2, r2, #1 //减16次,s表示每次减都要更新条件标志位 ldr r3, [r0], #4 str r3, [r1], #4 //将_start标号后的16个符号存到异常向量基地址0x0~0x3c处 bne copyex //直到r2减为0 #endif #ifdef CONFIG_S3C24X0 /* 关看门狗*/ # define pWTCON 0x53000000 # define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */ # define INTSUBMSK 0x4A00001C # define CLKDIVN 0x4C000014 /* clock divisor register */ ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] //关看门狗,使WTCON寄存器=0 /*关中断*/ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0] //关闭所有中断 # if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0] //关闭次级所有中断 # endif /* 设置时钟频率, FCLK:HCLK:PCLK = 1:2:4 ,而FCLK默认为120Mhz*/ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit //关闭mmu,并初始化各个bank #endif call_board_init_f: ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80 bic sp, sp, #7 //sp=0x30000f80 ldr r0,=0x00000000 bl board_init_f
上面的CONFIG_SYS_INIT_SP_ADDR =0x30000f80,是通过arm-linux-objdump -D u-boot>u-boot.dis生成反汇编,然后从u-boot.dis得到的,如下图所示:
7.然后进入第一个C函数:board_init_f()
该函数主要工作是:
清空gd指向的结构体、通过init_sequence函数数组,来初始化各个函数以及逐步填充gd结构体,最后划分内存区域,将数据保存在gd里,然后调用relocate_code()对uboot重定位
(gd是用来传递给内核的参数)
1)具体代码如下所示:
void board_init_f(ulong bootflag) // bootflag=0x00000000 { bd_t *bd; init_fnc_t **init_fnc_ptr; //函数指针 gd_t *id; ulong addr, addr_sp; #ifdef CONFIG_PRAM ulong reg; #endif bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f"); /* Pointer is writable since we allocated a register for it */ gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
其中gd是一个全局变量,用来传递给内核的参数用的,如下图所示,在arch/arn/include/asm/global_data.h中定义,*gd指向r8寄存器,所以r8专门提供给gd使用
而CONFIG_SYS_INIT_SP_ADDR,在6节里得到=0x30000f80,所以gd=0x30000f80
2)继续来看board_init_f():
__asm__ __volatile__("": : :"memory"); //memory:让cpu重新读取内存的数据 memset((void *)gd, 0, sizeof(gd_t)); //将0x30000f80地址上的gd_t结构体清0 gd->mon_len = _bss_end_ofs; // _bss_end_ofs =__bss_end__ - _start,在反汇编找到等于0xae4e0,所以mon_len等于uboot的数据长度 gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob); for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) //调用init_sequence[]数组里的各个函数 { if ((*init_fnc_ptr)() != 0) //执行函数,若函数执行出错,则进入hang() { hang (); //打印错误信息,然后一直while } }
上面的init_sequence[]数组里存了各个函数,比如有:
- board_early_init_f():设置系统时钟,设置各个GPIO引脚
- timer_init():初始化定时器
- env_init():设置gd的成员变量
- init_baudrate():设置波特率
- dram_init():设置gd->ram_size= 0x04000000(64MB)
3)继续来看board_init_f():
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; // addr=0x34000000 // CONFIG_SYS_SDRAM_BASE: SDRAM基地址,为0X30000000 // gd->ram_size: 等于0x04000000 #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) /* reserve TLB table */ addr -= (4096 * 4); //addr=33FFC000 addr &= ~(0x10000 - 1); // addr=33FF0000, gd->tlb_addr = addr; //将64kB分配给TLB,所以TLB地址为33FF0000~33FFFFFF #endif /* round down to next 4 kB limit */ addr &= ~(4096 - 1); //4kb对齐, addr=33FF0000 debug("Top of RAM usable for U-Boot at: %08lx\n", addr); /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ addr -= gd->mon_len; // 在前面分析过gd->mon_len=0xae4e0, //所以addr=33FF0000 -0xae4e0=33F41B20, addr &= ~(4096 - 1); //4095=0xfff,4kb对齐, addr=33F41000 //所以分配给uboot各个段的重定位地址为33F41000~33FFFFFF debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr); #ifndef CONFIG_SPL_BUILD addr_sp = addr - TOTAL_MALLOC_LEN; //分配一段malloc空间给addr_sp //TOTAL_MALLOC_LEN=1024*1024*4,所以malloc空间为33BF1000~33F40FFF addr_sp -= sizeof (bd_t); //分配一段bd_t结构体大的空间 bd = (bd_t *) addr_sp; //bd指向刚刚分配出来的bd_t结构体 gd->bd = bd; // 0x30000f80处的gd变量的成员bd等于bd_t基地址 addr_sp -= sizeof (gd_t); //分配一个gd_t结构体大的空间 id = (gd_t *) addr_sp; //id指向刚刚分配的gd_t结构体 gd->irq_sp = addr_sp; //0x30000f80处的gd变量的成员irq_sp等于gd_t基地址 addr_sp -= 12; addr_sp &= ~0x07; ... ... relocate_code(addr_sp, id, addr); //进入relocate_code()函数,重定位代码,以及各个符号 // addr_sp: 栈顶,该栈顶向上的位置用来存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb), //id: 存放 gd_t结构体的首地址 // addr: 等于存放uboot重定位地址33F41000 }
执行完board_init_f()后,最终内存会划分如下图所示:
其实此时uboot还在flash中运行,然后会进入start.S的relocate_code()里进行uboot重定位
8.接下来进入重定位
1)start.S的relocate_code()代码如下所示
relocate_code: mov r4, r0 /* save addr_sp */ // addr_sp栈顶值 mov r5, r1 /* save addr of gd */ // id值 mov r6, r2 /* save addr of destination */ // addr值:uboot重定位地址 /* Set up the stack */ stack_setup: mov sp, r4 //设置栈addr_sp adr r0, _start //在顶层目录下system.map符号文件中找到_start =0,所以r0=0 cmp r0, r6 //判断_start(uboot重定位之前的地址)和addr(重定位地址)是否一样 beq clear_bss /* skip relocation */ mov r1, r6 /* r1 <- scratch for copy_loop */ //r1= addr(重定位地址) ldr r3, _bss_start_ofs //_bss_start_ofs=__bss_start - _start(uboot代码大小) add r2, r0, r3 /* r2 <- source end address*/ //r2= uboot重定位之前的结束地址 copy_loop: ldmia r0!, {r9-r10} /* copy from source address [r0] */ //将r0处的两个32位数据拷到r9-r10中,然后r0+=8 stmia r1!, {r9-r10} /* copy to target address [r1]*/ //将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8 cmp r0, r2 /* until source end address [r2]*/ //判断拷贝的数据是否到结束地址 blo copy_loop
上面只是把代码复制到SDRAM上,而链接地址内容却没有改变,比如异常向量0x04的代码内容还是0x1e0,
我们以异常向量0x04为例,来看它的反汇编:
如上图所示,即使uboot在SDRAM运行,由于代码没修改,PC也会跳到0x1e0(flash地址)上
和之前老的uboot有很大区别,以前老的uboot直接是使用的SDRAM链接地址,如下图所示:
所以,新的uboot采用了动态链接地址的方法,在链接脚本uboot.lds中,可以看到这两个段(.rel.dyn、.dynsym):
该两个段里,便是保存了各个文件的相对动态信息(.rel.dyn)、动态链接地址的符号(.dynsym)
以上图的.rel.dyn段为例来分析,找到__rel_dyn_start符号处:
如上图所示,其中0x17表示的是符号的结束标志位,我们以0x20为例来讲解:
在之前,我们讲过0x20里面保存的是异常向量0x04跳转的地址(0x1e0),如下图所示:
所以,接下来的代码,便会根据0x20里的值0x1e0(flash地址),将SDRAM的33F41000+0x20的内容改为33F41000+0x1e0(SDRAM地址),来改变uboot的链接地址
2)重定位的剩余代码,如下所示:
#ifndef CONFIG_SPL_BUILD /* * fix .rel.dyn relocations */ ldr r0, _TEXT_BASE /* r0 <- Text base */ //r0=text段基地址=0 sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位后的偏移值=33F41000 ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */ //_dynsym_start_ofs =__dynsym_start - _start=0x73608 //所以r10=动态符号表的起始偏移值=0x73608 add r10, r10, r0 /* r10 <- sym table in FLASH */ //r10=flash上的动态符号表基地址=0x73608 ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */ //r2=__rel_dyn_start - _start=0x6b568 //所以r2=相对动态信息的起始偏移值=0x6b568 add r2, r2, r0 /* r2 <- rel dyn start in FLASH */ //r2=flash上的相对动态信息基地址=0x6b568 ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */ // _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608 //所以r3=相对动态信息的结束偏移值=00073608 add r3, r3, r0 /* r3 <- rel dyn end in FLASH */ //r3=flash上的相对动态信息结束地址=0x6b568 fixloop: ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */ //以0x20为例,r0=0x6b568地址处的内容= 0x20 add r0, r0, r9 /* r0 <- location to fix up in RAM */ //r0=33F41000+0x20=33F41020 ldr r1, [r2, #4] //r1= 33F41024地址处的内容=0x17 and r7, r1, #0xff cmp r7, #23 /* relative fixup? */ //0x17=23,所以相等 beq fixrel //跳到:fixerl cmp r7, #2 /* absolute fixup? */ beq fixabs /* ignore unknown type of fixup */ b fixnext fixabs: /* absolute fix: set location to (offset) symbol value */ mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */ add r1, r10, r1 /* r1 <- address of symbol in table */ ldr r1, [r1, #4] /* r1 <- symbol value */ add r1, r1, r9 /* r1 <- relocated sym addr */ b fixnext fixrel: /* relative fix: increase location by offset */ ldr r1, [r0] //r1=33F41020地址处的内容=0x1e0 add r1, r1, r9 //r1=0x1e0+33F41000= 33F411e0 fixnext: str r1, [r0] //改变链接地址里的内容, 33F41020=33F411e0 (之前为0x1e0) add r2, r2, #8 //r2等于下一个相对动态信息(0x24)的地址 cmp r2, r3 //若没到尾部__rel_dyn_end,便继续执行: fixloop blo fixloop #endif
9.清除bss段
/*重定位完成后,清除bss段*/ clear_bss: #ifndef CONFIG_SPL_BUILD ldr r0, _bss_start_ofs //获取flash上的bss段起始位置 ldr r1, _bss_end_ofs //获取flash上的bss段结束位置 mov r4, r6 /* reloc addr */ //获取r6(SDRAM上的uboot基地址) add r0, r0, r4 //加上重定位偏移值,得到SDRAM上的bss段起始位置 add r1, r1, r4 //得到SDRAM上的bss段结束位置 mov r2, #0x00000000 /* clear*/ clbss_l: str r2, [r0] /* clear loop... */ //开始清除SDRAM上的bss段 add r0, r0, #4 cmp r0, r1 bne clbss_l bl coloured_LED_init bl red_led_on #endif
9.1继续往下分析
#ifdef CONFIG_NAND_SPL //未定义,所以不执行 ... ... #else //执行else ldr r0, _board_init_r_ofs //r0=flash上的board_init_r()函数地址偏移值 adr r1, _start //0 add lr, r0, r1 //返回地址lr=flash上的board_init_r()函数 add lr, lr, r9 //加上重定位偏移值(r9)后,lr=SDRAM上的board_init_r()函数 /* setup parameters for board_init_r */ mov r0, r5 /* gd_t */ //r0=id值 mov r1, r6 /* dest_addr */ //r1=uboot重定位地址 /* jump to it ... */ mov pc, lr //跳转: board_init_r()函数 _board_init_r_ofs: .word board_init_r - _start //获取在flash上的board_init_r()函数地址偏移值 #endif
从上面代码看出, 接下来便会进入uboot的board_init_r()函数,该函数会对各个外设初始化、环境变量初始化等.
uboot的启动过程到此便结束了.