u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件
学习目标:
- 对start.S中每一行代码,都有基本了解
- 通过对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文件的更多相关文章
- 使用 Spring Boot 快速构建 Spring 框架应用--转
原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-spring-boot/ Spring 框架对于很多 Java 开发人员来说都不陌生.自从 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.但是在我们 ...
- 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 ...
- 如何解包,编辑,重新打包boot images
HOWTO: Unpack, Edit, and Repack Boot Images http://forum.xda-developers.com/showthread.php?t=443994 ...
- 使用 Spring Boot 快速构建 Spring 框架应用,PropertyPlaceholderConfigurer
Spring 框架对于很多 Java 开发人员来说都不陌生.自从 2002 年发布以来,Spring 框架已经成为企业应用开发领域非常流行的基础框架.有大量的企业应用基于 Spring 框架来开发.S ...
- Spring Boot 配置优先级顺序
一般在一个项目中,总是会有好多个环境.比如: 开发环境 -> 测试环境 -> 预发布环境 -> 生产环境 每个环境上的配置文件总是不一样的,甚至开发环境中每个开发者的环境可能也会有一 ...
- Spring Boot——开发新一代Spring应用
Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...
- 用Gradle构建Spring Boot项目
相比起Maven的XML配置方式,Gradle提供了一套简明的DSL用于构建Java项目,使我们就像编写程序一样编写项目构建脚本.本文将从无到有创建一个用Gradle构建的Spring Boot项目, ...
- 基于Groovy应用程序的spring boot
spring boot CLI 它是使用Spring Boot的最简单的和快速的的方法.他是一个基于Groovy脚本的命令工具.可以按照以下步骤安装次工具: 1.去spring官网下载 http:// ...
随机推荐
- windows程序设置开机自动启动
//调用方法:设置开机启动 SetAutoRun(Process.GetCurrentProcess().ProcessName, true, Application.StartupPath + @& ...
- Python实例---FTP小程序
[更多参考] 点击下载
- 沉淀再出发:ElasticSearch的中文分词器ik
沉淀再出发:ElasticSearch的中文分词器ik 一.前言 为什么要在elasticsearch中要使用ik这样的中文分词呢,那是因为es提供的分词是英文分词,对于中文的分词就做的非常不好了 ...
- 省事之通用Makefile模版
现在编译方案都偏爱使用cmake解决问题,这两条做unity插件,还是用Makefile,居然忘得光光,好记性不如烂笔头. 后面,翻箱倒柜找到以前为炼金术写的Makefiel,发现还真是挺好用,贴出来 ...
- 深入浅出SharePoint2012——安装Report Service
安装顺序 Microsoft .NET Framework 3.5 SP1 report service installation,pls SQLServer2008R2SP1-KB2528583-x ...
- Tomcat服务时区设置
tomcat服务不设置时间,会自动取系统时间,根据项目部署服务器位置不同时间会有差别,统一设置tomcat服务集群时间为东八区时间,具体设置如下: 在tomcat目录的bin文件夹下,找到文件cata ...
- 自定义控件(视图)2期笔记13:View的滑动冲突之 内部拦截法
1. 内部拦截法: 父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理. 这种方法和Android中的事件分发机制不一样,需要配合request ...
- Guava包学习---I/O
Guava的I/O平时使用不太多,目前项目原因导致基本上只有在自己写一些文本处理小工具才用得到.但是I/O始终是程序猿最常遇到的需求和面试必问的知识点之一.同时Guava的I/O主要面向是时JDK5和 ...
- [CTSC2018]假面
题目 先来考虑一下第一问,血量有\(P\)的概率减\(1\) 由于我们最后需要求每一个人的期望血量,于是考虑维护出每个人处于不同血量时候的概率 一个简单\(dp\)即可 \[dp_{i,j}=dp_{ ...
- C语言文件操作总结
文件的打开操作 fopen 打开一个文件,操作文件指针FILE * 文件的关闭操作 fclose 关闭一个文件 文件的读写操作 fgetc 从文件中读取一个字符 fputc 写一个字符到文件中去 fg ...