本篇文章是彭老师第一次在B站直播间,边直播边记录笔记,视频已经上传到B站。

现在完善整理成该篇文章,有想学习uboot启动的代码详细流程的老铁可以进入我B站空间配合视频一起学习。

【视频地址】
B站用户名:一口Linux

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9


目录

  • 前言
  • 一、uboot
    • 1)ubuntu环境
    • 2)开发板设置
    • 1. 概念
    • 2. uboot基本功能
    • 3. 常用命令
    • 4. 配置参数举例
  • 二、exynos-4412 Soc 启动顺序
    • 1. exynos-4412内存布局
    • 2. Booting Sequence
  • 三、内核启动流程概述
    • 1. 内核启动流程 概述
    • 2. 内核启动详细流程
  • 四、uboot启动流程代码详解
    • 第一阶段:
    • 第二阶段
    • 1. BL2
    • 2. lds文件
    • 3. uboot启动代码流程概要
    • 4. 启动代码详细分析
  • 五、uboot启动的几个关键知识点


前言

我们在前面的arm系列课程,已经讲解了arm的架构、汇编指令、异常、常用外设的控制器驱动,那么我们已经具备开发arm系列产品的基本技能。

本篇给大家介绍一款比较常用的bootloader:uboot,通过uboot的介绍以及源代码的详细分析,让大家把之前所有ARM相关的知识点融会贯通起来。

一、uboot

1. 概念

U-Boot 是一个主要用于嵌入式系统的引导加载程序,可以支持多种不同的计算机系统结构,包括PPC、ARM、AVR32、MIPS、x86、68k、Nios与MicroBlaze。这也是一套在GNU通用公共许可证之下发布的自由软件。

U-Boot不仅仅支持嵌入式Linux系统的引导,它还支持NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android嵌入式操作系统。其目前要支持的目标操作系统是OpenBSD, NetBSD, FreeBSD,4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS, android。

2. uboot基本功能

U-Boot可支持的主要功能列表:

  • 系统引导支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统;支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
  • 基本辅助功能强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤以Linux支持最为强劲;支持目标板环境参数多种存储方式,如FLASH、NVRAM、EEPROM;
  • CRC32校验可校验FLASH中内核、RAMDISK镜像文件是否完好;
  • 设备驱动串口、SDRAM、FLASH、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持;
  • 上电自检功能SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号。

3. 常用命令

uboot命令比较多,下面只列举网络启动要用到的命令:

命令含义
bootdelay执行自动启动(bootcmd中的命令)的等候秒数
baudrate串口控制台的波特率
netmask以太网的网络掩码
ethaddr以太网的MAC地址
bootfile默认的下载文件名
printenv打印Uboot环境变量
setenv设置Uboot环境变量
ipaddr本地的IP地址
serveripTFTP服务器端的IP地址
gateway以太网的网关
bootcmd自动启动时执行命令
bootargs传递给Linux内核的启动参数
bootm引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。

4. 配置参数举例

以下以网络下载内核、网络挂载nfs为例。

1)ubuntu环境

ubuntu ip:192.168.6.186

nfs配置:

配置文件如下:

/etc/exports

配置信息如下:

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_02

2)开发板设置

开发板ip:192.168.6.187

配置命令:

setenv ipaddr 192.168.6.187      ;板子的ip
setenv serverip 192.168.6.186    ;虚拟机的ip
setenv gatewayip 192.168.1.1     ;网关
saveenv                          ;保存配置

  • 加载内核和设备树
setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000

bootcmd:uboot2启动之后,首先先执行找到这个参数,执行后面的命令。
从tftp服务器下载内核镜像uImage到地址41000000,设备树文件exynos4412-fs4412.dtb到42000000,并通过命令bootm加载启动内核。

  • 挂载nfs
setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187

挂载nfs文件系统,

  • root=/dev/nfs
  • nfsroot=192.168.6.186:/rootfs nfs服务器地址192.168.6.186,目录为/rootfs,
  • rw 文件系统操作权限为可续写
  • console=ttySAC2,115200 串口名称和波特率
  • init=/linuxrc 内核启动后运行的进程为linuxrc
  • ip=192.168.6.187 开发板地址
二、exynos-4412 Soc 启动顺序

要想了解exynos-4412的启动顺序,我们首先需要了解该soc的内存布局。

1. exynos-4412内存布局

通常一款soc的内存在厂家设计的时候就已经规定死了,对于使用者来说,我们无法改变。

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_03我们只关心和启动相关的一个地址,

  1. iROM 在soc内部,出厂时厂家固化了特定的程序,iROM中程序对应用户来说不可改变
  1. iRAM 在soc内部,速度较快,但空间不大
  1. DMC RAM控制器,位于SOC内部,用于驱动RAM,大容量的RAM都需要连接到该控制器

2. Booting Sequence

不同的厂家的启动顺序是不太一样的,本篇主要以三星的exynos-4412 soc为基础,讲解该基于该板子的uboot启动顺序。

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_04
根据上图,系统启动的大概顺序:

  • iROM在SOC内部,是一个64KB的ROM,他树池化一些系统启动必须的功能。比如:时钟、栈。
  • iROM负责从特殊的启动外设加载BL1的image到soc内部的256KB的SRAM中。启动的外设由操作按钮来决定的。根据不同按键的值,iROM将会对bl1 的image做不同的校验。
  • BL1初始化系统时钟和DRAM控制器,然后从启动外设加载OS image到DRAM中。根据启动按钮的值的不同,BL1会对OS做不同的校验。
  • 启动完成之后,BL1跳转到操作系统(kernel)。

iROM会根据OM 引脚的不同选择不同的启动设备,对应的OM寄存器需要提供对应的启动信息。

三、内核启动流程概述

1. 内核启动流程 概述

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_05
如上图所示:

  1. 设备上电之后,先执行iROM中的出厂代码,先进行必要硬件的初始化
    去执行uboot,
  2. 通常把kernel、设备树文件放到flash中
  3. 程序启动之后,往往先从flash启动,运行uboot
  4. 第一步:先进行硬件的初始化(svc模式栈、clock、内存、串口)
    第二步:自搬移:把uboot从flash中拷贝到RAM中,跳转到RAM中执行剩下的uboot代码
    第三步:把内核拷贝到RAM中,执行内核,把控制权交给内核。

2. 内核启动详细流程

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_06

四、uboot启动流程代码详解

在三星的SoC中, 启动流程可以分为三个阶段BL0, BL1, BL2, BL3, 三星自己的手册对BL1的解释也不尽相同, 一种是将在iRAM中运行的程序都归结为BL1; 一种是将iRAM中三星加密的代码bl1.bin作为BL1, iRAM中剩余的部分作为BL2, 本文采用后者, 他们的主要分工如下:

  • BL0: ARM的起始地址都是0地址, 三星的芯片一般将0地址映射到iROM中, BL0就是指iROM中固化的启动代码, 主要负责加载BL1
  • BL1: 三星对于bootloader的加密代码bl1.bin, 要放在外设中uboot.bin的头上, 和一部分uboot.bin一起加载到iRAM中运行.
  • BL2: 从(nand/sd/usb)中拷贝的uboot.bin头最大14K到iRAM中代码中除去bl1.bin后剩余的部分, 负责设置CPU为SVC模式, 关闭MMU, 关闭中断, 关闭iCache, 关闭看门狗, 初始化DRAM,初始化时钟, 初始化串口, 设置栈, 校验BL2并将其搬移到DRAM高位地址, 重定位到DRAM中执行BL3
  • BL3:是指在代码重定向后在内存中执行的uboot的完整代码, 负责初始化外设,更新向量表, 清BSS, 准备内核启动参数, 加载并运行OS内核

可以借助下图理解这个流程

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_07

我们常说的uboot是一个两阶段bootloader,就是指上述的BL2和BL3. BL2主要做硬件直接相关的初始化,使用汇编编写;BL3主要为操作系统的运行准备环境,主要用C编写,这里以ARM平台为例分析其启动流程。下面是启动过程中主要涉及的文件

arch/arm/cpu/armv7/start.S
board/samsung/myboard/lowlevel_init.S
arch/arm/lib/crt0.S
arch/arm/lib/board.c
arch/samsung/myboard/myboard.c

1. BL2

BL2的主要文件和任务流程如下

arch/arm/cpu/armv7/start.S
\1. 设置CPU为SVC模式
\2. 关闭MMU
\3. 关闭Cache
\4. 跳转到lowlevel_init.S low_level_init
board/samsung/origen/lowlevel_init.S
\5. 初始化时钟
\6. 初始化内存
\7. 初始化串口
\8. 关闭看门狗
\9. 跳转到crt0.S _main
arch/arm/lib/crt0.S
\10. 设置栈
\11. 初始化C运行环境
\12. 调用board_init_f()
arch/arm/lib/board.c
\13. board_init_f对全局信息GD结构体进行填充
arch/arm/lib/crt0.S
\14. 代码重定位------------BL2的最后的工作, 执行完就进入DRAM执行BL2

2. lds文件

要想了解uboot整个项目的代码流程,必须首先了解链接脚本【链接脚本参考《7. 从0开始学ARM-GNU伪指令,lds使用》】。

该文件决定了uboot最终生成的镜像文件,各个段的布局。

uboot链接脚本如下:

u-boot-2013.01/arch/arm/cpu/u-boot.lds

文件内容:

26 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
 27 OUTPUT_ARCH(arm)
 28 ENTRY(_start)
 29 SECTIONS 30 {
 31     . = 0x00000000;
 32 
 33     . = ALIGN(4);
 34     .text :
 35     {
 36         __image_copy_start = .;
 37         CPUDIR/start.o (.text*)
 38         *(.text*)
 39     }
 40 
 41     . = ALIGN(4);
 42     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 43 
 44     . = ALIGN(4);
 45     .data : {
 46         *(.data*)
 47     }
 48 
 49     . = ALIGN(4);
 50 
 51     . = .;
 52 
 53     . = ALIGN(4);
 54     .u_boot_list : {
 55     #include <u-boot.lst>
 56     }
 57 
 58     . = ALIGN(4);
 59 
 60     __image_copy_end = .;
 61 
 62     .rel.dyn : {
 63         __rel_dyn_start = .;
 64         *(.rel*)
 65         __rel_dyn_end = .;
 66     }
 67 
 68     .dynsym : {
 69         __dynsym_start = .;
 70         *(.dynsym)
 71     }
 72 
 73     _end = .;
 74 
 75     /*
 76      * Deprecated: this MMU section is used by pxa at present but
 77      * should not be used by new boards/CPUs.
 78      */
 79     . = ALIGN(4096);
 80     .mmutable : {
 81         *(.mmutable)
 82     }
 83 
 84     .bss __rel_dyn_start (OVERLAY) : {
 85         __bss_start = .;
 86         *(.bss*)
 87          . = ALIGN(4);
 88         __bss_end__ = .;
 89     }
 90 
 91     /DISCARD/ : { *(.dynstr*) }
 92     /DISCARD/ : { *(.dynamic*) }
 93     /DISCARD/ : { *(.plt*) }
 94     /DISCARD/ : { *(.interp*) }
 95     /DISCARD/ : { *(.gnu*) }
 96 }
 97

核心内容解释:

 27 OUTPUT_ARCH(arm)       :    该镜像运行在arm架构的硬件上 28 ENTRY(_start)          :    程序的入口是 _start 29 SECTIONS 30 {
 31  . = 0x00000000;      :   程序的链接地址,不是运行地址【uboot一定是位置无关码】 34     .text :
 35     {
 36         __image_copy_start = .;    : 宏对应整个程序编译好后首地址,自搬移代码的初始位置 37         CPUDIR/start.o (.text*)    : 第一个目标文件CPUDIR/start.o中的代码段 38         *(.text*)                  : 剩下的目标文件的代码段 39     }
 60     __image_copy_end = .;          : 自搬移代码的结束为止

BSS全局未初始化变量、全局初始化为0的变量所在的段:

 84     .bss __rel_dyn_start (OVERLAY) : {
 85         __bss_start = .;
 88         __bss_end__ = .;
 89     }

3. uboot启动代码流程概要

代码只分析到uboot命令行,函数main_loop()位置。
23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_08

4. 启动代码详细分析

_start入口位于以下文件:

u-boot-2013.01/arch/arm/cpu/armv7/start.S

第一阶段:

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_09

第二阶段

第二阶段代码从_main开始:

23. 基于Cortex-A9 uboot代码启动分析_Cortex-A9_10

以上代码详细解释,请结合B站视频同步学习。

五、uboot启动的几个关键知识点
  1. 如何判断第一条机器指令的位置?

链接脚本决定了内存的布局。

uboot链接脚本如下:

u-boot-2013.01/arch/arm/cpu/u-boot.lds

文件内容:

 28 ENTRY(_start)
 29 SECTIONS 30 {
 31     . = 0x00000000;
 32

uboot的入口是
链接地址是

  1. uboot如何搬运代码?
    代码位于:
u-boot-2013.01/arch/arm/cpu/armv7/start.S

搬移代码如下:

ENTRY(relocate_code)
	mov	r4, r0	/* save addr_sp */
	mov	r5, r1	/* save addr of gd */
	mov	r6, r2	/* save addr of destination */

	adr	r0, _start
	cmp	r0, r6
	moveq	r9, #0/* no relocation. relocation offset(r9) = 0 */
	beq	relocate_done		/* skip relocation */
	mov	r1, r6			/* r1 <- scratch for copy_loop */
	ldr	r3, _image_copy_end_ofs
	add	r2, r0, r3		/* r2 <- source end address	    */copy_loop:
	ldmia	r0!, {r9-r10}/* copy from source address [r0]    */
	stmia	r1!, {r9-r10}/* copy to   target address [r1]    */
	cmp	r0, r2			/* until source end address [r2]    */
	blo	copy_loop

详情参考第四章,第3节。

  1. uboot中,如何判断此次开机是从断电状态开机还是从休眠状态启动的?
board/samsung/fs4412/lowlevel_init.S

代码如下:

 41   lowlevel_init:
 54     /* AFTR wakeup reset */
 55     ldr r2, =S5P_CHECK_DIDLE 56     cmp r1, r2 57     beq exit_wakeup 58 
 59     /* LPA wakeup reset */
 60     ldr r2, =S5P_CHECK_LPA 61     cmp r1, r2 62     beq exit_wakeup 63 
 64     /* Sleep wakeup reset */
 65     ldr r2, =S5P_CHECK_SLEEP 66     cmp r1, r2 67     beq wakeup_reset 112 wakeup_reset:
 113     bl system_clock_init 114     bl mem_ctrl_asm_init 115     bl tzpc_init 116 
 117 exit_wakeup:
 118     /* Load return address and jump to kernel */
 119     ldr r0, =(EXYNOS4_POWER_BASE + INFORM0_OFFSET)
 120 
 121     /* r1 = physical address of exynos4210_cpu_resume function */
 122     ldr r1, [r0]
 123 
 124     /* Jump to kernel*/
 125     mov pc, r1

由上可知,当手机因为各种原因进入休眠时,会将当前程序执行的上下文保护起来,并向一些pmic的寄存器中写入指定的数据,以表明此次是因为何种原因进入休眠。

而手机并没有完全断电,而是处于一个低功耗模式下,此时启动RAM仍然有数据,所以在此启动后,只需要从特殊的寄存器中读取相应的值,就可以知道之前是因为什么原因休眠,进而回复休眠之前的上下文即可。

  1. uboot代码搬到ram之后,代码的运行地址发生了变化,如何保证程序跳转不会出错?

除了要保证uboot代码是基于地址无关的,此外.rel.dyn帮我们解决了,其实主要还是编译器帮我们做了很多工作。

位置无关码参考《15. 从0开始学ARM-位置无关码》

  1. 设备启动的时候,有可能直接从ram启动, 如何知道当前是从flah启动还是ram启动的?

文件:

board/samsung/fs4412/lowlevel_init.S

代码:

lowlevel_init:

85     /*
 86      * If U-boot is already running in ram, no need to relocate U-Boot.
 87      * Memory controller must be configured before relocating U-Boot
 88      * in ram.
 89      */
 90     ldr r0, =0x0ffffff      /* r0 <- Mask Bits*/
 91     bic r1, pc, r0      /* pc <- current addr of code */
 92                     /* r1 <- unmasked bits of pc */
 93     ldr r2, _TEXT_BASE      /* r2 <- original base addr in ram */
 94     bic r2, r2, r0      /* r2 <- unmasked bits of r2*/
 95     cmp r1, r2          /* compare r1, r2 */
 96     beq 1f          /* r0 == r1 then skip sdram init */

原理:
RAM地址空间是:0x40000000-0xA0000000 0xA0000000-0x00000000
而iROM/iRAM地址的均是0,所以只需要读取出执行到lowlevel_init时pc的值,判断其是否是0即可知道现在代码是否运行在RAM中。

文中用到的源码、datasheet、交叉编译工具可以关注GH,公众号后台回复 【uboot2013】即可获得。