U-Boot启动过程源码分析(1)-第一阶段
先综述:第一阶段文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S进行初始化,再跳到第二阶段的入口点lib_arm/board.c中的start_armboot函数
U-boot的定义
U-boot,全称Universal Boot Loader,它的主要功能是完成硬件设备初始化、操作系统代码搬运,并提供一个控制台及一个指令集在操作系统运行前操控硬件设备。
一个嵌入式的存储设备通过通常包括四个分区:
第一分区:存放的当然是u-boot
第二个分区:存放着u-boot要传给系统内核的参数
第三个分区:是系统内核(kernel)
第四个分区:则是根文件系统
U-Boot源代码的目录结构
U-Boot的两种模式:
(一)启动加载(Boot loading)模式: Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。
(二)下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件。
u-boot 分为阶段 1(stage1)和阶段 2(stage2)两部分。依赖于 CPU 体系结构的代码(如 CPU 初始化代码等)通常都放在阶段 1 中且通常用汇编语言实现,而阶段 2 则通常用 C 语言来实现,有很好的可读性和移植性。
第一阶段文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S
结构分析
如smdk2410板子程序的入口点是在/board/smdk2410/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu/arm920t/start.o文件。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; //起始地址
. = ALIGN(4); //4字节对齐
.text : //test指代码段,上面3行标识是不占用任何空间的
{
cpu/arm920t/start.o (.text) //这里把start.o放在第一位就表示把start.s编译时放到最开始,这就是为什么把uboot烧到起始地址上它肯定运行的是start.s
*(.text)
}
. = ALIGN(4); //前面的 “.” 代表当前值,是计算一个当前的值,是计算上面占用的整个空间,再加一个单元就表示它现在的位置
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .; //bss表示归零段
.bss : { *(.bss) }
_end = .;
}
代码真正开始是在_start(u-boot-1.1.6\cpu\arm920t下),设置异常向量表,这样在cpu发生异常时就跳转到/cpu/arm920t/interrupts中去执行相应得中断代码。在interrupts文件中关键的是reset中断代码,跳到reset入口地址。
U-Boot启动第一阶段代码分析
cpu/arm920t/start.S
(1)上电先跳到reset处,这里b是无条件跳转。这里是中断向量表,和硬件合作,发生什么样的异常自动跳到对应异常处执行,
globl _startglobal
_start: b reset//b是不带返回的跳转,
ldr pc, _software_interrupt /* 软件中断向量 */
ldr pc, _prefetch_abort /* 预取指令异常向量 */
.......
/* 中断向量表入口地址 */
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
......
ldr pc, _undefined_instruction表示把_undefined_instruction存放的数值存放到pc指针上 _undefined_instruction: .word undefined_instruction表示未定义的这个异常是由.word来定义的,它表示定义一个字,一个32位的数
. word后面的数:表示把该标识的编译地址写入当前地址,标识是不占用任何指令的。把标识存放的数值copy到指针pc上面,那么标识上存放的值是什么?是由.word undefined_instruction来指定的。
(2)CPU进入SVC模式,reset在管理模式工作
reset:
/* set the cpu to SVC32 mode */
mrs r0, cpsr
bic r0, r0, #0x1f /*工作模式位清零 */ (管理模式),并将中断禁止位和快中断禁止位置1 */
msr cpsr, r0
以上代码将CPU的工作模式位设置为管理模式,即设置相应的CPSR程序状态字,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。
操作系统先注册一个总的中断,然后去查是由哪个中断源产生的中断,再去查用户注册的中断表,查出来后就去执行用户定义的用户中断处理函数。
(3)设置控制寄存器地址
(4)关闭看门狗
(5)屏蔽中断
(6)设置MPLLCON,UPLLCON, CLKDIVN 时钟
(7)关闭MMU,cache ——(也就是做bank的设置)
board/smdk2410/lowlevel_init.S
(8)初始化RAM控制寄存器
(9)复制U-Boot第二阶段代码到RAM
bBootFrmNORFlash函数作用是判断U-Boot是在NAND Flash启动还是NOR Flash启动,若在NOR Flash启动则返回1,否则返回0。根据ATPCS规则,函数返回值会被存放在r0寄存器中,因此调用bBootFrmNORFlash函数后根据r0的值就可以判断U-Boot在NAND Flash启动还是NOR Flash启动。bBootFrmNORFlash定义如下:
int bBootFrmNORFlash(void)
{
volatile unsigned int *pdw = (volatile unsigned int *)0;
unsigned int dwVal;
dwVal = *pdw; /* 先记录下原来的数据 */
*pdw = 0x12345678;
if (*pdw != 0x12345678) /* 写入失败,说明是在NOR Flash启动 */
{
return 1;
}
else /* 写入成功,说明是在NAND Flash启动 */
{
*pdw = dwVal; /* 恢复原来的数据 */
return 0;
}
}
下面来分析NOR Flash启动部分代码:
208 adr r0, _start /* r0 当前代码的开始地址*/
209 ldr r1, _TEXT_BASE /* r1是代码段的连接地址*/
210 cmp r0, r1
/* 判断U-Boot是否已经在RAM若是,则不用再复制到RAM中了,直接跳到设置堆栈部分,这种情况通常在调试U-Boot时才发生 */
211 beq stack_setup
212 /* 、NOR Flash启动的代码 */
213 ldr r2, _armboot_start /*flash中armboot_start的起始地址*/
214 ldr r3, _bss_start /*uboot_bss的起始地址*/
215 sub r2, r3, r2 /* r2 <- size of armboot uboot实际程序代码的大小 */
216 add r2, r0, r2 /* r2 <- source end address */
217 /* 搬运U-Boot自身到RAM中*/
218 copy_loop:
219 ldmia r0!, {r3-r10} /* 从地址为[r0]的NOR Flash中读入8个字的数据 */
220 stmia r1!, {r3-r10} /* 将r3至r10寄存器的数据复制给地址为[r1]的内存 */
221 cmp r0, r2 /* until source end addreee [r2] */
222 ble copy_loop
223 b stack_setup /* 跳过NAND Flash启动的代码 */
(10)设置堆栈
stack_setup:
ldr r0, _TEXT_BASE /* 代码段开始地址:0x33F80000 */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* 代码段下面留出一段给malloc */
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* 留一段存全局参数 */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
//IRQ、FIQ的栈
#endif
sub sp, r0, #12 /* 留12字节给abort异常,剩下内存就是栈 */
(11)清除BSS段
初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。应该将这些变量的初始值赋为0,否则这些变量的初始值将是一个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果。
(12)跳转到第二阶段代码入口 ldr pc, _start_armboot
start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口
ldr pc, _start_armboot
_start_armboot: .word start_armboot
————————————————
版权声明:本文为CSDN博主「张勇1234」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_23084801/article/details/75228959