分类: LINUX
2010-09-15 15:17:49
本文详细分析start.s文件--->
由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成,这在board/prochip/UB4020/u-boot.lds可以找到,u-boot的程序入口为_start,在cpu/sep4020/start.S中。
1.设置异常向量表
.globl _start /* 声明标量是全局函数,CPU加电启动后,就从这里执行代码*/
_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,oxdeadbeef
.word
ldr pc,_irq 从内存的某个位置读取数据并且赋值给pc,但是偏移量是那个位置的链接(运行)地址。
.balignl 16,0xdeadbeef 这条指令把deadbeef字符串填充进去,一共填到地址为16对齐的地方为止。
下面定义了几个全局变量,在后面的代码搬运,申请空间中将会用到:
/*
*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!如果不是从内存启动,做一些重要的初始化工作
* relocate u-boot to ram 搬运u-boot到ram中
* setup stack 设置堆栈
* jump to second stage 跳转到第二阶段
*
*************************************************************************
*/
_TEXT_BASE:
.word TEXT_BASE //程序在SDRAM运行的起始地址,即链接基地址,在/board/prochip/UB4020/config.mk中定义为0x30700000
.globl _armboot_start
_armboot_start:
.word _start //程序运行起始地址,其实是FLASH中程序的起始地址
/*
* These are defined in the board-specific linker script.这些变量定义在板级的链接脚本中
*/
.globl _bss_start
_bss_start:
.word __bss_start //bss段的链接起始地址
.globl _bss_end
_bss_end:
.word _end //bss段的连接结束地址
2.复位时切换到SVC32模式,并设置各个模式下的堆栈
/*
* the actual reset code 真正的复位代码
*/
reset:
/*stack setup for each mode 设置各个模式下的堆栈*/
/* SVC32 mode*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0x13
msr cpsr,r0
//以下这段代码对不是从NAND FLASH启动的代码才有作用,因为从NAND FLASH启动,已经把uboot
//搬运到了SDRAM中。
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN //动态缓冲区和全局环境变量大小
sub r0, r0, #CFG_GBL_DATA_SIZE //uboot唯一的全局变量区大小
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12
#ifdef CONFIG_USE_IRQ
/* IRQ mode*/
mov R4, #0xD2
msr cpsr, R4
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub r0, r0, #CFG_GBL_DATA_SIZE
sub r0, r0, #(CONFIG_STACKSIZE_FIQ)
sub sp, r0, #12
/* FIQ mode*/
mov R4, #0xD1
msr cpsr, R4
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub r0, r0, #CFG_GBL_DATA_SIZE
sub sp, r0, #12
#endif
/* ABORT mode*/
mov R4, #0xD7
msr cpsr, R4
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub r0, r0, #CFG_GBL_DATA_SIZE
sub sp, r0, #8
/* UNDEFINE mode*/
mov R4, #0xDB
msr cpsr, R4
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub r0, r0, #CFG_GBL_DATA_SIZE
sub sp, r0, #4
/* SYSTEM mode*/
mov R4, #0xDF
msr cpsr, R4
ldr r0, _TEXT_BASE
sub r0, r0, #CFG_MALLOC_LEN
sub sp, r0, #CFG_GBL_DATA_SIZE
/*Return to SVC mode*/
mov R4, #0xD3
msr cpsr, R4
各模式下堆栈设置完毕之后的内存分布图如下所示:
注意:ARM是满递减堆栈。。。
_TEXT_BASE CFG_GBL_DATASIZE CFG_MALLOC_LEN SP_USR&SP_SYSTEM 4字节 SP_UNDEFINE 4字节 4字节 SP_ABORT CONFIG_STACKSIZE_FIQ SP_FIQ CONFIG_STACKSIZE_IRQ SP_IRQ 低地址 高地址 _TEXT_BASE CFG_GBL_DATASIZE CFG_MALLOC_LEN SP_USR&SP_SYSTEM 4字节 SP_UNDEFINE 4字节 4字节 SP_ABORT CONFIG_STACKSIZE_FIQ SP_FIQ CONFIG_STACKSIZE_IRQ SP_IRQ 低地址 高地址 _TEXT_BASE CFG_GBL_DATASIZE CFG_MALLOC_LEN SP_USR&SP_SYSTEM 4字节 SP_UNDEFINE 4字节 4字节 SP_ABORT CONFIG_STACKSIZE_FIQ SP_FIQ CONFIG_STACKSIZE_IRQ SP_IRQ 低地址 高地址
3.进行CPU初始化(频率、正常模式、打开所有模块、串口、SDRAM时序参数)
/*
* we do sys-critical inits only at reboot, 我们只在重新启动的时候才进行系统重要部分初始化
* not when booting from ram! 从ram中启动的时候不执行
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT //这个很关键
bl cpu_init_crit //跳转到cpu初始化部分
/*
* before relocating, we have to setup RAM timing 在加载之前,先设置RAM的时序
* because memory timing is board-dependend, you will 因为RAM因板子的不同而不同
* find a lowlevel_init.S in your board directory. 在你板子目录里找到lowlevel_init.S这个文件
*/
bl lowlevel_init //跳转到配置EMI即SDRAM时序参数部分
#endif
cpu_init_crit代码如下:
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers 设置重要的寄存器
* setup memory timing 设置存储器的时序
*
*************************************************************************
*/
cpu_init_crit:
/* PLLCON */
ldr r0, =0x10001004 /*88M*/
ldr r1, =0x400B
str r1, [r0]
ldr r0, =0x10001014 /*Normal*/
ldr r1, =0x1
str r1, [r0]
ldr r0, =0x10001004 /*88M*/ //配置系统频率为88MHZ
ldr r1, =0xC00B
str r1, [r0]
ldr r0, =0x1000100C /*打开系统所有模块*/
ldr r1, =0xFFFFFFFF
str r1, [r0]
/*UARTCON*/ //进行串口的配置
#if 1
ldr r0, =0x1000500C /*databit:8*/
ldr r1, =0x83
str r1, [r0]
ldr r0, =0x10005004 /*baud=9600*/
ldr r1, =0x0
str r1, [r0]
ldr r0, =0x10005000
ldr r1, =0x2F
str r1, [r0]
ldr r0, =0x1000500C
ldr r1, =0x3
str r1, [r0]
#endif
mov pc, lr //程序返回
以下是low_level_init.S中的lowlevel_init代码
.globl lowlevel_init
lowlevel_init:
ldr r4, =EMI_CSECONF
ldr r5, =0x8ca6a6a1 //配置SDRAM参数
str r5, [ r4 ]
ldr r4, =EMI_SDCONF1
ldr r5, =0x1E184177
str r5, [ r4 ]
ldr r4, =EMI_SDCONF2
ldr r5, =0x80001860
str r5, [ r4 ]
mov pc, lr //程序返回
4. 地址重映射以及BSS段初始化,拷贝u-boot到RAM中,拷贝中断向量到RAM首地址
remap:
mov r0, pc
add r0, r0, #0x20000000
add r0, r0, #0x08
mov pc, r0 //到这条结束,其实就是跳到下一条去执行了
mov r0, r0 //这四个很关键,用来填充流水线
mov r0, r0
mov r0, r0
mov r0, r0
/*关于上面这段代码,我考虑了很久,我是这么认为的:系统启动时,硬件选择从norflash启动,这时的0x2000 0000就是0x0,pc的值都是0x0空间的,下面马上要进行地址重映射了,重映射完了后0x3000 0000就是0x0,而重映射完了之后仍然有代码在norflash中执行,这样就必须人为地将PC指针指向0x2000 0000这个地址段,而不是继续在0x0这个空间执行代码,而0x3000 0000和0x0已经绑定了,在这个地址段里暂时还没有可以执行的代码,因为代码还没有搬运过去。
Shixq说:
在BootLoadr里实现Remap是一个技巧。
首先假设Remap操作前不对PC进行处理,我们来看一下有什么问题。系统上电后,根据外部跳线,将NorFlash映射为零地址(此时NorFlash拥有两个地址:0x00000000和0x20000000),内核从零地址开始取指令,即开始执行BootLoader里的代码。
在BootLoader里有一个重要的步骤,就是地址重映射,即将SDRAM(0x30000000)映射到零地址,为将来建立中断向量表做准备。如果不对PC处理,那么一旦执行了地址重映射,零地址开始的一段地址将落在SDRAM上,而不再落在NorFlash上,由于SDRAM开始的一段地址还没有任何内容,这样将导致系统崩溃!
我们希望执行了Remap操作后,CPU能够继续从NorFlash取指,即继续执行BootLoader里的指令。这样在执行Remap前就必须对PC进行处理。由于Remap后,NorFlash只有0x20000000开始的地址,因此,需要将PC值加上0x20000000。假设执行Remap前PC是0x88,同时这个地址也是0x20000088,我们对PC加0x20000000不会影响系统的运行,而且可以保证Remap后,CPU可以继续从NorFlash取指。
ldr pc,=0x,就是做了上述处理。至于这个值是多少,我们可以看到在这一条指令下面是mov r0,r0,相当于NOP空指令。只要让PC落在其中任意一*/
ldr r4, =0x11000020 //对这个地址即寄存器写0xb完成地址重映射,这时0x30000000=0x0
ldr r5, =0xb
str r5, [ r4 ]
/*
init BSS section bss段清零
*/
ldr r0, = 0
ldr r1, _bss_start //BSS段起始地址
ldr r2, _bss_end //BSS段结束地址
bss_init:
str r0, [r1]
add r1,r1,#4
cmp r1,r2
blt bss_init
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM 拷贝u-boot到RAM中 */
adr r0, _start /* r0 <- current position of code 根据现在的PC值计算出的_start地址赋给r0 */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM 判断是从flash还是从RAM 启动*/
cmp r0, r1 /* don't reloc during debug 在调试的时候不需要拷贝*/
beq vector_copy //调试的时候全部自动加载到指定的地址了
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
/*
now copy to sram the interrupt vector
*/
vector_copy:
ldr r0, _TEXT_BASE
add r2, r0, #128 //拷贝128个字节到0x3000 0000,里面存放着中断向量
ldr r1, =0x30000000 /*modified by shixq from 0x0c000000 to 0x30000000*/
/* add r1, r1, #0x08 *//*deleted by shixq*/
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
6.使能IRQ中断,进入C代码部分
/*enable the irq*/
mrs R4, cpsr
bic R4, R4, #0x80
msr cpsr, R4
ldr pc, _start_armboot
/*这条语句不但跳转到C语言处开始执行,而且是从NORFLASH空间跳转到了SDRAM空间*/
/*该指令是取得标号(_start_armboot)绝对地址并且赋值给pc,而_start_armboot中存储的又是start_armboot的链接时候的绝对地址,它们都是0x3070 0000开始的空间的地址。所以可以利用它实现Flash到RAM的跳转。*/
_start_armboot: .word start_armboot
ldr与adr的区别
ldr r0, _start
adr r0, _start
ldr r0, =_start
nop
mov pc, lr
_start:
nop
编译的时候设置 RO 为 0x0c008000
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
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)
c008018: 0c008014 stceq 0, cr8, [r0], -#80
分析:
adr r0, _start
取得 _start 的地址到 r0,但是请看反编译的结果,它是与位置无关的。其实取得的是相对于当时pc的位置。例如这段代码在 0x0c008000 运行,那么 adr r0, _start 得到 r0 = 0x0c008014;如果在地址 0 运行,就是 0x00000014 了。
ldr r0, =_start
这个取得标号 _start 的绝对地址。这个绝对地址是在 link 的时候确定的。看上去这只是一个指令,但是它要占用 2 个 32bit 的空间,一条是指令,另一条是 _start 的数据(因为在编译的时候不能确定 _start 的值,而且也不能用 mov 指令来给 r0 赋一个 32bit 的常量,所以需要多出一个空间存放 _start 的真正数据,在这里就是 0x0c008014)。