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

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

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

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

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


目录

  • 前言
  • 一、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地址
serverip TFTP服务器端的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代码启动分析

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代码启动分析我们只关心和启动相关的一个地址,

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

2. Booting Sequence

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

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

  • 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代码启动分析
如上图所示:

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

2. 内核启动详细流程

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

四、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代码启动分析

我们常说的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代码启动分析

4. 启动代码详细分析

_start入口位于以下文件:

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

第一阶段:

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

第二阶段

第二阶段代码从_main开始:

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

以上代码详细解释,请结合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】即可获得。


上一篇:Cortex-M3&Cortex-M4-技术综述


下一篇:Cortex-M3&Cortex-M4架构简介