前言
我们在前面的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可支持的主要功能列表:
3. 常用命令
uboot命令比较多,下面只列举网络启动要用到的命令:
4. 配置参数举例
以下以网络下载内核、网络挂载nfs为例。
1)ubuntu环境
ubuntu ip:192.168.6.186
nfs配置:
配置文件如下:
- /etc/exports
配置信息如下:
nfs
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加载启动内核。
- setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187
挂载nfs文件系统
二、exynos-4412 Soc 启动顺序
要想了解exynos-4412的启动顺序,我们首先需要了解该soc的内存布局。
1. exynos-4412内存布局
通常一款soc的内存在厂家设计的时候就已经规定死了,对于使用者来说,我们无法改变。
我们只关心和启动相关的一个地址,
2. Booting Sequence
不同的厂家的启动顺序是不太一样的,本篇主要以三星的exynos-4412 soc为基础,讲解该基于该板子的uboot启动顺序。
根据上图,系统启动的大概顺序:
iROM会根据OM 引脚的不同选择不同的启动设备,对应的OM寄存器需要提供对应的启动信息。
三、内核启动流程概述
1. 内核启动流程 概述
uboot启动流程
如上图所示:
2. 内核启动详细流程
开发板从上电到启动内核的过程
四、uboot启动流程代码详解
1. 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 }
2. uboot启动代码流程概要
代码只分析到uboot命令行,函数main_loop()位置。
3. 启动代码详细分析
_start入口位于以下文件:
- u-boot-2013.01/arch/arm/cpu/armv7/start.S
第一阶段:
第二阶段
第二阶段代码从_main开始:
以上代码详细解释,请结合B站视频同步学习。
五、uboot启动的几个关键知识点
1.如何判断第一条机器指令的位置?
链接脚本决定了内存的布局。
uboot链接脚本如下:
- u-boot-2013.01/arch/arm/cpu/u-boot.lds
文件内容:
- 28 ENTRY(_start)
- 29 SECTIONS
- 30 {
- 31 . = 0x00000000;
- 32
uboot的入口是_start
链接地址是0x00000000
2.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节。
3.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仍然有数据,所以在此启动后,只需要从特殊的寄存器中读取相应的值,就可以知道之前是因为什么原因休眠,进而回复休眠之前的上下文即可。
3.uboot代码搬到ram之后,代码的运行地址发生了变化,如何保证程序跳转不会出错?
除了要保证uboot代码是基于地址无关的,此外.rel.dyn帮我们解决了,其实主要还是编译器帮我们做了很多工作。
位置无关码参考《15. 从0学ARM-什么是位置无关码?》
4.设备启动的时候,有可能直接从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地址的bit:28-31均是0,所以只需要读取出执行到lowlevel_init时pc的值,判断其bit:28-31是否是0即可知道现在代码是否运行在RAM中。
ajax 实现三级联动,相当于写了一个小插件,用的时候直接拿过来用就可以了,这里...
本文实例讲述了正则表达式中的操作符及说明。分享给大家供大家参考,具体如下: ...
Greediness(贪婪型):最大匹配 X、X*、X+、X{n,} 是最大匹配。例如你要用 “....
这些日子一直在简书上使用markdown写作,已经渐渐的痴迷于这种简洁纯粹的写作方...
1 . 目标 演示下图的git reset 各选项的效果。 2. Git Reset操作说明 图中说明:...
从另一台机器上复制过来的项目,由于两台机器的库目录不一致,导致了stdio.h等很...
3月22日消息 外媒 Winfuture 报道,此前微软面向 Insider 预览用户公布了 Window...
橡皮擦一个逗趣的互联网高级网虫。 观前提醒本篇文章涉及知识点巨大建议先收藏再...
Go原生就支持连接数据库,所以在使用 Golang 开发时,当需要数据库交互时,即可...
2月23日消息 据外媒 Windows Latest 今日报道,借助 Windows 10 Sun Valley 更新...