关于本文配套的视频《uboot启动详解-1》《uboot启动详解-2》

arm 启动方式(ARM上电后都干了哪些事)(1)

前言

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

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

一、uboot1. 概念

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可支持的主要功能列表:

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

配置信息如下:

arm 启动方式(ARM上电后都干了哪些事)(2)

nfs

2)开发板设置

开发板ip:192.168.6.187

配置命令:

setenvipaddr192.168.6.187;板子的ip setenvserverip192.168.6.186;虚拟机的ip setenvgatewayip192.168.1.1;网关 saveenv;保存配置

setenvbootcmdtftp41000000uImage\;tftp42000000exynos4412-fs4412.dtb\;bootm41000000-42000000

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

setenvbootargsroot=/dev/nfsnfsroot=192.168.6.186:/rootfsrwconsole=ttySAC2,115200init=/linuxrcip=192.168.6.187

挂载nfs文件系统,

二、exynos-4412 Soc 启动顺序

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

1. exynos-4412内存布局

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

arm 启动方式(ARM上电后都干了哪些事)(3)

我们只关心和启动相关的一个地址,

2. Booting Sequence

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

arm 启动方式(ARM上电后都干了哪些事)(4)

根据上图,系统启动的大概顺序:

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

三、内核启动流程概述1. 内核启动流程 概述

arm 启动方式(ARM上电后都干了哪些事)(5)

如上图所示:

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

arm 启动方式(ARM上电后都干了哪些事)(6)

开发板从上电到启动内核的过程

四、uboot启动流程代码详解

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

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

arm 启动方式(ARM上电后都干了哪些事)(7)

img

我们常说的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

文件内容:

26OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm","elf32-littlearm") 27OUTPUT_ARCH(arm) 28ENTRY(_start) 29SECTIONS 30{ 31.=0x00000000; 32 33.=ALIGN(4); 34.text: 35{ 36__image_copy_start=.; 37CPUDIR/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:thisMMUsectionisusedbypxaatpresentbut 77*shouldnotbeusedbynewboards/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

核心内容解释:

27OUTPUT_ARCH(arm):该镜像运行在arm架构的硬件上 28ENTRY(_start):程序的入口是_start 29SECTIONS 30{ 31.=0x00000000;:程序的链接地址,不是运行地址【uboot一定是位置无关码】 34.text: 35{ 36__image_copy_start=.;:宏对应整个程序编译好后首地址,自搬移代码的初始位置 37CPUDIR/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()位置。

arm 启动方式(ARM上电后都干了哪些事)(8)

4. 启动代码详细分析

_start入口位于以下文件:

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

第一阶段:

arm 启动方式(ARM上电后都干了哪些事)(9)

第二阶段

第二阶段代码从_main开始:

arm 启动方式(ARM上电后都干了哪些事)(10)

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

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

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

uboot链接脚本如下:

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

文件内容:

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

uboot的入口是_start 链接地址是0x00000000

  1. uboot如何搬运代码? 代码位于:

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

搬移代码如下:

ENTRY(relocate_code) movr4,r0/*saveaddr_sp*/ movr5,r1/*saveaddrofgd*/ movr6,r2/*saveaddrofdestination*/ adrr0,_start cmpr0,r6 moveqr9,#0/*norelocation.relocationoffset(r9)=0*/ beqrelocate_done/*skiprelocation*/ movr1,r6/*r1<-scratchforcopy_loop*/ ldrr3,_image_copy_end_ofs addr2,r0,r3/*r2<-sourceendaddress*/ copy_loop: ldmiar0!,{r9-r10}/*copyfromsourceaddress[r0]*/ stmiar1!,{r9-r10}/*copytotargetaddress[r1]*/ cmpr0,r2/*untilsourceendaddress[r2]*/ blocopy_loop

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

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

board/samsung/fs4412/lowlevel_init.S

代码如下:

41lowlevel_init: 54/*AFTRwakeupreset*/ 55ldrr2,=S5P_CHECK_DIDLE 56cmpr1,r2 57beqexit_wakeup 58 59/*LPAwakeupreset*/ 60ldrr2,=S5P_CHECK_LPA 61cmpr1,r2 62beqexit_wakeup 63 64/*Sleepwakeupreset*/ 65ldrr2,=S5P_CHECK_SLEEP 66cmpr1,r2 67beqwakeup_reset 112wakeup_reset: 113blsystem_clock_init 114blmem_ctrl_asm_init 115bltzpc_init 116 117exit_wakeup: 118/*Loadreturnaddressandjumptokernel*/ 119ldrr0,=(EXYNOS4_POWER_BASE INFORM0_OFFSET) 120 121/*r1=physicaladdressofexynos4210_cpu_resumefunction*/ 122ldrr1,[r0] 123 124/*Jumptokernel*/ 125movpc,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*IfU-bootisalreadyrunninginram,noneedtorelocateU-Boot. 87*MemorycontrollermustbeconfiguredbeforerelocatingU-Boot 88*inram. 89*/ 90ldrr0,=0x0ffffff/*r0<-MaskBits*/ 91bicr1,pc,r0/*pc<-currentaddrofcode*/ 92/*r1<-unmaskedbitsofpc*/ 93ldrr2,_TEXT_BASE/*r2<-originalbaseaddrinram*/ 94bicr2,r2,r0/*r2<-unmaskedbitsofr2*/ 95cmpr1,r2/*comparer1,r2*/ 96beq1f/*r0==r1thenskipsdraminit*/

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

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

更多嵌入式 ARM知识,请关注 一口Linux。

,