学习目标:

  1. 对start.S中每一行代码,都有基本了解
  2. 通过对start.S文件分析,对ARM920T架构的CPU的启动过程,有更清楚理解

U-boot属于两个阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关的,后者是开发板相关的。U-boot的第一阶段主要的任务是一些系统的初始化工作,从大的方面可以分为以下几个部分:

①设置CPU的模式

②关闭看门狗

③关闭中断

④关闭MMU、设置RAM时序

⑤代码重定位、设置堆栈SP指针

⑥清除BSS段

⑦异常中断处理

下面对start.S文件做出详细分析,研究每一部分内容如何实现。


 1 设置CPU的模式

 reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0

cpsr为当前状态寄存器,它的格式如下所示:

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

cpsr是32位寄存器,寄存器[31:28]位为状态标志位,[27:8]位保留未被使用,[7:0]位为控制位。控制位中的第7位和第6位是用来设置是否使能中断请求和快速中断请求,第5位是设置CPU操作状态,当设置为1处理器执行在Thumb状态,为0时执行在ARM状态,第0~4位一起用来决定CPU的工作模式,模式位具体说明如下图所示:

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

由上图可知我们把M[4:0]设置为0b10011时,CPU工作在管理模式下,下面来分析代码如何实现。

第114行,将当前程序状态寄存器内容,复制到r0寄存器内

第115行,将r0寄存器内低5位清零

第116行,将r0寄存器内容与立即数0xd3进行或运算,并把运算结果保存到r0寄存器,CPSR位域和含义如下表所示:

CPSR位域76543210
位域含义IFTM4M3M2M1M0
0xd311010011

即:

bit[7]=1->设置I位为1->关闭中断IQR,bit[6]=1->设置F位为1->关闭FIQ中断

bit[4:0]=0b10011->设置CPU位SVC管理模式

第117行,将r0寄存器内容,复制到cpsr寄存器中


2 关看门狗

 /* 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 /* Interupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
#endif #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]

分析代码之前,先来介绍如何查看CONFIG_S3C2400、CONFIG_S3C2410宏是否定义。在start.S文件开头部分,可以看到如下图所示的头文件的引用

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

打开config.h文件,可以看到config.h内容如下

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

所以#include <config.h>可以替换为#include <configs/smdk2410.h>,查看CONFIG_S3C2400、CONFIG_S3C2410宏是否定义,在config/smdk2410.h文件中直接搜索即可。

(config.h文件是根据配置U-boot命令自动生成,关于U-boot配置命令介绍,可以参考这篇文章:https://www.cnblogs.com/053179hu/p/9266553.html)

下面来分析代码:

第120行,使用#if语句进行判断是否定义了CONFIG_S3C2400这个宏,若定义了这个宏,则第121~123处语句有效

第124行,若120行#if语句为假,执行#elif处代码,判断是否定义CONFIG_S3C2410这个宏,则125~128处语句有效

第131行,使用#if语句判断是否定义CONFIG_S3C2400、CONFIG_S3C2410宏的任何一个,若定义则执行下面代码

这里我们引用的是smdk2410.h头文件,在该头文件中CONFIG_S3C2410宏被定义,编译时#elif下语句被编译。125~128行是对硬件外设寄存器地址宏定义,以pWTCON为例,编译器进行预处理时遇到pWTCON即把它换为0x53000000,进行宏定义的目的,是使编写代码更加方便。

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

第132行,将WTCON寄存器地址,复制到r0寄存器中

第133行,将立即数0移入寄存器r1中

第134行,将r1内容0,写入到r0地址中,即WTCON寄存器地址

从硬件手册可以看出当WTCON寄存器第0bit内容为0,即关闭定时器0复位功能,与上面代码相吻合。


3 关闭中断

     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 = :: */
/* default FCLK is MHz ! */
ldr r0, =CLKDIVN
mov r1, #
str r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

第139~145行,向INMSK和INTSUBMSK寄存器相应为位写1,屏蔽中断源

第148~152,设置时钟分频系数

4 关闭MMU、设置RAM时序

 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif

bl是跳转指令,除了包含b指令的单纯的跳转功能,在跳转之前,还把r15寄存器=PC=CPU地址,赋值给r14=lr,然后跳转到对应位置,等要做的事情执行完毕后,再用mov pc, lr使得cpu再跳转回来,所以整个逻辑就是调用子程序的意思。

上面的代码意思很清晰,就是当没有定义CONFIG_SKIP_LOWLEVEL_INIT的时候,就跳转到cpu_init_crit的位置,头文件中未定义CONFIG_SKIP_LOWLEVEL_INIT,CPU将跳转到cpu_init_crit处执行程序,cpu_init_crit入口地址处代码如下:

 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #
mcr p15, , r0, c7, c7, /* flush v3/v4 cache */
mcr p15, , r0, c8, c7, /* flush v4 TLB */ /*
* disable MMU stuff and caches
*/
mrc p15, , r0, c1, c0,
bic r0, r0, #0x00002300 @ clear bits , : (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits , : (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit (A) Align
orr r0, r0, #0x00001000 @ set bit (I) I-Cache
mcr p15, , r0, c1, c0, /*
* 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 */

MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:

MCR 协处理器编号,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。
        其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,
        源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。

第242~246行,使I/D cache失效: 协处理寄存器操作,将r0中的数据写入到协处理器p15的c7中,c7对应cp15的cache控制寄存器

第247行,使TLB操作寄存器失效:将r0数据送到cp15的c8、c7中。C8对应TLB操作寄存器

第252行,将c1、c0的值写入到r0中

第257行,将设置好的r0值写入到协处理器p15的c1、c0中,关闭MMU

第264行,将lr寄存器内容保存到ip寄存器中,用于子程序调用返回

第265行,跳转到lowlevel_init入口地址执行,lowlevel_init在lowlevel_init.S文件中,代码如下:

 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
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #*
:
ldr r3, [r0], #
str r3, [r1], #
cmp r2, r0
bne 0b /* everything is fine now */
mov pc, lr .ltorg
/* the literal pools origin */ SMRDATA:
.word (+(B1_BWSCON<<)+(B2_BWSCON<<)+(B3_BWSCON<<)+(B4_BWSCON<<)+(B5_BWSCON<<)+(B6_BWSCON<<)+(B7_BWSCON<<))
.word ((B0_Tacs<<)+(B0_Tcos<<)+(B0_Tacc<<)+(B0_Tcoh<<)+(B0_Tah<<)+(B0_Tacp<<)+(B0_PMC))
.word ((B1_Tacs<<)+(B1_Tcos<<)+(B1_Tacc<<)+(B1_Tcoh<<)+(B1_Tah<<)+(B1_Tacp<<)+(B1_PMC))
.word ((B2_Tacs<<)+(B2_Tcos<<)+(B2_Tacc<<)+(B2_Tcoh<<)+(B2_Tah<<)+(B2_Tacp<<)+(B2_PMC))
.word ((B3_Tacs<<)+(B3_Tcos<<)+(B3_Tacc<<)+(B3_Tcoh<<)+(B3_Tah<<)+(B3_Tacp<<)+(B3_PMC))
.word ((B4_Tacs<<)+(B4_Tcos<<)+(B4_Tacc<<)+(B4_Tcoh<<)+(B4_Tah<<)+(B4_Tacp<<)+(B4_PMC))
.word ((B5_Tacs<<)+(B5_Tcos<<)+(B5_Tacc<<)+(B5_Tcoh<<)+(B5_Tah<<)+(B5_Tacp<<)+(B5_PMC))
.word ((B6_MT<<)+(B6_Trcd<<)+(B6_SCAN))
.word ((B7_MT<<)+(B7_Trcd<<)+(B7_SCAN))
.word ((REFEN<<)+(TREFMD<<)+(Trp<<)+(Trc<<)+(Tchr<<)+REFCNT)
.word 0x32
.word 0x30
.word 0x30

此处代码实现内存控制器初始化,为后面代码重定位做准备。

第137行,将保存设置内存控制器的参数的初始地址(连接地址),保存到r0寄存器中。

第138行,将程序连接的入口地址存入到r1寄存器中

第139行,r0=r0-r1,获取保存设置内存控制器的参数的初始地址(此处代码未进行定位,代码处于加载地址中,获取的是在存储器存放的物理地址)

第140行,将内存控制器的第一个寄存器地址存到r1寄存器中

第141行,获取保存设置内存控制器的参数的结束地址地址(此处代码未进行定位,代码处于加载地址中,获取的是在存储器存放的物理地址)

第142~146行,将标号SMRDATA地址处存放的参数,写入到相应寄存器中,设置内存控制器工作方式

第149行,程序调用返回,返回调用节点

第266~267,程序调用返回,返回调用节点(PC寄存器内容为bl cpu_init_crit指令地址+4)

5 代码重定位、设置堆栈SP指针

 #ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup 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
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */ /* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */

第165行,获取当前代码存放起始地址,存入r0寄存器

第166行,获取代码连接的初始地址,存入r1寄存器

第167~168行,比较代码当前存放初始地址和设置连接地址是否相等,如果代码当前存放地址等于连接地址,则跳转stack_setup入口地址,设置堆栈;否则,执行重定位操作

第170~173行,获取U-boot代码长度,并进长度值存入r2寄存器中

第175~179行,循环操作,将代码从加载地址,复制到连接地址。

  ldmia r0!, {r3-r10}  从源地址[r0]读取4个字节到寄存器(低地址存入低编号寄存器,高地址存入高编号寄存器),每读一次就更新一次r0地址 ,r0=r0+4

    存放形式 [r0]-->r3 r0=r0+4;[r0]-->r4 r0=r0+4;........................[r0]-->r10 r0=r0+4

stmia r1!, {r3-r10}  拷贝寄存器r3-r10的值保存到 [r1]指明的地址(低地址存入低编号寄存器,高地址存入高编号寄存器),每写一个字节,r1=r1+4,

     存放形式 r3-->[r1]  r1=r1+4;r4-->[r1]  r1=r1+4;........................r10-->[r1]  r1=r1+4

第183~190行,设置堆栈,CFG_MALLOC_LEN 、CFG_GBL_DATA_SIZE、CONFIG_STACKSIZE_IRQ、CONFIG_STACKSIZE_FIQ等宏在smdk2410.h中有定义,内存使用如下图所示:

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

6 清除bss段

 .globl _bss_start
_bss_start:
.word __bss_start .globl _bss_end
_bss_end:
.word _end
 clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #
cmp r0, r1
ble clbss_l

第85~91行,_bss_start,_bss_end为标号地址中存放bss段起始地址和结束地址,_bss_start和_end在连接脚本中定义\u-boot-1.1.6\board\smdk2410\u-boot.lds,程序连接时动态确定。

第193~194行,把bss段起始地址存入r0寄存器中,结束地址存放到r1寄存器中。

第197~200行,循环操作,将bss段中的内存清零

7 跳转到u-boot第二阶段入口

 ldr    pc, _start_armboot

 _start_armboot:    .word start_armboot

初始化外设完成之后,程序跳转到u-boot第二阶段入口函数start_armboot。ldr pc,_start_armboot为绝对跳转命令,pc值等于_start_armboot的连接地址,程序跳到SDRAM中执行,再辞之前程序都是在flash中运行的,绝对跳转必须在初始SDRAM,执行代码重定位之后才能进行。

8 异常中断处理

ARM920T架构CPU异常向量表如下图所示:

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件-LMLPHP

当CPU发生异常时,程序计数器跳到相应异常向量表地址处读取指令。由上图很容易看出,不同异常入口地址之间只有4个字节,在这里肯定不能存放异常处理函数。因此我们在中断向量地址处存放相应跳转指令,当发生异常时CPU跳到相应异常地址,读取跳转指令,跳转到相应异常处理函数处执行异常处理。start.S代码处理如下:

 .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 ,0xdeadbeef

globl是个关键字,意思很简单,就是相当亍C语言中的extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问。

第41行,是上电或者复位后执行第一题指令,通过b命令跳转到reset地址处进行一系列初始化操作,reset地址标号后的代码已经在上面分析了。

第43~57行,以_undefined_instruction为例,就是,此处分配了一个word=32bit=4字节的地址空间,里面存放的值是undefined_instruction。

而此处_undefined_instruction也就是该地址空间的地址了。用C语言来表达就是:

_undefined_instruction = &undefined_instruction

或 *_undefined_instruction = undefined_instruction

在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。(其他几个对应的“软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”,也是同样的做法,跳转到对应的位置执行对应的代码。)

第59行,意思就是,接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。

总结:uboot第一阶段

1、完成了硬件设备初始化操作

2、为加载Bootloader的第二阶段准备好RAM空间

3、实现代码重定位

4、设置好栈,并跳转到第二阶段入口函数

05-11 20:13