• 嵌入式linux开发uboot启动过程源码分析(一)


     

    一、uboot启动流程简介

        与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段。BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结构,通常用汇编语言来实现;BL2阶段主要是对外部设备如网卡、Flash等的初始化以及uboot命令集等的自身实现,通常用C语言来实现。

    1、BL1阶段

        uboot的BL1阶段代码通常放在start.s文件中,用汇编语言实现,其主要代码功能如下:

      (1) 指定uboot的入口。在链接脚本uboot.lds中指定uboot的入口为start.S中的_start。

      (2)设置异常向量(exception vector)

      (3)关闭IRQ、FIQ,设置SVC模式

      (4)关闭L1 cache、设置L2 cache、关闭MMU

      (5)根据OM引脚确定启动方式

      (6)在SoC内部SRAM中设置栈

      (7)lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)

      (8)设置开发板供电锁存

      (9)设置SDRAM中的栈

      (10)将uboot从SD卡拷贝到SDRAM中

      (11)设置并开启MMU

      (12)通过对SDRAM整体使用规划,在SDRAM中合适的地方设置栈

      (13)清除bss段,远跳转到start_armboot执行,BL1阶段执行完

    2、BL2阶段

        start_armboot函数位于lib_arm/board.c中,是C语言开始的函数,也是BL2阶段代码中C语言的主函数,同时还是整个u-boot(armboot)的主函数,BL2阶段的主要功能如下:

      (1)规划uboot的内存使用

      (2)遍历调用函数指针数组init_sequence中的初始化函数

      (3)初始化uboot的堆管理器mem_malloc_init

      (4)初始化SMDKV210开发板的SD/MMC控制器mmc_initialize

      (5)环境变量重定位env_relocate

      (6)将环境变量中网卡地址赋值给全局变量的开发板变量

      (7)开发板硬件设备的初始化devices_init

      (8)跳转表jumptable_init

      (9)控制台初始化console_init_r

      (10)网卡芯片初始化eth_initialize

      (11)uboot进入主循环main_loop

    二、uboot程序入口分析

    1、link.lds链接脚本文件分析

    u-boot.lds文件是uboot工程的链接脚本文件,位于boardsamsungsmdkc110目录下,对于工程项目编译后期的链接阶段非常重要,决定了uboot程序的组装。

    u-boot.lds链接文件中的ENTRY(_start)指定了uboot程序的入口地址为_start。

    2、定位uboot程序入口地址

    在SourceInsight建立uboot工程,利用索引功能查找_start,在搜索结果中找到与三星smdkv210开发板相关的代码,最终锁定cpus5pc11xstart.S文件,定位到文件中的_start标识符。

    三、start.S文件分析

    1、头文件分析

    start.S有四个头文件:

    #include <config.h>

        config.h头文件在配置开发板时由mkconfig脚本创建的头文件,头文件内容即包含开发板的头文件:#include <configs/smdkv210single.h>

    #include <version.h>

        version.h头文件的内容为包含自动生成的版本头文件,头文件内容为:#include "version_autogenerated.h",version_autogenerated.h头文件定义了版本宏,宏定义为:#define U_BOOT_VERSION "U-Boot 1.3.4"。版本宏的值就是Makefile中定义的版本信息。

    #include <asm/proc/domain.h>

        domain.h头文件在定义了CONFIG_ENABLE_MMU宏时有效,为链接文件,实际指向的文件为include/asm-arm/proc-armv/domain.h。

    #include <regs.h>

    regs.h头文件为链接文件,指向s5pc110.h头文件,s5pc110.h文件内部使用宏定义了有关SoC内部寄存器的大量信息。

    2、头校验信息的占位

    #if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)

    .word 0x2000

    .word 0x0

    .word 0x0

    .word 0x0

    #endif

    定义uboot程序开头的16字节校验头信息填充空间,头校验信息块内的值需要在后面写入。

    3、异常向量表的构建

    .globl _start

    _start:

    b reset

    ldrpc, _undefined_instruction

    ldrpc, _software_interrupt

    ldrpc, _prefetch_abort

    ldrpc, _data_abort

    ldrpc, _not_used

    ldrpc, _irq

    ldrpc, _fiq

        uboot程序的入口点实际是定义了异常向量表,异常向量表由SoC硬件实现,因此uboot在开机上电复位时需要跳转到reset执行。

    4、复位reset分析

    SoC上电复位后运行的第一段代码就是reset。主要包括以下几部分:

    A、关闭IRQ、FIQ,并将处理器模式设置为SVC模式

    B、CPU关键寄存器的初始化cpu_init_crit:

        关闭L2 cache

        初始化L2 cache

        开启L2 cache

        关闭L1 cache

        关闭MMU

        读取OM启动引脚信息

        确定从启动设备SD卡启动

        设置SRAM中的栈为调用lowlevel_init做准备(lowlevel_init内部有嵌套调用)

        调用lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)

        设置开发板供电锁存

        设置SDRAM中的栈

        判断当前代码是否运行在SDRAM中,如果当前代码运行在SDRAM中,则跳过代码重定位。

        判断启动方式,选择SD卡启动设备,跳转到mmcsd_boot

        SD卡启动的准备工作,从SD卡拷贝uboot到SDRAM:movi_bl2_copy

    C、设置MMU,开启MMU

    D、通过对SDRAM整体使用规划,在SDRAM中合适的地方设置栈

    E、清除bss段,远跳转到start_armboot执行,BL1阶段执行完

    5、lowlevel_init分析

    lowlevel_init位于oardsamsungsmdkc110lowlevel_init.S中,主要功能如下:

        A、检查复位状态,判断启动的方式

    根据复位状态选择复位启动的方式,处于低功耗状态时复位启动可以跳过后续多个步骤。

        B、IO状态恢复

        C、关闭看门狗

        D、外部SRAM的GPIO初始化、外部SROM初始化

        E、开发板供电锁存设置

        F、判断当前代码是否运行在SDRAM,如果当前代码运行在SDRAM,说明目前从低功耗状态复位,可以跳过系统时钟初始化、串口初始化、SDRAM初始化等

        G、初始化系统时钟:system_clock_init

        H、初始化SDRAM内存:mem_ctrl_asm_init

        I、初始化串口,打印出’O’:uart_asm_init

        J、初始化trustzone:tzpc_init

        K、初始化nand或onenand

        L、检查复位状态

        M、关闭ABB

        N、串口打印出‘K’

    说明:”OK”是打印出的调试信息,如果打印出’O’则说明在串口初始化uart_asm_init前的所有代码是正确的。如果打印出”OK”则说明在开发板板级初始化lowlevel_init前的所有代码是正常工作的。

    system_clock_init、uart_asm_init、tzpc_init、nand_asm_init都位于lowlevel_init.S文件内,mem_ctrl_asm_init位于cpus5pc11xs5pc110cpu_init.S文件中。

    四、board.c文件分析

    uboot在执行完BL1阶段后远跳转到start_armboot函数执行BL2,start_armboot函数位于lib_armoard.c中。

    1、重要变量的说明

    typedef int (init_fnc_t) (void);函数类型

    init_fnc_t **init_fnc_ptr;//二级函数指针

    #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

    DECLARE_GLOBAL_DATA_PTR定义了一个存储在寄存器r8中的指向gd_t类型全局变量的指针gd。

    全局变量结构体的定义:

    typedefstructglobal_data {

    bd_t*bd;//boardinfo结构体信息,存放和开发板有关的信息

    unsigned longflags;//标志位

    unsigned longbaudrate;//串口通信波特率

    unsigned longhave_console;//控制台/* serial_init() was called */

    unsigned longreloc_off;//重定位偏移量/* Relocation Offset */

    unsigned longenv_addr;//环境变量结构体的地址/* Address  of Environment struct */

    unsigned longenv_valid;//环境变量使用标志/* Checksum of Environment valid? */

    unsigned longfb_base;//fb基地址/* base address of frame buffer */

    #ifdef CONFIG_VFD

    unsigned charvfd_type;///* display type */

    #endif

    void**jt;//跳转表/* jump table */

    } gd_t;

    开发板信息结构体变量的定义:

    typedef struct bd_info {

        intbi_baudrate;//硬件串口波特率/* serial console baudrate */

        unsigned longbi_ip_addr;//开发板IP地址/* IP Address */

        unsigned charbi_enetaddr[6];//开发板网卡地址 /* Ethernet adress */

        struct environment_s       *bi_env;//环境变量指针

        ulong        bi_arch_number;//机器码/* unique id for this board */

        ulong        bi_boot_params;//uboot启动参数/* where this board expects params */

        struct/* RAM configuration */

        {

    ulong start;

    ulong size;

        }bi_dram[CONFIG_NR_DRAM_BANKS];//内存插条信息

    #ifdef CONFIG_HAS_ETH1

        /* second onboard ethernet port */

        unsigned char   bi_enet1addr[6];//第二块网卡的地址

    #endif

    } bd_t;

    2、uboot的内存规划

    wKioL1drRfTyRHOGAADgnjy9DiE579.jpg

        SDRAM_BASE被MMU映射在0xC0000000,CFG_UBOOT_BASE是0xC3E00000

        在BL1段运行时,uboot镜像被拷贝到CFG_UBOOT_BASE开始的地址处。

      gd的地址:

    gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

      bd的地址:

    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

    3、start_armboot函数分析

        start_armboot函数的主要功能如下:

    (1)、遍历调用函数指针数组init_sequence中的初始化函数

    依次遍历调用函数指针数组init_sequence中的函数,如果有函数执行出错,则执行hang函数,打印出”### ERROR ### Please RESET the board ###”,进入死循环。

    (2)、初始化uboot的堆管理器mem_malloc_init

    (3)、初始化SMDKV210的SD/MMC控制器mmc_initialize

    (4)、环境变量重定位env_relocate

    (5)、将环境变量中网卡地址赋值给全局变量的开发板变量

    (6)、开发板硬件设备的初始化devices_init

    (7)、跳转表jumptable_init

    (8)、控制台初始化console_init_r

    (9)、网卡芯片初始化eth_initialize

    (10)、uboot进入主循环main_loop

    void start_armboot (void)
       {
       //全局数据变量指针gd占用r8。
       DECLARE_GLOBAL_DATA_PTR;       
       /* 给全局数据变量gd安排空间*/
       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
       memset ((void*)gd, 0, sizeof (gd_t));
       /* 给板子数据变量gd->bd安排空间*/
       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
       memset (gd->bd, 0, sizeof (bd_t));
       monitor_flash_len = _bss_start - _armboot_start;//u-boot长度。       
       /* 顺序执行init_sequence数组中的初始化函数 */
       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
           if ((*init_fnc_ptr)() != 0) {
               hang ();
                     }
              }
        /* 初始化堆空间 */
        mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
        /* 重新定位环境变量, */
         env_relocate ();
        /* 从环境变量中获取IP地址 */
        gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
        /* 以太网接口MAC 地址 */
        devices_init ();      /* 设备初始化 */
        jumptable_init ();  //跳转表初始化
        console_init_r ();    /* 完整地初始化控制台设备 */
        enable_interrupts (); /* 使能中断处理 */
         /* 通过环境变量初始化 */
         if ((s = getenv ("loadaddr")) != NULL) {
            load_addr = simple_strtoul (s, NULL, 16);
         }
         /* main_loop()循环不断执行 */
         for (;;) {
             main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */
              }
       }

    4、函数指针数组init_sequence

    函数指针数组init_sequence:

    init_fnc_t *init_sequence[] = {

    cpu_init,//CPU架构的初始化,为空cpus5pc11xcpu.c

    board_init,//开发板初始化boardsamsungsmdkc110smdkc110.c

    interrupt_init,//定时器timer4初始化cpus5pc11xinterrupts.c

    env_init,//环境变量初始化commonenv_movi.c

    init_baudrate,//波特率设置lib_armoard.c

    serial_init,//延时函数C,没有再次初始化串口cpus5pc11xserial.c

    console_init_f,//控制台第一阶段初始化,控制台未初始化好commonconsole.c

    display_banner,//用串口发送uboot版本信息lib_armoard.c

    #if defined(CONFIG_DISPLAY_CPUINFO)

    print_cpuinfo,//串口打印系统时钟信息cpus5pc11xs5pc110speed.c

    #endif

    #if defined(CONFIG_DISPLAY_BOARDINFO)

    checkboard,//打印开发板信Board:SMDKV210

    //boardsamsungsmdkc110smdkc110.c

    #endif

    #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)

    init_func_i2c,//SMDKV210未定义I2C,函数为空lib_armoard.c

    #endif

    dram_init,//初始化gd->bd->bi_dram,开发板的SDRAM配置信息

    boardsamsungsmdkc110smdkc110.c 

       display_dram_config,//串口打印出DRAM的大小信息,DRAM:xxxMB

    lib_armoard.c

    NULL,

    };

    board_init函数:

    dm9000_pre_init();//网卡初始化,GPIO和端口设置

    gd->bd->bi_arch_number = MACH_TYPE;//开发板的机器码,uboot的机器码和linux的机器码之间必须适配

    gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);//uboot给内核的传参地址

    display_banner函数:

    打印uboot版本信息:uboot-1.3.4

    print_cpuinfo函数:

    打印CPU时钟系统的时钟信息

    checkboard函数:

    打印出开发板信息Board:   SMDKV210

    display_dram_config函数:

    打印出DRAM的大小信息,DRAM:xxxMB

    打印出的信息可以作为调试使用,依次遍历调用函数指针数组init_sequence中的函数,如果有函数执行出错,则执行hang函数,打印出”### ERROR ### Please RESET the board ###”,进入死循环。

  • 相关阅读:
    thinkphp header模块中的CSS格式也要写在home页中,不然无效
    thinkphp header模块中设为首页的JS代码需要写在HOME页中
    dubbo框架-学习-dubbo原理
    java-面试题为什么redis这么快
    jsp学习——九大内置对象
    日志平台使用记录
    java-消息队列相关-activeMQ
    java——比较难和底层的面试题
    学习之道——感觉东西多不知道如何下手怎么办
    java-Freemarker-模板引擎学习
  • 原文地址:https://www.cnblogs.com/cyyljw/p/10998066.html
Copyright © 2020-2023  润新知