[uboot] (第五章)uboot流程——uboot启动流程

发布者:WhisperingHeart最新更新时间:2025-01-24 来源: cnblogs关键字:uboot  启动流程  s5pv210 手机看文章 扫描二维码
随时随地手机看文章

一、uboot说明

1、uboot要做的事情

CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,等待处理命令。 

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


arch级的初始化


关闭中断,设置svc模式

禁用MMU、TLB

关键寄存器的设置,包括时钟、看门狗的寄存器

板级的初始化


堆栈环境的设置

代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2CSPI等等的初始化

进行代码重定向

代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、emmc、nand flash、网络、中断等等的初始化。

进入命令行状态,等待终端输入命令以及对命令进行处理

上述工作,也就是uboot流程的核心。


2、疑问

在前面的文章中虽然已经说明了,在spl的阶段中已经对arch级进行了初始化了,为什么uboot里面还要对arch再初始化一遍? 

回答:spl对于启动uboot来说并不是必须的,在某些情况下,上电之后uboot可能在ROM上或者flash上开始执行而并没有使用spl。这些都是取决于平台的启动机制。因此uboot并不会考虑spl是否已经对arch进行了初始化操作,uboot会完整的做一遍初始化动作,以保证cpu处于所要求的状态下。


和spl在启动过程的差异在哪里? 

回答:以tiny210而言,前期arch的初始化流程基本上是一致的,出现本质区别的是在board_init_f开始的。


spl的board_init_f是由board自己实现相应的功能,例如tiny210则是在board/samsung/tiny210/board.c中。其主要实现了复制uboot到ddr中,并且跳转到uboot的对应位置上。一般spl在这里就可以完成自己的工作了。

uboot的board_init_f是在common下实现的,其主要实现uboot relocate前的板级初始化以及relocate的区域规划,其还需要往下走其他初始化流程。

3、代码入口

project-X/u-boot/arch/arm/cpu/u-boot.lds


ENTRY(_start)

1

1

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

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


二、代码整体流程

1、首先看一下主枝干的流程(包含了arch级的初始化)

在arch级初始化是和spl完全一致的 

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

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

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

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

………………………………———->cpu_init_crit————->lowlevel_init————->关键寄存器的配置和初始化 

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

………………………………———->_main————–>进入板级初始化,具体看下面


2、板级初始化的流程

_main————–>board_init_f_alloc_reserve —————>堆栈、GD、early malloc空间的分配 

…………| 

…………————->board_init_f_init_reserve —————>堆栈、GD、early malloc空间的初始化 

…………| 

…………————->board_init_f —————>uboot relocate前的板级初始化以及relocate的区域规划 

…………| 

…………————->relocate_code、relocate_vectors —————>进行uboot和异常中断向量表的重定向 

…………| 

…………————->旧堆栈的清空 

…………| 

…………————->board_init_r —————>uboot relocate后的板级初始化 

…………| 

…………————->run_main_loop —————>进入命令行状态,等待终端输入命令以及对命令进行处理


三、arch级初始化代码分析

1、_start

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

arch/arm/lib/vector.S


_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG

    .word   CONFIG_SYS_DV_NOR_BOOT_CFG

#endif

    b   reset

会跳转到reset中。


2、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

@@ 跳转到主函数,也就是板级初始化函数

@@ 下一节中进行说明。

3、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)

4、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.

@@ 其实,在tiny210的项目中,已经在spl里面对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已经执行完成。


三、板级初始化代码分析

1、_main

板级初始化代码的入口就是_main。从这里开始分析。 

建议可以和《[uboot] (番外篇)global_data介绍》和《[uboot] (番外篇)uboot relocation介绍》结合起来看。 

代码如下,去除无关代码部分 

arch/arm/lib/crt0.S


ENTRY(_main)


/*

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

 */

    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)

    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

@@ 以上是堆栈、GD、early malloc空间的分配,具体参考《[uboot] (番外篇)global_data介绍》


    mov r0, #0

    bl  board_init_f

@@ uboot relocate前的板级初始化以及relocate的区域规划,后续小节继续说明

@@ 其中relocate区域规划也可以参考一下《[uboot] (番外篇)uboot relocation介绍》


/*

 * Set up intermediate environment (new sp and gd) and call

 * relocate_code(addr_moni). Trick here is that we'll return

 * 'here' but relocated.

 */


    ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */

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

[1] [2]
关键字:uboot  启动流程  s5pv210 引用地址:[uboot] (第五章)uboot流程——uboot启动流程

上一篇:[uboot] (第四章)uboot流程——uboot编译流程
下一篇:[uboot] (番外篇)uboot relocation介绍

小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

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

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

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