U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

时间:2022-08-31 16:46:05

u-boot的启动阶段分为两个,第一部分主要为start.S文件,帖子尽可能的分析了每一行代码的意思,查看了很多手册,分析的目的也是为了学习吧,写博客也是想给自己的学习留下点东西,这些东西网上其实很多,但是感觉看别人写的和字自己分析写一遍,差别好大。转载请注明出处,下面进入正题。

第11行:

#include <asm-offsets.h>//由kbuild自动生成,且不管

#include <common.h>  //include下包含其他通用头文件的头文件

#include <config.h>    //很多体系架构下都有这个各自体系的配置头文件

 

一.start_codeCPSR分析

第24行:

.globl _start

_start:   b     start_code

      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

这里从_start开始,第一句,b   start_code意义是跳转到start_code这部分。我们切过去看。

在第79行:

start_code:

      /*

       * set the cpu to SVC32 mode

       */

      mrs r0, cpsr

      bic  r0, r0, #0x1f

      orr   r0, r0, #0xd3

      msr cpsr, r0

这部分是将cpu设置成SVC32模式,我们知道arm有7种工作模式,这里设置为等级最高,一般我们是工作在usr模式。

mrs       r0,cpsr

将状态寄存器cpsr的内容送到通用寄存器r0中

bic r0, r0, #0x1f

这句话起始就是对r0的低5位清零。bic指令的意思对 Rn 中的值和 Operand2 值的反码按位进行逻辑“与”运算。

orr  r0, r0,#0xd3

这里是设置r0里面的低5位为10011,即svc模式,同时关闭irq和fiq。

msr       cpsr,r0

将r0的值写回给cpsr。

这里我们看一下s3c2440的datasheet:

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

    这里给出了CPSR寄存器各个位的定义,我们这里着重关心低八位,0到5位设置工作模式,下面的图里面给出了各个模式对应的位应该设置为何值。5,6,7三位分别对应状态位,fiq和irq,上面给r0赋值0xd3就是11010011,对应进去一目了然。

 U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

二.关中断,时钟设置分析

下面第102行:

#ifdef CONFIG_S3C24X0

      /* turn offthe watchdog */

 

# if defined(CONFIG_S3C2400)

#  define pWTCON       0x15300000

#  define INTMSK 0x14400008 /*Interrupt-Controller base addresses */

#  defineCLKDIVN     0x14800014 /* clock divisor register */

#else

#  define pWTCON       0x53000000

#  define INTMSK 0x4A000008       /*Interrupt-Controller base addresses */

#  defineINTSUBMSK       0x4A00001C

#  defineCLKDIVN     0x4C000014       /* clock divisor register */

# endif

这段的功能是定义看门狗,中断和子中断以及分频器的寄存器地址,此处我们的是s3c2440,可以看看数据手册,以INTMSK为例。·

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

INTMSK的基地址果然是0x4A000008.

    下面,第116行:

      ldr   r0, =pWTCON

      mov       r1, #0x0

      str   r1, [r0]

ldr这里使用了伪指令,是将pWTCON的地址写到r0里面。mov讲0赋给r1,最后str是将r1的值放到r0代表的地址的内存里面去。这段话其实就是对pWTCON清零来关闭看门狗。

第123行:

mov      r1,#0xffffffff

ldr  r0, =INTMSK

str  r1, [r0]

参考上一段话,因为中断控制寄存器都是置一清零的,这里是关闭所有的中断。

第133行:

#if defined(CONFIG_S3C2440)

      ldr   r1, =0x7fff

      ldr   r0, =INTSUBMSK

      str   r1, [r0]

#endif

这部分是屏蔽子中断,2440的子中断控制寄存器只有低15位有效,别的位保留,所以这里为0x7fff。

第139行:

#if defined(CONFIG_S3C2440)

#define MPLLCON 0x4c000004

#define UPLLCON 0x4c000008

#define CAMDIVN 0x4c000018

 

      ldrr0,=CAMDIVN

      mov r1,#0

      str r1,[r0]

 

      ldrr0,=CLKDIVN

      movr1,#0x05

      str r1,[r0]

            

      mrcp15,0,r0,c1,c0,0

      orrr1,r1,#0xc0000000

      mcrp15,0,r0,c1,c0,0

      ldrr0,=UPLLCON

      ldrr1,=0x38022

      str r1,[r0]

      nop

      nop

      nop

      nop

      nop  

      nop

      nop

      ldrr0,=MPLLCON

      ldrr1,=0x5c011

      str r1,[r0]

     

#else

上面这段主要做了三个时钟的设置,第一个关闭了为摄像头的分频器,第二个设置USB的时钟频率为48M,第三设置系统的时钟频率为400MHZ.具体的为什么,我们来看看。

CLKDIVN寄存器参看数据手册如下:

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

mov r1,#0x05也就是0101,由于我们设置了CAMDIV均为0,因此对应的PCLK:HCLK:FCLK=1:2:8,分频系数已经设置好。

mrc p15,0,r0,c1,c0,0

orr r1,r1,#0xc0000000

mcr p15,0,r0,c1,c0,0

此处为将协处理器p15的寄存器中的数据传送到ARM处理器的寄存器r0中,其中1是协处理器操作码1,0是协处理器操作码2,c1存放第一个操作数的协处理器寄存器,c0存放第二个操作数的协处理器寄存器。最终目的是将arm从快速总线模式转换为异步总线模式。

ldr r0,=UPLLCON

ldr r1,=0x38022

str r1,[r0]

这里设置USB的时钟频率为48M,具体的参照手册。

ldr r0,=MPLLCON

ldr r1,=0x5c011

str r1,[r0]

这里最终将系统时钟频率设置为400M,MPLLCON是控制FCLK和Fin之间的关系,查看数据手册如下:

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

这里给出了m,p,s三个参数是如何来的,我们再看

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

这里设置为0x5c011也就是0101 1100 0000 0001 0001,根据数据位,可以计算得到s=1,p=1+2=3,m=172+8=100,又因为我们的晶振为12MHZ,所以mpll=fclk=(2*100*12)/(3*2)=400MHZ。

 

三.cpu_init_critMMU CP15协处理器配置分析

下面,第186行:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

      bl    cpu_init_crit

#endif

如果没有定义CONFIG_SKIP_LOWLEVEL_INIT,那就跳转到cpu_init_crit这个函数,这里使用了bl,也就是下一条指令的执行地址,存放在lr链接寄存器里面,说明子函数运行结束之后,使用mov pc lr,程序还是要回到这里继续执行,切过去,第212行:

       /*

        *flush v4 I/D caches

        */

       mov       r0,#0

       mcr p15,0, r0, c7, c7, 0    /* flush v3/v4 cache*/

       mcr p15,0, r0, c8, c7, 0    /* flush v4 TLB */

 

       /*

        *disable MMU stuff and caches

        */

       mrc p15,0, r0, c1, c0, 0

       bic  r0,r0, #0x00002300   @ clear bits 13, 9:8(--V- --RS)

       bic  r0,r0, #0x00000087   @ clear bits 7, 2:0(B--- -CAM)

       orr   r0,r0, #0x00000002   @ set bit 2 (A) Align

       orr   r0,r0, #0x00001000   @ set bit 12 (I) I-Cache

       mcr p15,0, r0, c1, c0, 0

首先来看看mcr和mrc这两个指令吧,查到如下:

MRC {条件}协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,{协处理器操作码2} 
  MCR {条件}协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,{协处理器操作码2} 

arm的cp15协处理器时只能够被mcr和mrc两个指令操作的,这里我们查看datasheet:

U-boot-2014.04移植到MINI2440(4) 第一启动阶段start.S等详细分析

这里由于是cp15,所以8到11位是1111,各个位对应的意思也可以去datesheet查到。这里不赘述了。

       mcr p15,0, r0, c7, c7, 0    /* flush v3/v4 cache*/

       mcr p15,0, r0, c8, c7, 0    /* flush v4 TLB */

这里先对r0清零以后,再对c7和c8进行清零,而c7和c8分别对应什么呢,可以继续看下手册里面的寄存器编号:

寄存器编号

基本作用

在MMU中的作用

在PU中的作用

0

ID编码(只读)

ID编码和cache类型

 

1

控制位(可读写)

各种控制位

 

2

存储保护和控制

地址转换表基地址

Cachability的控制位

3

存储保护和控制

域访问控制位

Bufferablity控制位

4

存储保护和控制

保留

保留

5

存储保护和控制

内存失效状态

访问权限控制位

6

存储保护和控制

内存失效地址

保护区域控制

7

高速缓存和写缓存

高速缓存和写缓存控制

 

8

存储保护和控制

TLB控制

保留

9

高速缓存和写缓存

高速缓存锁定

 

10

存储保护和控制

TLB锁定

保留

11

保留

 

 

12

保留

 

 

13

进程标识符

进程标识符

 

14

保留

 

 

15

因不同设计而异

因不同设计而异

因不同设计而异

    可以看到表格里面c7,c8在MMU中的作用是告诉缓存和写缓存控制以及TLB控制,其实c7和c8是两个只写寄存器,这里就是清除cache和写缓存,以及清除TLB就是这么做的。

mrc       p15,0, r0, c1, c0, 0

       bic  r0,r0, #0x00002300   @ clear bits 13, 9:8(--V- --RS)

       bic  r0,r0, #0x00000087   @ clear bits 7, 2:0(B--- -CAM)

       orr   r0,r0, #0x00000002   @ set bit 2 (A) Align

       orr   r0,r0, #0x00001000   @ set bit 12 (I) I-Cache

       mcr p15,0, r0, c1, c0, 0

    这段呢,其实就很明了了,首先将c0和c1的值读给r0,然后分别清除对应的位,分别是0到2位,7位,8,9,13位,这些位是干嘛的呢?为什么要清楚这些位。查找datasheet看到c1,下面有两个表格,清除的位和下面用orr指令设置的位我都标注出来了。

31 16

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

附加

L4

RR

V

I

Z

F

R

S

B

L

D

P

W

C

A

M

 

说    明

M

0:禁止MMU或者PU1:使能MMU或者PU

A

0:禁止地址对齐检查;1:使能地址对齐检查

C

0:禁止数据/整个cache1:使能数据/整个cache

W

0:禁止写缓冲;1:使能写缓冲

P

0:异常中断处理程序进入32位地址模式;1:异常中断处理程序进入26位地址模式

D

0:禁止26位地址异常检查;1:使能26位地址异常检查

L

0:选择早期中止模型;1:选择后期中止模型

B

0little endian1big endian

S

在基于MMU的存储系统中,本位用作系统保护

R

在基于MMU的存储系统中,本位用作ROM保护

F

0:由生产商定义

Z

0:禁止跳转预测功能;1:使能跳转预测指令

I

0:禁止指令cache1:使能指令cache

V

0:选择低端异常中断向量0x0~0x1c1:选择高端异常中断向量0xffff0000~ 0xffff001c

RR

0:常规的cache淘汰算法,如随机淘汰;1:预测性淘汰算法,如round-robin淘汰算法

L4

0:保持ARMv5以上版本的正常功能;1:将ARMv5以上版本与以前版本处理器兼容,不根据跳转地址的bit[0]进行ARM指令和Thumb状态切换:bit[0]等于0表示ARM指令,等于1表示Thumb指令

总结一下这一步具体做了哪些事情,首先禁止MMU,禁止对齐检查,禁止整个cache,选择为小尾数,取消系统保护,取消rom保护,禁止指令cache,选择低端的异常中断向量。然后再设置对齐检查,因为我们的ALIGN是四字节对齐的,然后再使能指令cache。看来简单的禁止MMU不是那么简单的。。。

 

四.lowlevel_init.S分析

第235行:

      mov       ip, lr

 

      bl    lowlevel_init

首先,ip是一个内部调用暂时寄存器,我们现在是在cpu_init_crit函数里面,而一开始从start_code跳转到cpu_init_crit里面的时候,lr保存的是从cpu_init_crit用于返回到start_code的地址,这里再跳转到lowlevel_init的时候我们需要将lr的值暂存到ip里面,待会儿从lowlevel_init回来的时候,再用ip把保存的地址还给lr,这样我们再进行跳转就回到start_code里面去了。说白了,就是函数的嵌套调用方法。

下面我们切换到lowlevel_init中去,这个文件在board/mini2440/mini2440下,打开。

第114行:

lowlevel_init:

      /* memorycontrol configuration */

      /* make r0relative the current location so that it */

      /* readsSMRDATA out of FLASH rather than memory ! */

      ldr     r0, =SMRDATA

      ldr   r1, =CONFIG_SYS_TEXT_BASE

      sub  r0, r0, r1

三段注释部分说的是这部分是内存控制的配置,请使r0相对当前位置从FLASH读出SMRDATA,而不是从存储器里面。(暂时不明白啥意思)

    ldr    r0, =SMRDATA

这里是一个伪指令,SMRDATA使用了后面的在low文件最后的.ltorg伪指令,将其作为了一个数据缓冲池,而SMRDATA就是数据缓冲池的名称。见文件最后第132行开始:

.ltorg

/* the literal pools origin */

 

SMRDATA:

    .word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

    .word((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))

    .word((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))

    .word((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))

    .word((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))

    .word((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))

    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))

    .word((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))

    .word((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

    .word((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

    .word 0x32

    .word 0x30

    .word 0x30

一般数据缓冲池都放在跳转指令最后或者子程序返回指令之后,这样处理器就不会把它当做指令来处理啦。但是由于LDR访问范围要在4KB之内,所以这个数据池也要放在距离这条LDR指令4KB之内的地方。

ldr     r0,=SMRDATA

其实就是让r0等于SMRDATA代表的数据池的起始地址。

ldr  r1,=CONFIG_SYS_TEXT_BASE

这是让r1等于CONFIG_SYS_TEXT_BASE代表的地址,有说是FLASH的0地址或者内部4K SRAM的0地址。这里我理解的是0x30008000,应该是SDRAM的起始地址。

sub r0, r0, r1

这句,计算r0和r1的差值,并且赋给r0,实际上,是计算SMRDATA代表的首地址和arm实际的地址空间的差值,也就是u-boot启动的时候,无论是从nor还是nand ,SMRDATA的绝对访问地址。

ldr  r1, =BWSCON  

将BWSCON代表的bank控制寄存器的地址赋给r1.

add     r2, r0,#13*4

    这一步是为下一步判断循环结束条件做准备,r2是用来存放这个地址的最大值,因为13个寄存器,每个偏移量为4。

0:

ldr    r3, [r0], #4

将r0地址中的值赋给r3,并将r0+4,指向下一个内存池里的数据

str    r3, [r1], #4

同样,把刚才r0给r3的值给r1,并且使r1+4,准备接收下一次的数据

cmp    r2, r0

这是判断循环条件,因为有13个寄存器,所以刚才r0+13*4,每次偏移4个,正好13次结束,比较是否相等,不等继续跳转到0处执行,相等的话说明配置结束。下一条指令,lr把保存的执行地址给PC,跳回cpu_init_crit。

bne    0b

    mov      pc,lr

    以上为配置结束以后,SDRAM就可以工作了,这样我们才能访问0x30008000开始的地址空间,同时要注意的是:SMRDATA的绝对地址(即相当于_start的地址)大于了4K,则这个代码就不能使用nand启动了。因为nand启动时只能映射前4k代码到SRAM。而nor启动则没有这个限制,因为nor启动在SDRAM运行代码前可支配的地址空间是0到norflash大小。

下面回到start.S文件,因为我们已经跳转回来了,接着第239行:

mov      lr, ip

mov      pc, lr

这里把刚才暂存的lr保存的下一条指令的运行地址还给lr,然后再由lr传给pc,也就是说继续跳转回去,到start_code,我们看第190行。

bl   _main

跳转到这个函数里面去了,这个_main其实也做了很多事,因为我现在还未移植从nor或者nand启动,这个会留到以后具体分析。其实它是跳转到了arch/arm/lib/crt0.S里面去了,在那里进行了__TEXT_BASE部分,CONFIG_SYS_TEXT_BASE的定义,以及设定了将代码如何拷贝到sdram里面去的过程。之后再跳转arch/arm/lib/board.c里面的board_init_f等等。这里就不赘述了。

start.S里面剩下从244行以后的是和中断有关的一些处理,u-boot这里不使用中断,所以不做分析。

至此,总结一下第一启动阶段,start.S做了些什么。

第一:设置CPU为SVC模式

第二:关闭看门狗

第三:关中断和子中断

第四:设置时钟

第五:MMU关闭,清除cache和TLB,使能地址对齐检查等

第六:初始化SDRAM

 

就分析到这里吧,如有不正确的地方,还请指出,共同进步。