1. u-boot 整体启动流程
bootloader是板子上电到linux系统加载之间的一段执行代码。分为两个启动阶段BL1,BL2。BL1主要用汇编语言编写,做一些初始化工作,并将自身从存储介质如flash拷贝到内存中,然后跳到BL2的c程序入口。BL2加载各个设备的驱动,并提供一个命令行的界面来提供各种操作,最终目的是为了加载linux内核。bootloader将启动参数传递给linux内核让其自举,它的使命也就完成了。下面是s3c6410的启动流程,非常经典。
板子自上电开始启动流程供分为4步:
- iRom(片内Rom)做一些初始化工作
- iRom将flash 或者 SD中的bootloader的前4KB 代码搬移到 stepping stone中。然后这些代码执行bootloader的BL1(第一阶段)
- 第一阶段主要初始化系统时钟、uart、SDRAM。然后将存储介质上的整个bootloader代码搬移到SDRAM中,并跳转到BL2(第二阶段)执行
- 初始化系统环境为加载os做准备
本文分析的s3c2440启动流程稍有差异。s3c2440支持两种启动方式,NOR flash和NAND flash. 无论是哪种方式启动,板子在上电以后都是从 0x00000000 地址开始运行,但是它们的工作方式或者叫地址的空间映射是不同的。由于NOR flash支持随机存取,当使用它启动时,它的地址直接被映射为0x00000000, 那么就直接从NOR flash运行bootloader. 由于NAND flash不支持随机存取,不能直接运行程序。为了从其启动,s3c2440 配备了片内的4KB SRAM(stepping stone)。 当s3c2440从NAND Flash启动时,NAND flash的头4KB程序被自动的加载到片内的SRAM中运行。然后控制权交由bootloader执行。下面是这个过程的示意图。
与s3c6410相比,s3c2440并没有iRom,u-boot的前4KB代码被自动加载到iRAM中运行。接下来就到了u-boot的执行流程了。
2. 两大启动阶段
U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下:
(1). 第一阶段的功能
硬件设备初始化
加载U-Boot第二阶段代码到RAM空间
设置好栈
跳转到第二阶段代码入口
(2). 第二阶段的功能
初始化本阶段使用的硬件设备
检测系统内存映射
将内核从Flash读取到RAM中
为内核设置启动参数
调用内核
3. 第一阶段代码搬移分析
本文学习的是u-boot启动的第一阶段。主要涉及到两个文件 cpu/arm920t/start.S 和board/samsung/mini2440/lowlevel_init.S 硬件初始化工作都是寄存器的操作,这里重点分析u-boot将SDRAM初始化之后将自身搬移到内存中的代码。下面是u-boot从NOR flash将自身搬移到内存的代码。
relocate: /* relocate U-Boot to RAM */ adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ cmp r0, r1 /* don‘t reloc during debug */ beq stack_setup ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 <- size of armboot */ add r2, r0, r2 /* r2 <- source end address */ copy_loop: ldmia r0!, {r3-r10} /* copy from source address [r0] */ stmia r1!, {r3-r10} /* copy to target address [r1] */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop
要理解上面的代码先对几个地址要搞清楚。
.globl _start
_start:
b start_code
_start为u-boot执行的开始标号。它的地址取决于当前u-boot在哪里执行。
_TEXT_BASE:
.word TEXT_BASE
那么去哪里找 TEXT_BASE 呢?它是在./board/samsung/mini2440/config.mk 定义的。如下:
所以如果此时 u-boot 是在内存中运行的话,adr r0, _start 读入的值为程序起始地址即 TEXT_BASE,如果不是在内存中运行,_start 为0,不等于 TEXT_BASE。由此判断当前u-boot是否在内存中,如果在跳转到stack_setup,否则继续往下运行。
.globl _armboot_start
_armboot_start:
.word _start
_armboot_start 与 _start 的值是一致的。
.globl _bss_start
_bss_start:
.word __bss_start
__bss_start 是在链接脚本 u-boot.lds 中定义的。是u-boot的BSS段的起始地址。
由于u-boot不在内存中,_start为0,_armboot也为0。_bss_start为BSS段的起始地址。那么它们两者之间的差值正好等于u-boot程序的大小(BSS段不会被编译到u-boot中)。那么接下来就可以进行拷贝了。
copy_loop: ldmia r0!, {r3-r10} /* copy from source address [r0] */ stmia r1!, {r3-r10} /* copy to target address [r1] */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop
拷贝函数通过 ldmia stmia 完成u-boot代码从nor flash到SDRAM的拷贝。
ldr pc, _start_armboot
_start_armboot: .word start_armboot
此后将pc指针指向start_armboot。也就到了u-boot的第二启动阶段。这个下篇文章继续分析。