学习目标:

  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为当前状态寄存器,它的格式如下所示:

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 关看门狗

 /* 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文件开头部分,可以看到如下图所示的头文件的引用

打开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 关闭中断

     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 */

第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中有定义,内存使用如下图所示:

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异常向量表如下图所示:

当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、设置好栈,并跳转到第二阶段入口函数

u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件的更多相关文章

  1. 使用 Spring Boot 快速构建 Spring 框架应用--转

    原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-spring-boot/ Spring 框架对于很多 Java 开发人员来说都不陌生.自从 2 ...

  2. MIT 6.828 JOS学习笔记7. Lab 1 Part 2.2: The Boot Loader

    Lab 1 Part 2 The Boot Loader Loading the Kernel 我们现在可以进一步的讨论一下boot loader中的C语言的部分,即boot/main.c.但是在我们 ...

  3. How to create a "BOOT USB DISK" for EXSI6.0

    1 准备工作 opensuse 13.2ESXi ISO文件  //vmware 官网下载 VMware-VMvisor-Installer-5.1.0-799733.x86_64.iso,XXXXX ...

  4. 如何解包,编辑,重新打包boot images

    HOWTO: Unpack, Edit, and Repack Boot Images http://forum.xda-developers.com/showthread.php?t=443994 ...

  5. 使用 Spring Boot 快速构建 Spring 框架应用,PropertyPlaceholderConfigurer

    Spring 框架对于很多 Java 开发人员来说都不陌生.自从 2002 年发布以来,Spring 框架已经成为企业应用开发领域非常流行的基础框架.有大量的企业应用基于 Spring 框架来开发.S ...

  6. Spring Boot 配置优先级顺序

    一般在一个项目中,总是会有好多个环境.比如: 开发环境 -> 测试环境 -> 预发布环境 -> 生产环境 每个环境上的配置文件总是不一样的,甚至开发环境中每个开发者的环境可能也会有一 ...

  7. Spring Boot——开发新一代Spring应用

    Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...

  8. 用Gradle构建Spring Boot项目

    相比起Maven的XML配置方式,Gradle提供了一套简明的DSL用于构建Java项目,使我们就像编写程序一样编写项目构建脚本.本文将从无到有创建一个用Gradle构建的Spring Boot项目, ...

  9. 基于Groovy应用程序的spring boot

    spring boot CLI 它是使用Spring Boot的最简单的和快速的的方法.他是一个基于Groovy脚本的命令工具.可以按照以下步骤安装次工具: 1.去spring官网下载 http:// ...

随机推荐

  1. 网络编程进阶---->>> hamc模块 socketserver模块验证合法性 两者进行通信连接

    我们在工作中经常遇到,你公司内的某一台电脑要去访问你的服务器或者一个服务端电脑,那么你是让每一台都进行连接吗?  那不可能的  你肯定要进行限定的 验证客户端链接的合法性: hamc模块 hamc也是 ...

  2. 企业级Apache详解2

    http_conf主配置说明 root@lamp01 apache]# grep -Ev "#|^$" conf/httpd.conf DocumentRoot "/us ...

  3. c# winform文本框数字,数值校验

    文本框数字,数值校验 public void DigitCheck_KeyPress(object sender, KeyPressEventArgs e) { e.Handled = !char.I ...

  4. 用valgrind检测php扩展内存泄露

    原文:https://bugs.php.net/bugs-getting-valgrind-log.php 前提 1,编译php的时候,必须要带上--enable-debug选项. 2,禁用php的内 ...

  5. IOS Singleton(单例)

    Singleton.h // .h #define singleton_interface(class) + (instancetype)shared##class; // .m #define si ...

  6. python BaseManager中register()的描述

    register(typeid[, callable[, proxytype[, exposed[, method_to_typeid[, create_method]]]]]) A classmet ...

  7. What Shape Layers Are-CAShapeLayer

    矢量图.gpu直接使用.占用内存小 What Shape Layers Are Shape layers are layers capable of defining shapes as vector ...

  8. 【[HEOI2016/TJOI2016]序列】

    压行真漂亮 首先这肯定是一个\(dp\)了 设\(dp_i\)表示\(i\)结尾的最长不下降子序列的长度 显然我们要找一个\(j\)来转移 也就是\(dp_i=max(dp_j+1)\) 那么什么样的 ...

  9. 让PHP更快的提供文件下载

    一般来说, 我们可以通过直接让URL指向一个位于Document Root下面的文件, 来引导用户下载文件. 但是, 这样做, 就没办法做一些统计, 权限检查, 等等的工作. 于是, 很多时候, 我们 ...

  10. oracle数据库之用户管理

    转载 Oracle创建用户.角色.授权.建表   一.oracle数据库的权限系统分为系统权限与对象权限: 系统权限( database system privilege )可以让用户执行特定的命令集 ...