时间:2018.3.22 作者:Tom 工作:HWE 说明:如需转载,请注明出处。
#ifndef CONFIG_ENABLE_MMU //首先我们肯定开启了MMU的,所以下面不看了
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE CFG_UBOOT_BASE
#endif
#endif
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/上面我们不管
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
这里的EVT1或者后面的EVT1是芯片的子版本号,EVT0是指第一版,EVT1是第二版。
X210.h头文件中定义了CONFIG_EVT1
.word 0x2000
.word相当于int类型,是GNU汇编中的元素,word=32bit=4字节。
.word 0x0
.word 0x0
.word 0x0
#endif
裸机中讲过,在SD卡启动/Nand启动等整个镜像开头需要16字节的校验头。(mkv210image.c中就是为了计算这个校验头)。我们以前做裸机程序时根本没考虑这16字节校验头,因为:1、如果我们是usb启动直接下载的方式启动的则不需要16字节校验头(irom application note);2、如果是SD卡启动mkv210image.c中会给原镜像前加16字节的校验头。
uboot这里start.S中在开头位置放了16字节的填充占位,这个占位的16字节只是保证正式的image的头部确实有16字节,但是这16字节的内容是不对的,还是需要后面去计算校验和然后重新填充的。
.globl _start
/*系统复位位置,整个程序入口*/
_start是GNU汇编器的默认入口标签,.globl将_start声明为外部程序可访问的标签,.globl是GNU汇编的保留关键字,前面加点是GNU汇编的语法。
.global就是相当于C语言中的Extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问 。
所以,你可以看到在连接脚本中:
,有用到此变量:
ENTRY(_start)
即指定入口为_start,而由下面的_start的含义可以得知,_start就是整个start.S的最开始,即整个uboot的代码的开始。
_start: b reset
_start后面加上一个冒号' :' ,表示其是一个标号Label,类似于C语言goto后面的标号。
而同时,_start的值,也就是这个代码的位置了,此处即为代码的最开始,相对的0的位置。而此处最开始的相对的0位置,在程序开始运行的时候,如果是从NorFlash启动,那么其地址是0,_start=0。但是片内RAM的内存起始地址是BL0占用了,因此链接地址才是我们的代码的起始地址。从我们的makefile中看到我们的链接地址为0xc3e00000.
但我们起始的代码中有16个字节的校验和,因此我们的_start应该是在0xc3e00010开始的。我们使用反汇编来看下reset的指令。
事实证明_start表示是代码段的基地址,即_start=TEXT_BASE=0xc3e00010。
其他知识点:
@reset用b,就是因为reset在MMU建立前后都有可能发生
@其他的异常只有在MMU建立之后才会发生
1)异常向量表是硬件决定的,软件只是参照硬件的设计来实现它。
2)异常向量表中每种异常都应该被处理,否则真遇到了这种异常就跑飞了。但是我们在uboot中并未非常细致的处理各种异常。
3)复位异常处的代码是:b reset,因此在CPU复位后真正去执行的有效代码是reset处的代码,因此reset符号处才是真正的有意义的代码开始的地方。
注:ARM微处理器支持字节(8位)、半字(16位)、字(32位)3种数据类型向量跳转表,每条占四个字节(一个字),地址范围为0x0000 0000~0x0000 0020 。
ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,通过硬件实现。他们就是异常向量表。ARM在上电复位后,是从0x00000000开始启动的,其实如果bootloader存在,在执行下面第一条指令后,就无条件跳转到start_code,下面一部分并没执行。设置异常向量表的作用是识别bootloader。以后系统每当有异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表做相应的处理:
;当一个异常出现以后,ARM会自动执行以下几个步骤:
;1.把下一条指令的地址放到连接寄存器LR(通常是R14).----------------------保存位置
;2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中--------------------保存CPSR
;3.根据异常类型,强制设置CPSR的运行模式位
;4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中
-------------------------------------reset的代码讲解-------------------------------------------------------------------
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode and IRQ & FIQ disable
*/
更改处理器模式为管理模式
31 30 29 28 --- 7 6 - 4 3 2 1 0
N Z C V I F M4 M3 M2 M1 M0
0 0 0 0 0 User26 模式
0 0 0 0 1 FIQ26 模式
0 0 0 1 0 IRQ26 模式
0 0 0 1 1 SVC26 模式
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 模式
1 1 1 1 1 SYS 模式
对状态寄存器的修改要按照:读-改-写的顺序来执行
@;mrs r0,cpsr
MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。
@;bic r0,r0,#0x1f
BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。
@;orr r0,r0,#0xd3
ORR指令用于在两个操作数上迕行逻辑戒运算,幵把结果放置到目的寄存器中。
@;msr cpsr,r0
MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。
msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 – SVC
1)msr cpsr_c, #0xd3 将CPU设置为禁止FIQ IRQ,ARM状态,SVC模式。
2)其实ARM CPU在复 位时默认就会进入SVC模式,但是这里还是使用软件将其置为SVC模式。整个uboot工作时CPU一直处于SVC模式。
---------------------------------------------reset代码结束--------------------------------------------------------------
ldr pc, _undefined_instruction/*未定义指令异常,0x04*/
ldr pc, _software_interrupt/*软中断异常,0x08*/
ldr pc, _prefetch_abort/*内存操作异常,0x0c*/
ldr pc, _data_abort/*数据异常,0x10*/
ldr pc, _not_used/*未适用,0x14*/
ldr pc, _irq/*慢速中断异常,0x18*/
ldr pc, _fiq/*快速中断异常,0x1c*/一般用在实时性比较高的中断
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
_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
_undefined_instruction: .word undefined_instruction。_undefined_instruction是一个标号,处理到这里时,asm会把undefined_instruction的值按32bit的形式放在此标号处。 .word就是在这个地方放一个值。相当于在这里定义一个数据变量。用.word定义了一个32bit的数据。ldr pc, _undefined_instruction 就是从_undefined_instruction处取值,即undefined_instruction, 并设置到pc中。并将underfined_instruction的值本身放在这里,因此pc=undefined_instruction,实现跳转。
-------------------------------- undefined_instruction代码讲解-------------------
/*
* exception handlers
*/
.align 5
从这段文字来看,ARM的.align 5就是2的5次方对齐,也就是4字节对齐。
undefined_instruction:
在跳转中断服务程序前,首先要有两个宏操作,一个是对stack的获取操作,一个是对用户reg保存;
get_bad_stack
------------------------get_bad_stack代码讲解----------------
此处的get_bad_stack被后面undefined_instruction,software_interrupt等处调用,目前能理解的意思是,在出错的时候,获得对应的堆栈的值
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack (enter in banked mode)
sub r13, r13, #(CFG_MALLOC_LEN) @ move past malloc pool
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ move to reserved a couple spots for 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
-------------------------get_bad_stack代码讲解结束----------------
bad_save_user_regs
------------------------- bad_save_user_regs代码讲解----------------
.macro bad_save_user_regs
Macro声明的是一个函数。
sub sp, sp, #S_FRAME_SIZE @ carve out a frame on current user stack
S_FRAME_SIZE=72。这句话意思就是sp = sp – 72;
stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12
LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。
将r0---r12一共13个寄存器的数据推入堆栈上,地址空间为13*4加上其中的状态寄存器等需要72 字节的空间。
ldr r2, _armboot_start
此处的含义是将_armboot_start的值传给r2,即0xc3e00010。
sub r2, r2, #(CFG_MALLOC_LEN)
此处
r2
= r2 - CFG_MALLOC_LEN
= r2 –(16*1024+896*1024)
= 0xc3e00010 - 912KB
= 0xC3D1C010
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
r2
= r2 –136
= 0xc3d1c010-136
= 0xC3D1BF88
Ldmia r2, {r2 - r3} @ get values for "aborted" pc and cpsr (into parm regs)
分别将地址为r2和r2+4的内容,即地址为0xC3D1BF88和0xC3D1BF8C中的内容,load载入给r2和r3寄存器。
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
将sp的值,加上72,送给r0
add r5, sp, #S_SP
将sp的值,加上52,送给r5
mov r1, lr
将lr给r1
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
然后将r0到r3中的内容,存储到地址为r5---àr5+12中的位置去。
mov r0, sp @ save current stack into r0 (param register)
将sp再赋值给r0
.endm
------------------------- bad_save_user_regs代码讲解结束----------------
bl do_undefined_instruction
其做的事情依次是:
获得出错时候的堆栈
保存用户模式寄存器
跳转到对应的函数:do_undefined_instructi
做了一些操作:打印提示警告,并打印什么中断号、pc指针的位置、寄存器的数值等。。。。然后重启CPU:reset
-------------------------------- undefined_instruction代码讲解结束-------------------
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
.balignl 16,0xdeadbeef
1).balignl 16,0xdeadbeef. 这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,并且向后走的那些内存要用0xdeadbeef来填充。
2)0xdeadbeef这是一个十六进制的数字,这个数字很有意思,组成这个数字的十六进制数全是abcdef之中的字母,而且这8个字母刚好组成了英文的dead beef这两个单词,字面意思是坏牛肉。
3)为什么要对齐访问?有时候是效率的要求,有时候是硬件的特殊要求。