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

来源:互联网 发布:数据库管理系统概念 编辑:程序博客网 时间:2024/06/06 15:45
[uboot] (第五章)uboot流程——uboot启动流程

 

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

以下例子都以project X项目tiny210(s5pv210平台,armv7架构)为例

[uboot] uboot流程系列: 
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2) 
[project X] tiny210(s5pv210)从存储设备加载代码到DDR 
[uboot] (第一章)uboot流程——概述 
[uboot] (第二章)uboot流程——uboot-spl编译流程 
[uboot] (第三章)uboot流程——uboot-spl代码流程 
[uboot] (第四章)uboot流程——uboot编译流程 
[uboot] (番外篇)global_data介绍 
[uboot] (番外篇)uboot relocation介绍

建议先看《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》,根据例子了解一下上电之后的BL0\BL1\BL2阶段,以及各个阶段的运行位置,功能。 
建议可以和《[uboot] (番外篇)global_data介绍》和《[uboot] (番外篇)uboot relocation介绍》结合起来看。

=================================================================================

一、uboot说明

1、uboot要做的事情

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

  • arch级的初始化

    • 关闭中断,设置svc模式
    • 禁用MMU、TLB
    • 关键寄存器的设置,包括时钟、看门狗的寄存器
  • 板级的初始化

    • 堆栈环境的设置
    • 代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2C\SPI等等的初始化
    • 进行代码重定向
    • 代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、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_retreset:    /* Allow the board to save important registers */    b   save_boot_paramssave_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处理器的部分寄存器清零即可。@@ 将协处理器的c7\c8清零等等,各个寄存器的含义请参考《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,memoryENDPROC(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 */    ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */    sub r9, r9, #GD_SIZE        /* new GD is below bd */    adr lr, here    ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */    add lr, lr, r0    ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */    b   relocate_codehere:/* * now relocate vectors */    bl  relocate_vectors@@ GD、uboot、异常中断向量表的relocate,可以参考《[uboot] (番外篇)uboot relocation介绍》,这里不详细说明/* Set up final (full) environment */    bl  c_runtime_cpu_setup /* we still call old routine here */@@ 通过操作协处理器的c7寄存器来关闭Icache    ldr r0, =__bss_start    /* this is auto-relocated! */    ldr r3, =__bss_end      /* this is auto-relocated! */    mov r1, #0x00000000     /* prepare zero to clear BSS */    subs    r2, r3, r0      /* r2 = memset len */    bl  memset@@ 因为堆栈段已经被relocate,所以这里需要清空原来的堆栈段的内容    bl coloured_LED_init    bl red_led_on@@ LED灯的初始化,可以不实现,想要实现的话,可以在board里重新实现一个函数定义。    /* call board_init_r(gd_t *id, ulong dest_addr) */    mov     r0, r9                  /* gd_t */    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */    /* call board_init_r */    ldr pc, =board_init_r   /* this is auto-relocated! */    /* we should not return here. */@@ uboot relocate后的板级初始化,注意,uboot必须在这里就完成工作,或者在里面实现死循环,不应该返回。ENDPROC(_main)

通过上述,有两个很重要的初始化函数,board_init_f和board_init_r,后续继续说明。

2、board_init_f

代码如下: 
common/board_f.c

void board_init_f(ulong boot_flags){    gd->flags = boot_flags;    gd->have_console = 0;// 设置global_data里面的一些标志位    if (initcall_run_list(init_sequence_f))        hang();// 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数,initcall_run_list这里不深究// 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉}

打开DEBUG宏之后,可以通过log观察哪些init函数被调用,如下log:

uboot log中有如下log:initcall: 23e005a4根据u-boot.map可以发现对应 .text.print_cpuinfo                0x23e005a4        0x8 arch/arm/cpu/armv7/built-in.o                0x23e005a4                print_cpuinfo也就是说print_cpuinfo被initcall调用了。

所以uboot relocate之前的板级初始化的核心就是init_sequence_f中定义的函数了。 
如下,这里只做简单的说明,需要的时候再具体分析:

static init_fnc_t init_sequence_f[] = {    setup_mon_len,// 计算整个镜像的长度gd->mon_len    initf_malloc,// early malloc的内存池的设定    initf_console_record,// console的log的缓存    arch_cpu_init,      /* basic arch cpu dependent setup */// cpu的一些特殊的初始化    initf_dm,    arch_cpu_init_dm,    mark_bootstage,     /* need timer, go after init dm */    /* TODO: can any of this go into arch_cpu_init()? */    env_init,       /* initialize environment */// 环境变量的初始化,后续会专门研究一下关于环境变量的内容    init_baud_rate,     /* initialze baudrate settings */// 波特率的初始化    serial_init,        /* serial communications setup */// 串口的初始化    console_init_f,     /* stage 1 init of console */// console的初始化    print_cpuinfo,      /* display cpu info (and speed) */// 打印CPU的信息    init_func_i2c,    init_func_spi,// i2c和spi的初始化    dram_init,      /* configure available RAM banks */// ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size// 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化//========================================    setup_dest_addr,    reserve_round_4k,    reserve_trace,    setup_machine,    reserve_global_data,    reserve_fdt,    reserve_arch,    reserve_stacks,// ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》    setup_dram_config,    show_dram_config,    display_new_sp,    reloc_fdt,    setup_reloc,// relocation之后gd一些成员的设置    NULL,};

注意,必须保证上述的函数都正确地返回0值,否则会导致hang。

3、board_init_r

代码如下: 
common/board_r.c

void board_init_r(gd_t *new_gd, ulong dest_addr){    if (initcall_run_list(init_sequence_r))        hang();// 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究// 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉    /* NOTREACHED - run_main_loop() does not return */    hang();// uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。}

所以uboot relocate之前的板级初始化的核心就是init_sequence_r中定义的函数了。 
如下,这里只做简单的说明,需要的时候再具体分析: 
common/board_r.c

init_fnc_t init_sequence_r[] = {    initr_trace,// trace相关的初始化    initr_reloc,// gd中一些关于relocate的标识的设置    initr_reloc_global_data,// relocate之后,gd中一些的成员的重新设置    initr_malloc,// malloc内存池的设置    initr_console_record,    bootstage_relocate,    initr_bootstage,#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)    board_init, /* Setup chipselects */// 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数#endif    stdio_init_tables,    initr_serial,// 串口初始化    initr_announce,// 打印uboot运行位置的log    initr_logbuffer,// logbuffer的初始化    power_init_board,#ifdef CONFIG_CMD_NAND    initr_nand,// 如果使用nand flash,那么这里需要对nand进行初始化#endif#ifdef CONFIG_GENERIC_MMC    initr_mmc,// 如果使用emmc,那么这里需要对nand进行初始化#endif    initr_env,// 初始化环境变量    initr_secondary_cpu,    stdio_add_devices,    initr_jumptable,    console_init_r,     /* fully init console as a device */    interrupt_init,// 初始化中断#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)    initr_enable_interrupts,// 使能中断#endif    run_main_loop,// 进入一个死循环,在死循环里面处理终端命令。};

最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。 
到此,uboot流程也就完成了,后续会专门说明uboot的run_main_loop是怎么运行的。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 无线网打王者卡怎么办 酷翼x9忘了密码怎么办 楼上的路由器楼下不好使怎么办 电信4g网络不好怎么办 农村只有2g网怎么办 电信卡4g网速慢怎么办 小米手机触屏失灵怎么办 荣耀v10电信网速很慢怎么办 华为路由器掉线了怎么办 三星s6只识别一张卡怎么办 华为手机卡不显示了怎么办 华为账号手机卡丢了怎么办 荣耀8耗电量太快怎么办 vivo卡2不显示怎么办 电信宽带玩王者荣耀卡怎么办 联通4g玩游戏卡怎么办 华为悦盒遥控器丢了怎么办 6s不能用电信卡怎么办 iphone6电信卡无服务怎么办 魅族手机电信卡怎么办 小米手机读不出sim卡怎么办 魅蓝note6耗电快怎么办 oppo手机下载密码忘了怎么办 华为v9玩飞车卡怎么办 苹果7耳机转换器不支持怎么办 华为mate10耳机声音小怎么办 200打一年到期了怎么办 手机欠费变成2g怎么办 手机4g网用不了怎么办 手机玩王者荣耀卡怎么办 华为隐私空间密码忘记了怎么办 华为手机王者太卡怎么办 华为手机太卡怎么办呢 华为手机5x太卡!怎么办 华为手机玩游戏时太卡了怎么办? 买到华为翻新机怎么办 华为畅享8声音小怎么办 华为5a安全模式怎么办 手机跳屏怎么办金立 苹果手机触控不灵敏怎么办 华为mate8手机声音小怎么办