[uboot] (第三章)uboot流程——uboot-spl代码流程

发布者:科技先锋最新更新时间:2025-01-24 来源: cnblogs关键字:uboot 手机看文章 扫描二维码
随时随地手机看文章

一、说明

1、uboot-spl入口说明

通过uboot-spl编译脚本project-X/u-boot/arch/arm/cpu/u-boot-spl.lds


ENTRY(_start)

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

对应于路径project-X/u-boot/arch/arm/lib/vector.S的_start,后续就是从这个函数开始分析。


2、CONFIG_SPL_BUILD说明

前面说过,在编译SPL的时候,编译参数会有如下语句: 

project-X/u-boot/scripts/Makefile.spl


KBUILD_CPPFLAGS += -DCONFIG_SPL_BUILD

所以说在编译SPL的代码的过程中,CONFIG_SPL_BUILD这个宏是打开的。 

uboot-spl和uboot的代码是通用的,其区别就是通过CONFIG_SPL_BUILD宏来进行区分的。


二、uboot-spl需要做的事情

CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。 

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


关闭中断,svc模式

禁用MMU、TLB

芯片级、板级的一些初始化操作 

IO初始化

时钟

内存

选项,串口初始化

选项,nand flash初始化

其他额外的操作

加载BL2,跳转到BL2

上述工作,也就是uboot-spl代码流程的核心。


三、代码流程

1、代码整体流程

代码整体流程如下,以下列出来的就是spl核心函数。 

_start———–>reset————–>关闭中断 

………………………………| 

………………………………———->cpu_init_cp15———–>关闭MMU,TLB 

………………………………| 

………………………………———->cpu_init_crit————->lowlevel_init————->平台级和板级的初始化 

………………………………| 

………………………………———->_main————–>board_init_f_alloc_reserve & board_init_f_init_reserve & board_init_f———->加载BL2,跳转到BL2 

board_init_f执行时已经是C语言环境了。在这里需要结束掉SPL的工作,跳转到BL2中。


2、_start

上述已经说明了_start是整个spl的入口,其代码如下: 

arch/arm/lib/vector.S


_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG

    .word   CONFIG_SYS_DV_NOR_BOOT_CFG

#endif

    b   reset

会跳转到reset中。 

注意,spl的流程在reset中就应该被结束,也就是说在reset中,就应该转到到BL2,也就是uboot中了。 

后面看reset的实现。


3、reset

建议先参考[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断,了解一下为什么要设置SVC、关闭中断以及如何操作。


代码如下: 

arch/arm/cpu/armv7/start.S


    .globl  reset

    .globl  save_boot_params_ret


reset:

    /* Allow the board to save important registers */

    b   save_boot_params

save_boot_params_ret:

    /*

     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,

     * except if in HYP mode already

     */

    mrs r0, cpsr

    and r1, r0, #0x1f       @ mask mode bits

    teq r1, #0x1a       @ test for HYP mode

    bicne   r0, r0, #0x1f       @ clear all mode bits

    orrne   r0, r0, #0x13       @ set SVC mode

    orr r0, r0, #0xc0       @ disable FIQ and IRQ

    msr cpsr,r0

@@ 以上通过设置CPSR寄存器里设置CPU为SVC模式,禁止中断

@@ 具体操作可以参考《[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断》的分析


    /* the mask ROM code should have PLL and others stable */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

    bl  cpu_init_cp15

@@ 调用cpu_init_cp15,初始化协处理器CP15,从而禁用MMU和TLB。

@@ 后面会有一小节进行分析


    bl  cpu_init_crit

@@ 调用cpu_init_crit,进行一些关键的初始化动作,也就是平台级和板级的初始化

@@ 后面会有一小节进行分析

#endif


    bl  _main

@@ 跳转到主函数,也就是要加载BL2以及跳转到BL2的主体部分

 


4、cpu_init_cp15

建议先参考[kernel 启动流程] (第六章)第一阶段之——打开MMU两篇文章的分析。 

cpu_init_cp15主要用于对cp15协处理器进行初始化,其主要目的就是关闭其MMU和TLB。 

代码如下(去掉无关部分的代码): 

arch/arm/cpu/armv7/start.S


ENTRY(cpu_init_cp15)

    /*

     * Invalidate L1 I/D

     */

    mov r0, #0          @ set up for MCR

    mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs

    mcr p15, 0, r0, c7, c5, 0   @ invalidate icache

    mcr p15, 0, r0, c7, c5, 6   @ invalidate BP array

    mcr     p15, 0, r0, c7, c10, 4  @ DSB

    mcr     p15, 0, r0, c7, c5, 4   @ ISB

@@ 这里只需要知道是对CP15处理器的部分寄存器清零即可。

@@ 将协处理器的c7c8清零等等,各个寄存器的含义请参考《ARM的CP15协处理器的寄存器》


    /*

     * disable MMU stuff and caches

     */

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

    bic r0, r0, #0x00002000 @ clear bits 13 (--V-)

    bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)

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

    orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB

#ifdef CONFIG_SYS_ICACHE_OFF

    bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache

#else

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

#endif

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

@@ 通过上述的文章的介绍,我们可以知道cp15的c1寄存器就是MMU控制器

@@ 上述对MMU的一些位进行清零和置位,达到关闭MMU和cache的目的,具体的话去看一下上述文章吧。


ENDPROC(cpu_init_cp15)

5、cpu_init_crit

cpu_init_crit,进行一些关键的初始化动作,也就是平台级和板级的初始化。其代码核心就是lowlevel_init,如下 

arch/arm/cpu/armv7/start.S


ENTRY(cpu_init_crit)

    /*

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

     */

    b   lowlevel_init       @ go setup pll,mux,memory

ENDPROC(cpu_init_crit)

所以说lowlevel_init就是这个函数的核心。 

lowlevel_init一般是由板级代码自己实现的。但是对于某些平台来说,也可以使用通用的lowlevel_init,其定义在arch/arm/cpu/lowlevel_init.S中 

以tiny210为例,在移植tiny210的过程中,就需要在board/samsung/tiny210下,也就是板级目录下面创建lowlevel_init.S,在内部实现lowlevel_init。(其实只要实现了lowlevel_init了就好,没必要说在哪里是实现,但是通常规范都是创建了lowlevel_init.S来专门实现lowlevel_init函数)。


在lowlevel_init中,我们要实现如下: 

* 检查一些复位状态 

* 关闭看门狗 

* 系统时钟的初始化 

* 内存、DDR的初始化 

* 串口初始化(可选) 

* Nand flash的初始化


下面以tiny210的lowlevel_init为例(这里说明一下,当时移植tiny210的时候,是直接把kangear的这个lowlevel_init.S文件拿过来用的) 

这部分代码和平台相关性很强,简单介绍一下即可 

board/samsung/tiny210/lowlevel_init.S


lowlevel_init:

    push    {lr}


    /* check reset status  */


    ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)

    ldr r1, [r0]

    bic r1, r1, #0xfff6ffff

    cmp r1, #0x10000

    beq wakeup_reset_pre

    cmp r1, #0x80000

    beq wakeup_reset_from_didle

@@ 读取复位状态寄存器0xE010_a000的值,判断复位状态。


    /* IO Retention release */

    ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)

    ldr r1, [r0]

    ldr r2, =IO_RET_REL

    orr r1, r1, r2

    str r1, [r0]

@@ 读取混合状态寄存器E010_e000的值,对其中的某些位进行置位,复位后需要对某些wakeup位置1,具体我也没搞懂。


    /* Disable Watchdog */

    ldr r0, =ELFIN_WATCHDOG_BASE    /* 0xE2700000 */

    mov r1, #0

    str r1, [r0]

@@ 关闭看门狗


@@ 这里忽略掉一部分对外部SROM操作的代码


    /* when we already run in ram, we don't need to relocate U-Boot.

     * and actually, memory controller must be configured before U-Boot

     * is running in ram.

     */

    ldr r0, =0x00ffffff

    bic r1, pc, r0      /* r0 <- current base addr of code */

    ldr r2, _TEXT_BASE      /* r1 <- original base addr in ram */

    bic r2, r2, r0      /* r0 <- current base addr of code */

    cmp     r1, r2                  /* compare r0, r1                  */

    beq     1f          /* r0 == r1 then skip sdram init   */

@@ 判断是否已经在SDRAM上运行了,如果是的话,就跳过以下两个对ddr初始化的步骤

@@ 判断方法如下:

@@ 1、获取当前pc指针的地址,屏蔽其低24bit,存放与r1中

@@ 2、获取_TEXT_BASE(CONFIG_SYS_TEXT_BASE)地址,也就是uboot代码段的链接地址,后续在uboot篇的时候会说明,并屏蔽其低24bit

@@ 3、如果相等的话,就跳过DDR初始化的部分


    /* init system clock */

    bl system_clock_init

@@ 初始化系统时钟,后续有时间再研究一下具体怎么配置的


    /* Memory initialize */

    bl mem_ctrl_asm_init

@@ 重点注意:在这里初始化DDR的!!!后续会写一篇文章说明一下s5pv210平台如何初始化DDR.


1:

    /* for UART */

    bl uart_asm_init

@@ 串口初始化,到这里串口会打印出一个'O'字符,后续通过写字符到UTXH_OFFSET寄存器中,就可以在串口上输出相应的字符。


    bl tzpc_init


#if defined(CONFIG_NAND)

    /* simple init for NAND */

    bl nand_asm_init

@@ 简单地初始化一下NAND flash,有可能BL2的镜像是在nand  flash上面的。

#endif


    /* Print 'K' */

    ldr r0, =ELFIN_UART_CONSOLE_BASE

    ldr r1, =0x4b4b4b4b

    str r1, [r0, #UTXH_OFFSET]

@@ 再串口上打印‘K’字符,表示lowlevel_init已经完成


    pop {pc}

@@ 弹出PC指针,即返回。

 


当串口中打印出‘OK’的字符的时候,说明lowlevel_init已经执行完成。 

system_clock_init是初始化时钟的地方。 mem_ctrl_asm_init这个函数是初始化DDR的地方。后续应该有研究一下这两个函数。这里先有个印象。


6、_main

spl的main的主要目标是调用board_init_f进行先前的板级初始化动作,在tiny210中,主要设计为,加载BL2到DDR上并且跳转到BL2中。DDR在上述lowlevel_init中已经初始化好了。 

由于board_init_f是以C语言的方式实现,所以需要先构造C语言环境。 

注意:uboot-spl和uboot的代码是通用的,其区别就是通过CONFIG_SPL_BUILD宏来进行区分的。 

所以以下代码中,我们只列出spl相关的部分,也就是被CONFIG_SPL_BUILD包含的部分。 

arch/arm/lib/crt0.S


ENTRY(_main)


/*

 * Set up initial C runtime environment and call board_init_f(0).

 */

@ 注意看这里的注释,也说明了以下代码的主要目的是,初始化C运行环境,调用board_init_f。

    ldr sp, =(CONFIG_SPL_STACK)

    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */

    mov r0, sp

    bl  board_init_f_alloc_reserve

    mov sp, r0

    /* set up gd here, outside any C code */

    mov r9, r0

    bl  board_init_f_init_reserve


    mov r0, #0

    bl  board_init_f


ENDPROC(_main)

代码拆分如下: 

(1)因为后面是C语言环境,首先是设置堆栈


    ldr sp, =(CONFIG_SPL_STACK)

@@ 设置堆栈为CONFIG_SPL_STACK


    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */

@@ 堆栈是8字节对齐,2^7bit=2^3byte=8byte


    mov r0, sp

@@ 把堆栈地址存放到r0寄存器中

 


关于CONFIG_SPL_STACK,我们通过前面的文章《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》 

我们已经知道s5pv210的BL1(spl)是运行在IRAM的,并且IRAM的地址空间是0xD002_0000-0xD003_7FFF,IRAM前面的部分放的是BL1的代码部分,所以把IRAM最后的空间用来当作堆栈。 

所以CONFIG_SPL_STACK定义如下: 

include/configs/tiny210.h


#define CONFIG_SPL_STACK    0xD0037FFF

1

1

注意:上述还不是最终的堆栈地址,只是暂时的堆栈地址!!!


(2)为GD分配空间


    bl  board_init_f_alloc_reserve

@@ 把堆栈的前面一部分空间分配给GD使用


    mov sp, r0

@@ 重新设置堆栈指针SP


    /* set up gd here, outside any C code */

    mov r9, r0

@@ 保存GD的地址到r9寄存器中

注意:虽然sp的地址和GD的地址是一样的,但是堆栈是向下增长的,而GD则是占用该地址后面的部分,所以不会有冲突的问题。 

关于GD,也就是struct global_data,可以简单的理解为uboot的全局变量都放在了这里,比较重要,所以后续有会写篇文章说明一下global_data。这里只需要知道在开始C语言环境的时候需要先为这个结构体分配空间。 

board_init_f_alloc_reserve实现如下 

[1] [2]
关键字:uboot 引用地址:[uboot] (第三章)uboot流程——uboot-spl代码流程

上一篇:[uboot] (第二章)uboot流程——uboot-spl编译流程
下一篇:[uboot] (第四章)uboot流程——uboot编译流程

推荐阅读最新更新时间:2026-03-20 16:46

u-boot移植(二)---修改前工作:代码流程分析1
一、代码执行总体流程图 1.1 代码路径  U-boot.lds (archarmcpu) vectors.S (archarmlib) start.S (archarmcpuarm920t) lowlevel_init.S (boardsamsungjz2440) crt0.S (archarmlib) relocate.S (archarmlib) Board_init.c (commoninit) Board_f.c (common) Jz2440.h (includeconfigs) Generic-asm-offsets.h (includegenerated) 1.2 启动代码流
[单片机]
利用西门子低代码实现企业质量管理流程的敏捷性
利用西门子低代码实现企业质量管理流程的敏捷性 质量管理(QM)对于企业来说是个关键的业务流程,没有它,企业就无法持续生产出符合客户期望的优质产品。随着业务转型以及客户要求提高产品的可配置性,拥有敏捷的质量管理流程变得比以往任何时候都更加重要。本文将针对企业质量管理、实现敏捷质量管理流程的必要性,以及低代码应用程序开发的价值进行阐述。 什么是质量管理及其重要性? 质量管理是企业为了确保产品质量符合客户期望及设计规格和监管要求而部署的流程。以前的质量管理流程指的是在生产阶段建立和进行检查机制,而现今该流程的起点已经前移到工程设计阶段。企业会在需求管理流程中确认当前的产品设计和生产流程可以满足特定产品的要求,并且成品拥有稳定
[工业控制]
带你梳理下ARM代码编译链接的工作流程
梳理下下ARM代码编译链接的工作流程,以及过程中需要的相关概念信息,不具体关注编译链接的具体命令。 一、编译过程 编译过程就是把源代码编译生成目标代码的过程。而采用ARM编译命令,可以将源代码编译成带有ELF格式的目标文件。除了编译命令可以选择相应的编译选项之外,源代码中的pragmas以及特别的关键字也会对编译过程/结果产生一定影响。 1、makefile文件 Makefiile类似一个脚本文件,这个文件用来定义了编译过程,其中包含了需要编译的文件、文件顺序,编译的宏定义等等,可以看做完整编译需要的信息及过程的集合。 2、ELF格式文件 ELF文件:(Executable and Linkable For
[单片机]
MSP430 SPI驱动 代码设计流程
平常工作中,如果使用MSP430作为主控芯片,经常会遇到需要编写SPI 或 I2C 驱动,来读取和控制外设(比如LCD屏幕,一些传感器)的情况。为了减少重复性工作,本文以具体实例来总结SPI驱动编写的详细步骤(用MSP430FR6989来驱动集成模拟前端AFE4400): 单片机SPI引脚设置 SPI读写时序设置 寄存器写入 写在最后 单片机SPI引脚设置 一般SPI有3线和4线之分,区别在于是否带片选端——STE引脚,4个引脚功能说明: UCxS0MI:主模式数据输入,从模式下数据输出; UCxSIMO:主模式数据输出,从模式下数据输入; UCxCLK:USCI SPI的时钟; UCxSTE:USCI SPI的使
[单片机]
嵌入式学习丨4412开发板-uboot源码-汇编-源码分析(一)嵌入式学习丨4412开发板-uboot源码-汇
在第一章中,介绍了迅为4412 的 iROM、启动方式、源码组成等;在第二章中,介绍uboot 编译等。通过前面对编译的详细分析,了解到 uboot 源码中有以下几个文件是非常重要的: “cpu/arm_cortexa9/start.S” “board/samsung/smdkc210/lowlevel_init_SCP.S 或者 lowlevel_init_POP.S” “include/configs/itop_4412_android.h 或者 itop_4412_ubuntu.h” 其中“cpu/arm_cortexa9/start.S”是 uboot 代码入口文件,分析 uboot 一般是从 “start.S”文件开始,
[单片机]
嵌入式学习丨4412开发板-<font color='red'>uboot</font>源码-汇编-源码分析(一)嵌入式学习丨4412开发板-<font color='red'>uboot</font>源码-汇
uboot段相关变量
在分析relocate_code函数之前,先来总结一下相关的uboot段相关变量,这些段的地址在uboot代码重定位的时候需要用到,将uboot源码进行编译后,会在源码根目录生成u-boot.lds链接文件和u-boot.map内存映射文件,通过这两个文件,可以寻找到uboot段的地址 在上面寻找到的变量值中,除了_start和__image_copy_start值不会该变,当修改了uboot的源码或改变了编译条件,其他的变量都可能会发生改变,因此分析时,一定要以实际编译时进行uboot分析。 relocate_code函数 relocate_code函数会传入一个参数,该参数为gd- relocaddr,也就是uboot
[单片机]
<font color='red'>uboot</font>段相关变量
uboot初步-01
步骤: 1、选择合适的uboot版本 2、uboot修改 3、交叉编译出bin文件 4、烧写到SD卡或flash中 uboot的版本选择 在uboot中IP内核称为CPU 内核外围的各种外设称为broad 可在S5PV210上参考使用的uboot版本:goni 中断和异常的区别: 中断可以被cpu忽略,但是异常必须被执行 异常的优先级高于中断。 异常的类型: 1、undefined_instruction 未定义指令 2、software_interrupt 软中断 3、prefetch_abort 预取值中止 4、data_abort 数据中止 5、irq 普通中断
[单片机]
<font color='red'>uboot</font>初步-01
uboot 2014.04 运行过程记录
uboot启动流程分析,针对S5PV210 BL1阶段,SPL,u-boot-spl.bin 1、首先运行arch/arm/cpu/armv7/start.S 里面的_start函数,进行异常向量表设置,然后跳转到reset复位处理函数,设置处理器SVC模式,关闭IRQ和FIQ中断。设置cp15协处理器   的SCTRL寄存器V(bit13)为0,设置异常向量表在0x00000000-0x0000001C,设置异常向量表地址为_start。跳转到cpu_init_cp15初始化协处理器,清除TLB,关闭cache,   关闭MMU,如果没有定义CONFIG_SYS_ICACHE_OFF则打开icache。继续执行cpu_ini
[单片机]
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved