ARM上电启动及Uboot代码分析
https://blog.csdn.net/qianlong4526888/article/details/27698707
注意:由于文档是去年写的,内有多个图片。上传图片非常麻烦(须要截图另存插入等等),我把文章的PDF版本号上传到了CSDN下载资源中。
为了给自己赚点积分,所以标价2分,没有积分的同学能够直接留言跟我要。记得留下邮箱。
下面是文章内容,由于我懒得编辑图片了。所以文章看来会非常不爽,强烈推荐点击以上红色链接下载pdf版。
文件编号:DCC01
版本号号:1.0
ARM上电启动及Uboot代码分析
部 门: |
|
作 者: |
|
联系方式: |
|
日 期: |
2013.03.08 |
文件修订记录
时间 |
作者 |
主要修订内容 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
文件夹
1.1.1norflash和nandflash的异同... 5
摘要
网上关于ARM的bootloader(以Uboot为例)的启动顺序的资料有好多,可是对于Uboot的地址映射、体系结构级操作介绍非常少。都是直接開始Start.s代码的阅读。本文拟详细分析Uboot从上电。到第一条指令的执行,同一时候分析代码对于cache、TLB等部件的操作过程。
下面内容以u-boot-2012.04.01源代码为例。从网上非常easy下载该版本号。
1 ARM上电取第一条指令流程
1.1 上电后的第一条指令在哪里?
首先明白:对于ARM芯片,启动时pc值由CPU设计者规定,不同的ARM CPU有不同的值。比如S3C2440芯片上电后PC值被硬件设计者规定为0x0;其它ARM芯片不一定是0x0。
第一章讲述的上电取第一条指令过程以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地址处。
2 Uboot.lds链接脚本分析
2.1 为什么要分析uboot链接脚本?
由于u-boot.lds决定了u-boot可执行映像的链接方式,以及各个段的装载地址(装载域)和执行地址(执行域),也就是说,Uboot.lds文件指定uboot.bin可执行文件放到ROM中的哪个地址、在执行时在RAM中执行的起始地址,详细内容涉及装载域和执行域的概念。这里不详述。
2.2 连接代码详细分析
以u-boot-2012.04.01版本号为例。
总结:
1、SECTION后面的段都是依照顺序放到内存中的,比如text段后面跟着rodata段
2、该文件里没有指定段的载入地址(用AT命令)。没指定的情况下载入地址和执行地址是同样的,也就是说在uboot.bin在rom和ram中的地址同样。
3 Uboot中start.S文件分析
3.1 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.1 _start
文本段第一条指令就是一条跳转指令:
依据1.1章的分析,假设uboot是烧写到norflash中的,那么一上电的_start标号肯定是在0x0处,假设uboot已经启动,程序执行过了relocate(见后面)。那么该标号就会被移动到TEXT_BASE标号处,该标号是编译uboot时程序猿指定的。详细数值见开发板的board/~/config.mk文件。
3.1.2 reset
CPSR的位域见ARM手冊,下图截图方便參考:
3.1.3 cpu_init_cp15
3.1.4 cpu_init_crit
3.1.5 lowlevel_init
3.1.6 s_init
3.1.7 call_board_init_f
3.1.8 board_init_f
3.1.9 relocate_code
为什么uboot代码须要relocate?见问题总结及解答。
3.1.10 clear_bss
3.1.11 jump_2_ram
3.2 本章小结
主要分析了uboot启动的第一阶段代码。
4 板级初始化及跳入Linux内核执行
4.1 board_init_r
该函数在u-boot-2012.04.01\arch\arm\cpu\armv7\omap-common\spl.c中。
其功能是:
(1) 初始化内存分配函数
(2) 假设系统有mmc设备,则初始化mmc设备
(3) 假设系统有nand设备。则初始化nand设备
(4) 进入uboot命令循环或者直接開始执行linux内核。
眼下临时不须要详细分析该部分代码,后期若须要会加上。
千万不要删除行尾的分节符,此行不会被打印。“结论”曾经的全部正文内容都要编写在此行之前。
5 Uboot异常处理
5.1 Uboot异常向量表
紧跟b reset后面的就是异常向量表,发生异常后pc会被自己主动置为对应的值,进入对应异常处理程序。
5.1.1 异常处理入口函数
5.1.2 异常处理函数跳转
/*
* exception handlers
*/
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl 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*/
5.1.3 异常真正处理函数
// u-boot-2012.04.01\arch\arm\lib\Interrupts.c
void do_undefined_instruction (struct pt_regs *pt_regs)
{
printf ("undefinedinstruction\n");
show_regs (pt_regs);
bad_mode ();
}
void do_software_interrupt (struct pt_regs *pt_regs)
{
printf ("software interrupt\n");
show_regs (pt_regs);
bad_mode ();
}
void do_prefetch_abort (struct pt_regs *pt_regs)
{
printf ("prefetchabort\n");
show_regs (pt_regs);
bad_mode ();
}
void do_data_abort (struct pt_regs *pt_regs)
{
printf ("dataabort\n");
show_regs (pt_regs);
bad_mode ();
}
void do_not_used (struct pt_regs *pt_regs)
{
printf ("notused\n");
show_regs (pt_regs);
bad_mode ();
}
void do_fiq (struct pt_regs *pt_regs)
{
printf ("fastinterrupt request\n");
show_regs (pt_regs);
bad_mode ();
}
#ifndef CONFIG_USE_IRQ
void do_irq (struct pt_regs *pt_regs)
{
printf ("interruptrequest\n");
show_regs (pt_regs);
bad_mode ();
}
#endif
void bad_mode (void) //以上异常处理函数都跳转到bad_mode,该函数仅仅是
//挂起CPU,木有详细处理。
{
panic ("Resetting CPU ...\n");
reset_cpu (0);
}
5.2 本章总结
总结uboot下异常处理流程,发现ARMv7下的uboot没有实现异常处理。ARM的其它架构有。有可能是由于uboot代码不够新的原因。Uboot中的ARM异常处理流程都同样。
[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中执行。
千万不要删除行尾的分节符。此行不会被打印!