一、概述

u-boot的启动流程:
  从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c, 
  先来分析start.s 
   在flash中执行的引导代码,也就是bootloader中的stage1,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳到lib_arm/board.c中的start_armboot中去执行。

  uboot-第一阶段硬件初始化主要实现了:
  1.执行设置CPSR程序程序状态寄存器为管理模式
  2.关看门狗
  3.屏蔽中断
  4.关闭MMU,初始化SDRAM
  5.设置栈
  6.时钟设置
  7.重定位(代码从Flash拷贝至SDRAM中)
  8.清bss段(未初始的全局/静态变量)
  9.跳转到start_armboot函数(位于u-boot-1.1.6/lib_arm/borad.c,用来实现第2阶段硬件相关的初始化)

二、细节分析(参考一参考二

1、建立异常向量表

 #include <config.h>   //实际上定位的头文件是include/configs/smdk2410.h
#include <version.h>  //实际上定位的头文件是include/version_autogenerated.h
//建立异常向量表
1 .globl _start // .globl定义一个全局符号"_start",表明_start这个符号要被链接器用到
_start: //_start:系统复位设置,以下共8种不同的异常处理
b reset //复位异常 0x0
ldr pc, _undefined_instruction //未定义的指令异常 0x4
ldr pc, _software_interrupt // 软件中断异常 0x8
ldr pc, _prefetch_abort //内存操作异常 0xc
ldr pc, _data_abort //数据异常 0x10
ldr pc, _not_used //未使用 0x14
ldr pc, _irq //中断IRQ异常 0x18
ldr pc, _fiq //快速中断FIQ异常 0x1c _undefined_instruction: .word undefined_instruction //0x20
_software_interrupt: .word software_interrupt //0x24
_prefetch_abort: .word prefetch_abort // 0x28
_data_abort: .word data_abort //0x2c
_not_used: .word not_used //0x30
_irq: .word irq //0x34
_fiq: .word fiq //0x38 .balignl ,0xdeadbeef //0x3c

在第1行中".globl _start":使用.globol声明全局符号_start,在 board/100ask24x0/u-boot.lds中ENTRY(_start)这里用到
其中符号保存的地址都在顶层目录/system.map中列出来了

33f80000 t $a
33f80000 T _start //_start符号被链接在33f80000,其中33f80000是生成bin文件的运行启始地址.
33f80020 t $d
33f80020 t _undefined_instruction //_undefined_instruction符号被链接在33f80020
...
33f80160 t undefined_instruction //_undefined_instruction指向的undefined_instruction符号被链接在33f80160
33f801c0 t software_interrupt
33f80220 t prefetch_abort
33f80280 t data_abort
33f802e0 t not_used
33f80340 T Launch
33f803b0 t On_Steppingstone
33f80400 t irq
...

在第2行中_start之所以有8种不同的异常处理,是在2440芯片手册中已经规定好了的,如下图1:

CPU一上电设置了入口地址"ENTRY(_start)"后,就会进入"_start"全局符号中执行上面第3行跳转到复位异常字段: "b reset"。由上,复位异常处理需要进入管理模式(0x0000 0000),所以"b reset"跳转到了设置管理模式。

问题1:后面的异常处理为什么用ldr 不用b指令?

ldr是绝对跳转,b是相对跳转,它的地址与代码位置无关。复位异常在CPU运行前,并未初始化SDRAM(故不能使用0x3000 0000以上的地址),在正常工作后也可能触发复位,这时由于CPU已经对SDRAM、MMU(虚拟内存管理)等初始化了,
此时的虚拟地址和物理地址完全不同,所以reset使用b指令相对跳转。

2.后面的异常处理是怎么执行的?执行后异常处理又怎么退出的?

在2440芯片手册上给出,例如当处理一个中断IRQ异常时:
a.保存当前PC现场到寄存器R14,  
b.把当前程序状态寄存器(CPSR)保存到备份程序状态寄存器(SPSR)中.从异常退出的时候,就可以由SPSR来恢复CPSR。
c.根据中断IRQ异常处理,强制将 CPSR 模式位设为中断模式,如下图

d.强制 PC 从相关异常向量处取下条指令。跳转到0x18实现中断异常处理.
退出中断IRQ异常时:
  a). 将中断IRQ所对应的是R14_irq寄存器并放入到 PC 中,如下图,中断IRQ所对应的是R14_irq               寄存器,执行MOVS R14_svc .

   b). 复制 SPSR 的内容返回给 CPSR 中。
   c). 如果在异常进入时置位了中断禁止标志位异常,清除中断禁止标志位

问题3. 第12行中 .word:        类似于(unsigend long)      

以第12行中 _undefined_instruction: .word undefined_instruction为例
_undefined_instruction和undefined_instruction都是一个标号,
表示_undefined_instruction指向一个32位(4字节)地址,该地址用undefined_instruction符号变量代替。

用C语言表示就是:_undefined_instruction = &undefined_instruction

相当于PC从_undefined_instruction取值时,即undefined_instruction地址存到了PC中。
问题4. 第20行中 .balignl 16,0xdeadbeef:
它的意思就是在以当前地址开始,在地址为16的倍数的指令位置的上一个指令填入为0xdeadbeef的内容。
此时当前地址刚好0x3c=60,由于ARM每个指令间隔4个字节,且64%16=0,所以在0x3c中填入0xdeadbeef。
它们的作用就是为内存做标记,插在那里,这个位置往前有特殊作用的内存,禁止访问。

 

2、设置管理模式、关看门狗、屏蔽中断

reset--->start.o

reset:
/* 设置CPSR程序程序状态寄存器为管理模式 */
mrs r0,cpsr //MRS读出CPSR寄存器值到R0
bic r0,r0,#0x1f //将R0低5位清空
orr r0,r0,#0xd3 //R0与b'110 10011按位或,禁止IRQ和FIQ中断,10011:复位需要设为管理模式(图1)
msr cpsr,r0 //MSR写入CPSR寄存器 /* 关看门狗 */
# define pWTCON 0x53000000 //(WitchDog Timer)看门狗定时器寄存器WTCON,设为0X0表示关闭看门狗
# define INTMOD 0X4A000004 //(Interrupt Mode)中断模式寄存器INTMOD,相应位=0:IRQ模式,相应位=1:IRQ模式,
# define INTMSK 0x4A000008 //(Interrupt Mask)中断屏蔽寄存器INTMSK,相应位=0:开启中断服务,相应位=1:关闭中断服务
# define INTSUBMSK 0x4A00001C //中断次级屏蔽寄存器,相应位=0:开启中断服务,相应位=1:关闭中断服务
# define CLKDIVN 0x4C000014 //时钟分频寄存器 #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) //宏定义CONFIG_S3C2410已定义
ldr r0, =pWTCON //R0等于WTCON地址
mov r1, #0x0 //R1=0x0
str r1, [r0] //关闭WTCON寄存器,pWTCON=0;
/* 关中断 */
mov r1, #0xffffffff //R1=0XFFFF FFFF
ldr r0, =INTMSK //R0等于INTMSK地址
str r1, [r0] //*0x4A000008=0XFFFF FFFF(关闭所有中断)
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff //R1=0x3FF
ldr r0, =INTSUBMSK //R0等于INTSUBMSK地址
str r1, [r0] //*0x4A00001C=0x3FF(关闭次级所有中断)
# endif /*
判断系统是从nand启动的还是直接将程序下载到SDRAM中运行,
若系统从nand启动,这里得到r0和r1值是不一样的,r1=0x33f80000,
而r0=0x00000000。说明没初始化SDRAM,ne(no equal)标识符为真,所以bl cpu_init_crit执行跳转.
*/
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
blne cpu_init_crit

CPU复位后是从这里开始执行,这里初始化了:
  1.执行设置CPSR程序程序状态寄存器为管理模式
  2.关看门狗
  3.屏蔽中断
  4.进入cpu_init_crit函数关闭MMU,进入lowlevel_init初始化13个BANK寄存器来初始化SDR

3、进入cpu_init_crit函数(关闭MMU):

cpu_init_crit:

mov    r0, #
mcr p15, , r0, c7, c7, //关闭ICaches(指令缓存,关闭是为了降低MMU查表带来的开销)和DCaches(数据缓存,DCaches使用的是虚拟地址,开启MMU之前必须关闭)
mcr p15, , r0, c8, c7, //使无效整个数据TLB和指令TLB(TLB就是负责将虚拟内存地址翻译成实际的物理内存地址) mrc p15, , r0, c1, c0,
bic r0, r0, #0x00002300 @ clear bits , : (--V- --RS) //bit8:系统不保护,bit9:ROM不保护,bit13:设置正常异常模式0x0~0x1c,即异常模式基地址为0X0
bic r0, r0, #0x00000087 @ clear bits , : (B--- -CAM) //bit0~2:禁止MMU,禁止地址对齐检查,禁止数据Cache.bit7:设为小端模式
orr r0, r0, #0x00000002 @ set bit (A) Align //bit2:开启数据Cache
orr r0, r0, #0x00001000 @ set bit (I) I-Cache //bit12:开启指令Cache
mcr p15, , r0, c1, c0,
/*
mcr/mrc:
Caches:是一种高速缓存存储器,用于保存CPU频繁使用的数据。在使用Cache技术的处理器上,当一条指令要访问内存的数据时,
首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据。处理器会定期回写cache中的数据到内存。
根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。
其中DCaches和ICaches分别用来存放数据和执行这些数据的指令 TLB:就是负责将虚拟内存地址翻译成实际的物理内存地址,TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。
当应用程序访问一个虚拟地址的时候,会从TLB中查询出对应的物理地址,然后访问物理地址。TLB通常是一个分层结构,
使用与Cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。
这里禁用MMU,是方便后面直接使用物理地址来设置控制寄存器
*/
mov ip, lr //临时保存当前子程序返回地址,因为接下来执行bl会覆盖当前返回地址.
bl lowlevel_init //跳转到lowlevel_init(位于u-boot-1.1.6/board/100ask24x0/lowlevel_init.S)
mov lr, ip //恢复当前返回地址
mov pc, lr //退出

4、进入lowlevel_init函数 (初始化各个bank和SDRAM)、

lowlevel_init:
ldr r0, =SMRDATA //将SMRDATA的首地址(0x33F806C8)存到r0中
ldr r1, _TEXT_BASE //r1等于_TEXT_BASE内容,也就是TEXT_BASE(0x33F80000)
sub r0, r0, r1 //将0x33F806C8与0x33F80000相减,得到现在13个寄存器值在NOR Flash上存放的开始地址
ldr r1, =BWSCON //将BWSCON寄存器地址值存到r1中 (第一个存储器寄存器首地址)
add r2, r0, #* //每个寄存器4字节,r2=r0+13*4=NOR Flash上13个寄存器值最后一个地址
:
ldr r3, [r0], # //将r0的内容存到r3的内容中(r3等于SMRDATA里面值), 同时r0地址+=4;
str r3, [r1], # //将r3的内容存到r1所指的地址中(向寄存器地址里写入r3值),同时r1地址+=4;
cmp r2, r0 // 判断r2和r0
bne 0b //不等则跳转到第6行继续执行 mov pc, lr //跳回到返回地址中继续执行 SMRDATA:
.word (+(B1_BWSCON<<)+(B2_BWSCON<<)+(B3_BWSCON<<)+(B4_BWSCON<<)+(B5_BWSCON<<)+(B6_BWSCON<<)+(B7_BWSCON<<))
//设置每个BWSCON,注意BANK0由硬件连线决定了
.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))
//设置BANKCON0~BANKCON5
.word ((B6_MT<<)+(B6_Trcd<<)+(B6_SCAN))
.word ((B7_MT<<)+(B7_Trcd<<)+(B7_SCAN))
//设置BANKCON6~BANKCON7
.word ((REFEN<<)+(TREFMD<<)+(Trp<<)+(Trc<<)+(Tchr<<)+REFCNT)
//设置REFRESH,在S3C2440中11~17位是保留的,也即(Tchr<<16)无意义
.word 0xb1 //设置BANKSIZE,对于容量可以设置大写,多出来的空内存会被自动检测出来
.word 0x30 //设置MRSRB6
.word 0x30 //设置MRSRB7

5、返回start.s -- 设置栈

stack_setup: //设置栈,方便调用C函数
ldr r0, _TEXT_BASE //代码段的初始地址:r0=0x33f80000
sub r0, r0, #CFG_MALLOC_LEN //留出一段内存以实现malloc:r0=0x33f50000
sub r0, r0, #CFG_GBL_DATA_SIZE //再留出一段存一些全局参数的变量:r0=0x33F4FF80 #ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //中断与快中断的栈:r0=0x33F4DF7C
#endif
sub sp, r0, # //留出12字节内存给abort异常 设置栈顶sp=r0-12; #ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl clock_init //进入clock_init函数
#endif

6、进入clock_init函数设置时钟

void clock_init(void)
{
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000; //定义一个S3C24X0_CLOCK_POWER型结构体指针,clk_power->LOCKTIME=0x4C000000 if (isS3C2410) //isS3C2410为0,执行else
{... ...}
else
{
/* FCLK:HCLK:PCLK = 1:4:8 */
clk_power->CLKDIVN = S3C2440_CLKDIV; //S3C2440_CLKDIV=0X05 /* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" //使其从快总线模式改变为异步总线模式,在2440手册上看到
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1" //:::"r1" 向GCC声明:我对r1作了改动
); /* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF; //PLL 锁定时间计数寄存器 /* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //UCLK=48Mhz /* some delay between MPLL and UPLL */
delay (); //等待UCLK时钟波形稳定 /* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //FCLK=400Mhz /* some delay between MPLL and UPLL */
delay (); //等待FCLK时钟波形稳定
}
}

7、重定位、清bss段、跳转到start_armboot函数

relocate:    /* 拷贝u-boot到SDRAM */
adr r0, _start //r0:当前代码开始地址
ldr r1, _TEXT_BASE //r1:代码段连接地址(0X3FF8 0000)
cmp r0, r1 //测试现在在FLASH中还是RAM中
beq clear_bss //若_start==_TEXT_BASE,表示已经进行代码从Flash拷贝SDRAM了(通常是调试时直接下载到RAM中) ldr r2, _armboot_start //r2等于_armboot_start里的内容,也就是_start
ldr r3, _bss_start //r3等于_bss_start里的内容,(在连接脚本u-boot.lds中定义,是代码段的结束地址)
sub r2, r3, r2 //r2等于代码段长度 bl CopyCode2Ram // r0: source, r1: dest, r2: size 将从NOR FLASH上代码段(r0~r0+r2)拷贝到sdram地址(r3)0x3ff80000代码段地址上 clear_bss:
ldr r0, _bss_start //r0=__bss_start
ldr r1, _bss_end //r0等于_bss_end里的内容,也就是_end(在u-boot.lds里定义,是存bss的结束地址)
mov r2, #0x00000000 //r2=0;用来清bss所有段 clbss_l:
str r2, [r0] //*r0=0;
add r0, r0, # //r0+=4;
cmp r0, r1
ble clbss_l //小于等于一直执行clbss_l ldr pc, _start_armboot //pc等于_start_armboot里的内容,也就是跳转到start_armboot函数
_start_armboot: .word start_armboot *(_start_armboot)=start_armboot

小结:

uboot-第一阶段硬件初始化主要实现了:
1.执行设置CPSR程序程序状态寄存器为管理模式
2.关看门狗
3.屏蔽中断
4.关闭MMU,初始化SDRAM
5.设置栈
6.时钟设置
7.重定位(代码从Flash拷贝至SDRAM中)
8.清bss段(未初始的全局/静态变量)
9.跳转到start_armboot函数(位于u-boot-1.1.6/lib_arm/borad.c,用来实现第2阶段硬件相关的初始化)
接下来开始分析uboot-第二阶段硬件初始化。

(主要摘自第1阶段——uboot分析之硬件初始化start.S(4)

 

U-BOOT启动流程分析--start.s(二)的更多相关文章

  1. Spring Boot启动流程分析

    引言 早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧.今天让我们从spring boot启动开始 ...

  2. Spring Boot -- 启动流程分析之ApplicationContext 中

    上一节我们已经分析到AbsractApplicationContext类refresh方法中的postProcessBeanFactory方法,在分析registerBeanPostProcessor ...

  3. SpringBoot启动流程分析(二):SpringApplication的run方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. Uboot启动流程分析(二)

    1.前言 在前面的文章Uboot启动流程分析(一)中,链接如下: https://www.cnblogs.com/Cqlismy/p/12000889.html 已经简单地分析了low_level_i ...

  5. Uboot启动流程分析(转载)

    最近一段时间一直在做uboot移植相关的工作,需要将uboot-2016-7移植到单位设计的ARMv7的处理器上.正好元旦放假三天闲来无事,有段完整的时间来整理下最近的工作成果.之前在学习uboot时 ...

  6. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  7. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  8. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  9. u-boot启动流程分析(1)_平台相关部分

    转自:http://www.wowotech.net/u-boot/boot_flow_1.html 1. 前言 本文将结合u-boot的“board—>machine—>arch—> ...

  10. imx6 uboot启动流程分析

    参考http://blog.csdn.net/skyflying2012/article/details/25804209 这里以imx6平台为例,分析uboot启动流程对于任何程序,入口函数是在链接 ...

随机推荐

  1. Qt5.7新特性

    简述 Qt5.7发布了,新特性如下. 简述 新特性 C11 Support Required from the compiler New Features within existing module ...

  2. python 工具包安装

    (1)wxPython是python的常用gui yum install wxPython (2)numpy, scipy是常用的数学处理工具包 yum install scipy

  3. 2.Maven特点,Maven约定,建立第一个Maven项目

     1 Maven是跨平台的项目管理工具.主要服务于基于java平台的项目构建,依赖管理和项目信息管理. 项目构建 清理à编译à測试à报告à打包à部署 理想的项目构建: 高度自己主动化 跨平台 可重 ...

  4. [Typescript] Installing Promise Type Definitions Using the lib Built-In Types

    To fix Promise is not recolized in TypeScript, we can choose to use a lib: npm i @types/es6-promise ...

  5. iOS6和iOS7处理push不同之处,解决反复push,-(void) application: didReceiveRemoteNotification: fetchCompletionHandl

    如果读者已经知道push的基本知识,本文仅仅是解决一些适配,兼容问题.如果对push 不甚了解,參考以下的文章 1.[iOS push全方位解析](一) push的概述 2.[iOS push全方位解 ...

  6. C语言之文件操作04——输入矩阵a,b,求乘积c,并打印a,b,c到文件

    //文件与数组结合 /* ================================================================= 题目:输入矩阵a,b,求乘积c,并打印a, ...

  7. PGA和UGA内存的使用情况

    引用thomas kyte的PGA和UGA的统计内存的使用情况,用排序区不同的大小来查看pga.UGA.物理读取不同内存使用 会话1:创建测试表和测试不同排序大小 create table t as ...

  8. OpenGL编程逐步深入(八)伸缩变换

    准备知识 伸缩变换非常简单,它的目的是增大或者缩小对象的尺寸.例如:你可能希望用同一个模型创建不同大小的对象(例如形状相同,但大小不同的树木)或者你想改变对象的大小使它和游戏场景匹配.这些例子中你可能 ...

  9. BAPC 2014 Preliminary(第一场)

    D:Lift Problems On the ground floor (floor zero) of a large university building a number of students ...

  10. yii2.0缓存篇之页面缓存

    页面缓存: 如果整个页面都不会发生改变,就可以使用页面缓存缓存整个页面. public function behaviors(){            //此方法[也叫行为]会提前控制器内其他方法执 ...