/********************************************************************
Bootloader第一阶段的功能:(运行在Flash当中)
-- 硬件设备初始化 (cpu/arm920t/start.S下的reset函数)
-- 为加载Bootloader的第二阶段代码准备RAM空间 (board/smdk2410/lowlevel_init.S)
-- 复制Bootloader的第二阶段代码到RAM空间中 (cpu/arm920t/start.S下的relocate函数)
-- 设置好栈(原因:为下一步准备的,因为C语言的运行需要堆栈)(cpu/arm920t/start.S下的stack_setup函数)
-- 跳转到第二阶段代码的C入口点 (cpu/arm920t/start.S下的clear_bss函数)
硬件初始化包括:将CPU的工作模式设为管理模式(SVC),关闭WATCHDOG、设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存
器),关闭MMU、CACHE
********************************************************************/
调用内核前,下列条件要满足:
(1)CPU寄存器的设置
R0 = 0
R1 = 机器类型ID
R2 = 启动参数标记列表在RAM中起始基地址
(2)CPU工作模式
必须禁止中断(IRQs和FIQs)
CPU必须为SVC模式
(3)Cache和MMU的设置
MMU必须关闭
指令Cache可以打开也可以关闭,数据Cache必须关闭
********************************************************************/
这个图特别重要,在网上流传比较经典的一个图
第一阶段代码-----源代码“/arch/arm920t/cpu/start.s”分析
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 //在当前地址处放入一个16bits值
********************************************************************/
中断向量表放在0x0开始的地方,系统上电后,会从0x0处取指令,而0x0处放置的是reset标签,直接就跳去reset标签处去启动系统了
利用.word来在当前位置放置一个值,这个值实际上就用对应的中断处理函数的地址
_undefined_instruction: .word undefined_instruction
undefined_instruction在后面给出了具体的操作:
undefined_instruction:
get_bad_stack //宏:对stack的操作
bad_save_user_regs //宏:用户regs的保存
bl do_undefined_instruction
在跳转到中断服务子程序前,先有两个宏代码,一个是对stack的操作,另外一个是用户regs的保存,然后跳转到中断服务子程序中执行。中断子程序都在“cpu/arm920t/interrupts.c”中定义
2.U-Boot存储器映射定义
/********************************************************************
_TEXT_BASE:
.word TEXT_BASE //编译器指定的内存运行地址
.globl _armboot_start
_armboot_start:
.word _start //_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
********************************************************************/
如上图“U-Boot存储器映射”所示
3.上电后CPU为svc模式
/********************************************************************
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f //0x1f=00011111,相当于清除低5位,刚好是模式位
orr r0,r0,#0xd3 //0xd3=11010011,设置5,6,7位的状态位。禁止 FIQ,IRQ,处于arm状态
msr cpsr,r0
********************************************************************/
CPSR 寄存器(和保存它的 SPSR 寄存器)中的位分配如下:
31 30 29 28 --- 7 6 5 4 3 2 1 0
N Z C V I F T M4 M3 M2 M1 M0
1 0 0 0 0 User 模式
1 0 0 0 1 FIQ 模式
1 0 0 1 0 IRQ 模式
1 0 0 1 1 SVC 模式
1 0 1 1 1 ABT 模式
1 1 0 1 1 UND 模式
I=1:禁止IRQ中断 F=1:禁止FIQ中断
T=0:为ARM状态 T=1:为THUMB状态
4.关闭看门狗、屏蔽中断、修改时钟除数寄存器
/********************************************************************
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses
# define CLKDIVN 0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON 0x53000000 //喂狗寄存器
# define INTMSK 0x4A000008 //中断屏蔽寄存器 1:屏蔽中断
# define INTSUBMSK 0x4A00001C //中断次级屏蔽寄存器 1:屏蔽中断
# define CLKDIVN 0x4C000014 //时钟分频控制寄存器
#endif
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*******************************************************************
5.调用cpu_init_crit
/*******************************************************************
#ifndef CONFIG_SKIP_LOWLEVEL_INIT //防止重复引用cpu_init_crit函数
bl cpu_init_crit
#endif
*******************************************************************/
/*******************************************************************
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0 //把协处理器c1写入r0中,查看寄存器c1各控制位,《ARM体系结构与编程》174页
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
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
*******************************************************************/
mov ip, lr说明:
为了使单独编译的C语言程序和汇编程序之间能相互调用,必须为子程序间的调用规定一定的规则,这就是所谓的ATPCS规则。在ATPCS规则中,寄存器R12作用于子程序间的scratch寄存器,记作ip。如果这里使用别的通用寄存器来代替ip,实现的功能是一样的
6.调用lowlevel_init
cpu_init_crit函数又调用了lowlevel_init函数,lowlevel_init函数用来设置存储器控制器,使得外接的SDRAM可用,对SDRAM进行初始化,代码在
“board/smdk2410/lowlevel_init.S”
/*******************************************************************
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA // SMRDATA表示13个寄存器值的存放开始地址(链接地址),处于内存中
ldr r1, _TEXT_BASE // _TEXT_BASE表示编译器指定的内存运行地址
sub r0, r0, r1 //r0 = SMRDATA - TEXT_BASE+0x0,r0就是13个寄存器在NOR FLASH上存放的起始地址
ldr r1, =BWSCON // BWSCON = 0x48000000
add r2, r0, #13*4 //r2 = r0 + 13*4
0:
ldr r3, [r0], #4 // [r0]->r3, r0 = r0 + 4
str r3, [r1], #4 // r3->[r1], r1 = r1 + 4
r1 = BWSCON的地址=0x48000000
BANKCON0 = 0x48000004
BANKCON1 = 0x48000008
BANKCON2 = 0x4800000C
BANKCON3 = 0x48000010
BANKCON4 = 0x48000014
BANKCON5 = 0x48000018
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA: //13个寄存器的值保存在文字池中
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
*******************************************************************/
lowlevel_init函数中的代码、数据都只保存在NOR FLASH上,内存中还没有,所以读取数据时要变换地址
关于地址SMRDATA和TEXT_BASE:
SMRDATA:表示这13个寄存器存放的起始地址(连接地址),处于内存中,因为此时代码、数据都只保存在NOR FALSH上,内
存中还没有,所以要计算出相对在FLASH上对应的地址,然后读取其值。ARM上电后,假设从NAND FLASH运行,它
会把从NAND FLASH的前4K加载到内存中运行
TEXT_BASE: 编译器指定的内存运行地址,其在内存中,因为从FLASH中复制代码到内存中时,是从FLASH的0x0地址处开始复
制到内存的TEXT_BASE开始处,代码复制过程如下:
FLASH SDRAM(内存)
0x0 -----------------> TEXT_BASE
所以 SMRDATA - TEXT_BASE + 0x0 为13个寄存器相对于在FLASH中的起始地址
7.代码搬移
/********************************************************************
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: //将U-Boot复制到RAM中
adr r0, _start //_start是程序的入口地址是实际运行的地址
ldr r1, _TEXT_BASE //编译器指定的内存运行地址
cmp r0, r1 //查看当前代码地址信息,若从RAM中运行,则_start = _TEXT_BASE,否则_start = 0x0
beq stack_setup //若相等,则设置栈
ldr r2, _armboot_start //_armboot_start:第一条指令的运行地址
ldr r3, _bss_start //代码段的结束地址
sub r2, r3, r2 //r2 = 代码长度
add r2, r0, r2 //r2 = NOR FLASH上代码段的结束地址
copy_loop:
ldmia r0!, {r3-r10} //复制代码,r0为代码的起始地址,r1为ram中地址,r2为代码的终止地址,每次copy后
将r0的值递增同r2比较来判断是否复制完成
stmia r1!, {r3-r10}
cmp r0, r2
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
********************************************************************/
搬移代码的原因:
1.运行速度的考虑
flash的读写速度远小于SDRAM的读写速度,搬移到SDRAM后,可提高运行效率
2.空间的考虑
如果是NANDFLASH启动模式,那么只有4KB的空间供用户使用,实际的代码是远大于4KB的,因此需要重新开辟空间来进行代码的运行工作
8.栈空间的设置
/********************************************************************
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE //代码段开始地址
sub r0, r0, #CFG_MALLOC_LEN //代码段下面,留出一段实现malloc
sub r0, r0, #CFG_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异常,往下的内存就是栈了
********************************************************************/
9.BSS段的清零
/********************************************************************
clear_bss:
ldr r0, _bss_start //BSS段的起始地址
ldr r1, _bss_end //BSS段的结束地址
mov r2, #0x00000000
clbss_l:str r2, [r0] //往BSS段中写入0值
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot //进入C代码执行,_start_armboot函数在lib_arm/board.c
_start_armboot: .word start_armboot
********************************************************************/