注意:由于文档是去年写的,内有多个图片,上传图片很麻烦(需要截图另存插入等等),我把文章的PDF版本上传到了CSDN下载资源中。为了给自己赚点积分,所以标价2分,没有积分的同学可以直接留言跟我要,记得留下邮箱。
以下是文章内容,由于我懒得编辑图片了,所以文章看来会很不爽,强烈推荐点击以上红色链接下载pdf版。
文件编号:DCC01
版本号:1.0
ARM上电启动及Uboot代码分析
部 门:
作 者:
联系方式:
日 期:
2013.03.08
文件修订记录...1
目录...2
摘要...4
1ARM上电取第一条指令流程...5
1.1上电后的第一条指令在哪里?...5
1.1.1norflash和nandflash的异同...5
2Uboot.lds链接脚本分析...7
2.1为什么要分析uboot链接脚本?...7
2.2连接代码具体分析...7
3Uboot中start.S文件分析...9
3.1start.S详解...9
3.1.1_start9
3.1.2reset9
3.1.3cpu_init_cp15.11
3.1.4cpu_init_crit11
3.1.5lowlevel_init13
3.1.6s_init14
3.1.7call_board_init_f15
3.1.8board_init_f15
3.1.9relocate_code.16
3.1.10clear_bss.17
3.1.11jump_2_ram..17
3.2本章小结...18
4板级初始化及跳入Linux内核执行...19
4.1board_init_r19
4.1.1三级标题...19
4.2本章小结...19
5Uboot异常处理...20
5.1Uboot异常向量表...20
5.1.1异常处理入口函数...20
5.1.2异常处理函数跳转...21
5.1.3异常真正处理函数...22
5.2本章总结...24
结论...25
参考文献...26
问题总结及解答...27
附录...29
1 ARM上电取第一条指令流程
第一章讲述的上电取第一条指令过程以S3C2440为例,该芯片是ARMv4T架构,其他芯片在原理上类似。
S3C2440的启动时读取的第一条指令是在内存0x00地址处,不管是从nand flash还是nor flash启动。
但是上电后内存中是没有数据的,那么0x00地址处的指令是如何放进去的?针对不同的flash(nandflash、norflash),操作方式是不同的,下面讲述从nandflash和norflash启动的不同流程。
1.1.1 norflash和nandflash的异同
nandflash:价格低,容量大,适合大容量数据存储,地址线和数据线共用I/O线,所有信息都通过一条线传送,类比于PC的硬盘,
norflash:价格贵,容量小,适合小容量的程序或数据存储,类似硬盘,但是能在其中运行程序;有独立地址线、数据线
sdram:主要用于程序执行时的程序存储、执行或计算,类比于PC的内存;
综上:norflash比较适合频繁随即读写的场合,通常用于存储代码并直接在其中运行。nandflash用于存储资料。
只要知道以上大概区别就行。以下说明ARM从两种flash启动方式的异同。
1.1.1.1 ARM从nandFlash启动
若从nandflash启动,上电后nandflash控制器自动把nandflash存储器中的0——4K内容加载到芯片内的起步石(Steppingstone,起步石这个机制是处理器中集成的功能,对程序员透明),即内部SRAM缓冲器中,同时把内部SRAM的起始地址设置为0x0(不同的CPU上电后的PC值不尽相同,对不同的CPU该值也不尽相同),然后把这段片内SRAM映射到nGCS0片选的空间,进而CPU开始从内部SRAM的0x0处开始取得第一条指令,该过程全部是硬件自动完成,不需要程序代码控制。
或许你有个疑问,为什么不能直接把nandflash映射到0x0地址处?非要经过内部SRAM缓冲?
答案是,nandflash根本没有地址线,没法直接映射,必须使用SRAM做一个载体,通过SRAM把剩余的nandflash代码(即剩余的uboot启动代码)复制到SDRAM中运行。
若想从nandflash启动,那么uboot最核心的代码必须放在前4k完成。这4k代码要完成ARM CPU的核心配置以及将剩余的代码拷贝到SDRAM中(若从norflash启动则没有4k这个大小的限制,但是还会在完成最主要的设置后进入SDRAM中运行)。
1.1.1.2 ARM从norflash启动
若从norflash启动,则norflash直接被映射到内存的0x0地址处(就是nGCS0,这里就不需要片内SRAM来辅助了,所以片内SRAM的起始地址不变,还是0x40000000),然后cpu从0x00000000开始执行(也就是在Norfalsh中执行)。
需要说明的是,uboot代码段(.text段)起始位置必须是与上电后PC值一致,即编译uboot时,TEXT_BASE宏必须设置成0x0 ,反汇编uboot文件后,文本段第一条指令的地址也是0.
总结:
1、从norflash还是从nandflash启动,是由ARM的OM1和OM0引脚组合决定
2、不管从norflash还是nandflash启动,S3C2440上电后的pc值为0x0
3、如果某芯片上电后PC值不是0x0,假如是0x38ff0000,那么从norflash启动时,硬件就要自动将其映射到0x38ff0000地址处;如果从nandflash启动,那么硬件就要自动将nandflash中的前4K内容加载到0x38ff0000地址处。
3 Uboot中start.S文件分析
arch/arm/cpu/armv7/start.S。对于uboot的start.S,主要做的事情就是系统的各个方面的初始化,然后复制剩余代码到RAM中继续运行。
(1) 设置CPU模式
(2) 关闭cache, MMU, TLBs
(3) 设置栈,pll, mux, memory
(4) 设置watchdog, muxing, and clocks
(5) 板级初始化
(6) 自我拷贝到RAM中,并跳转到RAM中继续运行。
以下内容按照程序执行流程进行讲解,以ARMv7架构为例。
3.1.5lowlevel_init
3.1.8board_init_f
3.1.11jump_2_ram
5.1.1异常处理入口函数
do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack_swi
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 //如果在uboot中启用了用户中断,则跳入相应处理函
//数运行
.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 effective 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 /* CONFIG_USE_IRQ */
#endif /* CONFIG_SPL_BUILD*/
void bad_mode (void) //以上异常处理函数都跳转到bad_mode,该函数只是
//挂起CPU,木有具体处理。
{
panic ("Resetting CPU ...
");
reset_cpu (0);
}
结论
参考文献
[1]
[2]
[3]
千万不要删除行尾的分节符,此行不会被打印。
问题总结及解答
1、对于加载时地址和运行时地址不同的段,运行时它是怎么跳转到运行时地址的?
答: 连接地址<==>运行地址
存储地址<==>加载地址
(1)对于有操作系统时,运行地址与加载地址不同,在加载过程中装载器就把段加载到它应该去的连接地址处(也就是生成该段时的运行地址)
(2)对于uboot,运行地址与加载地址不同时,需要它自己(例如前4k代码)将自己加载到运行地址处执行。
Uboot.lds文件中起始地址是0x00,但是config.mk中的TEXT_BASE是0x57e00000,但是生成的uboot反汇编文件中,为什么start.s的第一条指令地址也是0x57e00000?不应该是0x00么?因为start.s的加载地址和运行地址都是0x00啊!?
答:Uboot.lds的0x00:
跟在SECTION后面的第一条
location counter,总是默认初始化为0。config.mk中的TEXT_BASE就是ROM在CPU上的地址,也就是说,不同的CPU已经规定了不同的ROM地址
2、关于为何不能直接用mov指令,而非要用adr伪指令?
把所有uboot代码拷贝到内存新地址处。
在分析uboot的start.S中,看到一些指令,比如:
adr r0, _start
觉得好像可以直接用mov指令实现即可,为啥还要这么麻烦地,去用ldr去实现?
关于此处的代码,为何要用adr指令:
adr r0, _start
其被编译器编译后,会被翻译成:sub r0, pc, #172
而不直接用mov指令直接将_start的值赋值给r0,类似于这样:
mov r0, _start
呢?
其原因主要是,
sub r0, pc, #172
这样的代码,所处理的值,都是相对于PC的偏移量来说的,这样的代码中,没有绝对的物理地址值,都是相对的值,利用产生位置无关代码。因为如果用mov指令:
mov r0, _start
那么就会被编译成这样的代码:
mov r0, 0x33d00000
如果用了上面这样的代码:
mov r0, 0x33d00000
那么,如果整个代码,即要执行的程序的指令,被移动到其他位置,那么
mov r0, 0x33d00000
这行指令,执行的功能,就是跳转到绝对的物理地址,而不是跳转到相对的_start的位置了,就不能实现我们想要的功能了,这样包含了绝对物理地址的代码,也就不是位置无关的代码了。
与此相对,这行指令:
sub r0, pc, #172
即使程序被移动到其他位置,那么该行指令还是可以跳转到相对PC往前172字节的地方,也还是我们想要的_start的位置,这样包含的都是相对的偏移位置的代码,就叫做位置无关代码。其优点就是不用担心你的代码被移动,即使程序的基地址变了,所有的代码的相对位置还是固定的,程序还是可以正常运行的。
关于,之所以不用上面的:
mov r0, 0x33d00000
类似的代码,除了上面说的,不是位置无关的代码之外,其还有个潜在的问题,那就是,关于mov指令的源操作数,此处即为0x33d00000,不一定是合法的mov 指令所允许的值。
【总结】
之所以用adr而不用mov,主要是为了生成地址无关代码,以及由于不方便判断一个数,是否是有效的mov的操作数。
3、为什么uboot代码需要relocate?
因为uboot启动时不在片外RAM中,为了加快运行,需要将uboot重新拷贝到RAM中运行。
附录
千万不要删除行尾的分节符,此行不会被打印!