bootloader启动详解

本文详细解析了U-Boot启动加载过程中的关键步骤,包括硬件初始化、内存映射、异常处理向量表的设置以及从Flash到RAM的重定位等。通过分析U-Boot的start.S文件,深入探讨了ARM处理器的异常处理机制及其对启动过程的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ubootstart.S详解

#include
#include
/*
这段代码的主要作用是初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境 */
#if defined(CONFIG_OMAP1610)
#include <./configs/omap1510.h>
#elif defined(CONFIG_OMAP730)
#include <./configs/omap730.h>
#endif
/*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************/
.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 /*
快速中断异常向量
*/
_undefined_instruction:
.word undefined_instruction
_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
/************************************************
分析
****************************************************
*
从中我们可以看出,ARM支持7种异常。问题是发生了异常后ARM是如何响应的呢?

*
第一个复位异常很好理解,它放在0x0的位置,一上电就执行它,而且我们的程序总是从
*
复位异常处理程序开始执行的,因此复位异常处理程序不需要返回。那么什么时候会执行
*
到后面几个异常处理函数呢?步骤是这样的:
*
*
当一个异常出现以后,ARM会自动执行以下几个步骤:
* (1)
把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。
* (2)
将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR
* (3)
根据异常类型,强制设置CPSR的运行模式位。
* (4) PC
(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中。
*
*
当异常处理完毕后,ARM会执行以下几步操作从异常返回:
* (1)
将连接寄存器LR的值减去相应的偏移量后送到PC
* (2)
SPSR复制回CPSR
* (3)
若在进入异常处理时设置了中断禁止位,要在此清除
*
* ARM
规定了异常向量的地址:
* b reset
; 复位 0x0
* ldr pc, _undefined_instruction
; 未定义的指令异常
0x4
* ldr pc, _software_interrupt
; 软件中断异常
0x8
* ldr pc, _prefetch_abort
; 预取指令
0xc
* ldr pc, _data_abort
; 数据
0x10
* ldr pc, _not_used
; 未使用
0x14
* ldr pc, _irq
; 慢速中断异常
0x18
* ldr pc, _fiq
; 快速中断异常
0x1c
*
这样理解这段代码就非常简单了。碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到

*
相应的处理程序,然后再返回到主程序继续执行。
*
* .balignl 16,0xdeadbeef,
将地址对齐到16的倍数,如果地址寄存器的值(PC)跳过4个字节才是16的倍数,
*
则使用0xdeadbeef填充这4个字节,如果它跳过123个字节,则填充值不确定。如果地址寄存器的值(PC
*
16的倍数,则无需移动。
********************************************************************************************************/
/*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram
* setup stack
*
*************************************************************************/
/*
保存变量的数据区 */
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/* These are defined in the board-specific linker script.*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/*************************************************
分析
**********************************************************
*
上面这段代码,主要保存一些全局变量,用于BOOT程序从FLASH拷贝到RAM,或者其它的使用。

*
还有一些变量的值是通过连接脚本得到的,比如TEXT_BASE位于/u-boot-1.1.6/board/xxx(开发板目录名称)/config.mk
*
文件里。__bss_start_end位于/u-boot-1.1.6/board/xxx(开发板目录名称)/u-boot.lds文件里,具体值是由编译器算出来的。

***************************************************************************************************************/
/* the actual reset code*/
/*
系统的复位代码。系统一上电,就跳到这里运行 */
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr /*
取得当前程序状态寄存器cpsr
r0 */
bic r0,r0,#0x1f /*
这里使用位清除指令,把中断全部清除,只置位模式控制位为中断提供服务通常是
OS
*
设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。

*/
orr r0,r0,#0xd3 /*
计算为超级保护模式 */
msr cpsr,r0 /*
设置cpsr为超级保护模式
*/
/***********************************************
分析
*************************************************************
*
设置cpu运行在SVC32模式。ARM共有7种模式
:
*
用户模式(usr) arm处理器正常的程序执行状态

*
快速中断模式(fiq): 用于高速数据传输或通道处理
*
外部中断模式(irq): 用于通用的中断处理
*
超级保护模式(svc): 操作系统使用的保护模式
*
数据访问终止模式(abt): 当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护
*
系统模式(sys): 运行具有特权的操作系统任务
*
未定义指令中止模式(und): 当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真
*
*
通过设置ARMCPSR寄存器,让CPU运行在操作系统保护模式,为后面进行其它操作作好准备了。
****************************************************************************************************************/
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
/******************************************************************************
* BL
为相对寻址,以程序计数器PC 的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址
* ARM
指令集中的4条跳转指令可以完成从当前指令向前或向后的32MB 的地址空间的跳转,
*
用的是相对寻址,它们是:BBLBLXBX
*******************************************************************************/
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
/*
重定位Boot代码到RAM内存,将Boot代码从FLASH移到RAM
*/
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
/**************************************************************************
*
_start的相对地址移到r0, 相对寻址以程序计数器PC 的当前值为基地址,

*
指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。
*
它是与位置无关的,主要看Boot在哪里运行,也就是PC指针在哪里 (假设_start偏移量为0)
*
例如这段代码在 0x02000000 (FLASH起始地址)运行,即此时PC=0x02000000,那么 adr r0, _start 得到 r0 = 0x02000000
*
如果在地址 0x81008000BootRAM中加载地址)运行,即此时PC=0x81008000,那么r0就是 0x81008000 了。
*
*
此处要注意ldradr的区别,看下面的代码片段:
* ldr r0, _start
* adr r0, _start
* ldr r0, =_start
* nop
* mov pc, lr
* _start:
* nop
*
下面是反汇编的结果:
* 0c008000 <_start-0x14>:
* c008000: e59f000c ldr r0, [pc, #12] ; c008014 <_start>
* c008004: e28f0008 add r0, pc, #8 ; 0x8
* c008008: e59f0008 ldr r0, [pc, #8] ; c008018 <_start+0x4>
* c00800c: e1a00000 nop (mov r0,r0)
* c008010: e1a0f00e mov pc, lr
*
* 0c008014 <_start>:
* c008014: e1a00000 nop (mov r0,r0)
*
*
分析:
* ldr r0, _start
*
从内存地址 _start 的地方把值读入。执行这个后,r0 = 0xe1a00000
*
* adr r0, _start
*
取得 _start 的地址到 r0,但是请看反编译的结果,它是与位置无关的。其实取得的是相对的位置。例如这段代码在 0x0c008000 运行,

*
那么 adr r0, _start 得到 r0 = 0x0c008014;如果在地址 0 运行,就是 0x00000014 了。即当前PC值加上_start的偏移量。
*
* ldr r0, =_start
*
这个取得标号 _start 的绝对地址。这个绝对地址是在 link 的时候确定的。看上去这只是一个指令,但是它要占用 2 32bit 的空间,
*
一条是指令,另一条是 _start 的数据(因为在编译的时候不能确定 _start 的值,所以不能直接用 mov 指令来给 r0 赋一个 32bit 的常量,
*
所以需要多出一个空间存放 _start 的真正数据,这个数据是在 link 的时候确定的,在这里就是 0x0c008014)。
*
因此可以看出,这个是绝对的寻址,不管这段代码在什么地方运行,它的结果都是 r0 = 0x0c008014
**************************************************************************/
ldr r1, _TEXT_BASE/* test if we run from flash or RAM */ /*
_TEXT_BASE地址处的值TEXT_BASE,也就是BOOTRAM中运行地址移到
r1 */
cmp r0, r1 /* don't reloc during debug */ /*
比较两个地址是否相同,如果相同,就已经在RAM运行,否则就是FLASH中运行 */

 

 

 

beq stack_setup
/*
如果是在FLASH中运行, 则把FLASH中的Boot代码移到RAM中,然后再运行 */
ldr r2, _armboot_start /*
_armboot_start地址处的值也就是_start绝对地址(也即在内存中的地址,这个绝对

*
地址是在 link 的时候确定的,如0x81008000)移到r2 */
ldr r3, _bss_start /*
_bss_start地址处的值也就是__bss_start绝对地址(也即在内存中的地址,这个绝对

*
地址是在 link 的时候确定的)移到r3 */
sub r2, r3, r2 /* r2 <- size of armboot */ /*
计算引导代码大小并存到
r2 */
add r2, r0, r2 /* r2 <- source end address */ /*
计算引导代码最后相对地址并存入
r2 */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */ /*
从源地址[r0]读取32个字节到寄存器,并更新
r0 */
stmia r1!, {r3-r10} /* copy to target address [r1] */ /*
拷贝寄存器r3-r1032个字节值保存到 [r1]指明的地址,并更新r1的值
*/
cmp r0, r2 /* until source end addreee [r2] */ /*
循环拷贝,直到把所有引导代码都移到内存
*/
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack */
/*
在内存中建立起堆栈
*/
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
/*
初始化内存中bss段中数据为
0 */
clear_bss:
ldr r0, _bss_start /* find start of bss segment*/ /*
_bss_start地址处存储的绝对地址移到
r0 */
ldr r1, _bss_end /* stop here */ /*
_bss_end地址处存储的绝对地址移到
r1 */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... STR
指令用于从源寄存器中r2将一个32 位的字数据传送到存储器中
[r0]*/
add r0, r0, #4
cmp r0, r1
ble clbss_l /*
小于或等于跳转
*/
ldr pc, _start_armboot /***********************************************************
*
已经准备好了堆栈,就可跳到C写的代码里了,也就是

*
跳到内存中的/u-boot-1.1.6/board.c --> start_armboot中运行了
*
_start_armboot地址处的值也就是start_armboot绝对地址值移到pc
*
神啊!终于跳到C代码了。

***********************************************************/
_start_armboot:
.word start_armboot
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
/**************************************************************************
* 1
、关闭 MMUCPU 内部指令/数据 (I/Dcache
* 2
、设置 CPU 的速度和时钟频率。
* 3
RAM 初始化。
****************************************************************************/
cpu_init_crit:
/* flush v4 I/D caches*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/******************************************************************************************************
* MCR
指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:
* MCR
协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2
*
其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,
*
源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。

******************************************************************************************************/
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/ * disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
/ * Go setup Memory and board specific bits prior to relocation.*/
mov ip, lr /* perserve link reg across call */
bl lowlevel_init /* go setup pll,mux,memory */ /*
位于u-boot-1.1.6/board/xxx(开发板目录名称)/lowlevel_init.S */
mov lr, ip /* restore link */
mov pc, lr /* back to my caller */ /*
cpu_init_crit子函数返回
*/
/*************************************************************************
*
* Interrupt handling
*
*************************************************************************/
@ IRQ stack frame.
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
@ carve out a frame on current user stack
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
@ get values for "aborted" pc and cpsr (into parm regs)
ldmia r2, {r2 - r3}
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp @ save current stack into r0 (param register)
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
@ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
str lr, [r13] @ save caller lr in position 0 of saved stack
mrs lr, spsr @ get the spsr
str lr, [r13, #4] @ save spsr in position 1 of saved stack
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13 @ switch modes, make sure moves will execute
mov lr, pc @ capture return pc
movs pc, lr @ jump to next instruction & switch modes.
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
.align 5 /*‘.align 5’
向后移动位置计数器直至32(2^5)的倍数(计数器的最低的5位为0)。如果地址已经是32倍数,则无需移动。
*/
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值