U-boot的初始化主要分为两个阶段
第一阶段:主要是SOC内部的初始化,板级的初始化比较少,所以移植的修改量比较小。此阶段由汇编语言编写,代码主体分布在start.S和lowlevel_init.S中。
其中start.S作为主干,其主要流程为:
注:加粗的比较重要,和板级有点关系
1. 填充16字节的校验位
2. 设置异常向量表
3. 设置cpu为SVC模式
4. 禁用cach和mmu
5. 判断启动介质
6. 在SRAM中设置栈
7. 跳入 lowlevel_init.S
关看门狗
设置供电锁存
判断当前执行位置
初始化时钟
初始化DDR
初始化串口(打印‘OK’)
8. 再次供电锁存
9. 在DDR中设置栈
10. 通过引脚判断启动介质
11. 跳到movi.c
重定位
12. 跳入 lowlevel_init.S设置虚拟地址映射
13. 在DDR中合理的地方设置栈
14. 清bss段
15. 跳入board.c,到DDR中执行程序
第二阶段:主要是板级的初始化,SOC内部的初始化比较小,移植的修改量主要在此。此阶段由c语言编写,代码主体分布在board.c中
- 初始化全局变量gd_t结构体
- 遍历数组执行所有的初始化函数
cpu初始化(没做事)
开始板级初始化,配置网卡用到的GPIO、机器码、内存传参地址
定时器的初始化
环境变量判定
设置波特率
设置串口(没做事)
初始化控制台(没做事)
显示uboot的logo
打印cpu的信息
确认开发板信息
确认DDR信息
打印DDR信息 - 初始化堆
- 利用了linux的设备驱动初始化mmc
- 环境变量重定位
- 往gd赋值ip地址
- 往gd赋值以太网地址
- linux设备驱动初始化
- 跳转表初始化(没做事)
- 控制台的第二部分的初始化
- 中断初始化(没做事)
- 开发板上比较晚期的初始化(没做事)
- 利用驱动初始化网卡
- 升级uboot功能(没做事)
执行完初始化的两个阶段后就进入了uboot的命令行循环部分
个人感觉U-boot的初始化相对于其他部分来说,是真正和板级相关的,所以对于移植来讲是比较重要的,毕竟上层的逻辑代码都是不用修改的…..
1.第一阶段(start.S部分)
下面是简要的代码流程节选分析
- 由于在config.mk中为编译器指定了头文件搜索路径(即顶层目录下的include文件夹),故头文件将默认从顶层目录下的include文件夹内搜索
- 这些头文件其实都不是真正被包含的文件,它们大多是在配置编译阶段产生的符号链接或者是具有符号链接功能的头文件。总之,它们的功能都类似于符号链接,目的是让uboot的源码更具灵活性和移植性
- 比如第一句代码中的config.h,其实产生的效果是让start.S包含了根目录下的include/configs/x210_sd.h
#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>
- 这一部分主要功能是在代码段最开始处放置16字节的填充位,因为sd/nand启动时需要16字节的校验头,这个校验头是在编译阶段通过mkv210image.c中计算并填充的
- 通过伪.word指令定义4个word(32位)的空间来在代码段最开始处占16字节
- 这段空间的地址最后将位于BL1的起始地址之前,即IROM中0xD0020000至0xD0020010,而且并不作为BL1的一部分
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
- 这段代码是设置异常向量表
- .globl是把_start这个标号全局化,是编译器的操作,并不是汇编指令
- 标号_start代表uboot的第一句代码b reset的地址值,即$(TEXT_BASE)指定的重定位后DDR内的虚拟地址即0xc3e00000,但事实上在重定位前,这句代码的实际地址是cpu规定的BL1的起始地址0xD0020010(sram内)
- 从b reset开始其实是在设置异常向量表,这句代码所处的位置是与异常向量表基地址偏移量为的0的地方。当复位异常发生时(开机也是复位异常),cpu会自动跳转入异常表基址偏移量为0处执行复位异常程序
- 其实uboot并没有很细致的处理各种异常,因为uboot比较简单,作为引导程序并不是OS,所以没有必要
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
- 开机后程序正式从此开始
- msr cpsr_c, #0xd3 功能是将IRQ和FIQ禁能,同时把cpu设为SVC模式。具体操作是将0xd3写入cpsr的controlbits,msr是程序状态寄存器读写指令,cpsr_c是指令集中的关键字,表示cpsr的controlbits
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
@;mrs r0,cpsr /*前四句被注释掉了*/
@;bic r0,r0,#0x1f
@;orr r0,r0,#0xd3
@;msr cpsr,r0
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
- 这段开始是和cpu有关初始化相关代码,和板级没有关系,故一般不会去改动
- bl是带返回的跳转,此处跳转去执行禁能L2 cache 相关的代码,然后返回。后面又刷新L1 cache的和Dcache,再后面禁能了MMU功能。
- 为什么要禁能cache和mmu?Icache与Dcache用来加快cpu与内存之间数据与指令的传输速率,但在上电之初,内存初始化比cpu慢一拍,当cpu初始化了,但内存还没准备好就读内存,那么会造成指令取址异常,所以在上电之初要关cache;至于mmu,纯粹是初始化阶段用不到,就把它关了
cpu_init_crit:
一大堆无用的代码,就不贴了
bl disable_l2cache
bl set_l2cache_auxctrl_cycle
bl enable_l2cache
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
- 这里比较重要,是启动介质的判断。最开始从宏内读取启动介质,然后把读到的数据经过处理存放在r2寄存器中
- 后面都是通过比较r2的值来判断启动介质,最后判断得到当前的启动介质是SD/MMC然后把宏#BOOT_MMCSD写入寄存器r3中,宏的值为0x03
- 再将启动介质信息再从寄存器r3中写入INF_REG3_OFFSET寄存器
/* Read booting information */
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
中间一大堆就不贴了
/* NAND BOOT */
cmp r2, #0x0 @ 512B 4-cycle
moveq r3, #BOOT_NAND
cmp r2, #0x2 @ 2KB 5-cycle
moveq r3, #BOOT_NAND
cmp r2, #0x4 @ 4KB 5-cycle 8-bit ECC
moveq r3, <