uboot-spl的程序流程主要包含下面的几个函数:

_start->reset->save_boot_params->cpu_init_crit->lowlevel_init->_main->board_init_f

在armv7架构的uboot-spl,主要需要做如下事情

    • 关闭中断,svc模式
    • 禁用MMU、TLB
    • 芯片级、板级的一些初始化操作 
      • IO初始化
      • 时钟
      • 内存
      • 选项,串口初始化
      • 选项,nand flash初始化
      • 其他额外的操作
    • 加载BL2,跳转到BL2

下面详细分析一下它的过程。(注意,本人是以uboot2011的源码版本来分析的。)

一、首先,通过uboot-spl编译脚本/u-boot/arch/arm/cpu/armv7/u-boot-spl.lds,程序如下:

...//前面省略
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.text :
{
__start = .;
arch/arm/cpu/armv7/start.o (.text)
*(.text*)
} >.sram . = ALIGN();
.rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram . = ALIGN();
.data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
. = ALIGN();
__image_copy_end = .;
_end = .; .bss :
{
. = ALIGN();
__bss_start = .;
*(.bss*)
. = ALIGN();
__bss_end__ = .;
} >.sdram
}

对于任何程序,入口函数是在链接时决定的,uboot的入口是由链接脚本决定的。uboot下armv7链接脚本默认目录为arch/arm/cpu/u-boot.lds。这个可以在配置文件中与CONFIG_SYS_LDSCRIPT来指定。

入口地址也是由连接器决定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定。这个会在编译时加在ld连接器的选项-Ttext中。

链接脚本中这些宏的定义在linkage.h中,看字面意思也明白,程序的入口是在_start.,后面是text段,data段等。

所以uboot-spl的代码入口函数是_start。

二、_start的定义在/u-boot/arch/arm/cpu/armv7/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 /* irq中断向量 */
ldr pc, _fiq /* fiq中断向量 *
#ifdef CONFIG_SPL_BUILD /* 中断向量表入口地址 */
_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
_pad: .word 0x12345678 /* now 16*4=64 */
#else
_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
_pad: .word 0x12345678 /* now 16*4=64 */
#endif /* CONFIG_SPL_BUILD */ .global _end_vect
_end_vect: .balignl ,0xdeadbeef

.global声明_start为全局符号,_start就会被连接器链接到,也就是链接脚本中的入口地址了。
以上代码是设置arm的异常向量表,arm异常向量表如下:

 

地址

异常

进入模式

描述

0x00000000

复位

管理模式

复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行

0x00000004

未定义指令

未定义模式

遇到不能处理的指令时,产生未定义指令异常

0x00000008

软件中断

管理模式

执行SWI指令产生,用于用户模式下的程序调用特权操作指令

0x0000000c

预存指令

中止模式

处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常

0x00000010

数据操作

中止模式

处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常

0x00000014

未使用

未使用

未使用

0x00000018

IRQ

IRQ

外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常

0x0000001c

FIQ

FIQ

快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常

8种异常分别占用4个字节,因此每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,reset异常是直接跳转到reset函数,其他7种异常是用ldr将处理函数入口地址加载到pc中。
后面汇编是定义了7种异常的入口函数,这里没有定义CONFIG_SPL_BUILD,所以走后面一个。
接下来定义的_end_vect中用.balignl来指定接下来的代码要16字节对齐,空缺的用0xdeadbeef,方便更加高效的访问内存。

后面,开始声明中断起始地址:

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de /* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif /* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de

这里声明中断处理函数栈起始地址,给出的值是0x0badc0de,是一个非法值,注释也说明了,这个值会在运行时重新计算,代码是在interrupt_init中。

/*
* the actual reset code
*/ reset:
bl save_boot_params
/*
* set the cpu to SVC32 mode
*/
@ mov r0, #
@ cmp r0, #
@ beq reset
here:
mrs r0, cpsr
bic r0, r0, #0x1f  /*工作模式位清零 */ 
orr r0, r0, #0xd3 /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */
msr cpsr,r0
/*以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。*/ #if defined(CONFIG_ARM_A7)
@set SMP bit
mrc p15, , r0, c1, c0,
orr r0, r0, #(<<)
mcr p15, , r0, c1, c0,
#endif
@#if defined(CONFIG_ARCH_SUN9IW1P1)
@ ldr r0, =0x008000e0
@ ldr r1, =0x16aa0001
@ str r1, [r0]
@#endif

在上电或者重启后,处理器取得第一条指令就是b reset,所以会直接跳转到reset函数处,而reset首先是跳转到save_boot_params中。

三、save_boot_params的定义如下:

void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)__attribute__((weak, alias("save_boot_params_default")));

save_boot_params函数在/u-boot/arch/arm/cpu/armv7/cpu.c中定义,该函数什么都没做。
这句话的意思:__attribute__((weak))将本模块的save_boot_params转成弱符号类型,如果该函数在其他地方没有定义,则为空函数。
由于它没有定义,所以这是个空函数。

接着,后面运行这段程序:

#if defined(CONFIG_OMAP34XX)
/* Copy vectors to mask ROM indirect addr */
adr r0, _start @ r0 <- current position of code
add r0, r0, # @ skip reset vector
mov r2, # @ r2 <- size to copy
add r2, r0, r2 @ r2 <- source end address
mov r1, #SRAM_OFFSET0 @ build vect addr
mov r3, #SRAM_OFFSET1
add r1, r1, r3
mov r3, #SRAM_OFFSET2
add r1, r1, r3
next:
ldmia r0!, {r3 - r10} @ copy from source address [r0]
stmia r1!, {r3 - r10} @ copy to target address [r1]
cmp r0, r2 @ until source end address [r2]
bne next @ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
/* No need to copy/exec the clock code - DPLL adjust already done
* in NAND/oneNAND Boot.
*/
bl cpy_clk_code @ put dpll adjust code behind vectors
#endif /* NAND Boot */
#endif /* CONFIG_OMAP34XX */
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif

这里需要注意,ARM默认的异常向量表入口在0x0地址,uboot的运行介质(norflash nandflash sram等)映射地址可能不在0x0起始的地址,所以需要修改异常向量表入口。
与网上的不同的是,这里没有对协处理器cp15的操作。
在cpy_clk_code之前的汇编的作用如下:

  1. 初始化异常向量表
  2. 设置cpu svc模式,关中断
  3. 设置异常向量入口

如果没有定义CONFIG_SYS_NAND_BOOT和CONFIG_SYS_ONENAND_BOOT,则进入cpy_clk_code。
四、下面直接看cpu_init_crit。

cpu_init_crit:
/*
* Invalidate L1 I/D
*/
...//省略详细的内容 /*
* disable MMU stuff and caches
*/
...//省略详细的内容 /*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
mov ip, lr @ persevere link reg across call
bl lowlevel_init @ go setup pll,mux,memory
mov lr, ip @ restore link
mov pc, lr @ back to my caller
#endif

在禁止了L1 I/D、MMU之后,调用了lowlevel_init。他在/u-boot/arch/arm/cpu/armv7/lowlevel_init.S中有定义
五、lowlevel_init函数

它是与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从mem启动,则会做memory初始化,方便后续拷贝到mem中运行。
lowlevel_init函数则是需要移植来实现,做clk初始化以及ddr初始化
从cpu_init_crit返回后,_start的工作就完成了,接下来就要调用_main,总结一下_start工作:

  1. 前面总结过的部分,初始化异常向量表,设置svc模式,关中断
  2. 初始化mmu cache tlb
  3. 板级初始化,pll memory初始化

六、初始化栈指针sp为调用board_init_f做准备,这个过程一般是在_main中实现的。

call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, # /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000 @bl boot_standby_relocat bl board_init_f

首先进行堆栈的设置,然后就跳转到board_init_f函数,其中传递给该函数的参数为0。

然后调用/u-boot/arch/arm/lib/board.c
此时已经进入C语言的代码,spl就结束了。

参考:

http://blog.csdn.net/xiaohai1232/article/details/60775435#t0

http://blog.csdn.net/skyflying2012/article/details/25804209

http://blog.csdn.net/ooonebook/article/details/52957395

https://wenku.baidu.com/view/254d6bc3d15abe23482f4dec.html

https://wenku.baidu.com/view/eb73e1edb8f67c1cfad6b89b.html

上一篇:http://www.cnblogs.com/yeqluofwupheng/p/7341989.html

下一篇:http://www.cnblogs.com/yeqluofwupheng/p/7355248.html

uboot学习之uboot-spl的程序流程分析的更多相关文章

  1. u-boot 学习系列 1 - SPL

    u-boot这个东西从自我N年前使用到现在,变化好多,今天开始重新研究下,本系列的研究都是基于BeagleBoneBlack(bbb)开发板和 u-boot v201801版本的. SPL介绍 在源代 ...

  2. uboot学习之uboot启动流程简述

    一.uboot启动分为了三个阶段BL0.BL1.BL2:BL0表示上电后运行ROM中固化的一段程序,其中ROM中的程序是厂家写进去的,所以具体功能可能根据厂家芯片而有所不同.功能如下: 初始化系统时钟 ...

  3. 01 . Go框架之Beego简介部署及程序流程分析

    Beego简介 beego是一个使用Go语言来开发WEB引用的GoWeb框架,该框架起始于2012年,由一位中国的程序员编写并进行公开,其目的就是为大家提供一个高效率的web应用开发框架.该框架采用模 ...

  4. u-boot的SPL源码流程分析

    上次梳理了一下SPL的基本概念和代码总体思路,这次就针对代码跑的流程做个梳理.SPL中,入口在u-boot-spl.lds中 ENTRY(_start) SECTIONS { .text : { __ ...

  5. uboot学习之uboot.bin的运行流程

    上篇博客:http://www.cnblogs.com/yeqluofwupheng/p/7347925.html 讲到uboot-spl的工作流程,接下来简述一下uboot.bin的工作流程,这对应 ...

  6. uboot学习之五-----uboot如何启动Linux内核

    uboot和内核到底是什么?uboot实质就是一个复杂的裸机程序:uboot可以被配置也可以做移植: 操作系统内核本身就是一个裸机程序,和我们学的uboot和其他裸机程序没有本质的区别:区别就是我们操 ...

  7. ucore操作系统学习笔记(一) ucore lab1系统启动流程分析

    一.ucore操作系统介绍 操作系统作为一个基础系统软件,对下控制硬件(cpu.内存.磁盘网卡等外设),屏蔽了底层复杂多样的硬件差异:对上则提供封装良好的应用程序接口,简化应用程序开发者的使用难度.站 ...

  8. stm32 usb数据接收与数据发送程序流程分析

    http://blog.csdn.net/u011318735/article/details/17424349 既然学习了USB,那就必须的搞懂USB设备与USB主机数据是怎么通讯的.这里主要讲设备 ...

  9. WordPress程序流程分析

    index.php 统一入口文件 包含wp-blog-heaer.php 包含wp-load.php 包含wp-config.php 数据库.语言包配置等 包含wp-setting.php 对各种运行 ...

随机推荐

  1. Hibernate中Criteria的完整用法2

    Criteria的完整用法 QBE (Query By Example) Criteria cri = session.createCriteria(Student.class); cri.add(E ...

  2. java多线程----悲观锁与乐观锁

    java多线程中悲观锁与乐观锁思想 一.悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线 ...

  3. Android进阶之绘制-自定义View完全掌握(一)

    Android的UI设计可以说是决定一个app质量的关键因素,因为人们在使用app的时候,最先映入眼帘的就是app的界面了,一个美观.充实的界面能够给用户带来非常好的体验,会在用户心中留下好的印象. ...

  4. 详解golang net之transport

    关于golang http transport的讲解,网上有很多文章读它进行了描述,但很多文章讲的都比较粗,很多代码实现并没有讲清楚.故给出更加详细的实现说明.整体看下来细节实现层面还是比较难懂的. ...

  5. unity_数据结构(常见数据结构及适用场景)

    常见的数据结构: 1.Array: 最简单的数据结构 特点:数组存储在连续的内存上.数组的内容都是相同类型.数组可以直接通过下标访问.优点:由于是在连续内存上存储的,所以它的索引速度非常快,访问一个元 ...

  6. Django之使用中间件解决前后端同源策略问题

    问题描述 前端时间在公司的时候,要使用angular开发一个网站,因为angular很适合前后端分离,所以就做了一个简单的图书管理系统来模拟前后端分离. 但是在开发过程中遇见了同源策略的跨域问题,页面 ...

  7. Delphi - 本地路径的创建、清空本地指定文件夹下的文件

    本地路径的创建 在做下载操作时,我们一般先把文件下载到本地指定的路径下,然后再做其他使用. 为了防止程序出现异常,我们通常需要先判断本地是否存在指定的路径. 以C盘Tmp文件夹为例,我们可以这样做,代 ...

  8. Jedis操作Redis--Set类型

    /** * Set(集合) * SADD,SCARD,SDIFF,SDIFFSTORE,SINTER,SINTERSTORE,SISMEMBER,SMEMBERS,SMOVE,SPOP,SRANDME ...

  9. Rikka with Prefix Sum(组合数学)

    Rikka with Prefix Sum 题目描述 Prefix Sum is a useful trick in data structure problems. For example, giv ...

  10. 2018中国大学生程序设计竞赛 - 网络选拔赛 hdu6438 Buy and Resell 买入卖出问题 贪心

    Buy and Resell Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)To ...