备注:本文中使用的u-boot版本是2010.3
u-boot启动过程分为两个阶段:汇编语言阶段和C语言启动阶段,这两个阶段分别实现做了些,下面说说我的理解,有不对的地方欢迎指出纠正:
第一阶段:初始化SOC相关的配置,例如:系统时钟、MMU、看门狗等;并为C语言提供运行环境
第二阶段:初始化板级相关的外设,例如:网卡、DDR有效地址等;并初始化环境变量,加载kernel
1、第一阶段:汇编语言阶段
‘ENTRY(_start)’中的符号标签‘_start’为程序的入口,在‘_start’标签处定义的异常向量表的跳转地址:
.globl _start
_start: b reset //复位异常 --> 进入管理模式:复位电平有效时触发
ldr pc, _undefined_instruction //未定义指令异常 --> 未定义指令模式:CPU无法识别的指令时触发
ldr pc, _software_interrupt //软中断异常 -->管理模式:SWI指令触发
ldr pc, _prefetch_abort //预取指令异常 -->中止模式:获取指令不存在时触发
ldr pc, _data_abort //数据异常 --> 中止模式:获取数据不存在时触发
ldr pc, _not_used //保留位
ldr pc, _irq //中断异常 -->IRQ模式
ldr pc, _fiq //快速中断异常 -->FIQ模式
1.1进入管理模式
当CPU上电时,复位电平有效,CPU当做是复位异常处理,因此在此异常向量表中将运行一下指令,使得进入管理模式(supervisor):
b reset
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
1.2 关闭数据Cache 和 MMU
为什么要关闭数据Cache呢,这是因为在设备刚上电的时候,内存的初始化会比较慢,当CPU初始化完成后,
内存此时还没做好准备,如果此时对内存进行数据读取,会导致数据预取异常。
为什么要关闭MMU?在设备刚上电时,MMU并为初始化;其次,在会汇编语言阶段一般是通过操作寄存器的实际物理地址
进行配置。因此为了避免MMU的干扰,把其给屏蔽。
1.3 lowlevel_init
跳转至符号标签‘lowlevel_init’,关闭看门狗、对系统时钟、SDRAM等进行初始:
bl system_clock_init //系统时钟初始化
bl mem_ctrl_asm_init_ddr3 //初始化ddr3
bl uart_asm_init //初始化串口
/* 初始化串口会先后打印‘O’、‘K’ 两个字符 */
/* Print 'O' */
ldr r1, =0x4f4f4f4f
str r1, [r0, #UTXH_OFFSET]
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
1.4 重定位
重定位:将u-boot冲内部的SRAM搬到SDRAM(DDR3)中运行,如果u-boot引进运行在SDRAM中时,则不需要再进行重定位。配置虚拟地址映射表,启动MMU
mmu_table:
……
.set __base,0x400 //物理地址
// 512MB for SDRAM with cacheable
.rept 0x600 - 0x400 //虚拟地址范围
FL_SECTION_ENTRY __base,3,0,1,1
.set __base,__base+1
.endr
……
1.5 初始化C语言运行环境
初始化栈stack_setup:为C语言提供运行环境
清除bss段:bss段用于存放初始化值为0的全局变量和未初始化的全局变量;并将未初始化的全局变量初始化为0,这也是为什么在C语言中为什么一个未初始化全局变量其值为0的原因。
跳转到C语言运行阶段:start_armboot
stack_setup:
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot //将pc指针指向start_armboot函数的地址
_start_armboot:
.word start_armboot
2、第二阶段:C语言阶段
C语言的启动阶段的启动函数:start_armboot,在该函数中中实现了板子相关的初始化配置
重要结构:init_sequence指针数组,包含了板级初始化的相关函数,以下列出了函数的调用关系和功能:
start_armboot:
init_sequence指针数组
board_init,板级相关的初始化(board/samsung/smdkc210),例如网卡驱动
interrupt_init,中断相关的初始化
env_init,环境变量相关的初始化
init_baudrate,波特率初始化
serial_init,串口初始化
console_init_f,控制台第一阶段初始化
display_banner,打印uboot相关的信息
print_cpuinfo,打印CPU相关的信息
checkboard,打印开发板相关的信息
dram_init,配置可用内存大小
display_dram_config,显示内存大小函数:
env_relocate (),初始化环境变量
enable_interrupts,启动中断
main_loop:根据启动延时进行判断,是进入启动kernel阶段,还是进行u-boot命令解析控制台
2.1 命令解析、并将参数传递给kernel
main_loop函数:通过调用run_command函数,执行命令解析
void main_loop (void)
{
……
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
……
run_command (s, 0);
……
}
……
for (;;) {
……
}
}
下面列出了解析名后的函数调用关系:
run_command-->(cmdtp->cmd) (cmdtp, flag, argc, argv):‘s = getenv ("bootcmd")’-->do_bootm函数
do_bootm
do_bootm_linux<-->boot_fn(0, argc, argv, &images)(typedef 类型重命名)
do_bootm_linux:
theKernel = (void (*)(int, int, uint))images->ep;//指向kernel的起始地址
theKernel(0,machid,bd->bi_boot_params);//将3个参数传分别写入寄存器r0,r1,r2传递给kernel