热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

嵌入式linux开发uboot启动过程源码分析

一、uboot启动流程简介与大多数BootLoader一样,uboot的启动过程分为BL1和BL2两个阶段。BL1阶段通常是开发板的配置等设备初始化代码,需要依赖依赖于SoC体系结

 

一、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工程的链接脚本文件,位于board\samsung\smdkc110目录下,对于工程项目编译后期的链接阶段非常重要,决定了uboot程序的组装。

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

2、定位uboot程序入口地址

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

三、start.S文件分析


1、头文件分析

start.S有四个头文件:

#include

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

#include

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

#include

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

#include

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位于\board\samsung\smdkc110\lowlevel_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位于cpu\s5pc11x\s5pc110\cpu_init.S文件中。

四、board.c文件分析

uboot在执行完BL1阶段后远跳转到start_armboot函数执行BL2,start_armboot函数位于lib_arm\board.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的内存规划

技术分享图片

    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架构的初始化,为空cpu\s5pc11x\cpu.c

board_init,//开发板初始化board\samsung\smdkc110\smdkc110.c

interrupt_init,//定时器timer4初始化cpu\s5pc11x\interrupts.c

env_init,//环境变量初始化common\env_movi.c

init_baudrate,//波特率设置lib_arm\board.c

serial_init,//延时函数C,没有再次初始化串口cpu\s5pc11x\serial.c

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

display_banner,//用串口发送uboot版本信息lib_arm\board.c

#if defined(CONFIG_DISPLAY_CPUINFO)

print_cpuinfo,//串口打印系统时钟信息cpu\s5pc11x\s5pc110\speed.c

#endif

#if defined(CONFIG_DISPLAY_BOARDINFO)

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

//board\samsung\smdkc110\smdkc110.c

#endif

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

init_func_i2c,//SMDKV210未定义I2C,函数为空lib_arm\board.c

#endif

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

board\samsung\smdkc110\smdkc110.c 

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

lib_arm\board.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 ###”,进入死循环。


推荐阅读
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
我是奥特曼8
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有