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

学习目标:

  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的模式

110 reset:
111     /*
112      * set the cpu to SVC32 mode
113      */
114     mrs    r0,cpsr
115     bic    r0,r0,#0x1f
116     orr    r0,r0,#0xd3
117     msr    cpsr,r0

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

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

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

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

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

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

CPSR位域 7 6 5 4 3 2 1 0
位域含义 I F T M4 M3 M2 M1 M0
0xd3 1 1 0 1 0 0 1 1

即:

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

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

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


 2 关看门狗

119 /* turn off the watchdog */
120 #if defined(CONFIG_S3C2400)
121 # define pWTCON        0x15300000
122 # define INTMSK        0x14400008    /* Interupt-Controller base addresses */
123 # define CLKDIVN    0x14800014    /* clock divisor register */
124 #elif defined(CONFIG_S3C2410)
125 # define pWTCON        0x53000000
126 # define INTMSK        0x4A000008    /* Interupt-Controller base addresses */
127 # define INTSUBMSK    0x4A00001C
128 # define CLKDIVN    0x4C000014    /* clock divisor register */
129 #endif
130 
131 #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
132     ldr     r0, =pWTCON
133     mov     r1, #0x0
134     str     r1, [r0]

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

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

所以#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,进行宏定义的目的,是使编写代码更加方便。

        

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

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

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

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


 3 关闭中断

139     mov    r1, #0xffffffff
140     ldr    r0, =INTMSK
141     str    r1, [r0]
142 # if defined(CONFIG_S3C2410)
143     ldr    r1, =0x3ff
144     ldr    r0, =INTSUBMSK
145     str    r1, [r0]
146 # endif
147 
148     /* FCLK:HCLK:PCLK = 1:2:4 */
149    /* default FCLK is 120 MHz ! */
150     ldr    r0, =CLKDIVN
151     mov    r1, #3
152    str    r1, [r0]
153 #endif    /* CONFIG_S3C2400 || CONFIG_S3C2410 */        

 

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

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

4 关闭MMU、设置RAM时序

159 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
160     bl    cpu_init_crit
161 #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入口地址处代码如下:

240 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
241 cpu_init_crit:
242     /*
243     * flush v4 I/D caches
244     */
245     mov    r0, #0
246     mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
247     mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */
248
249     /*
250      * disable MMU stuff and caches
251      */
252     mrc    p15, 0, r0, c1, c0, 0
253     bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS)
254     bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM)
255     orr    r0, r0, #0x00000002    @ set bit 2 (A) Align
256     orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache
257     mcr    p15, 0, r0, c1, c0, 0
258 
259     /*
260      * before relocating, we have to setup RAM timing
261     * because memory timing is board-dependend, you will
262      * find a lowlevel_init.S in your board directory.
263      */
264     mov    ip, lr
265     bl    lowlevel_init
266     mov    lr, ip
267     mov    pc, lr
268 #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文件中,代码如下:

133 lowlevel_init:
134     /* memory control configuration */
135     /* make r0 relative the current location so that it */
136    /* reads SMRDATA out of FLASH rather than memory ! */
137     ldr     r0, =SMRDATA
138     ldr    r1, _TEXT_BASE
139     sub    r0, r0, r1
140     ldr    r1, =BWSCON    /* Bus Width Status Controller */
141    add     r2, r0, #13*4
142 0:
143     ldr     r3, [r0], #4
144     str     r3, [r1], #4
145    cmp     r2, r0
146     bne     0b
147 
148     /* everything is fine now */
149     mov    pc, lr
150 
151     .ltorg
152 /* the literal pools origin */
153
154 SMRDATA:
155     .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
156     .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
157     .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
158     .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
159     .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
160     .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
161     .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
162     .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
163     .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
164     .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
165     .word 0x32
166     .word 0x30
167     .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指针

163 #ifndef CONFIG_SKIP_RELOCATE_UBOOT
164 relocate:                /* relocate U-Boot to RAM        */
165     adr    r0, _start        /* r0 <- current position of code   */
166     ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM */
167     cmp     r0, r1                  /* don't reloc during debug         */
168    beq     stack_setup
169 
170     ldr    r2, _armboot_start
171     ldr    r3, _bss_start
172     sub    r2, r3, r2        /* r2 <- size of armboot            */
173     add    r2, r0, r2        /* r2 <- source end address         */
174 
175 copy_loop:
176     ldmia    r0!, {r3-r10}        /* copy from source address [r0]    */
177     stmia    r1!, {r3-r10}        /* copy to   target address [r1]    */
178     cmp    r0, r2            /* until source end addreee [r2]    */
179     ble    copy_loop
180 #endif    /* CONFIG_SKIP_RELOCATE_UBOOT */
181 
182     /* Set up the stack                            */
183 stack_setup:
184     ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot   */
185     sub    r0, r0, #CFG_MALLOC_LEN    /* malloc area                      */
186     sub    r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
187 #ifdef CONFIG_USE_IRQ
188     sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
189 #endif
190     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中有定义,内存使用如下图所示:

6 清除bss段

85 .globl _bss_start
86 _bss_start:
87     .word __bss_start
88 
89 .globl _bss_end
90 _bss_end:
91     .word _end
192 clear_bss:
193     ldr    r0, _bss_start        /* find start of bss segment        */
194     ldr    r1, _bss_end        /* stop here                        */
195     mov     r2, #0x00000000        /* clear                            */
196 
197 clbss_l:str    r2, [r0]        /* clear loop...                    */
198     add    r0, r0, #4
199     cmp    r0, r1
200     ble    clbss_l

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

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

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

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

223 ldr    pc, _start_armboot
224 
225 _start_armboot:    .word start_armboot

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

8 异常中断处理

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

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

41 .globl _start
42 _start:    b       reset
43     ldr    pc, _undefined_instruction
44     ldr    pc, _software_interrupt
45     ldr    pc, _prefetch_abort
46     ldr    pc, _data_abort
47     ldr    pc, _not_used
48     ldr    pc, _irq
49     ldr    pc, _fiq
50 
51 _undefined_instruction:    .word undefined_instruction
52 _software_interrupt:    .word software_interrupt
53 _prefetch_abort:    .word prefetch_abort
54 _data_abort:        .word data_abort
55 _not_used:        .word not_used
56 _irq:            .word irq
57 _fiq:            .word fiq
58 
59     .balignl 16,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、设置好栈,并跳转到第二阶段入口函数

原文地址:https://www.cnblogs.com/053179hu/p/9313548.html