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

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

include

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

include

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

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

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

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

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

  1. 关闭L2 cache
  2. 初始化L2 cache
  3. 开启L2 cache
  4. 关闭L1 cache
  5. 关闭MMU
  6. 读取OM启动引脚信息
  7. 确定从启动设备SD卡启动
  8. 设置SRAM中的栈为调用lowlevel_init做准备(lowlevel_init内部有嵌套调用)
  9. 调用lowlevel_init(主要初始化系统时钟、SDRAM初始化、串口初始化等)
  10. 设置开发板供电锁存
  11. 设置SDRAM中的栈
  12. 判断当前代码是否运行在SDRAM中,如果当前代码运行在SDRAM中,则跳过代码重定位。
  13. 判断启动方式,选择SD卡启动设备,跳转到mmcsd_boot
  14. SD卡启动的准备工作,从SD卡拷贝ubootSDRAMmovi_bl2_copy

C、设置 MMU,开启 MMU

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

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

5、lowlevel_init 分析
lowlevel_init 位于\board\samsung\smdkc110\lowlevel_init.S 中,主要功能如下:

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

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

  1. BIO状态恢复
  2. C、关闭看门狗
  3. D、外部SRAMGPIO初始化、外部SROM初始化
  4. E、开发板供电锁存设置
  5. F、判断当前代码是否运行在SDRAM,如果当前代码运行在SDRAM,说明目前从低功耗状态复位,可以跳过系统时钟初始化、串口初始化、SDRAM初始化等
  6. G、初始化系统时钟:system_clock_init
  7. H、初始化SDRAM内存:mem_ctrl_asm_init
  8. I、初始化串口,打印出’O’:uart_asm_init
  9. J、初始化trustzonetzpc_init
  10. K、初始化nandonenand
  11. L、检查复位状态
  12. M、关闭ABB
  13. 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 {undefined

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 {undefined

  1. intbi_baudrate;//硬件串口波特率/* serial console baudrate */
  2. unsigned longbi_ip_addr;//开发板IP地址/* IP Address */
  3. unsigned charbi_enetaddr[6];//开发板网卡地址 /* Ethernet adress */
  4. struct environment_s *bi_env;//环境变量指针
  5. ulong bi_arch_number;//机器码/* unique id for this board */
  6. ulong bi_boot_params;//uboot启动参数/* where this board expects params */
  7. struct/* RAM configuration */
  8. {

ulong start;

ulong size;

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

ifdef CONFIG_HAS_ETH1

  1. /* second onboard ethernet port */
  2. unsigned char bi_enet1addr[6];//第二块网卡的地址

endif

} bd_t;

2、uboot 的内存规划
uboot启动流程概述 - 图1

wKioL1drRfTyRHOGAADgnjy9DiE579.jpg

  1. SDRAM_BASEMMU映射在0xC0000000CFG_UBOOT_BASE0xC3E00000
  2. 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 startarmboot (void)
{undefined
// 全局数据变量指针 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) {undefined
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);
}
/
mainloop() 循环不断执行 /
for (;😉 {
mainloop (); / 主循环函数处理执行用户命令 – common/main.c */
}
}

4、函数指针数组 init_sequence
函数指针数组 init_sequence:

init_fnc_t *init_sequence[] = {undefined

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 ###”,进入死循环。
https://blog.csdn.net/qq_37400022/article/details/113819972?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165052265616782246425929%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165052265616782246425929&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~hot_rank-1-113819972.nonecase&utm_term=uboot%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E6%A6%82%E8%BF%B0&spm=1018.2226.3001.4450