【源码分析】u-boot-2014.04 分析(未完待续)

本文最后更新于:2022年6月8日 晚上

u-boot-2014.04 分析

​ 由于要移植一个 kernel 和 u-boot 到 smart210 平台,所以就有了该篇记录。

本文使用的版本是 u-boot-2022.04-rc4.tar.bz2 u-boot-2014.04(因作者能力不够,遂从2014.04开始)

s5pv210

芯片启动流程

由三星官方手册可知,整个启动分为三阶段,BL0、BL1、BL2,其中BL0运行内部iROM中,BL1和BL2运行在SRAM中,但在实际项目中使用的比较通用的启动流程与官方流程有较大差异,为什么呢?因为编译的u-boot过大,超过最大80k限制。所以BL2在SDRAM中运行,而三星官方是放在内部SRAM中运行。

BL0

  • 运行在iROM上
  • 代码固定在s5pv210的IROM中,无法修改。上电后直接从IROM中开始执行
  • 主要工作
    • 初始化系统时钟、特殊设备控制器、启动设备、看门狗、SRAM等
    • 验证BL1镜像
    • 从存储介质上(比如SD/eMMC/nand flash)等加载BL1镜像到内部SRAM中
    • 跳转到BL1镜像所在地址

BL1

  • 运行在SRAM上
  • BL1代码在BL0段被加载到SRAM中
  • 主要工作
    • 初始化部分时钟(SDRAM相关)
    • 初始化DDR(外部SDRAM)
    • 从存储介质上将BL2镜像加载到SDRAM中
    • 验证BL2镜像合法性
    • 跳转到BL2镜像所在的地址上

BL2

  • 运行在SDRAM上
  • BL2代码在BL1段被加载到SDRAM中
  • BL2就是传统意义上的bootloader,主要负责加载OS和启动OS

地址映射

起始地址 结束地址 长度 映射区域描述
0x0000_0000 0x1FFF_FFFF 512MB Boot area(取决于启动模式)
0x2000_0000 0x3FFF_FFFF 512MB DRAM 0
0x4000_0000 0x7FFF_FFFF 1024MB DRAM 1
0x8000_0000 0x87FF_FFFF 128MB SROM Bank 0
0x8800_0000 0x8FFF_FFFF 128MB SROM Bank 1
0x9000_0000 0x97FF_FFFF 128MB SROM Bank 2
0x9800_0000 0x9FFF_FFFF 128MB SROM Bank 3
0xA000_0000 0xA7FF_FFFF 128MB SROM Bank 4
0xA800_0000 0xAFFF_FFFF 128MB SROM Bank 5
0xB000_0000 0xBFFF_FFFF 256MB OneNAND/NAND Controller and SFR
0xC000_0000 0xCFFF_FFFF 256MB MP3_SRAM output buffer
0xD000_0000 0xD000_FFFF 64KB IROM
0xD001_0000 0xD001_FFFF 64KB Reserved
0xD002_0000 0xD003_7FFF 96KB IRAM
0xD800_0000 0xDFFF_FFFF 128MB DMZ ROM
0xE000_0000 0xFFFF_FFFF 512MB SFR region

s5pv210芯片上电之后,CPU会直接从0x0地址取指令,也就是直接运行BL0。

起始地址 结束地址 长度 映射区域描述
0x0000_0000 0x0000_FFFF 64KB Internal ROM

BL1运行在IRAM中,IRAM空间如下

起始地址 结束地址 长度 映射区域描述
0xD002_0000 0xD003_7FFF 96KB IRAM

但需注意:BL1运行在IRAM上,但并不意味着就从0xD002_0000开始。其实0xD002_0000开头的16B被用做BL1的header,BL1真正是从0xD002_0010开始运行的。前16字节被用来验证BL1镜像的完整性,其格式如下:

地址 数据
0xD002_0000 BL1镜像包括header的长度
0xD002_0004 保留,设置为0
0xD002_0008 BL1镜像除去header的校验和
0xD002_000c 保留,设置为0

BL2运行地址在SDRAM中, smart210使用的是DRAM0,所以地址为

起始地址 结束地址 长度 映射区域描述
0x2000_0000 0x3FFF_FFFF 512MB DRAM 0

u-boot

由以上可知,上电后首先运行BL0阶段(BL0段代码起始地址为0)。

BL0 阶段运行的代码为IROM中自带的,其主要功能为 初始化系统时钟、特殊设备控制器、启动设备、看门狗、SRAM等,验证BL1镜像是否完整(0xD002_0000处存在长度和校验)然后跳转到BL1处(0xD002_0010)执行。

BL1 阶段运行在IRAM中,其主要功能是初始化部分时钟和DDR,拷贝BL2段代码到SDRAM中,验证BL2并跳转执行。

那么,BL1段代码是怎么被拷贝到SRAM中的?

答:在BL0段将BL1的代码拷贝到SRAM中(参考S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf2.2 iROM(BL0) boot-up sequence (Refer 2.3 V210 boot-up diagram)),由于BL0三星不开源,所以我我们只能按照三星的要求存放BL1的位置(参考S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf2.8 Boot Block Assignment Guide),如果从SD卡启动,也即从SD卡的第1个块开始;如果从NAND Flash启动,就放到Flash中的0块处。拷贝多少呢?这个不知道,有人说是16k也有人说是8k,尽可能将BL1的代码控制在8K以内。

BL2 阶段运行在SDRAM中,就是传统意义上的bootloader,主要负责加载OS和启动OS

u-boot代码中有两部分:u-boot-spl、u-boot,其中u-boot-spl与芯片启动流程中的BL1对应、u-boot与BL2对应。

那么问题来了,u-boot-spl是如何生成的?u-boot又是怎么生成的?

spl的编译时编译uboot的一部分,和uboot.bin走的是两条编译流程。

一般来说,会先编译主体uboot,也就是uboot.bin,在编译uboot-spl,也就是uboot-spl.bin,两个流程。

编译成功后有几个文件名需要注意下,

文件 说明
u-boot-spl 初步链接后得到的spl文件
u-boot-spl-nodtb.bin 在u-boot-spl的基础上,经过objcopy去除符号表信息之后的可执行程序
u-boot-spl.bin 在不需要dtb的情况下,直接由u-boot-spl-nodtb.bin复制而来,也就是编译spl的最终目标
smart210-spl.bin 由s5pv210平台决定,需要在u-boot-spl.bin的基础上加上16B的header用作校验
u-boot-spl.lds spl的连接脚本
u-boot-spl.map 连接之后的符号表文件
u-boot-spl.cfg 由spl配置生成的文件

框架

一般情况下,u-boot采用”board–>machine–>arch–>cpu”框架,如图:

基于这个架构,u-boot和平台有关的初始化流程就很清晰了。

  1. u-boot 启动后,会先执行CPU(如armv8)的初始化代码
  2. CPU相关的代码, 会调用ARCH的公共代码(如arch/arm)
  3. ARCH的公共代码,在适当的时候,调用board有关的接口。u-boot的功能逻辑,大多是由common代码实现,部分和平台有关的部分,则由公共代码声明,由board代码实现。
  4. board代码在需要的时候,会调用machine(arch/arm/mach-xxx)提供的接口,实现特定的功能。因此machine的定位是提供一些基础的代码支持,不会直接参与到u-boot的逻辑功能中去。

由此可知,当u-boot上电后,首先执行的是CPU的初始化代码。板卡型号为smart210,CPU型号为s5pv210,armv7指令集。

通过 u-boot-spl.ldsarch/arm/cpu/u-boot-spl.lds) 和 u-boot.lds(arch/arm/cpu/u-boot.lds) 中 ENTRY(_start)可知,不管是u-boot-spl还是u-boot都是从_start开始的。

代码分析

首先解决编译问题,其次是分析代码,在分析代码的过程中遇到不会的指令和寄存器及时记录下来,防止下次还是不会。

编译问题

错误1

/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found

dirname: missing operand

具体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
glj0@glj0-ubuntu21:~/worksapce/os/smart210/u-boot-2014.04$ ./make.sh 
make: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: No such file or directory
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
dirname: missing operand
Try 'dirname --help' for more information.
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
dirname: missing operand
Try 'dirname --help' for more information.
make: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: No such file or directory
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
dirname: missing operand
Try 'dirname --help' for more information.
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
dirname: missing operand
Try 'dirname --help' for more information.
Configuring for smart210 board...
make: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: No such file or directory
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
dirname: missing operand
Try 'dirname --help' for more information.
GEN include/autoconf.mk.dep
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
GEN include/autoconf.mk
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
CHK include/config/uboot.release
CHK include/generated/timestamp_autogenerated.h
UPD include/generated/timestamp_autogenerated.h
UPD include/config/uboot.release
HOSTCC scripts/basic/fixdep
CHK include/generated/version_autogenerated.h
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-ld: not found
UPD include/generated/version_autogenerated.h
CC lib/asm-offsets.s
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
make[1]: *** [/home/glj0/worksapce/os/smart210/u-boot-2014.04/./Kbuild:35: lib/asm-offsets.s] Error 127
make[1]: *** Waiting for unfinished jobs....
CC arch/arm/lib/asm-offsets.s
/bin/sh: 1: /opt/FriendlyARM/toolschain/4.5.1/bin/arm-none-linux-gnueabi-gcc: not found
make[1]: *** [/home/glj0/worksapce/os/smart210/u-boot-2014.04/./Kbuild:84: arch/arm/lib/asm-offsets.s] Error 127
make: *** [Makefile:999: prepare0] Error 2

缺少32位库,安装就可以了。

1
sudo apt-get install libgl1-mesa-dri:i386

寄存器

32位处理器能同时处理32位的数据,所以对应寄存器为32位的

64位处理器能同时处理32位的数据,所以对应寄存器为64位的

ARM处理器用到的指令集分为ARM 和 THUMB两种。

ARM指令集长度固定为32bit,THUMB指令集长度固定为16bit。ARM64指令集长度也是32bit。

类别 寄存器 APCS(ARM 过程调用标准) 说明
通用寄存器/不分组寄存器 r0 a1 用作传入函数参数,传出函数返回值。在子程序调用之间,可以将r0-r3用于任何用途
r1 a2 同上
r2 a3 同上
r3 a4 同上
r4 v1 存放函数的局部变量如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值
r5 v2 同上
r6 v3 同上
r7 v4 同上
通用寄存器/分组寄存器 r8 v5 同上
r9 v6 同上
r10 sl (stack limit) 同上
r11 fp () 同上
r12 ip (intra-prpcedure-call scratch regiser) 内部调用暂时寄存器。
r13 sp (stack pointer) 栈指针sp,存放的值在退出被调用函数时必须与进入时的值相同。
r14 lr (link register) 通常被用作子程序链接寄存器,也称lr,指向函数的返回地址。通常用来保存子程序执行的下一条指令。
通用寄存器/程序计数器 r15 pc (program counter) 程序计数器,保留下一条CPU即将执行的指令。不能用于其它用途。
程序状态字寄存器 cpsr
spsr
协处理器 cp15 在基于ARM的嵌入式应用系统中,存储系统的操作通常是由协处理器CP15完成的。CP15包含16个32位的寄存器,其编号为0~15。

CP15的寄存器列表如表所示:

寄存器编号 基本作用 在MMU中的作用 在PU中的作用
0 ID 编码(只读) ID 编码和 cache 类型
1 控制位(可读写) 各种控制位
2 存储保护和控制 地址转换表基地址 Cachability 的控制位
3 存储保护和控制 域访问控制位 Bufferablity 控制位
4 存储保护和控制 保留 保留
5 存储保护和控制 内存失效状态 访问权限控制位
6 存储保护和控制 内存失效地址 保护区域控制
7 高速缓存和写缓存 高速缓存和写缓存控制
8 存储保护和控制 TLB 控制 保留
9 高速缓存和写缓存 高速缓存锁定
10 存储保护和控制 TLB 锁定 保留
11 保留
12 保留
13 进程标识符 进程标识符
14 保留
15 因不同设计而异 因不同设计而异 因不同设计而异

指令

指令集二进制编码

条件码

指令

类别 指令 说明 示例
条件(有符号数) GE 大于等于
LE 小于等于
GT 大于
LT 小于
条件(用于无符号数) HS 大于等于
LS 小于等于
HI 大于
LO 小于
STR{cond} 源寄存器,<存储器地址> 从源寄存器中将一个32位的字数据传送到存储器中 STR r1, [r0]将r1里面值复制到以r0里面的值作为地址的内存中
STRLO 源寄存器,<存储器地址> 当满足条件小于时,从源寄存器中将一个32位的字数据传送到存储器中 CMP r0,r1;STRLO r2,[r0]; 首先r0=r0-r1,当满足条件r0<r1时,将r2中的值复制到以r0里面的值作为地址的内存中。
地址读取伪指令 ADR{cond}目的寄存器,相对地址或标签 将基于PC相对偏移的地址值或基于寄存器相对地址值传送到目的寄存器中(小范围) ADR r0, here;here: ....将标签here处的相对地址传递给寄存器r0
LDR{cond}目的寄存器,<存储器地址> 从存储器中将一个32位的字数据传送到目的寄存器中(大范围) LDR r0, [r1]将r1里面的值作为地址,将地址里面的值复制给寄存器r0
跳转指令 B{cond} 目标地址 跳转指令 B Label程序无条件跳转到标号Label处执行
BL{cond} 目标地址 带返回的跳转指令(跳转之前,将PC内容存到R14中) BL Label程序将当前PC值存到R14中,程序无条件跳转至标号Label中执行
BX{cond} Rm 带状态切换的跳转指令,最低位为1时,切换到Thumb指令执行,为0时,解释为ARM指令执行(最低位指的是Rm的第0位)
BLX 带返回和状态切换的跳转指令
算术运算指令 SUB 寄存器1,寄存器2,寄存器3 寄存器1=寄存器2-寄存器3 SUB r0,r1,#1 r0=r1-1
SUBS 寄存器1,寄存器2,寄存器3 寄存器1=寄存器2-寄存器3;S表示并将进位结果写到CPSR中 SUBS r0,r1,#1 r0=r1-1,并将进位结果写道CPSR
CMP 寄存器1,寄存器2 寄存器1=寄存器1-寄存器2 CMP r0,r1 也就是r0=r0-r1
多数据传输条件 IA(Increase After) 每次传送后地址加4,其中的寄存器从左到右执行,例如:STMIA R0,{R1,LR} 先存R1,再存LR
IB(Increase Before) 每次传送前地址加4,同上
DA(Decrease After) 每次传送后地址减4,其中的寄存器从右到左执行,例如:STMDA R0,{R1,LR} 先存LR,再存R1
DB(Decrease Before) 每次传送前地址减4,同上
FD 满递减堆栈 (每次传送前地址减4)
FA 满递增堆栈 (每次传送后地址减4)
ED 空递减堆栈 (每次传送前地址加4)
EA 空递增堆栈 (每次传送后地址加4)
多数据传输(加载) LDM{cond} Rn{!},reglist{^} 多数据加载,将地址上的值加载到寄存器中。Rn:基址寄存器,装有传送数据的起始地址,Rn不允许为R15;!:表示最后的地址写回到Rn中;reglist:可包含多于一个寄存器范围,用“,”隔开,如{R1,R2,R6-R9},寄存器由小到大顺序排列;^:不允许在用户模式和系统模式下运行 LDR R0,=0x100000;LDMIA R0!,{R1-R8}也就是从左往右加载,首先将0x100000中的数据加载到R1,然后R0=R0+4,然后将0x1000004中的数据加载到R2,然后R0=R0+4….
多数据传输(存储) STM{cond} mode Rn{!}, reglist{^} 多数据存储,将寄存器的值存到地址上。同上
协处理器CP15操作 MRC{cond} p15,,,,, 协处理器寄存器到ARM处理器寄存器的数据传送指令(读出协处理器寄存器)。cond为指令执行条件码,当忽略时无条件执行。Opcode1:协处理器的特性操作码,对于CP15来说,其为0。Rd:源寄存器的ARM寄存器,其值被传送到协处理器中或者将协处理器的值传送到该寄存器中。CRn:目标寄存器的的协处理器寄存器,C~C15。CRm:协处理器中附件的目标寄存器或源操作数寄存器,默认为c0。Opcode2:可选的协处理器特定操作码。 mrc p15, 0, r0, c1, c0, 0 将CP15的寄存器C1的值读到r0中
MCR{cond} p15,,,,, ARM处理器寄存器到协处理器寄存器的数据传送指令(写入协处理器寄存器) mcr p15, 0, r0, c7, c7, 0 关闭ICaches和DCachesmcr
p15, 0, r0, c8, c7, 0 使无效整个数据TLB和指令TLB
.globl 伪指令,声明一个全局变量 .globl _start声明_start,下面调用时才不会出现链接错误。类似c语言中的extern。

源码分析

2022中的_start不看

所以首先会执行armv7中的start.S代码,但是呢,通过代码,我们没有发现start.S中有 _start相关的定义,而且也没有一些向量表的处理,这就很奇怪了,代码放哪呢了呢?没有_start又要怎么启动呢?带着问题,我们接着往下走。

由于我们的代码是最新的,但有印象在2014版本中是有这一部分的,所以我们直接去查找git上这个文件的history

于是有了找到了以下

https://source.denx.de/u-boot/u-boot/-/commit/41623c91b09a0c865fab41acdaff30f060f29ad6

在这个链接右上角可以看到,删除了2426行,新增了313行。看了下发现删除的都是 下面这种代码

1
2
3
4
.global _start
_start: b reset
...
.balignl 16,0xdeadbeef

那这些代码去哪了呢?

接着搜索armv7,发现也是这种代码被删除。

接着走,看看新增了什么,在u-boot-spl.lds和u-boot.lds中 代码中新增了一个 *(vectors)并且位置比start.o靠前,什么意思?中断向量表存到这个地方了嘛?是的,接着往下看,一切都会真相大白

终于看到vector.S了,这部分代码是存在 arch/arm/lib下,有没有发现很熟悉,没错,就是上面删掉的代码,而且 .global _start也在这,由此可以猜测,开发人员将各个处理器的公共代码抽离,最终以vector.S体现

借助 u-boot.lds(arch/arm/cpu/u-boot.lds)可知,ENTRY(_start),整个u-boot的开始依然是_start,由以上分析可知,_start在vectors.S中定义,所以整个U-boot的入口为vector.S中的_start,接着 ARM_VECTORS 是一个宏,展开就是 .macro/.endm之间包裹的。接着往下走,就会走入b reset,调用reset。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* arch/arm/lib/vectors.S */
.globl _start

.section ".vectors", "ax"

#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
#include <asm/arch/boot0.h>
#else /*走这个分支 (怎么确认的呢?在此处随便加个乱七八糟的字符,然后编译,如果编译过,则说明走的不是此分支)*/
_start: /*U-boot程序入口*/
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS /* 一个宏*/
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
/* 在.macro 和 .endm 中的为*/
.macro ARM_VECTORS
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
b reset // 走这个分支
#endif
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
.endm


reset标签在哪呢?arch/arm/cpu/armv7/start.S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* arch/arm/cpu/armv7/start.S */

reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:

/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
* 禁用中断,同时设置CPU模式,除非已经处于HYP模式下
*/
mrs r0, cpsr ;读取寄存器cpsr中的值,并保存到r0寄存器中。
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0

ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
ENDPROC(save_boot_params)

mrs r0, cpsr ;读取寄存器cpsr中的值,并保存到r0寄存器中。

and r1, r0, #0x1f寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式

以上为 u-boot-2022.04中的内容


代码函数调用图

https://www.zhixi.com/view/18da9b99

_start

由于我们的处理器使用的指令集为armv7,所以**_start**的路径为arch/arm/cpu/armv7/start.S

其实对于u-boot的start.S,主要做的几件事就是系统各方面的初始化。

从大的方面分,可分为以下部分

  • 设置 CPU 模式

  • 关闭看门狗

  • 关闭中断

  • 设置堆栈 sp 指针

  • 清除 bss 段

  • 异常中断处理

接着我们看代码,

.globl _start 声明_start标号对全局可见,类似于c语言中的extern

_start: b reset _start后加一个冒号,表示其实一个Label。而同时_start的值,也就是代码的最开始的位置,相对是0。在u-boot-spl中其地址是0,在u-boot中,由于经过了relocate之后,代码的运行地址是我们定义的基地址(也就是重定位后的偏移地址gd->relocaddr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
; arch/arm/cpu/armv7/start.S
.globl _start /* .globl指示告诉汇编器,_start是一个全局符号*/
_start: b reset /* 跳转到reset符号处*/
ldr pc, _undefined_instruction /* 未定义指令异常*/
ldr pc, _software_interrupt /* 软中断,Linux系统调用*/
ldr pc, _prefetch_abort /* 预取址中止,取不到下一条指令*/
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq /* 中断*/
ldr pc, _fiq /* 快中断*/
...
reset:
bl save_boot_params //跳转到save_boot_params符号处
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr //将cpsr寄存器的值读到r0中
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode //更改处理器模式为SVC模式
orr r0, r0, #0xc0 @ disable FIQ and IRQ //将I、F位(7、6位)置1 也即0xc0,关闭快中断和中断
msr cpsr,r0 //将r0的内容写入cpsr寄存器


ENTRY(save_boot_params)
bx lr @ back to my caller
ENDPROC(save_boot_params)
.weak save_boot_params

这一段,首先会跳转到 save_boot_params 标号处,而 ENTRY(save_boot_params) 表示 save_boot_params 入口,进入后就一句 bx lr,其作用为 跳转到 lr 中存放的地址处。而lr的用途:当通过BL或者BLX调用子程序时,硬件将自动将子程序返回地址保存在R14寄存器中,在子程序返回时,把LR的值复制到程序计数器就可以实现子程序返回。所以这段代码的意思就是跳转到调用者处。也就是接着执行bl save_boot_params后面的代码。

mrs r0, cpsr读取寄存器cpsr中的值,并保存到r0寄存器中。

and r1, r0, #0x1f寄存器r0中的值与0X1F进行与运算,结果保存到r1寄存器中,目的就是提取cpsr的bit0~bit4这5位,这5位为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式。

如果r10X1A(0b11010)不相等,也就是CPU不处于Hyp模式的话就将r0寄存器的bit0~5进行清零,其实就是清除模式位。

如果处理器不处于Hyp模式的话就将r0的寄存器的值与0x13进行或运算,0x13(0b10011),也就是设置处理器进入管理模式(SVC)。

r0寄存器的值再与0xC0(0b1100_0000)进行或运算,那么r0寄存器此时的值就是0xD3(0b1101_0000),cpsr的I位和F位分别控制IRQ和FIQ这两个中断的开关,设置为1就关闭了FIQ和IRQ!

r0寄存器写回到cpsr寄存器中。完成设置CPU处于SVC32模式,并且关闭FIQ和IRQ这两个中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; arch/arm/cpu/armv7/start.S
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register

/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif

这部分代码通过对协处理器CP15进行操作,设置了处理器的异常向量入口地址为_start

这是因为ARM默认的异常向量表入口在0x0地址,然而S5PV210中0x0地址存放的是IROM,不可修改,自然不可能存放异常向量表,所以需要修改异常向量表入口,将它们映射到其他位置上去。

接着是三个跳转,每个跳转后都会返回到当前位置的下一条地址处。

1
2
3
4
5
6
7
8
; arch/arm/cpu/armv7/start.S
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif

bl _main

cpu_init_cp15

首先跳转到 cpu_init_cp15 中,我们可以看到在ENDPROC(cpu_init_cp15)之前,有一条 mov pc, lr,此命令作用和bx lr相同,跳转回子函数调用的地方。在 cpu_init_cp15 中,主要是失效L1、I/D、关闭 MMU 等

1
2
3
4
5
; arch/arm/cpu/armv7/start.S
ENTRY(cpu_init_cp15)
...
mov pc, lr @ back to my caller //子过程运行结束,跳转回去
ENDPROC(cpu_init_cp15)

cpu_init_crit

接着跳转到 cpu_init_crit 中,里面又一个跳转 lowlevel_init

1
2
3
4
; arch/arm/cpu/armv7/start.S
ENTRY(cpu_init_crit)
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

lowlevel_init(分析错误)

注意:以下分析错误,请直接看下一个 lowlevel_init

lowlevel_init 位于 arch/arm/cpu/armv7/lowlevel_init.S,主要是设置栈顶指针为 CONFIG_SYS_INIT_SP_ADDR ,然后栈顶指针8字节对齐。如果在SPL,设置r9数据为 gdata 地址,否则为GD数据分配空间,并将分配后的栈顶指针给r9。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ENTRY(lowlevel_init)
/*
* Setup a temporary stack
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}

/*
* go setup pll, mux, memory
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)

CONFIG_SYS_INIT_SP_ADDRinclude/configs/smart210.h,也就是说设置栈顶指针为SDRAM结束的高地址处。

1
2
3
4
5
6
7
8
#define CONFIG_SYS_INIT_SP_ADDR	(CONFIG_SYS_LOAD_ADDR + PHYS_SDRAM_1_SIZE)
...
#define CONFIG_SYS_LOAD_ADDR CONFIG_SYS_SDRAM_BASE
...
/* DRAM Base */
#define CONFIG_SYS_SDRAM_BASE 0x20000000
...
#define PHYS_SDRAM_1_SIZE (512 << 20) /* 0x2000_0000, 512 MB Bank #1 */

其中 gddata 定义在 arch/arm/lib/spl.c中,这里需要补充一个高级用法。具体用法查看 https://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html ,这里则是将 gdata 位置存放到 .data 段中。

_attribute_

该关键字允许您指定变量、函数参数或结构体、联合体以及类成员的特殊属性

而 _attribute_ ((section(“.data”)))

则说明将 前者定义的放入.data段中

1
2
3
4
5
6
/* Pointer to as well as the global data structure for SPL */
DECLARE_GLOBAL_DATA_PTR;
gd_t gdata __attribute__ ((section(".data")));
...
// 在 arch/arm/include/asm/global_data.h 中
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")

此时需要注意下 DECLARE_GLOBAL_DATA_PTR 这个宏,这个宏的意思是定义了全局数据结构gd(寄存器r9来表示全局数据结构gd

register volatile gd_t *gd asm ("r9") 其中register关键字是必须的,asm (“r9”)为嵌入式汇编,表示用r9寄存器存储gd指针,r9和CPU体系结构相关。寄存器变量。

所以不管 ldr r9, =gdata还是mov r9, sp都是更新 gd 的地址,不同的是在SPL下,gd结构体空间被分配在 .data段中,在u-boot中,gd结构体空间被分配在CONFIG_SYS_INIT_SP_ADDR - GD_SIZE (需要8字节对齐)处。

接着往下走,push {ip, lr}用stack的形式保存lr(返回指针),将ip和lr入栈

然后一个跳转到bl s_init中。

下面 pop {ip, pc} 恢复到ippc,意思是改变pc的指向,将lr内容恢复到pc中,也即返回到lr指向的地方,在此处应为bl cpu_init_crit的下方,也即bl _main

接着分析 s_init,在与smart210相关的任何地方找不到与s_init相关的定义,也找不到与s5pv210任何相关的,所以此处应该是有问题的,通过在指令间添加一些异常数据,编译,编译通过,得知在调用 lowlevel_init时并不是找的此处。

以上分析错误,请直接查看下一个 lowlevel_init


lowlevel_init

lowlevel_init 位于 board/samsung/smart210/lowlevel_init.S ,这个相对简单,但还是说一下,首先将lr返回地址保存到r9中(此处不应该使用r9,或者说在使用r9前先保存r9的值),如果在SPL下,则需要初始化时钟和ddr,并配置串口寄存器PA0CON值为0x00002222(查看手册 S5PV210_UM_REV1.1.pdf2.2.2.1 Port Group GPA0 Control Register (GPA0CON, R/W, Address = 0xE020_0000) ),也即配置GPA0CON功能为串口UART1。之后,恢复pc值为lr,返回到调用 lowlevel_init 的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
; board/samsung/smart210/lowlevel_init.S

.globl lowlevel_init
lowlevel_init:
mov r9, lr

#ifdef CONFIG_SPL_BUILD
bl clock_init /* clock init */
bl ddr_init /* DDR init */

/* add by Flinn, for uart */
ldr r0, =0xE0200000 /* GPA0_CON */
ldr r1, =0x22222222
str r1, [r0]

#endif
mov pc, r9 /* return */

返回到bl cpu_init_crit的下一句指令,也即执行 bl _main

1
2
3
4
5
6
7
8
; arch/arm/cpu/armv7/start.S

/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main

_main

_main位于arch/arm/lib/crt0.S中,具体代码一会回来分析。

终于来到_main中了,首先如果定义了CONFIG_SPL_BUILDCONFIG_SPL_STACK,也即SPL时,sp值为CONFIG_SPL_STACK,但经过验证没有走这个分支;所以sp的值只可能为CONFIG_SYS_INIT_SP_ADDR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; arch/arm/lib/crt0.S
/*
* entry point of crt0 sequence
*/

ENTRY(_main)

/*
* Set up initial C runtime environment and call board_init_f(0).
*/

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
...
ENDPROC(_main)

CONFIG_SYS_INIT_SP_ADDRinclude/configs/smart210.h,也就是说设置栈顶指针为SDRAM结束的高地址处。

1
2
3
4
5
6
7
8
9
/* include/configs/smart210.h */
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_LOAD_ADDR + PHYS_SDRAM_1_SIZE)
...
#define CONFIG_SYS_LOAD_ADDR CONFIG_SYS_SDRAM_BASE
...
/* DRAM Base */
#define CONFIG_SYS_SDRAM_BASE 0x20000000
...
#define PHYS_SDRAM_1_SIZE (512 << 20) /* 0x2000_0000, 512 MB Bank #1 */

接着往下走,是给gd数据结构分配空间。gd是什么?

gd是一个寄存器变量。此时需要注意下 DECLARE_GLOBAL_DATA_PTR 这个宏,这个宏的意思是定义了全局数据结构gd(寄存器r9来表示全局数据结构gd

1
2
3
4
5
>// board/samsung/smart210/smart210.c
>DECLARE_GLOBAL_DATA_PTR;
>---------------------------------------------------------------------------------------
>// arch/arm/include/asm/global_data.h
>#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")

register volatile gd_t *gd asm ("r9") 其中register关键字是必须的,asm ("r9")为嵌入式汇编,表示用r9寄存器存储gd指针,r9和CPU体系结构相关。

先8字节对齐,然后给gd结构分配大小为GD_SIZE的空间,然后继续8字节对齐。GD_SIZE是一个宏,在include/generated/generic-asm-offsets.h中定义为160sizeof(struct global_data)),该文件也是生成的。

更新r9值为sp,此时r9 = sp = [CONFIG_SYS_INIT_SP_ADDR - GD_SIZE][CONFIG_SYS_INIT_SP_ADDR - GD_SIZE]为对应地址处的值),接着给r0清零

1
2
3
4
5
6
; arch/arm/lib/crt0.S
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
sub sp, sp, #GD_SIZE /* allocate one GD above SP */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r9, sp /* GD is above SP */
mov r0, #0

接着往下走,分为两种情况,第一种SPL下,跳转到 copy_bl2_to_ram(完成BL2阶段镜像校验并拷贝到SDRAM中),执行结束后返回,更改PC指针为CONFIG_SYS_SDRAM_BASE,也即跳转到 BL2阶段(SDRAM中)。

1
2
3
4
5
6
7
8
9
10
; arch/arm/lib/crt0.S
#ifdef CONFIG_SPL_BUILD
bl copy_bl2_to_ram
ldr pc,=CONFIG_SYS_SDRAM_BASE
#else
bl board_init_f
#endif
-----------------------------------------------
; include/configs/smart210.h
#define CONFIG_SYS_SDRAM_BASE 0x20000000

copy_bl2_to_ram

copy_bl2_to_ram函数在 board/samsung/smart210/smart210.c

首先是几个宏定义,让我们看一下是什么东东?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* board/samsung/smart210/smart210.c */
/*
* 从SD/MMC拷贝(加载)块到内存中的函数
** ch: 通道号
** sb: 起始块号
** bs: 块数量
** dst: 要拷贝到内存的什么位置上
** i: 是否需要初始化
*/
#define CopySDMMCtoMem(ch, sb, bs, dst, i) \
(((u8(*)(int, u32, unsigned short, u32*, u8))\
(*((u32 *)0xD0037F98)))(ch, sb, bs, dst, i))

#define MP0_1CON (*(volatile u32 *)0xE02002E0)
#define MP0_3CON (*(volatile u32 *)0xE0200320)
#define MP0_6CON (*(volatile u32 *)0xE0200380)

#define NF8_ReadPage_Adv(a,b,c) (((int(*)(u32, u32, u8*))(*((u32 *)0xD0037F90)))(a,b,c))
1
2
#define CopySDMMCtoMem(ch, sb, bs, dst, i) \
(((u8(*)(int, u32, unsigned short, u32*, u8))(*((u32 *)0xD0037F98)))(ch, sb, bs, dst, i))

这个宏定义分为三段来看,分别对应着 函数返回值数据类型 (*指针变量名)(函数的实际参数或者函数参数的类型)
第一段:((u8 (*)(int, u32, unsigned short, u32*, u8)) 是一个函数类型强制类型转换,其中u8为返回值,(int, u32, unsigned short, u32*, u8)为传入参数类型。
第二段:(*((u32 *)0xD0037F98))) ,在地址0xD0037F98中存放了一个名字叫CopySDMMCtoMem的函数,armv7 为32位cpu,所以先转成指针类型为u32*,然后再把这个地址解引用,就得到了地址中存在的值也就是CopySDMMCtoMem

第三段:将函数的实际参数传入即可(ch, sb, bs, dst, i),最外层加上一个大括号。

接着三个宏表示 将对对应地址处的值操作。

1
2
3
#define MP0_1CON  (*(volatile u32 *)0xE02002E0) /* 查看手册S5PV210_UM_REV1.1.pdf中 2.2.25.1*/
#define MP0_3CON (*(volatile u32 *)0xE0200320) /* 查看手册S5PV210_UM_REV1.1.pdf中 2.2.27.1*/
#define MP0_6CON (*(volatile u32 *)0xE0200380) /* 查看手册S5PV210_UM_REV1.1.pdf中 2.2.30.1*/

NF8_ReadPage_Adv(a,b,c)CopySDMMCtoMem(ch, sb, bs, dst, i)一致,不在讲解。

作用
CopySDMMCtoMem(ch, sb, bs, dst, i) 从SD/MMC拷贝(加载)块到内存中的函数 (从通道ch的sb块处,加载bs块内存到以dst起始的地方)
NF8_ReadPage_Adv(a,b,c) 从nand flash拷贝(加载)页到内存中的函数(a:要复制的源块地址号,b:要复制的源页地址号,c:目标缓冲区指针,返回值成功或失败)

接着就进入到copy_bl2_to_ram,整个copy_bl2_to_ram框架如下。也就是上来先定义bl2的大小为250k(编译结束后生成u-boot.bin大小为240k),所以此处最好大小调大一些(建议512k)。OM为寄存器0xE0000004中存储的值,通过手册(SIAP3 Boot configuration)可知OM低5位控制着从哪里启动,所以下面两个分支也就是选择从哪边启动。0x2(0b0010)也即Nand 2KB,5cycle X-TAL;0xc(0b1100)也即SD/MMC X-TAL

1
2
3
4
5
6
7
8
9
10
11
12
/* board/samsung/smart210/smart210.c */
void copy_bl2_to_ram(void)
{
u32 bl2Size = 250 * 1024; // 250K
u32 OM = *(volatile u32 *)(0xE0000004); // OM Register
OM &= 0x1F; // 取出低5位OM[4:0]
if (OM == 0x2) { // Nand 2KB,5cycle X-TAL
...
} else if (OM == 0xC) { // SD/MMC X-TAL
...
}
}

先分析SD/MMC下的代码(SD卡下的代码),首先取出当前boot的V210_SDMMC_BASE基地址(查看手册SIAP2.6 Global Variable ),接着看手册S5PV210_UM_REV1.1.pdf中的7.9.1 REGISTER MAP 找出对应地址的channel,然后从对应channel的SD卡中将以32块开始的250k数据拷贝到SDRAM中的CONFIG_SYS_SDRAM_BASE0x20000000)中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* board/samsung/smart210/smart210.c */
else if (OM == 0xC) {
u32 V210_SDMMC_BASE = *(volatile u32 *)(0xD0037488); // V210_SDMMC_BASE
u8 ch = 0;

/* 7.9.1 SD/MMC REGISTER MAP */
if (V210_SDMMC_BASE == 0xEB000000)
ch = 0;
else if (V210_SDMMC_BASE == 0xEB200000)
ch = 2;
// 将BL2 从SD卡(32块开始的250k区域数据)拷贝到SDRAM中
CopySDMMCtoMem(ch, 32, bl2Size / 512, (u32 *)CONFIG_SYS_SDRAM_BASE, 0);
}

接着分析如果u-boot在nand中是怎样将BL2拷贝到内存中的?

首先初始化配置,将配置写入到 nand_reg->nfconfnand_reg->nfcont 中,接着配置GPIO。pages = bl2Size/2048,看一下BL2一共有多少页。下一个offset = 0x4000/2048,意思是BL2在nand的页偏移。0x4000/512 = 32blocks,也就是在nand中第32个block开始,后256k

void writel(unsigned char data, unsigned short addr) 往内存映射的IO空间上写数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* board/samsung/smart210/smart210.c */
if (OM == 0x2) {
u32 cfg = 0;
struct s5pv210_nand *nand_reg = (struct s5pv210_nand *)(struct s5pv210_nand *)samsung_get_base_nand();

/* initialize hardware */
/* HCLK_PSYS=133MHz(7.5ns) */
cfg = (0x1 << 23) | /* Disable 1-bit and 4-bit ECC */
(0x3 << 12) | /* 7.5ns * 2 > 12ns tALS tCLS */
(0x2 << 8) | /* (1+1) * 7.5ns > 12ns (tWP) */
(0x1 << 4) | /* (0+1) * 7.5 > 5ns (tCLH/tALH) */
(0x0 << 3) | /* SLC NAND Flash */
(0x0 << 2) | /* 2KBytes/Page */
(0x1 << 1); /* 5 address cycle */

writel(cfg, &nand_reg->nfconf);

writel((0x1 << 1) | (0x1 << 0), &nand_reg->nfcont);
/* Disable chip select and Enable NAND Flash Controller */

/* Config GPIO */
MP0_1CON &= ~(0xFFFF << 8);
MP0_1CON |= (0x3333 << 8);
MP0_3CON = 0x22222222;
MP0_6CON = 0x22222222;

int i = 0;
int pages = bl2Size / 2048; //
int offset = 0x4000 / 2048; // u-boot.bin
u8 *p = (u8 *)CONFIG_SYS_SDRAM_BASE;
for (; i < pages; i++, p += 2048, offset += 1)
NF8_ReadPage_Adv(offset / 64, offset % 64, p);
}

board_init_f

board_init_farch/arm/lib/board.c中,天哪,这是个什?这么多代码。

一点点分析吧,首先定义一堆变量,指针等。将gdglobal data)中清零,什么,gd又是个什?gd是在什么时候赋值?什么时候分配空间的呢?

_main中通过sub sp,sp,#GD_SIZE给gd分配了空间,后面通过 mov r9,sp更新gd指针,使之指向分配的空间。所以此处直接清零没有任何问题。

global_data

在某些情况下,uboot运行在某些只读存储器上,在uboot被重定向到RAM(可读可写)之前,我们都无法写入数据,更无法通过全局变量来传递数据。而global_data则是为了解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* include/asm-generic/global_data.h */
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
unsigned long fb_base; /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
unsigned long post_log_word; /* Record POST activities */
unsigned long post_log_res; /* success of POST test */
unsigned long post_init_f_time; /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPES
unsigned long board_type;
#endif
unsigned long have_console; /* serial_init() was called */
#ifdef CONFIG_PRE_CONSOLE_BUFFER
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif
#ifdef CONFIG_MODEM_SUPPORT
unsigned long do_mdm_init;
unsigned long be_quiet;
#endif
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */

unsigned long ram_top; /* Top address of RAM used by U-Boot */

unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len */
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off;
struct global_data *new_gd; /* relocated global data */

#ifdef CONFIG_DM
struct device *dm_root; /* Root instance for Driver Model */
struct list_head uclass_root; /* Head of core tree */
#endif

const void *fdt_blob; /* Our device tree, NULL if none */
void *new_fdt; /* Relocated FDT */
unsigned long fdt_size; /* Space reserved for relocated FDT */
void **jt; /* jump table */
char env_buf[32]; /* buffer for getenv() before reloc. */
#ifdef CONFIG_TRACE
void *trace_buff; /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)
int cur_i2c_bus; /* current used i2c bus */
#endif
unsigned long timebase_h;
unsigned long timebase_l;
struct arch_global_data arch; /* architecture-specific data */
} gd_t;

重点说明

  • bd_t *bd:board info数据结构定义,位于文件 include/asm-arm/u-boot.h定义,主要是保存开发板的相关参数。
  • unsigned long env_addr:环境变量的地址。
  • unsigned long ram_top:RAM空间的顶端地址
  • unsigned long relocaddr:UBOOT重定向后地址
  • phys_size_t ram_size:物理ram的size
  • unsigned long irq_sp:中断的堆栈地址
  • unsigned long start_addr_sp:堆栈地址
  • unsigned long reloc_off:uboot的relocation的偏移
  • struct global_data *new_gd:重定向后的struct global_data结构体
  • const void *fdt_blob:设备的dtb地址
  • void *new_fdt:relocation之后的dtb地址
  • unsigned long fdt_size:dtb的长度
  • struct udevice *cur_serial_dev:当前使用的串口设备。

计算监视区(monitor)长度(__bss_end - _start)并给gd->mon_len

从环境变量中查找fdtcontroladdr,如果存在则更新gd->fdt_blob

ulong getenv_ulong(const char *name, int base, ulong default_val)从环境变量中查找name,存在返回name对应的数值,不存在返回default_val。其中base为进制,例如10、16等。

环境变量的存储用hash表实现

这一段的意思遍历init_sequenceinit_sequence存放的是一系列的函数首地址,通过循环不断将函数地址取出解引用并调用。如果函数返回不为0,表示有错误则一直在此循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* arch/arm/lib/board.c */
void board_init_f(ulong bootflag){
...
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
...
}
----------------------------------------------------------------------------
init_fnc_t *init_sequence[] = {
arch_cpu_init, /* basic arch cpu dependent setup */
mark_bootstage,
#ifdef CONFIG_OF_CONTROL
fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
timer_init, /* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INIT
board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
print_cpuinfo, /* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
NULL,
};

让我们一个个的看看这些函数都是干什么的。

arch_cpu_init

这个函数做的是针对特定CPU的初始化,u-boot支持很多CPU,不同CPU的初始化也不尽相同,因此u-boot提供了arch_cpu_init用于CPU初始化。这个函数由移植者根据自己的硬件(CPU)的情况来提供,如果不提供,则默认的是一个仅返回0的函数。

1
2
3
4
5
6
7
/* arch/arm/lib/board.c */
int arch_cpu_init(void) __attribute__((weak, alias("__arch_cpu_init")));

int __arch_cpu_init(void)
{
return 0;
}
mark_bootstage

其中bootstage_mark_name(id,name)用于标记当前运行的id和名字。这个函数也就是用来标记当前运行的函数为board_init_f

1
2
3
4
5
6
/* arch/arm/lib/board.c */
static int mark_bootstage(void)
{
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
return 0;
}
fdtdec_check_fdt

由于未配置CONFIG_OF_CONTROL,所以不会检查fdt。

这个函数的功能是,在 控制台未准备好之前必须有一个FDT,否则会panic。

1
2
3
4
5
6
7
8
9
10
11
12
/* lib/fdtdec.c */
int fdtdec_check_fdt(void)
{
/*
* We must have an FDT, but we cannot panic() yet since the console
* is not ready. So for now, just assert(). Boards which need an early
* FDT (prior to console ready) will need to make their own
* arrangements and do their own checks.
*/
assert(!fdtdec_prepare_fdt());
return 0;
}
board_early_init_f

由于未配置CONFIG_BOARD_EARLY_INIT_F,不会执行

timer_init

这个配置貌似跟cpu有关系,在每个不同的cpu有不同的 timer_init,另外在 lib/time.c 中也有一个弱空实现。

s5pv210芯片的在 arch/arm/cpu/armv7/s5p-common/time.c中。

使用SOC的Pwm Timer4作为定时器,Timer4 没有输出引脚,不耽误其Pwm功能。设置Pwm Timer4 4分频,设置占空比为100000ns、周期为100000ns,开启定时器4。

接着将gd->arch.timer_reset_value复位。更新gd->arch.lastinc,复位gd->arch.lastinc = readl(&timer->tcnto4);gd->arch.tbl = 0;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// arch/arm/cpu/armv7/s5p-common/time.c
int timer_init(void)
{
/* PWM Timer 4 */
pwm_init(4, MUX_DIV_4, 0);
pwm_config(4, 100000, 100000);
pwm_enable(4);

/* Use this as the current monotonic time in us */
gd->arch.timer_reset_value = 0;

/* Use this as the last timer value we saw */
gd->arch.lastinc = timer_get_us_down();
reset_timer_masked();
return 0;
}
board_postclk_init

由于未配置CONFIG_BOARD_POSTCLK_INIT,不会执行

get_clocks

由于未配置CONFIG_FSL_ESDHC,不会执行

env_init

通过SI在Symbols中搜索发现有多个env_init,猜测是不同的板子可能存到不同的存储设备上。

  1. 环境变量可能位于很多地方,比如EEPROM、FLASH等地方,枚举enum env_location定义了一些常量用于标识环境变量的位置。这些常量有ENVL_EEPROM、ENVL_EXT4等

  2. uboot获取环境变量的位置时,有一个先后顺序,比如优先看环境变量是否在EEPROM中。这个优先顺序实现很简单:定义一个数组env_locations数组里存的是环境变量各个的位置,存的顺序就是优先顺序,然后只要从下标为0开始访问这个数组,即可按照数组指定的优先顺序来获取环境变量的位置。

  3. 对于存在于不同位置的环境变量,u-boot会使用U_BOOT_ENV_LOCATION定义一个相应的struct env_driver类型的entry,多个entry在内存中连续分布,因此可以向遍历数组元素那样遍历这些entry。而实现内存连续分布的关键在于自定义段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
    在链接脚本中将这些段按照段名的字母顺序排列:
    .u_boot_list : {
    KEEP(*(SORT(.u_boot_list*)));
    }
    因此可用u_boot_list_2_env_driver_1标记这些entry的起始地址
    而使用u_boot_list_2_env_driver_3标记相应的结束地址
    */
    .u_boot_list_2_env_driver_1
    .u_boot_list_2_env_driver_2_eeprom
    .u_boot_list_2_env_driver_2_ext4
    .u_boot_list_2_env_driver_3

smart210这个板子在执行saveenv是直接存储环境变量到NAND的0x60000处。实际上通过编译脚本来说更有说服力,首先,看下common/Makefile,要编译对应的文件,需要使能相关宏后才会编译。再次查看include/configs/smart210.h,其定义了#define CONFIG_ENV_IS_IN_NAND 1,所以env_init的路径为common/env_nand.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# environment
obj-y += env_attr.o
obj-y += env_callback.o
obj-y += env_flags.o
obj-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o
obj-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o
extra-$(CONFIG_ENV_IS_EMBEDDED) += env_embedded.o
obj-$(CONFIG_ENV_IS_IN_EEPROM) += env_embedded.o
extra-$(CONFIG_ENV_IS_IN_FLASH) += env_embedded.o
obj-$(CONFIG_ENV_IS_IN_NVRAM) += env_embedded.o
obj-$(CONFIG_ENV_IS_IN_FLASH) += env_flash.o
obj-$(CONFIG_ENV_IS_IN_MMC) += env_mmc.o
obj-$(CONFIG_ENV_IS_IN_FAT) += env_fat.o
obj-$(CONFIG_ENV_IS_IN_NAND) += env_nand.o
obj-$(CONFIG_ENV_IS_IN_NVRAM) += env_nvram.o
obj-$(CONFIG_ENV_IS_IN_ONENAND) += env_onenand.o
obj-$(CONFIG_ENV_IS_IN_SPI_FLASH) += env_sf.o
obj-$(CONFIG_ENV_IS_IN_REMOTE) += env_remote.o
obj-$(CONFIG_ENV_IS_IN_UBI) += env_ubi.o
obj-$(CONFIG_ENV_IS_NOWHERE) += env_nowhere.o

所以我们只关心common/env_nand.c中的,别看代码这么长。实际上ENV_IS_EMBEDDEDCONFIG_NAND_ENV_DST我们都没有定义,所以真正的代码只有两句。

从注释就基本可以看出这个函数的作用,因为env_init要早于静态存储器的初始化,所以无法进行env的读写,这里将gd中的env相关变量进行配置,默认设置envvaliddefault_environment是什么?TODO:待分析

1
2
gd->env_addr	= (ulong)&default_environment[0];
gd->env_valid = 1;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// common/env_nand.c
/*
* This is called before nand_init() so we can't read NAND to
* validate env data.
*
* Mark it OK for now. env_relocate() in env_common.c will call our
* relocate function which does the real validation.
*
* When using a NAND boot image (like sequoia_nand), the environment
* can be embedded or attached to the U-Boot image in NAND flash.
* This way the SPL loads not only the U-Boot image from NAND but
* also the environment.
*/
int env_init(void)
{
#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
int crc1_ok = 0, crc2_ok = 0;
env_t *tmp_env1;

#ifdef CONFIG_ENV_OFFSET_REDUND
env_t *tmp_env2;

tmp_env2 = (env_t *)((ulong)env_ptr + CONFIG_ENV_SIZE);
crc2_ok = crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc;
#endif
tmp_env1 = env_ptr;
crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc;

if (!crc1_ok && !crc2_ok) {
gd->env_addr = 0;
gd->env_valid = 0;

return 0;
} else if (crc1_ok && !crc2_ok) {
gd->env_valid = 1;
}
#ifdef CONFIG_ENV_OFFSET_REDUND
else if (!crc1_ok && crc2_ok) {
gd->env_valid = 2;
} else {
/* both ok - check serial */
if (tmp_env1->flags == 255 && tmp_env2->flags == 0)
gd->env_valid = 2;
else if (tmp_env2->flags == 255 && tmp_env1->flags == 0)
gd->env_valid = 1;
else if (tmp_env1->flags > tmp_env2->flags)
gd->env_valid = 1;
else if (tmp_env2->flags > tmp_env1->flags)
gd->env_valid = 2;
else /* flags are equal - almost impossible */
gd->env_valid = 1;
}

if (gd->env_valid == 2)
env_ptr = tmp_env2;
else
#endif
if (gd->env_valid == 1)
env_ptr = tmp_env1;

gd->env_addr = (ulong)env_ptr->data;

#else /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */

return 0;
}
init_baudrate

上面我们介绍了getenv_ulong,从环境变量中获取参数,所以本函数的作用是从环境变量中取出baudrate对应的值给gd,以10进制,如果没有,默认为115200

1
2
3
4
5
6
/* arch/arm/lib/board.c */
static int init_baudrate(void)
{
gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
return 0;
}
serial_init

获取当前的串口设备,并调用当前串口设备的start成员函数

1
2
3
4
5
/* drivers/serial/serial.c */
int serial_init(void)
{
return get_current()->start();
}

由于此时重定位还没做,GD_FLG_RELOC标志未设置,因此条件满足,会进入第一个分支,因此dev = default_serial_console();后面调用start。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* drivers/serial/serial.c */
static struct serial_device *get_current(void)
{
struct serial_device *dev;

if (!(gd->flags & GD_FLG_RELOC))
dev = default_serial_console();
else if (!serial_current)
dev = default_serial_console();
else
dev = serial_current;

/* We must have a console device */
if (!dev) {
#ifdef CONFIG_SPL_BUILD
puts("Cannot find console\n");
hang();
#else
panic("Cannot find console\n");
#endif
}

return dev;
}

后面在重定位完成后,且当前serial_current也不为空,会进入第三个分支,而第三个分支的dev = serial_current;serial_current是怎么确认是哪个串口的呢?

board_init_r中会有一个串口初始化 serial_initialize函数,这个函数中前面的都是初始化各种串口,比如我们板卡对应型号的为 s5p_serial_initialize,最后一个是设置要使用串口。

1
2
3
4
5
6
7
8
// drivers/serial/serial.c
void serial_initialize(void)
{
...
s5p_serial_initialize();
...
serial_assign(default_serial_console()->name);
}

进去看下,四个注册函数,对应着我们板卡上的串口0-3

1
2
3
4
5
6
7
8
// drivers/serial/serial_s5p.c
void s5p_serial_initialize(void)
{
serial_register(&s5p_serial0_device);
serial_register(&s5p_serial1_device);
serial_register(&s5p_serial2_device);
serial_register(&s5p_serial3_device);
}

而这个注册函数,则是把当前注册的串口添加到链表的最前面。哦对,所有注册的设备节点都串在一个链表中,该链表的头部,也就是最近注册过的。

所以上面初始化函数实际上就是将所有串口设备串到同一个链表中serial_devices。那么,如何知道使用哪个串口呢?

1
2
3
4
5
6
7
8
9
// drivers/serial/serial.c
static struct serial_device *serial_devices;
static struct serial_device *serial_current;
...
void serial_register(struct serial_device *dev)
{
dev->next = serial_devices;
serial_devices = dev;
}

实际上,我们在其定义的结构中也许能看出一些端倪。其结构中有一个name成员,正是通过这个来确定是哪个串口的。那怎么确定的呢?名字又是从哪里来的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// include/serial.h
struct serial_device {
/* enough bytes to match alignment of following func pointer */
char name[16];

int (*start)(void);
int (*stop)(void);
void (*setbrg)(void);
int (*getc)(void);
int (*tstc)(void);
void (*putc)(const char c);
void (*puts)(const char *s);
#if CONFIG_POST & CONFIG_SYS_POST_UART
void (*loop)(int);
#endif
struct serial_device *next;
};

别忘记,我们在serial_initialize还有另一个函数,serial_assign(default_serial_console()->name);,没错,这个就是决定你使用哪个串口的关键。首先我们看下这个函数,之后在去default_serial_console()。代码很少,看着就很喜欢,其意思是什么呢?遍历上面我们初始化时注册的那些串口设备的链表,然后拿出名字来与传进来的名字比较,如果一致,strcmp返回0,条件不成立,继续往下走,serial_current = s就把从链表中获取的要使用串口描述符地址取出来了然后赋值给serial_current,所以后面通过get_current返回的地址为我们设定的串口描述符的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
int serial_assign(const char *name)
{
struct serial_device *s;

for (s = serial_devices; s; s = s->next) {
if (strcmp(s->name, name))
continue;
serial_current = s;
return 0;
}

return -EINVAL;
}

接着我们看下 default_serial_console(),在serial_s5p中存在一个弱实现,CONFIG_OF_CONTROL我们没定义,所以选择另一个分支,config.enable=1,然后我们在smart210.h中定义了CONFIG_SERIAL0为1,所以此处return &s5p_serial0_device;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// drivers/serial/serial_s5p.c
__weak struct serial_device *default_serial_console(void)
{
#ifdef CONFIG_OF_CONTROL
int index = 0;

if ((!config.base_addr) && (fdtdec_decode_console(&index, &config))) {
debug("Cannot decode default console node\n");
return NULL;
}

switch (config.port_id) {
case 0:
return &s5p_serial0_device;
case 1:
return &s5p_serial1_device;
case 2:
return &s5p_serial2_device;
case 3:
return &s5p_serial3_device;
default:
debug("Unknown config.port_id: %d", config.port_id);
break;
}

return NULL;
#else
config.enabled = 1;
#if defined(CONFIG_SERIAL0)
return &s5p_serial0_device;
#elif defined(CONFIG_SERIAL1)
return &s5p_serial1_device;
#elif defined(CONFIG_SERIAL2)
return &s5p_serial2_device;
#elif defined(CONFIG_SERIAL3)
return &s5p_serial3_device;
#else
#error "CONFIG_SERIAL? missing."
#endif
#endif
}

在同文件中,s5p_serial0_device 是这样被定义的,也就是上面这三行,实际上定义了一堆函数,并把函数与结构体对应起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
DECLARE_S5P_SERIAL_FUNCTIONS(0);
struct serial_device s5p_serial0_device =
INIT_S5P_SERIAL_STRUCTURE(0, "s5pser0");
...
#define INIT_S5P_SERIAL_STRUCTURE(port, __name) { \
.name = __name, \
.start = s5p_serial##port##_init, \
.stop = NULL, \
.setbrg = s5p_serial##port##_setbrg, \
.getc = s5p_serial##port##_getc, \
.tstc = s5p_serial##port##_tstc, \
.putc = s5p_serial##port##_putc, \
.puts = s5p_serial##port##_puts, \
}
#define DECLARE_S5P_SERIAL_FUNCTIONS(port) \
static int s5p_serial##port##_init(void) { return serial_init_dev(port); } \
static void s5p_serial##port##_setbrg(void) { serial_setbrg_dev(port); } \
static int s5p_serial##port##_getc(void) { return serial_getc_dev(port); } \
static int s5p_serial##port##_tstc(void) { return serial_tstc_dev(port); } \
static void s5p_serial##port##_putc(const char c) { serial_putc_dev(c, port); } \
static void s5p_serial##port##_puts(const char *s) { serial_puts_dev(s, port); }

------------------------------------------------------------------------------------------
static int s5p_serial0_init(void) { return serial_init_dev(port); }
static void s5p_serial0_setbrg(void) { serial_setbrg_dev(port); }
static int s5p_serial0_getc(void) { return serial_getc_dev(port); }
static int s5p_serial0_tstc(void) { return serial_tstc_dev(port); }
static void s5p_serial0_putc(const char c) { serial_putc_dev(c, port); }
static void s5p_serial0_puts(const char *s) { serial_puts_dev(s, port); }

struct serial_device s5p_serial0_device = {
.name = "s5pser0", \
.start = s5p_serial0_init \
.stop = NULL, \
.setbrg = s5p_serial0_setbrg, \
.getc = s5p_serial0_getc, \
.tstc = s5p_serial0_tstc, \
.putc = s5p_serial0_putc, \
.puts = s5p_serial0_puts, \
}
console_init_f

gdhave_console置为1,然后打印Pre-Console Buffer中的数据。

1
2
3
4
5
6
7
8
9
10
11
/* common/console.c */
int console_init_f(void)
{
gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT;
#endif
print_pre_console_buffer();
return 0;
}
display_banner

上面我们已经配置好串口,现在可以输出信息到终端了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* arch/arm/lib/board.c */
static int display_banner(void)
{
printf("\n\n%s\n\n", version_string);
debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",(ulong)&_start,(ulong)&__bss_start, (ulong)&__bss_end);
#ifdef CONFIG_MODEM_SUPPORT
debug("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
debug("IRQ Stack: %08lx\n", IRQ_STACK_START);
debug("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
return (0);
}

此处就是打印,如果定义了宏 DEBUG,将输出更多信息。

1
2
3

U-Boot 2014.04-g8819fbf-dirty (Apr 12 2022 - 10:19:11) for SMART210

打印CPU信息,所以该函数位置为 arch/arm/cpu/armv7/s5p-common/cpu_info.c

输出CPU名字、ID、频率信息。如

1
CPU:	S5PC110@1000MHz
1
2
3
4
5
6
7
8
9
10
11
/* arch/arm/cpu/armv7/s5p-common/cpu_info.c */
int print_cpuinfo(void)
{
char buf[32];

printf("CPU:\t%s%X@%sMHz\n",
s5p_get_cpu_name(), s5p_cpu_id,
strmhz(buf, get_arm_clk()));

return 0;
}
checkboard

配置了宏CONFIG_DISPLAY_BOARDINFO,位于board/samsung/smart210/smart210.c

1
Board:	SMART210
1
2
3
4
5
/* board/samsung/smart210/smart210.c */
int checkboard(void) {
printf("Board:\tSMART210\n");
return 0;
}
init_func_i2c

似乎没有配置,暂不分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* board/samsung/smart210/smart210.c */
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
static int init_func_i2c(void)
{
puts("I2C: ");
#ifdef CONFIG_SYS_I2C
i2c_init_all();
#else
i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endif
puts("ready\n");
return (0);
}
#endif
dram_init

更新gd中的内存大小,其值为 get_ram_size((long *)PHYS_SDRAM_1, PHYS_SDRAM_1_SIZE)get_ram_size又是什么呢?是用来检测内给定范围内的内存是否有效的一个小函数,在当前函数中检测范围为[PHYS_SDRAM_1, PHYS_SDRAM_1+PHYS_SDRAM_1_SIZE]。如果没有问题,返回PHYS_SDRAM_1_SIZETODO:剩下的下次在分析

1
2
3
4
5
6
// board/samsung/smart210.c
int dram_init(void)
{
gd->ram_size = get_ram_size((long *)PHYS_SDRAM_1, PHYS_SDRAM_1_SIZE);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// common/memsize.c
long get_ram_size(long *base, long maxsize)
{
volatile long *addr;
long save[32];
long cnt;
long val;
long size;
int i = 0;

for (cnt = (maxsize / sizeof (long)) >> 1; cnt > 0; cnt >>= 1) {
addr = base + cnt; /* pointer arith! */
sync ();
save[i++] = *addr;
sync ();
*addr = ~cnt;
}

addr = base;
sync ();
save[i] = *addr;
sync ();
*addr = 0;

sync ();
if ((val = *addr) != 0) {
/* Restore the original data before leaving the function.
*/
sync ();
*addr = save[i];
for (cnt = 1; cnt < maxsize / sizeof(long); cnt <<= 1) {
addr = base + cnt;
sync ();
*addr = save[--i];
}
return (0);
}

for (cnt = 1; cnt < maxsize / sizeof (long); cnt <<= 1) {
addr = base + cnt; /* pointer arith! */
val = *addr;
*addr = save[--i];
if (val != ~cnt) {
size = cnt * sizeof (long);
/* Restore the original data before leaving the function.
*/
for (cnt <<= 1; cnt < maxsize / sizeof (long); cnt <<= 1) {
addr = base + cnt;
*addr = save[--i];
}
return (size);
}
}

return (maxsize);
}
其它

上文我们将那一个初始化列表讲完了。下面接着对board_init_f分析。根据打印日志,删除没有用的宏开关,方便我们分析代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
U-Boot 2014.04-g8819fbf-dirty (Apr 12 2022 - 10:16:10) for SMART210

U-Boot code: 20000000 -> 2003A2D0 BSS: -> 200706D0
CPU: S5PC110@1000MHz
Board: SMART210
monitor len: 000706D0
ramsize: 20000000
TLB table from 3fff0000 to 3fff4000
Top of RAM usable for U-Boot at: 3fff0000
Reserving 449k for U-Boot at: 3ff7f000
Reserving 1280k for malloc() at: 3fe3f000
Reserving 32 Bytes for Board Info at: 3fe3efe0
Reserving 160 Bytes for Global Data at: 3fe3ef40
New Stack Pointer is: 3fe3ef30
RAM Configuration:
Bank #0: 20000000 512 MiB
relocation Offset is: 1ff7f000
WARNING: Caches not enabled

接着打印gd->mon_lengd->ram_size,然后addr=CONFIG_SYS_SDRAM_BASE+get_effective_memsize(); ,其中CONFIG_SYS_SDRAM_BASE为SDRAM起始地址,也就是0x20000000get_effective_memsizecommon/memsize.c 有一个弱实现,其余与板级相关的地方没有发现该函数。

所以确认调用为common/memsize.cget_effective_memsize(),其实也就是判断定义内存大小是否超过了最大界限,超过则返回最大值,否则gd->ram_size。而gd->ram_sizedram_init中得到的大小为PHYS_SDRAM_1_SIZE,所以addr此时为CONFIG_SYS_SDRAM_BASE+PHYS_SDRAM_1_SIZE

然后更新gd->arch.tlb_size 值为PGTABLE_SIZE4096*4,也就是4k),然后下面一个64kb对齐(换句话说PGTABLE_SIZE最大可设置为64kb),接着更新gd->arch.tlb_addr。然后4kb对齐,addr -= gd->mon_len,继续4kb对齐。

最终内存分布是这样的。

总结一下,这段代码首先用来更新gd结构体中的内容,然后将gd结构体在拷贝到内存中对应位置。

此处需要注意下,当前代码还没有重定位,所以_start还是在起始位置,也就是0x20000000处,

gd->relocaddr = addrgd->reloc_off = addr - (ulong)&_startgd->relocaddr存储的为重定位后的地址,所以gd->reloc_off存储的是重定位前后_start的绝对值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/* arch/arm/lib/board.c */
void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif
void *new_fdt = NULL;
size_t fdt_size = 0;

...
debug("monitor len: %08lX\n", gd->mon_len);
debug("ramsize: %08lX\n", gd->ram_size);
addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize(); /* CONFIG_SYS_SDRAM_BASE+PHYS_SDRAM_1_SIZE */

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
gd->arch.tlb_size = PGTABLE_SIZE;
addr -= gd->arch.tlb_size;

/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1);

gd->arch.tlb_addr = addr;
debug("TLB table from %08lx to %08lx\n", addr, addr + gd->arch.tlb_size);
#endif

/* round down to next 4 kB limit */
addr &= ~(4096 - 1);
debug("Top of RAM usable for U-Boot at: %08lx\n", addr);

/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len;
addr &= ~(4096 - 1);

debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);

#ifndef CONFIG_SPL_BUILD
/*
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN;
debug("Reserving %dk for malloc() at: %08lx\n",
TOTAL_MALLOC_LEN >> 10, addr_sp);
/*
* (permanently) allocate a Board Info struct
* and a permanent copy of the "global" data
*/
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
debug("Reserving %zu Bytes for Board Info at: %08lx\n",
sizeof (bd_t), addr_sp);

#ifdef CONFIG_MACH_TYPE
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif

addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
debug("Reserving %zu Bytes for Global Data at: %08lx\n",
sizeof (gd_t), addr_sp);

#ifndef CONFIG_ARM64
/* setup stackpointer for exeptions */
gd->irq_sp = addr_sp;
#ifdef CONFIG_USE_IRQ
addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
/* leave 3 words for abort-stack */
addr_sp -= 12;

/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
#else /* CONFIG_ARM64 */
/* 16-byte alignment for ABI compliance */
addr_sp &= ~0x0f;
#endif /* CONFIG_ARM64 */
#else
addr_sp += 128; /* leave 32 words for abort-stack */
gd->irq_sp = addr_sp;
#endif

debug("New Stack Pointer is: %08lx\n", addr_sp);

gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize(); // 更新gd->bd->bi_dram[0] 的start(PHYS_SDRAM_1)和size(PHYS_SDRAM_1_SIZE)
display_dram_config(); /* and display it */

gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - (ulong)&_start;
debug("relocation Offset is: %08lx\n", gd->reloc_off);
if (new_fdt) {
memcpy(new_fdt, gd->fdt_blob, fdt_size);
gd->fdt_blob = new_fdt;
}
memcpy(id, (void *)gd, sizeof(gd_t));
}

_main 继续,接上文

在非SPL下,接着就是更新spgd,然后重定位代码。

1
2
3
4
5
6
7
8
9
; arch/arm/lib/crt0.S
...
#if ! defined(CONFIG_SPL_BUILD)
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
...
#endif

GD_START_ADDR_SPinclude/generated/generic-asm-offsets.h中定义,其值为 start_addr_spglobal_data 结构中的相对偏移,同样是编译后才生成的。

由上面的代码可知,刚刚为gd也就是global_data分配了大小为GD_SIZE的空间,现在r9sp内的数据为地址CONFIG_SYS_INIT_SP_ADDR - GD_SIZE (需要8字节对齐)中的数据。换句话说此时r9中为gd的地址,那么再加上 GD_START_ADDR_SP,也就是gd+GD_START_ADDR_SP,将gd+GD_START_ADDR_SP地址中的值(gd->start_addr_sp)传递给sp,接着8字节对齐。

然后更新r9的值为[r9 + GD_BD],也就是r9 = gd->bd

然后又分配一个大小为GD_SIZE的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; arch/arm/lib/crt0.S
...
#if ! defined(CONFIG_SPL_BUILD)
...
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code

here:
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
...

#endif

here处的相对地址复制给lr,将gd结构中的reloc_off值(其相对于gd偏移为GD_RELOC_OFF)给r0

lrr0相加给lr,即lr = lr + r0,将gd结构中的relocaddr值(其相对于gd偏移为GD_RELOCADDR)给r0

此时lr = here + gd->reloc_offr0 = gd->relocaddr,然后调用relocate_code

relocate_code

其定义位于 include/common.h中,由于定义了宏CONFIG_ARM,所以其声明为如下代码

1
2
// include/common.h
void relocate_code(ulong);

其实现位于 arch/arm/lib/relocate.S

是一个比较麻烦的地方,并且需要函数传参。

看代码,进入到relocate_code后,此时r0寄存器中数据为gd->relocaddr,也就是为重定位后的u-boot程序地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
; arch/arm/lib/relocate.S
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */

copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop

/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext

/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop

relocate_done:

#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif

/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif

ENDPROC(relocate_code)

在进入第一句代码之前,我们先了解一下__image_copy_start__image_copy_end,在arch/arc/lib/sections.c定义,并通过__attribute__放到了对应段中。在编译之后,这些段有什么用呢?

1
2
3
4
5
6
7
8
9
10
// arch/arc/lib/sections.c
char __bss_start[0] __attribute__((section(".__bss_start")));
char __bss_end[0] __attribute__((section(".__bss_end")));
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
char __rel_dyn_start[0] __attribute__((section(".__rel_dyn_start")));
char __rel_dyn_end[0] __attribute__((section(".__rel_dyn_end")));
char __text_start[0] __attribute__((section(".__text_start")));
char __text_end[0] __attribute__((section(".__text_end")));
char __init_end[0] __attribute__((section(".__init_end")));

查看u-boot.lds,在 arch/arm/cpu/u-boot.lds,但这个并不是最终的,最终的链接脚本是在此基础上生成的。需要编译u-boot后会在根目录下才会生成u-boot.lds。

GNU编译器生成的目标文件缺省为elf格式,elf文件由若干段(section)组成,如不特殊指明,由C源程序生成的目标代码中包含如下段:

  • .text(正文段)包含程序的指令代码;
  • .data(数据段)包含固定的数据,如常量、字符串;
  • .bss(未初始化数据段)包含未初始化的变量、数组等。

C++源程序生成的目标代码中还包括

  • .fini(析构函数代码)
  • .init(构造函数代码)等.
    链接器的任务就是将多个目标文件的.text、.data和.bss等段链接在一起,而链接脚本文件是告诉链接器从什么地址开始放置这些段.简而言之,由于一个工程中有多个.c文件,当它们生成.o文件后如何安排它们在可执行文件中的顺序,这就是链接脚本的作用.

这里以u-boot的lds为例说明uboot的链接过程,首先看一下GNU官方网站上对.lds文件形式的完整描述:

1
2
3
4
5
6
>SECTIONS {
>...
>secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
>{ contents } >region :phdr =fill
>...
>}

其中,secname和contents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段,以下是对这个描述中的一些关键字的解释。

  • secname:段名
  • contents:决定哪些内容放在本段,可以是整个目标文件(如start.o),也可以是目标- 文件中的某段(代码段、数据段等)(如start.o (.text .rodata))
  • start:是段的重定位地址,本段链接(运行)的地址,如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上。start可以用任意一种描述地址的符号来描述。
    AT(ldadr):定义本段存储(加载)的地址,如果不使用这个选项,则加载地址等于运行地址,通过这个选项可以控制各段分别保存于输出文件中不同的位置。

上面我们了解到lds文件的格式之后,就可以直接阅读下面的lds文件, . = 0x00000000;定义当前位置为0x00000000; 紧接着.text:{...},所以.text的相对地址也是0,而在.text中*(.__image_copy_start)又在开始的位置,也就是说把我们前面定义的.__image_copy_start段在链接的时候挪到这个位置当前位置(0x0),紧接着arch/arm/cpu/armv7/start.o (.text*)arch/arm/cpu/armv7/start.o中的.text*段全部挪到当前位置(当前位置为 0x0 + .__image_copy_start大小),后面也是如此。

.__image_copy_end挪到.image_copy_end段。所以从.__image_copy_start.__image_copy_end包括了代码段、只读数据段、读写数据段、.u_boot_list(uboot的命令)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
arch/arm/cpu/armv7/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
}
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
.end :
{
*(.__end)
}
_image_binary_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}

接着我们回到代码ldr r1, =__image_copy_start则是将__image_copy_start的地址取出给到r1,r4=r0-r1,r0为重定位后的代码位置,r1为当前__image_copy_start的位置,那么r4为代码重定位前后的差,也就是重定位的偏移beq实际上是一个跳转指令b和一个条件变量eq组合起来的,其意思也就是如果相等则跳转到relocate_done。什么意思呢?这个地方怎么可能会相等呢?不对,你在想想,当前代码的起始位置是__image_copy_start,重定位后代码的位置是r0即gd->relocaddr,当重定位后,当前代码的起始位置是哪呢?不就是gd->relocaddr,所以此时r0-r10,也即满足相等的条件,跳转到relocate_done,不需要重定位(此处有疑问?该代码上电后会执行两次吗?如果不会执行两次,那么相等这个条件也就不会满足,也就不会有跳转这个分支了。。。。)。重定位完成后执行xxxxxxx操作后跳出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; arch/arm/lib/relocate.S
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
...

relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
...
ENDPROC(relocate_code)

建议先去寄存器中查看LDM、STM命令以及模式的用法。此时r0=gd->relocaddrr1=&__image_copy_startr2=&__image_copy_end

此处从r1地址开始,将数据拷贝r10、r11,每次拷贝后r1地址加4,结束后r1的值为刚开始的值+8。

然后此处从r0地址开始,将r10、r11寄存器中的值拷贝到r0地址处,每次拷贝结束后r0地址加4,所以,结束后r0的值为刚开始的值+8。

比较r1和r2的值,当r1<r2时,跳转到 copy_loop,继续拷贝。直到r1>=r2时,拷贝结束。此时__image_copy_start__image_copy_end地址的数据已经拷贝到gd->relocaddr处了。

1
2
3
4
5
6
7
8
9
10
; arch/arm/lib/relocate.S
ENTRY(relocate_code)
...
copy_loop:
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
...
ENDPROC(relocate_code)

接着 r2=__rel_dyn_startr3=__rel_dyn_end,从r2处加载数据到r0和r1中,取出r1中数据的低8位,与23比较,如果不相等,跳转到fixnext,比较r2、r3,如果r2<r3,说明没修正结束,接着修正;如果相等,r0=r0+r4,停,r4是什么?大家还有印象不,前面我们在刚进入relocate_code,r4为重定位前后的代码的相对地址偏移。所以代码的意思就是r0=r0+r4,然后以r0中存的值为地址取出 已拷贝到新地址的.text段中的值;将该值加上新旧.text段的偏移,在写回到 新的.text段中。这样就完成了重定位。此处需明白重定位,到底重定位的是什么?

代码解释完了,但是这又是什么意思啊?完全不理解,从.rel.dyn取出的是什么值啊,接下来我们了解了解.rel.dyn段,之后在回来看这段代码,应该就一目了然了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
; arch/arm/lib/relocate.S
ENTRY(relocate_code)
...
/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext

/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop

relocate_done:
...
ENDPROC(relocate_code)

.rel.dyn 存放.text段中需要重定位地址的集合

接下来我们来说明一个很重要的概念。

 u-boot在启动过程中,会把自己拷贝到RAM的顶端去执行。这一拷贝带来的问题是执行地址的混乱。代码的执行地址通常都是在编译时有链接地址指定的,如何保证拷贝前后都可以执行呢?
  一个办法是使用拷贝到RAM后的地址作为编译时的链接地址,拷贝前所有函数与全局变量的调用都增加偏移量。(如VxWorks的bootloader)尽量减少拷贝前需要执行的代码量。
  另一个办法是把image编译成与地址无关的程序,也就是PIC - Position independent code。编译器无法保证代码的独立性,它需要与加载器配合起来。U-boot自己加载自己,所以它自己就是加载器。PIC依赖于下面两种技术:
1) 使用相对地址
2) 加载器可以自动更新涉及到绝对地址的指令
  对于PowerPC架构,u-boot只是在编译时使用了-fpic,这种方式会生成一个.got段来存储绝对地址符号。对于ARM架构,则是在编译时使用-mword-relocations,生成与位置无关代码,链接时使用-pie生成.rel.dyn段,该段中的每个条目被称为一个LABEL,用来存储绝对地址符号的地址。

为了理解地址表的概念我们分析一段代码。

借助两个工具 readelfobjdump

通过readelf的-r选项查看relocate段readelf -r u-boot | less打开,看到类型是可重定位段,其中有了offset、info、type以及后面没有为空的项。m每个需要修改地址的信息占用8个字节,第一个为可重定位的地址,第二个为0x17,0x17对应的是Arm32位。那么让我们看一下第一条可重定位地址处(0x20000020)放的是什么?

通过命令 objdump -S u-boot | less ,看到此处存的正是 start.S 中_undefined_instruction: .word undefined_instruction入口地址。

因为这个入口地址如果直接.text段拷贝过去,将来执行跳转的还是旧的uboot里面的undefined_instruction,而不是我们新uboot里面的undefined_instruction,所以这个要修改。

即要修改所有位置有关码的地址。

如何修改?

很简单,旧的地址的值是什么我们取出来加上 新地址和旧地址的偏移,然后存入新的地址就可以了

因为编译器已经帮我们剥离出来要修改的地址和它所属的类型,存放进了rel.dyn段,所以我们只要修改新地址的就可以。

未定义__ARM_ARCH_4__,所以执行bx lr,而此时lr中的值为here + gd->reloc_off,怎么解释?此时重定位已经完成,重定位后的地址差值为gd->reloc_offherehere处的相对地址,所以二者相加后为重定位后的here处的地址,然后跳回去。

1
2
3
4
5
6
7
8
; arch/arm/lib/relocate.S
/* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif

_main 继续,接上文:here

回到here处开始跳转到 c_runtime_cpu_setupc_runtime_cpu_setup执行结束后返回。

c_runtime_cpu_setup

c_runtime_cpu_setup位于 arch/arm/cpu/armv7/start.S

配置协处理器CP15

加载程序起始地址到r0,设置CP15 VBAR register

TODO:待补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; arch/arm/cpu/armv7/start.S
ENTRY(c_runtime_cpu_setup)
/*
* If I-cache is enabled invalidate it
*/
#ifndef CONFIG_SYS_ICACHE_OFF
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
#endif
/*
* Move vector table
*/
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR

bx lr

ENDPROC(c_runtime_cpu_setup)

继续执行r0 = __bss_startr1 = __bss_endr2 = 0

1
2
3
4
5
6
7
8
9
10
11
; arch/arm/lib/crt0.S
here:

/* Set up final (full) environment */

bl c_runtime_cpu_setup /* we still call old routine here */

ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */

mov r2, #0x00000000 /* prepare zero to clear BSS */

[__bss_start,__bss_end]范围内的内存清零。

比较r0r1的值,首先r0=r0-r1,当满足条件r0<r1时,将r2中的值0复制给以将r0的值作为地址的内存中,当满足条件r0<r1时,r0=r0+4,当满足条件r0<r1时,跳转到clbss_l处。接着循环,直到二者相等时,不满足条件,继续执行bl coloured_LED_init

1
2
3
4
5
6
; arch/arm/lib/crt0.S

clbss_l:cmp r0, r1 /* while not at end of BSS */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l

跳转到coloured_LED_init,跳转到red_led_on,没啥内容。主要关注一下语法即可。

1
2
3
4
5
6
7
8
9
10
11
; arch/arm/lib/crt0.S

bl coloured_LED_init
bl red_led_on
------------------------------
// common/board_f.c
inline void __coloured_LED_init(void) {}
void coloured_LED_init(void)
__attribute__((weak, alias("__coloured_LED_init")));
inline void __red_led_on(void) {}
void red_led_on(void) __attribute__((weak, alias("__red_led_on")));

r0 = r9 = gdr1 = [r9(gd) + GD_RELOCADDR],也就是说r0此时为gd,r1为相对于gd偏移GD_RELOCADDR的地址中的值。

pc值更改为board_init_r,也就是跳转到board_init_r执行,此处不需要返回。因为要跳转到c代码,而c代码入口有两个参数,有APCS可知前四个整形实参被存入到a1-a4中,也即r0-r3中。此时r0为gd,r1为gd->relocaddr

1
2
3
4
5
6
7
8
; arch/arm/lib/crt0.S
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
ldr pc, =board_init_r /* this is auto-relocated! */

/* we should not return here. */

到此 _main就结束了。

board_init_r

board_init_r函数位于common/board_r.c中,位置分析错误,可以看下common/Makefile,通过命令查找 grep -nr CONFIG_SYS_GENERIC_BOARD有关其定义,在我们的smart210.h中并没有定义这个宏,所以调用的board_init_r不在此处的board_r.c中。

1
2
3
# boards
obj-$(CONFIG_SYS_GENERIC_BOARD) += board_f.o
obj-$(CONFIG_SYS_GENERIC_BOARD) += board_r.o

所以 board_init_r函数其实位于 arch/arm/lib/board.c,跟board_init_f函数位于同一个文件中。

代码如下,我滴个老天爷这也太多了吧。一点点来,先把没用到的宏开关去掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
ulong flash_size;
#endif

gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");

monitor_flash_len = (ulong)&__rel_dyn_end - (ulong)_start;

/* Enable caches */
enable_caches();

debug("monitor flash len: %08lX\n", monitor_flash_len);
board_init(); /* Setup chipselects */

serial_initialize();

debug("Now running in RAM - U-Boot at: %08lx\n", dest_addr);

/* The Malloc area is immediately below the monitor copy in DRAM */
malloc_start = dest_addr - TOTAL_MALLOC_LEN;
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);

#ifdef CONFIG_ARCH_EARLY_INIT_R
arch_early_init_r();
#endif
power_init_board();

#if !defined(CONFIG_SYS_NO_FLASH)
puts("Flash: ");

flash_size = flash_init();
if (flash_size > 0) {
# ifdef CONFIG_SYS_FLASH_CHECKSUM
print_size(flash_size, "");
/*
* Compute and print flash CRC if flashchecksum is set to 'y'
*
* NOTE: Maybe we should add some WATCHDOG_RESET()? XXX
*/
if (getenv_yesno("flashchecksum") == 1) {
printf(" CRC: %08X", crc32(0,
(const unsigned char *) CONFIG_SYS_FLASH_BASE,
flash_size));
}
putc('\n');
# else /* !CONFIG_SYS_FLASH_CHECKSUM */
print_size(flash_size, "\n");
# endif /* CONFIG_SYS_FLASH_CHECKSUM */
} else {
puts(failed);
hang();
}
#endif

#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init(); /* go init the NAND */
#endif


#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif

/* initialize environment */
if (should_load_env())
env_relocate();
else
set_default_env(NULL);

stdio_init(); /* get the devices list going. */

jumptable_init();

#if defined(CONFIG_API)
/* Initialize API */
api_init();
#endif

console_init_r(); /* fully init console as a device */

#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
checkboard();
#endif

#if defined(CONFIG_ARCH_MISC_INIT)
/* miscellaneous arch dependent initialisations */
arch_misc_init();
#endif
#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r();
#endif

/* set up exceptions */
interrupt_init();
/* enable exceptions */
enable_interrupts();

/* Initialize from environment */
load_addr = getenv_ulong("loadaddr", 16, load_addr);

#ifdef CONFIG_BOARD_LATE_INIT
board_late_init();
#endif

#ifdef CONFIG_BITBANGMII
bb_miiphy_init();
#endif
#if defined(CONFIG_CMD_NET)
puts("Net: ");
eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
debug("Reset Ethernet PHY\n");
reset_phy();
#endif
#endif

#ifdef CONFIG_POST
post_run(NULL, POST_RAM | post_bootmode_get(0));
#endif

#if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
/*
* Export available size of memory for Linux,
* taking into account the protected RAM at top of memory
*/
{
ulong pram = 0;
uchar memsz[32];

#ifdef CONFIG_PRAM
pram = getenv_ulong("pram", 10, CONFIG_PRAM);
#endif
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
/* Also take the logbuffer into account (pram is in kB) */
pram += (LOGBUFF_LEN + LOGBUFF_OVERHEAD) / 1024;
#endif
#endif
sprintf((char *)memsz, "%ldk", (gd->ram_size / 1024) - pram);
setenv("mem", (char *)memsz);
}
#endif

/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}

/* NOTREACHED - no way out of command loop except booting */
}

分析这个函数,这也太麻烦了。。。。。

先看一部分,首先设置gd中的 flags 告诉其它,我已经重定位完了。然后标记当前段为board_init_r,然后计算flash监控区的长度 从_start__rel_syn_end包括了代码段、只读数据段、读写数据段、.u_boot_list(uboot的命令)和重定位地址表段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
ulong malloc_start;
#if !defined(CONFIG_SYS_NO_FLASH)
ulong flash_size;
#endif

gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");

monitor_flash_len = (ulong)&__rel_dyn_end - (ulong)_start;
...
/* NOTREACHED - no way out of command loop except booting */
}
enable_caches

看下 enable_caches 函数的实现。

1
2
3
4
5
6
7
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
enable_caches();
...
}

arch/arm/lib/cache.c为默认实现,其也就是打印一下cache未开启。在此处,需注意下,__attribute__的高级用法,声明一个弱符号函数,如果外部没有实现enable_caches,就去调用默认的。

1
2
3
4
5
6
7
8
// arch/arm/lib/cache.c
void __enable_caches(void)
{
puts("WARNING: Caches not enabled\n");
}
void enable_caches(void)
__attribute__((weak, alias("__enable_caches")));

board_init

接着我们看下board_init,网卡的初始化

1
2
3
4
5
6
7
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
board_init();
...
}

board_init是在对应的板卡文件中实现的,实现dm9000网卡的初始化,配置板卡的类型代号以及板卡中boot的参数区,起始地址为PHYS_SDRAM_1 + 0x100,也即0x20000100

1
2
3
4
5
6
7
8
9
10
11
// board/samsung/smart210/smart210.c
int board_init(void)
{
//smc9115_pre_init();
dm9000_pre_init();

gd->bd->bi_arch_number = MACH_TYPE_SMDKV210;
gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100;

return 0;
}

接着看一下网卡的预初始化。呃,定义两个参数,对两个参数赋值,然后在将两个参数写入到SROM中。这个值怎么来的呢?

smc_bw_conf 和 smc_bc_conf的配置可查看S5PV210_UM_REV1.1.pdf2.4.1.1 SROM Bus Width2.4.1.2 SROM Bank Control Register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// board/samsung/smart210/smart210.c
static void dm9000_pre_init(void)
{
u32 smc_bw_conf, smc_bc_conf;

/* Ethernet needs bus width of 16 bits */
smc_bw_conf = SMC_DATA16_WIDTH(CONFIG_ENV_SROM_BANK)
| SMC_BYTE_ADDR_MODE(CONFIG_ENV_SROM_BANK);
smc_bc_conf = SMC_BC_TACS(0) | SMC_BC_TCOS(1) | SMC_BC_TACC(2)
| SMC_BC_TCOH(1) | SMC_BC_TAH(0) | SMC_BC_TACP(0) | SMC_BC_PMC(0);

/* Select and configure the SROMC bank */
s5p_config_sromc(CONFIG_ENV_SROM_BANK, smc_bw_conf, smc_bc_conf);
}

这段展开也太多了,先留着,后续仔细分析TODO

首先我们了解一下板卡硬件是怎么连的,由于是百兆口,只有四根线

1
2
3
网线 			   <---> RJ45(HR911105A) 		 	<---> phy(DM9000A)  <---> 芯片(s5pv210)
4根线 自带变压器转换信号也还是4根线
两组差分信号 两组差分信号

serial_initialize

上面讲了,看 serial_init

mem_malloc_init

接着是计算malloc内存的起始地址和初始化

1
2
3
4
5
6
7
8
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
malloc_start = dest_addr - TOTAL_MALLOC_LEN;
mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);
...
}

计算malloc_start的地址,由c_runtime_cpu_setup可知gd->relocaddrboard_init_r函数的第二个参数,也即dest_addr=gd->relocaddr

TOTAL_MALLOC_LEN被定义为(CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)=(CONFIG_ENV_SIZE + (1<<20) + CONFIG_ENV_SIZE)= 128<<10 * 2 + 1<<20=256k+1024k=1280k,所以通过dest_addr-TOTAL_MALLOC_LEN获得可分配的最低位置。

1
#define	TOTAL_MALLOC_LEN	(CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)

接着便是对这段范围内的内存初始化,设置malloc的开始、结束及brk的位置,并将内存内的数据清零。由于未定义CONFIG_NEEDS_MANUAL_RELOC宏,所以此处malloc_bin_reloc为空。

1
2
3
4
5
6
7
8
9
10
11
// common/dlmalloc.c
void mem_malloc_init(ulong start, ulong size)
{
mem_malloc_start = start;
mem_malloc_end = start + size;
mem_malloc_brk = start;

memset((void *)mem_malloc_start, 0, size);

malloc_bin_reloc();
}
power_init_board

电源初始化,由于未定义CONFIG_POWER宏,所以此处实现为空

1
2
3
4
__weak int power_init_board(void)
{
return 0;
}
nand_init

此处开始初始化nand,跳转到nand_init看一下。

1
2
3
4
5
6
7
8
9
10
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init(); /* go init the NAND */
#endif
...
}

由于我们没有定义CONFIG_SYS_NAND_SELF_INIT,CONFIG_SYS_MAX_NAND_DEVICE大小为1,所以会进入下面的循环初始化中,CONFIG_SYS_NAND_SELECT_DEVICE没有定义,所以此函数中进有nand_init_chip(0);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// drivers/mtd/nand/nand.c
void nand_init(void)
{
#ifdef CONFIG_SYS_NAND_SELF_INIT
board_nand_init();
#else
int i;
for (i = 0; i < CONFIG_SYS_MAX_NAND_DEVICE; i++)
nand_init_chip(i);
#endif
printf("%lu MiB\n", total_nand_size / 1024);

#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
/*
* Select the chip in the board/cpu specific driver
*/
board_nand_select_device(nand_info[nand_curr_device].priv, nand_curr_device);
#endif
}

接着进入nand_init_chip,了解mtd_infonand_info结构体,mtd指向全局变量nand_info,这个变量就是nand设备的信息;再看初始化:mtd->private=nand; mtd的私有数据就是一个指向struct nand_chip类型的结构体

从编程的角度来说,一个硬件驱动应该有两个面,一个面向上层,提供接口;一个面向底层,提供硬件操作。

从广义上来看:

struct mtd_info 就是面向上层,提供数据接口

struct nand_chip 面向nand设备,提供硬件接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// drivers/mtd/nand/nand.c
#ifndef CONFIG_SYS_NAND_SELF_INIT
static void nand_init_chip(int i)
{
struct mtd_info *mtd = &nand_info[i];
struct nand_chip *nand = &nand_chip[i];
ulong base_addr = base_address[i];
int maxchips = CONFIG_SYS_NAND_MAX_CHIPS;

if (maxchips < 1)
maxchips = 1;

mtd->priv = nand;
nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr;

if (board_nand_init(nand))
return;

if (nand_scan(mtd, maxchips))
return;

nand_register(i);
}
#endif

基于这个思路,看一下struct mtd_info,记住几个

mtd_info描述原始设备层的一个分区的结构, 描述一个设备或一个多分区设备中的一个分区

  • type MTD设备类型,有MTD_RAM、MTD_ROM、MTD_NORFLASH、MTD_NAND_FLASH等
  • flags 读写及权限标志位,有MTD_WRITEABLE、MTD_BIT_WRITEABLE、MTD_NO_ERASE、MTD_UP_LOCK
  • size MTD设备的大小
  • erase 主要擦除块的大小,NandFlash 就是块的大小
  • writesize 最小可写字节数,NandFlash 对应着”页”
  • oobsize 一个blokc中可用的oob的字节数
  • oobavail 一个block中可用oob字节数
  • 接着下面就是一些函数指针,用于上层调用
  • priv 私有数据指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
struct mtd_info {
u_char type; /* 设备类型 */
u_int32_t flags;
uint64_t size; /* Total size of the MTD */

/* "Major" erase size for the device. Naïve users may take this
* to be the only erase size available, or may use the more detailed
* information below if they desire
*/
u_int32_t erasesize;
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
* it is of ECC block size, etc. It is illegal to have writesize = 0.
* Any driver registering a struct mtd_info must ensure a writesize of
* 1 or larger.
*/
u_int32_t writesize;

u_int32_t oobsize; /* Amount of OOB data per block (e.g. 16) */
u_int32_t oobavail; /* Available OOB bytes per block */

/*
* read ops return -EUCLEAN if max number of bitflips corrected on any
* one region comprising an ecc step equals or exceeds this value.
* Settable by driver, else defaults to ecc_strength. User can override
* in sysfs. N.B. The meaning of the -EUCLEAN return code has changed;
* see Documentation/ABI/testing/sysfs-class-mtd for more detail.
*/
unsigned int bitflip_threshold;

/* Kernel-only stuff starts here. */
const char *name;
int index;

/* ECC layout structure pointer - read only! */
struct nand_ecclayout *ecclayout;

/* max number of correctible bit errors per ecc step */
unsigned int ecc_strength;

/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;
struct mtd_erase_region_info *eraseregions;

/*
* Do not call via these pointers, use corresponding mtd_*()
* wrappers instead.
*/
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, phys_addr_t *phys);
void (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);

/* In blackbox flight recorder like scenarios we want to make successful
writes in interrupt context. panic_write() is only intended to be
called when its known the kernel is about to panic and we need the
write to succeed. Since the kernel is not going to be running for much
longer, this function can break locks and delay to ensure the write
succeeds (but not sleep). */

int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);

int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*_get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
size_t len);
int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf,
size_t len);
int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, u_char *buf);
int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len);
void (*_sync) (struct mtd_info *mtd);
int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
/*
* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
*/
int (*_get_device) (struct mtd_info *mtd);
void (*_put_device) (struct mtd_info *mtd);

/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;

void *priv;

struct module *owner;
int usecount;
};

结构知道了,其值在哪定义的呢,在同文件下,定义了一个结构体数组,其大小为CONFIG_SYS_MAX_NAND_DEVICE

1
2
3
4
nand_info_t nand_info[CONFIG_SYS_MAX_NAND_DEVICE];
----------------------------------------------------------------------------------------
// include/configs/smart210.h
#define CONFIG_SYS_MAX_NAND_DEVICE 1

接着看mtd->priv = nand也就是将nand的结构体指针给了mtd的私有变量,说明mtd的下层操作的设备为nand。接着往下走,board_nand_init(nand)初始化nand,配置nand一些参数及操作(代码可见drivers/mtd/nand/s5pv210_nand.c中的board_nand_init)。

nand_scan_ident扫描设备,获取当前mtd中的私有变量priv中的数据结构(刚刚我们在nand_init_chip函数中赋值priv为nand,实际是全局变量,并经board_nand_init初始化),获取buswidth并设置、获取chip数量和大小。

nand_scan_tail用默认值填充所有未初始化的函数指针

1
2
3
4
5
6
7
8
9
10
11
nand_scan(mtd, maxchips)
------------------------------------------
// drivers/mtd/nand/nand_base.c
int nand_scan(struct mtd_info *mtd, int maxchips)
{
int ret;
ret = nand_scan_ident(mtd, maxchips, NULL); // 获取当前mtd下
if (!ret)
ret = nand_scan_tail(mtd);
return ret;
}

还有一个注册函数,其实就是配置设备名称,形如nand0,然后计算总的total_nand_size大小,并将上面的&nand_info[devnum]添加到mtd_table中,over。这个地方就结束了,其实想要更深层次的了解最好去看下参考22

总结一下,其实就是定义了2个结构体数组,分别为nand_infonand_chip,分别初始化,然后赋值,&nand_info[0]->prvi=&nand_chip[0],并在nand_chip中实现对应的操作函数。之后呢,在将&nand_info[0]放到mtd_table中,后续在使用时应该就是上层直接使用mtd_table来完成相应的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int nand_register(int devnum)
{
struct mtd_info *mtd;

if (devnum >= CONFIG_SYS_MAX_NAND_DEVICE)
return -EINVAL;

mtd = &nand_info[devnum];

sprintf(dev_name[devnum], "nand%d", devnum);
mtd->name = dev_name[devnum];

#ifdef CONFIG_MTD_DEVICE
/*
* Add MTD device so that we can reference it later
* via the mtdcore infrastructure (e.g. ubi).
*/
add_mtd_device(mtd);
#endif

total_nand_size += mtd->size / 1024;

if (nand_curr_device == -1)
nand_curr_device = devnum;

return 0;
}
should_load_env

发现 CONFIG_OF_CONTROLCONFIG_DELAY_ENVIRONMENT均没有定义,返回1。

1
2
3
4
5
6
7
8
9
10
11
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
/* initialize environment */
if (should_load_env())
env_relocate();
else
set_default_env(NULL);
...
}

也就是env_relocate(),由于CONFIG_NEEDS_MANUAL_RELOCCONFIG_ENV_IS_NOWHERE没有定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void env_relocate(void)
{
if (gd->env_valid == 0) {
#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)
/* Environment not changable */
set_default_env(NULL);
#else
bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
set_default_env("!bad CRC");
#endif
} else {
env_relocate_spec();
}
}

所以代码可精简为如下, 由于在env_init中配置为1,所以此处走else分支。

1
2
3
4
5
6
7
8
9
void env_relocate(void)
{
if (gd->env_valid == 0) {
bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
set_default_env("!bad CRC");
} else {
env_relocate_spec();
}
}

由于上述我们使用的为nand,所以此处env_relocate_spec位置在common/env_nand.c中,先看下这个又臭又长的宏定义。

1
2
3
4
5
6
7
8
9
10
11
12
// common/env_nand.c
void env_relocate_spec(void)
{
int ret;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
if (ret) {
set_default_env("!readenv() failed");
return;
}
env_import(buf, 1);
}

看下下面的分析,其实也就是定义了一个数组,然后返回一个数组指针,只不过其中保证了内存对齐,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// common/env_nand.c
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
//-------------------------------------------------------------------------------------------
// include/common.h
#define ALLOC_CACHE_ALIGN_BUFFER(type, name, size) ALLOC_ALIGN_BUFFER(type, name, size, ARCH_DMA_MINALIGN)
...
#define ALLOC_ALIGN_BUFFER(type, name, size, align) ALLOC_ALIGN_BUFFER_PAD(type, name, size, align, 1)
...
#define ALLOC_ALIGN_BUFFER_PAD(type, name, size, align, pad) \
char __##name[ROUND(PAD_SIZE((size) * sizeof(type), pad), align) \
+ (align - 1)]; \
\
type *name = (type *) ALIGN((uintptr_t)__##name, align)
//---------------------------------------------------------------------------------------------
#define ALLOC_CACHE_ALIGN_BUFFER(type, name, size) \
char __##name[ROUND(PAD_SIZE((size) * sizeof(type), 1), ARCH_DMA_MINALIGN) \
+ (ARCH_DMA_MINALIGN - 1)]; \
type *name = (type *) ALIGN((uintptr_t)__##name, ARCH_DMA_MINALIGN)
//------------------------------------------------------------------------------------------------
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
char __buf[CONFIG_ENV_SIZE];
char *buf = (char *)ALIGN((uintptr_t)__buf,ARCH_DMA_MINALIGN);

接着看readenv函数,在实际中打印

1
2
readenv
end of readenv,amount_loaded=131072 CONFIG_ENV_SIZE:131072

nand_info[0].erasesize又是在哪赋值的呢?

nand_init_chip中,没有与mtd->erasesize相关的,而board_nand_init仅与nand_chip有关,nand_register中也没有与之有关的。

所以一路找下去,最终是在nand_get_flash_type中的nand_decode_id(或者nand_decode_ext_id)获取类型中给mtd->erasesize赋值的,具体值的大小与nand的厂商有关。

1
2
3
4
5
6
7
8
9
10
11
12
--> nand_init
|
--> nand_init_chip
|
--> nand_scan
|
--> nand_scan_ident
|
--> nand_get_flash_type
|
--> nand_decode_ext_id
--> nand_decode_id

blocksize或者CONFIG_ENV_SIZE中获取最小值,接着一个循环 条件呢?就是总共加载的大小与配置的环境大小相比且当前的偏移小于配置的环境的大小。

先判断当前块是否是坏的,如果是,则跳过,offset += blocksize

否则,在里面接着以跳过坏块的方式读,并将读取的环境变量写入buf中,二者相等返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int readenv(size_t offset, u_char *buf)
{
size_t end = offset + CONFIG_ENV_RANGE;
size_t amount_loaded = 0;
size_t blocksize, len;
u_char *char_ptr;

debug("readenv\r\n");
blocksize = nand_info[0].erasesize;
if (!blocksize)
return 1;

len = min(blocksize, CONFIG_ENV_SIZE);

while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
if (nand_block_isbad(&nand_info[0], offset)) {
offset += blocksize;
} else {
char_ptr = &buf[amount_loaded];
if (nand_read_skip_bad(&nand_info[0], offset,
&len, NULL,
nand_info[0].size, char_ptr))
return 1;

offset += blocksize;
amount_loaded += len;
}
}
debug("end of readenv,amount_loaded=%d CONFIG_ENV_SIZE:%d\r\n",amount_loaded,CONFIG_ENV_SIZE);
if (amount_loaded != CONFIG_ENV_SIZE)
return 1;
return 0;
}

返回到env_relocate_spec中,执行env_import(buf,1)这个函数是个难点,也是个重点,涉及到对哈希表的操作。

这个函数有两个操作,一个是crc校验;另一个是创建哈希表,并将环境变量中的数据提取出来插入表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int env_import(const char *buf, int check)
{
env_t *ep = (env_t *)buf;
debug("env_import\r\n");
if (check) {
uint32_t crc;
memcpy(&crc, &ep->crc, sizeof(crc));
if (crc32(0, ep->data, ENV_SIZE) != crc) {
debug("env_import --> crc32\r\n");
set_default_env("!bad CRC");
return 0;
}
}
if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,0, NULL)) {
debug("env_import --> himport_r\r\n");
gd->flags |= GD_FLG_ENV_READY;
return 1;
}

error("Cannot import environment: errno = %d\n", errno);
set_default_env("!import failed");
return 0;
}

主要是分析下创建哈希表和哈希表的插入操作。TODO:明天挑一段长的时间分析吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// common/env_common.c
himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,0, NULL)
// lib/hashtable.c
int himport_r(struct hsearch_data *htab,const char *env, size_t size, const char sep, int flag,int nvars, char * const vars[])
{
char *data, *sp, *dp, *name, *value;
char *localvars[nvars];
int i;

/* Test for correct arguments. */
if (htab == NULL) {
__set_errno(EINVAL);
return 0;
}

/* we allocate new space to make sure we can write to the array */
if ((data = malloc(size)) == NULL) {
debug("himport_r: can't malloc %zu bytes\n", size);
__set_errno(ENOMEM);
return 0;
}
memcpy(data, env, size);
dp = data;

/* make a local copy of the list of variables */
if (nvars)
memcpy(localvars, vars, sizeof(vars[0]) * nvars);

if ((flag & H_NOCLEAR) == 0) {
/* Destroy old hash table if one exists */
debug("Destroy Hash Table: %p table = %p\n", htab, htab->table);
if (htab->table) hdestroy_r(htab);
}

if (!htab->table) {
int nent = CONFIG_ENV_MIN_ENTRIES + size / 8;
if (nent > CONFIG_ENV_MAX_ENTRIES) nent = CONFIG_ENV_MAX_ENTRIES;
debug("Create Hash Table: N=%d\n", nent);
if (hcreate_r(nent, htab) == 0) {
free(data);
return 0;
}
}

/* Parse environment; allow for '\0' and 'sep' as separators */
do {
ENTRY e, *rv;
/* skip leading white space */
while (isblank(*dp))
++dp;
/* skip comment lines */
if (*dp == '#') {
while (*dp && (*dp != sep))
++dp;
++dp;
continue;
}

/* parse name */
for (name = dp; *dp != '=' && *dp && *dp != sep; ++dp)
;

/* deal with "name" and "name=" entries (delete var) */
if (*dp == '\0' || *(dp + 1) == '\0' ||
*dp == sep || *(dp + 1) == sep) {
if (*dp == '=')
*dp++ = '\0';
*dp++ = '\0'; /* terminate name */

debug("DELETE CANDIDATE: \"%s\"\n", name);
if (!drop_var_from_set(name, nvars, localvars))
continue;

if (hdelete_r(name, htab, flag) == 0)
debug("DELETE ERROR ##############################\n");

continue;
}
*dp++ = '\0'; /* terminate name */

/* parse value; deal with escapes */
for (value = sp = dp; *dp && (*dp != sep); ++dp) {
if ((*dp == '\\') && *(dp + 1))
++dp;
*sp++ = *dp;
}
*sp++ = '\0'; /* terminate value */
++dp;

if (*name == 0) {
debug("INSERT: unable to use an empty key\n");
__set_errno(EINVAL);
return 0;
}

/* Skip variables which are not supposed to be processed */
if (!drop_var_from_set(name, nvars, localvars))
continue;

/* enter into hash table */
e.key = name;
e.data = value;

hsearch_r(e, ENTER, &rv, htab, flag);
if (rv == NULL)
printf("himport_r: can't insert \"%s=%s\" into hash table\n",name, value);
debug("INSERT: table %p, filled %d/%d rv %p ==> name=\"%s\" value=\"%s\"\n",htab, htab->filled, htab->size,rv, name, value);
} while ((dp < data + size) && *dp); /* size check needed for text */
/* without '\0' termination */
debug("INSERT: free(data = %p)\n", data);
free(data);

for (i = 0; i < nvars; i++) {
if (localvars[i] == NULL)
continue;
if (hdelete_r(localvars[i], htab, flag) == 0)
printf("WARNING: '%s' neither in running nor in imported env!\n", localvars[i]);
else
printf("WARNING: '%s' not in imported env, deleting it!\n", localvars[i]);
}

debug("INSERT: done\n");
return 1; /* everything OK */
}

先把打印的日志摆这,对着分析还容易点。。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Destroy Hash Table: 3ffb4f0c table = 00000000
Create Hash Table: N=512
INSERT: table 3ffb4f0c, filled 1/521 rv 3fe62868 ==> name="baudrate" value="115200"
INSERT: table 3ffb4f0c, filled 2/521 rv 3fe643fc ==> name="bootargs" value="root=/dev/nfs rw nfsroot=10.0.0.97:/home/acoollib/workspace/git/smart210-SDK/rootfs ip=10.0.0.98:10.0.0.97:10.0.0.1:255.0.0.0::eth0:off init=/linuxrc console=ttySAC0,115200"
INSERT: table 3ffb4f0c, filled 3/521 rv 3fe62c8c ==> name="bootcmd" value="nfs 20000000 10.0.0.97:/home/acoollib/workspace/git/smart210-SDK/rootfs/uImage;nfs 21000000 10.0.0.97:/home/acoollib/workspace/git/smart210-SDK/rootfs/s5pv210-smart210.dtb;bootm 20000000 - 21000000"
INSERT: table 3ffb4f0c, filled 4/521 rv 3fe634e8 ==> name="bootdelay" value="1"
INSERT: table 3ffb4f0c, filled 5/521 rv 3fe62bb0 ==> name="ethact" value="dm9000"
INSERT: table 3ffb4f0c, filled 6/521 rv 3fe625fc ==> name="ethaddr" value="1a:2a:3a:4a:5a:6a"
INSERT: table 3ffb4f0c, filled 7/521 rv 3fe64320 ==> name="ipaddr" value="10.0.0.98"
INSERT: table 3ffb4f0c, filled 8/521 rv 3fe6232c ==> name="machid" value="0xffffffff"
INSERT: table 3ffb4f0c, filled 9/521 rv 3fe63e48 ==> name="qqqqq" value="1111"
INSERT: table 3ffb4f0c, filled 10/521 rv 3fe62a84 ==> name="serverip" value="10.0.0.20"
INSERT: table 3ffb4f0c, filled 11/521 rv 3fe62fac ==> name="stderr" value="serial"
INSERT: table 3ffb4f0c, filled 12/521 rv 3fe627c8 ==> name="stdin" value="serial"
INSERT: table 3ffb4f0c, filled 13/521 rv 3fe638f8 ==> name="stdout" value="serial"
INSERT: free(data = 3fe41bd0)
INSERT: done
stdio_init
1
2
3
4
5
6
7
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
stdio_init(); /* get the devices list going. */
...
}

因为一些宏在smart210.h中未定义,所以 此处留下会执行的代码。

1
2
3
4
5
6
7
8
9
// common/stdio.c
int stdio_init (void)
{
/* Initialize the list */
INIT_LIST_HEAD(&(devs.list));
drv_system_init ();
serial_stdio_init ();
return (0);
}

首先初始化列表,双向列表的头初始化时 其下一个和前一个节点均指向自身。此处尽管INIT_LIST_HEAD为static函数,但是其在common.h实现,在c中展开相当于仅在当前c中使用。再看下devs.list是什么东西?

1
2
3
4
5
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}

此处又引出一个结构体stdio_dev,其中包括标志位、支持的扩展、设备名称、设备操作函数、私有扩展和一个双向列表。

1
2
// common/stdio.c
static struct stdio_dev devs;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// include/stdio_dev.h
/* Device information */
struct stdio_dev {
int flags; /* Device flags: input/output/system */
int ext; /* Supported extensions */
char name[16]; /* Device name */

/* GENERAL functions */
int (*start) (void); /* To start the device */
int (*stop) (void); /* To stop the device */

/* OUTPUT functions */
void (*putc) (const char c); /* To put a char */
void (*puts) (const char *s); /* To put a string (accelerator) */

/* INPUT functions */
int (*tstc) (void); /* To test if a char is ready... */
int (*getc) (void); /* To get that char */

/* Other functions */
void *priv; /* Private extensions */
struct list_head list;
}

接着看下drv_system_init,其中定义了struct stdio_dev dev,然后初始化。最后注册这个结构。直接看下是如何注册的?

1
2
3
4
5
6
7
8
9
10
11
12
13
// common/stdio.c
static void drv_system_init (void)
{
struct stdio_dev dev;
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, "serial");
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
dev.putc = serial_putc;
dev.puts = serial_puts;
dev.getc = serial_getc;
dev.tstc = serial_tstc;
stdio_register (&dev);
}

在注册函数中,首先克隆一个当前的结构(先分配空间,在拷贝数据),然后把克隆出的结构体在挂到devs.list上(TODO:此处待理解双向列表添加后在分析)。

1
2
3
4
5
6
7
8
9
10
11
// common/stdio.c
int stdio_register (struct stdio_dev * dev)
{
struct stdio_dev *_dev;

_dev = stdio_clone(dev);
if(!_dev)
return -1;
list_add_tail(&(_dev->list), &(devs.list));
return 0;
}

再看下 serial_stdio_initserial_devices不知各位看官姥爷还有没有印象,反正笔者是没有印象了。。。。好了,话说回来,在serial_init中我们分析了串口的初始化,其节点都挂到 serial_devices列表中,serial_current中保存着当前串口的指针。

这段代码的意思是 遍历serial_devices中的所有节点,将其添加到devs中去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void serial_stdio_init(void)
{
struct stdio_dev dev;
struct serial_device *s = serial_devices;
while (s) {
memset(&dev, 0, sizeof(dev));
strcpy(dev.name, s->name);
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT;
dev.start = s->start;
dev.stop = s->stop;
dev.putc = s->putc;
dev.puts = s->puts;
dev.getc = s->getc;
dev.tstc = s->tstc;
stdio_register(&dev);
s = s->next; // 移到下一个节点
}
}
jumptable_init
1
2
3
4
5
6
7
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
jumptable_init();
...
}

common/exports.c中实现

1
2
3
4
5
6
// common/exports.c
void jumptable_init(void)
{
gd->jt = malloc(XF_MAX * sizeof(void *));
#include <_exports.h>
}

手动模拟编译器展开(滑稽)看一下,此处只需要记住,宏直接展开。注意下在exports.c中包含exports.h,而exports.h定义了一个enum,那个enum展开后对应的恰是_exports.h中的所有函数的索引(0开始,XF_MAX结束,所以前面分配内存时可直接XF_MAX)。所以在exports.c就可以直接使用索引访问数组并给每个数组变量赋值。至于这些个函数就不一一分析了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// common/exports.c
#define EXPORT_FUNC(sym) gd->jt[XF_##sym] = (void *)sym;
void jumptable_init(void)
{
gd->jt = malloc(XF_MAX * sizeof(void *));
EXPORT_FUNC(get_version)
EXPORT_FUNC(getc)
EXPORT_FUNC(tstc)
EXPORT_FUNC(putc)
EXPORT_FUNC(puts)
EXPORT_FUNC(printf)
EXPORT_FUNC(install_hdlr)
EXPORT_FUNC(free_hdlr)
...
EXPORT_FUNC(spi_xfer)
}
//--------------------------------------------------------------------------------------
void jumptable_init(void)
{
gd->jt = malloc(XF_MAX * sizeof(void *));
gd->jt[XF_getc] = (void *)get_c;
gd->jt[XF_get_version] = (void *)get_version;
gd->jt[XF_tstc] = (void *)tstc;
gd->jt[XF_putc] = (void *)putc;
gd->jt[XF_puts] = (void *)puts;
gd->jt[XF_printf] = (void *)printf;
gd->jt[XF_install_hdlr] = (void *)install_hdlr;
gd->jt[XF_free_hdlr] = (void *)free_hdlr;
...
gd->jt[XF_spi_xfer] = (void *)spi_xfer;
}

由于头文件一般都在函数之前,所以此处定义EXPORT_FUNC(x)不会出错,待使用结束后undef,所以后面使用时也没有问题。

1
2
3
4
5
6
7
enum {
#define EXPORT_FUNC(x) XF_ ## x ,
#include <_exports.h>
#undef EXPORT_FUNC

XF_MAX
};
console_init_r
1
2
3
4
5
6
7
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
console_init_r(); /* fully init console as a device */
...
}

看下CONFIG_SYS_CONSOLE_IS_IN_ENV宏是否定义,没有定义,那么代码就是下面这一段。

list_for_each(pos, list)是一个迭代器,好吧,也就是一个for循环,但是注意里面有一个prefetch,此处需分析一下。TODO:待分析

然后list_entry也需要分析一下。TODO:待分析

这个循环的目的,根据标志位判断输入输出节点,并将其赋值给inputdevoutputdev,如果这两者都有的话,结束这个循环。

一个console_setfile将dev设置到stdio_devices[i],同时也设给console_devices[i][0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// common/console.c
int console_init_r(void)
{
struct stdio_dev *inputdev = NULL, *outputdev = NULL;
int i;
struct list_head *list = stdio_get_list();
struct list_head *pos;
struct stdio_dev *dev;

/* Scan devices looking for input and output devices */
list_for_each(pos, list) {
dev = list_entry(pos, struct stdio_dev, list);

if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
inputdev = dev;
}
if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
outputdev = dev;
}
if(inputdev && outputdev)
break;
}

/* Initializes output console first */
if (outputdev != NULL) {
console_setfile(stdout, outputdev);
console_setfile(stderr, outputdev);
#ifdef CONFIG_CONSOLE_MUX
console_devices[stdout][0] = outputdev;
console_devices[stderr][0] = outputdev;
#endif
}

/* Initializes input console */
if (inputdev != NULL) {
console_setfile(stdin, inputdev);
#ifdef CONFIG_CONSOLE_MUX
console_devices[stdin][0] = inputdev;
#endif
}

#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
stdio_print_current_devices();
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */

/* Setting environment variables */
for (i = 0; i < 3; i++) {
setenv(stdio_names[i], stdio_devices[i]->name);
}

gd->flags |= GD_FLG_DEVINIT; /* device initialization completed */
return 0;
}

然后调用stdio_print_current_devices打印,debug输出如下。并设置环境变量stderrstdinstdoutserial

1
2
3
In:    serial
Out: serial
Err: serial

最后一个设置标志位GD_FLG_DEVINIT表示dev初始化完成。

checkboard
1
2
3
4
5
6
7
8
9
10
11
12
13
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
checkboard(); /* fully init console as a device */
...
}
// board/samsung/smart210/smart210.c
int checkboard(void)
{
printf("Board:\tSMART210\n");
return 0;
}
interrupt_init
1
2
3
4
5
6
7
8
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
/* set up exceptions */
interrupt_init();
...
}

没有定义宏CONFIG_USE_IRQ,走这个分支,这个代码也只是设置IRQ_STACK_START_IN中断栈起始地址为gd->irq_sp + 8?不应该是-8吗?

1
2
3
4
5
6
// arch/arm/lib/interrupts.c
int interrupt_init (void)
{
IRQ_STACK_START_IN = gd->irq_sp + 8;
return 0;
}

enable_interrupts
1
2
3
4
5
6
7
8
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
/* enable exceptions */
enable_interrupts();
...
}

空实现

1
2
3
4
5
// arch/arm/lib/interrupts.c
void enable_interrupts (void)
{
return;
}
eth_initialize
1
2
3
4
5
6
7
8
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
puts("Net: ");
eth_initialize(gd->bd);
...
}

类似串口,这个地方又有个数据结构struct eth_device,定义了网络设备的基本操作。其中从面向对象的角度来说,在驱动层面只需要把对应的init、send、recv、halt、weite_hwaddr这几种方法实现,就可以正常收发数据了,至于数据收回来要怎么办,那是上层决定的,我们并不需要关心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// include/net.h
struct eth_device {
char name[16];
unsigned char enetaddr[6];
int iobase;
int state;

int (*init) (struct eth_device *, bd_t *);
int (*send) (struct eth_device *, void *packet, int length);
int (*recv) (struct eth_device *);
void (*halt) (struct eth_device *);
#ifdef CONFIG_MCAST_TFTP
int (*mcast) (struct eth_device *, const u8 *enetaddr, u8 set);
#endif
int (*write_hwaddr) (struct eth_device *);
struct eth_device *next;
int index;
void *priv;
};

接着看eth_initialize函数,上来首先将eth_devices(网卡设备链表)、eth_current(当前网卡设备)置空,然后标记当前阶段为BOOTSTAGE_ID_NET_ETH_START,从环境变量查找"bootfile",如果有,则拷贝至BootFile中。

接着往下走,实际上是一个初始化函数,只不过分了几种情况,如果board-specific存在的话,就调用它;如果没有,调用CPU-specific那个。

本文中走的是第一个分支,调用的board_eth_init(bis)board/samsung/smart210/smart210.c中的,我们分析一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// net/eth.c
int eth_initialize(bd_t *bis)
{
int num_devices = 0;
eth_devices = NULL;
eth_current = NULL;

bootstage_mark(BOOTSTAGE_ID_NET_ETH_START);

eth_env_init(bis);

/*
* If board-specific initialization exists, call it.
* If not, call a CPU-specific one
*/
if (board_eth_init != __def_eth_init) {
if (board_eth_init(bis) < 0)
printf("Board Net Initialization Failed\n");
} else if (cpu_eth_init != __def_eth_init) {
if (cpu_eth_init(bis) < 0)
printf("CPU Net Initialization Failed\n");
} else
printf("Net Initialization Skipped\n");
...
}

先是调用的board_eth_init,接着调用dm9000_initialize,在这个函数里,就是将dm9000_info.netdev结构给初始化,并且从EEPROM中获取MAC地址,初始化函数,然后注册(在注册中,如果eth_devices网卡设备链表为空,则使eth_deviceseth_devices指向当前设备dev,并且如果当前网卡存在则设置环境变量中ethact的名称为当前网卡名,也就是说如果手动更改环境变量中ethact的value,即使保存后,下次重启后ethact为前面设置的dm9000。接着就是将网卡状态置位,将next指针指向自己,索引自加)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// board/samsung/smart210/smart210.c
int board_eth_init(bd_t *bis)
{
int rc = 0;
rc = dm9000_initialize(bis);
return rc;
}
//----------------------------------------------------------------------------------
// drivers/net/dm9000x.c
int dm9000_initialize(bd_t *bis)
{
struct eth_device *dev = &(dm9000_info.netdev);

/* Load MAC address from EEPROM */
dm9000_get_enetaddr(dev);

dev->init = dm9000_init;
dev->halt = dm9000_halt;
dev->send = dm9000_send;
dev->recv = dm9000_rx;
sprintf(dev->name, "dm9000");

eth_register(dev);

return 0;
}

接着我们返回到eth_initialize,看一下剩下的部分。从eth_devices中取出,从环境变量中取出ethprime的value(应该为空,因为环境变量中没有此项),标记一下当前阶段。接着就是如果有多个网卡,则在显示的时候中间用,分开,比如:Net: dm9000,dm9001。然后eth_write_hwaddr写网卡的MAC地址到dev中,接着更新下一个,但是我们刚刚设置dev->next指向自己,所以条件不满足,退出循环,返回网卡的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// net/eth.c
int eth_initialize(bd_t *bis)
{
...
if (!eth_devices) {
puts("No ethernet found.\n");
bootstage_error(BOOTSTAGE_ID_NET_ETH_START);
} else {
struct eth_device *dev = eth_devices;
char *ethprime = getenv("ethprime");

bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT);
do {
if (dev->index)
puts(", ");
printf("%s", dev->name);

if (ethprime && strcmp(dev->name, ethprime) == 0) {
eth_current = dev;
puts(" [PRIME]");
}

if (strchr(dev->name, ' '))
puts("\nWarning: eth device name has a space!" "\n");

if (eth_write_hwaddr(dev, "eth", dev->index))
puts("\nWarning: failed to set MAC address\n");

dev = dev->next;
num_devices++;
} while (dev != eth_devices);

eth_current_changed();
putc('\n');
}
return num_devices;
}
main_loop

终于到最后一个函数了,

1
2
3
4
5
6
7
8
9
10
// arch/arm/lib/board.c
void board_init_r(gd_t *id, ulong dest_addr)
{
...
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}
/* NOTREACHED - no way out of command loop except booting */
}

已定义宏 CONFIG_SYS_HUSH_PARSERCONFIG_BOOTDELAY精简后代码如下,首先标记当前阶段BOOTSTAGE_ID_MAIN_LOOP,接着看一下这几个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// common/main.c
void main_loop(void)
{
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
u_boot_hush_start();
process_boot_delay();
/*
* Main Loop for Monitor Command Processing
*/
parse_file_outer();
/* This point is never reached */
for (;;);
}
u_boot_hush_start

u_boot_hush_startcommon/hush.c中,给top_vars分配空间,然后初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
// common/hush.c
int u_boot_hush_start(void)
{
if (top_vars == NULL) {
top_vars = malloc(sizeof(struct variables));
top_vars->name = "HUSH_VERSION";
top_vars->value = "0.01";
top_vars->next = NULL;
top_vars->flg_export = 0;
top_vars->flg_read_only = 1;
}
return 0;
}
process_boot_delay

process_boot_delaycommon/main.c中,从环境变量中获取bootdelay对应的value,获取bootcmd对应的value,然后进入abortboot(bootdelay)函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// common/main.c
static void process_boot_delay(void)
{
char *s;
int bootdelay;
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n", bootdelay);
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay != -1 && s && !abortboot(bootdelay)) {
run_command_list(s, -1, 0);
}
}

abortboot处理延迟逻辑

1
2
3
4
5
// common/main.c
static int abortboot(int bootdelay)
{
return abortboot_normal(bootdelay);
}

如果获取到字符,就停止计时,否则一直在此空转,bootdelay秒后,函数返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// common/main.c
static int abortboot_normal(int bootdelay)
{
int abort = 0;
unsigned long ts;

if (bootdelay >= 0)
printf("Hit any key to stop autoboot: %2d ", bootdelay);

/*
* Check if key already pressed
* Don't check if bootdelay < 0
*/
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press */
(void) getc(); /* consume input */
puts ("\b\b\b 0");
abort = 1; /* don't auto boot */
}
}

while ((bootdelay > 0) && (!abort)) {
--bootdelay;
/* delay 1000 ms */
ts = get_timer(0);
do {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
(void) getc(); /* consume input */
break;
}
udelay(10000);
} while (!abort && get_timer(ts) < 1000);
printf("\b\b\b%2d ", bootdelay);
}
putc('\n');
return abort;
}

然后运行 run_command_list(s, -1, 0);,此时s中数据为bootcmd="nfs 20000000 10.0.0.97:/home/acoollib/workspace/git/smart210-SDK/rootfs/uImage;nfs 21000000 10.0.0.97:/home/acoollib/workspace/git/smart210-SDK/rootfs/s5pv210-smart210.dtb;bootm 20000000 - 21000000"

run_command_list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int run_command_list(const char *cmd, int len, int flag)
{
int need_buff = 1;
char *buff = (char *)cmd; /* cast away const */
int rcode = 0;

if (len == -1) {
len = strlen(cmd);
/* hush will never change our string */
need_buff = 0;
}
rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);// 1<<1
return rcode;
}

run_command_list只是对hush shell中的函数parse_string_outer进行了一层封装。parse_string_outer函数调用了hush shell的命令解释器parse_stream_outer函数来解释bootcmd的命令。

parse_string_outer

我们看下parse_stream_outer这个执行过程实在太复杂了。从这开始整个执行流程是这样的,最后就跳转到调用的地方了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--> parse_string_outer
|
--> parse_stream_outer
|
--> parse_stream
--> run_list
|
--> run_list_real
|
--> run_pipe_real
|
--> cmd_process
|
--> cmd_call

这个过程需要理解一系列的代码以及各种神奇操作,让我们先省略这一段继续往后吧。

最后调用parse_string_outer,在源码中,我们定义了__U_BOOT__宏,所以parse_string_outer源码如下

首先进入代码后先判空,然后搜索\n,如果以\n结尾,会走if分支,否则走else分支,其区别就是是否需要分配空间,拷贝一下,添加\n到尾部。

然后转换一下格式,在setup_string_in_str中将其转为结构体struct in_str,然后进入parse_stream_outer此处hush解析器不在分析。还是得分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// common/hush.c
int parse_string_outer(const char *s, int flag)
{
struct in_str input;
char *p = NULL;
int rcode;
if ( !s || !*s)
return 1;
if (!(p = strchr(s, '\n')) || *++p) {
p = xmalloc(strlen(s) + 2);
strcpy(p, s);
strcat(p, "\n");
setup_string_in_str(&input, p);
rcode = parse_stream_outer(&input, flag);
free(p);
return rcode;
} else {
setup_string_in_str(&input, s);
return parse_stream_outer(&input, flag);
}
}
parse_stream_outer

先进入parse_stream函数对数据进行处理,识别到执行命令后,最后进入run_list函数,parse_streamrun_list中间的数据是怎么衔接的呢?借助ctx,一会看一下ctxp_context)的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// common/hush.c
static int parse_stream_outer(struct in_str *inp, int flag)
{
struct p_context ctx;
o_string temp=NULL_O_STRING;
int rcode;
int code = 0;
do {
ctx.type = flag;
initialize_context(&ctx);
update_ifs_map();
if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0);
inp->promptmode=1;
rcode = parse_stream(&temp, &ctx, inp, '\n');
if (rcode == 1) flag_repeat = 0;
if (rcode != 1 && ctx.old_flag != 0) {
syntax();
flag_repeat = 0;
}
if (rcode != 1 && ctx.old_flag == 0) {
done_word(&temp, &ctx);
done_pipe(&ctx,PIPE_SEQ);
code = run_list(ctx.list_head);
if (code == -2) { /* exit */
b_free(&temp);
code = 0;
/* XXX hackish way to not allow exit from main loop */
if (inp->peek == file_peek) {
printf("exit not allowed from main input shell.\n");
continue;
}
break;
}
if (code == -1)
flag_repeat = 0;
} else {
if (ctx.old_flag != 0) {
free(ctx.stack);
b_reset(&temp);
}
if (inp->__promptme == 0) printf("<INTERRUPT>\n");
inp->__promptme = 1;
temp.nonnull = 0;
temp.quote = 0;
inp->p = NULL;
free_pipe_list(ctx.list_head,0);
}
b_free(&temp);
} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP)); /* loop on syntax errors, return on EOF */
return (code != 0) ? 1 : 0;
}
p_context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* This holds pointers to the various results of parsing */
struct p_context {
struct child_prog *child;
struct pipe *list_head;
struct pipe *pipe;
#ifndef __U_BOOT__
struct redir_struct *pending_redirect;
#endif
reserved_style w;
int old_flag; /* for figuring out valid reserved words */
struct p_context *stack;
int type; /* define type of parser : ";$" common or special symbol */
/* How about quoting status? */
};
run_list

由于定义了__U_BOOT__,所以此处会直接运行rcode = run_list_real(pi);,进入到run_list_real函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int run_list(struct pipe *pi)
{
int rcode=0;
#ifndef __U_BOOT__
if (fake_mode==0) {
#endif
rcode = run_list_real(pi);
debug("rcode:%d\n",rcode);
#ifndef __U_BOOT__
}
#endif
/* free_pipe_list has the side effect of clearing memory
* In the long run that function can be merged with run_list_real,
* but doing that now would hobble the debugging effort. */
free_pipe_list(pi,0);
return rcode;
}
run_list_real

TODO:待分析

只会运行一次,且不会返回。

跳转到rcode = run_pipe_real(pi);

run_pipe_real

先看一下struct pipe是什么东西,在u-boot中有__U_BOOT__宏,去掉了相关无用的代码。

此处看一下busybox中的hush_doc.txt中的示例就明白了。

1
2
3
4
5
6
7
struct pipe {
int num_progs; /* 在当前任务流中的命令总数 */
struct child_prog *progs; /* 在管道中的命令数组 */
struct pipe *next; /* 下一个命令的指针 */
pipe_style followup; /* 表示这一截管道的类型 PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND;比如a && b,则此处应为PIPE_AND */
reserved_style r_mode; /* 表示控制流 比如if,for, while, until这种 */
};

解析管道中的命令,处理条件语句,比如IF、THEN、ELSE等。

cmd_process

此处函数在重启时会执行三次,因为在bootcmd 启动命令中有三段。

nfs 20000000 10.0.0.97:/home/glj0/worksapce/os/smart210/rootfs/uImage;

nfs 21000000 10.0.0.97:/home/glj0/worksapce/os/smart210/rootfs/s5pv210-smart210.dtb;

bootm 20000000 - 21000000

这个函数会在之前定义的boot_list中找命令对应的函数,比如nfs、bootm等,找到后将其填充到 cmd_tbl_t *cmdtp中,然后跳入cmd_call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable, ulong *ticks)
{
enum command_ret_t rc = CMD_RET_SUCCESS;
cmd_tbl_t *cmdtp;
debug("flag:%d argc:%d\r\n",flag,argc);
int i=0;
for(;i<argc;i++){
printf("argv[%d]:%s\r\n",i,argv[i]);
}
/* Look up command in command table */
cmdtp = find_cmd(argv[0]);
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[0]);
return 1;
}

/* found - check max args */
if (argc > cmdtp->maxargs)
rc = CMD_RET_USAGE;

#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
else if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = CMD_RET_FAILURE;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif

/* If OK so far, then do the command */
if (!rc) {
if (ticks)
*ticks = get_timer(0);
debug("cmd_call cmdtp->name:%s\r\n",cmdtp->name);
rc = cmd_call(cmdtp, flag, argc, argv);
if (ticks)
*ticks = get_timer(*ticks);
*repeatable &= cmdtp->repeatable;
}
if (rc == CMD_RET_USAGE)
rc = cmd_usage(cmdtp);
return rc;
}
cmd_call
1
2
3
4
5
6
7
8
9
10
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;
debug("cmdtp:%p flag:%d argc:%d\r\n",cmdtp,flag,argc);
debug("cmdtp->name:%s maxargs:%d cmd:%p usage:%s \r\n",cmdtp->name,cmdtp->maxargs,cmdtp->cmd,cmdtp->usage);
result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
if (result)
debug("Command failed, result=%d", result);
return result;
}

首先看下是怎么调用的,最终在cmd_call中完成调用,最关键的一句result = (cmdtp->cmd)(cmdtp, flag, argc, argv);此时并不能发现什么猫腻,无法就是cmdtp->cmd是一个函数地址,后面这一堆是参数,那么,这个地址指向的是什么呢?肯定是一个函数。由于我们DEBUG模式下加了许多打印,所以可以轻松得到这个地址值为3ff8b590,前面我们有个重定位还有印象吗?没有印象去查看一下relocate_code代码的地址:编译后函数的地址+重定位偏移量 接着查日志中,发现relocate偏移量为1ff7b000,那么这个函数偏移前地址也就是3ff8b590-1ff7b000=20010590,看一下u-boot.map文件下该地址对应的是什么,对应的是do_nfs

查一下该符号grep -nr do_nfs,发现存在该函数。那么我们就可以理解了,在cmd_call之前通过一系列的骚操作,得到某指令对应的do_某指令函数地址,然后跳转到这个函数地址就可以进行后面的操作了。

在do_nfs附近有一个U_BOOT_CMD ...

1
2
3
4
5
6
7
8
9
10
static int do_nfs(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
return netboot_common(NFS, cmdtp, argc, argv);
}

U_BOOT_CMD(
nfs, 3, 1, do_nfs,
"boot image via network using NFS protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);

该宏在include/command.h定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// include/command.h
/**
 各个参数的意义如下:
_name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
_maxargs:命令的最大参数个数
_rep:是否自动重复(按Enter键是否会重复执行)
_cmd:该命令对应的响应函数指针
_usage:简短的使用说明(字符串)
_help:较详细的使用说明(字符串)
*/
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

...
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,_usage, _help, _comp);
...
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
--------------------------------------------------------------------------------------------------------------------
// 手动替换 U_BOOT_CMD_COMPLETE 以及 U_BOOT_CMD_MKENT_COMPLETE
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = { #_name, _maxargs, _rep, _cmd, _usage,_help ,NULL }
--------------------------------------------------------------------------------------------------------------------
// include/linker_lists.h
// ll_entry_declare 在 include/linker_lists.h 定义
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) __attribute__((unused,section(".u_boot_list_2_"#_list"_2_"#_name)))
--------------------------------------------------------------------------------------------------------------------
// 示例 -- 手动解释这段宏
U_BOOT_CMD(
nfs, 3, 1, do_nfs,
"boot image via network using NFS protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
// 替换后也就是
cmd_tbl_t _u_boot_list_2_cmd_2_nfs __aligned(4) __attribute__((unused,section(".u_boot_list_2_cmd_2_nfs"))) = \
{nfs,3,1,do_nfs,"boot image via network using NFS protocol","[loadAddress] [[hostIPaddr:]bootfilename]"}

cmdtp->cmd我们知道是什么了,cmdtp呢?看下地址:3ffb8f64(通过日志查看)在减去重定位偏移1ff7b000,也就是2003DF64,通过在u-boot.map中查看对应地址

发现其也就是我们上面使用U_BOOT_CMD定义的这一串,而这一串的格式也恰恰与struct cmd_tbl_s一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct cmd_tbl_s {
char *name; /* 指令名称 */
int maxargs; /* 命令的最大参数个数*/
int repeatable; /* 是否自动重复 */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* 简短的使用说明 */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* 较详细的使用说明 */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
do_nfs

前面我们知道了在cmd_call中会调用do_nfs函数,接下来就分析一下该函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
do_nfs
|
--> netboot_common
|
--> NetLoop
|
--> eth_halt
|
--> dm9000_halt
|
eth_set_current
eth_init
|
--> dm9000_init
|
--> NfsStart
|
--> Nfs_Send
--> eth_rx
|
--> dm9000_rx

根据日志,我们接着分析。先分析这一段

通过上面调用关系,直接看netboot_common,在这个函数中,上面首先是各种初始化,根据参数的不同设定对应的load_addr,这部分我们先不看,在函数的中间有一个NetLoop函数,其中参数为协议类型,此处也就是nfs

1
2
3
4
5
6
7
8
9
10
11
// common/cmd_net.c
static int netboot_common(enum proto_t proto, cmd_tbl_t *cmdtp, int argc,
char * const argv[])
{
...
if ((size = NetLoop(proto)) < 0) {
bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK);
return 1;
}
...
}

由于eth_is_on_demand_init返回值为1,所以会进入if分支下,然后 eth_halt也就是调用 eth_current->halt(eth_current)

接着eth_set_current:在环境变量中找到ethact的名字,在eth_current链表中查找该设备,找到就退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// net/net.c
int NetLoop(enum proto_t protocol)
{
bd_t *bd = gd->bd;
int ret = -1;

NetRestarted = 0;
NetDevExists = 0;
NetTryCount = 1;
debug_cond(DEBUG_INT_STATE, "--- NetLoop Entry\n");

bootstage_mark_name(BOOTSTAGE_ID_ETH_START, "eth_start");
net_init();
if (eth_is_on_demand_init() || protocol != NETCONS) {
eth_halt();
eth_set_current();
if (eth_init(bd) < 0) {
eth_halt();
return -1;
}
} else
eth_init_state_only(bd);
...
}

eth_init中,先是打印Trying ...,然后调用eth_current->init(eth_current, bis) ,这个init函数调到什么地方去了呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int eth_init(bd_t *bis)
{
...
do {
debug("Trying %s\n", eth_current->name);

if (eth_current->init(eth_current, bis) >= 0) {
eth_current->state = ETH_STATE_ACTIVE;

return 0;
}
debug("FAIL\n");
eth_try_another(0);
} while (old_current != eth_current);
return -1;
}

不知各位看官还有印象没,我们在前面 board_init_r 中的eth_initialize初始化的时候,会调用到

int dm9000_initialize(bd_t *bis),在此函数中,绑定了针对dm9000芯片的操作,然后注册到了eth_devices,所以我们刚刚调用eth_current->init(eth_current, bis)就是直接执行 dm9000_init(struct eth_device *dev, bd_t *bd),所以日志中也就会这些输出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
static int dm9000_init(struct eth_device *dev, bd_t *bd)
{
int i, oft, lnk;
u8 io_mode;
struct board_info *db = &dm9000_info;

DM9000_DBG("%s\n", __func__);

/* RESET device */
dm9000_reset();

if (dm9000_probe() < 0)
return -1;

/* Auto-detect 8/16/32 bit mode, ISR Bit 6+7 indicate bus width */
io_mode = DM9000_ior(DM9000_ISR) >> 6;

switch (io_mode) {
case 0x0: /* 16-bit mode */
printf("DM9000: running in 16 bit mode\n");
db->outblk = dm9000_outblk_16bit;
db->inblk = dm9000_inblk_16bit;
db->rx_status = dm9000_rx_status_16bit;
break;
case 0x01: /* 32-bit mode */
printf("DM9000: running in 32 bit mode\n");
db->outblk = dm9000_outblk_32bit;
db->inblk = dm9000_inblk_32bit;
db->rx_status = dm9000_rx_status_32bit;
break;
case 0x02: /* 8 bit mode */
printf("DM9000: running in 8 bit mode\n");
db->outblk = dm9000_outblk_8bit;
db->inblk = dm9000_inblk_8bit;
db->rx_status = dm9000_rx_status_8bit;
break;
default:
/* Assume 8 bit mode, will probably not work anyway */
printf("DM9000: Undefined IO-mode:0x%x\n", io_mode);
db->outblk = dm9000_outblk_8bit;
db->inblk = dm9000_inblk_8bit;
db->rx_status = dm9000_rx_status_8bit;
break;
}

/* Program operating register, only internal phy supported */
DM9000_iow(DM9000_NCR, 0x0);
/* TX Polling clear */
DM9000_iow(DM9000_TCR, 0);
/* Less 3Kb, 200us */
DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
/* Flow Control : High/Low Water */
DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
/* SH FIXME: This looks strange! Flow Control */
DM9000_iow(DM9000_FCR, 0x0);
/* Special Mode */
DM9000_iow(DM9000_SMCR, 0);
/* clear TX status */
DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
/* Clear interrupt status */
DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);

printf("MAC: %pM\n", dev->enetaddr);
if (!is_valid_ether_addr(dev->enetaddr)) {
#ifdef CONFIG_RANDOM_MACADDR
printf("Bad MAC address (uninitialized EEPROM?), randomizing\n");
eth_random_enetaddr(dev->enetaddr);
printf("MAC: %pM\n", dev->enetaddr);
#else
printf("WARNING: Bad MAC address (uninitialized EEPROM?)\n");
#endif
}

/* fill device MAC address registers */
for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
DM9000_iow(oft, dev->enetaddr[i]);
for (i = 0, oft = 0x16; i < 8; i++, oft++)
DM9000_iow(oft, 0xff);

/* read back mac, just to be sure */
for (i = 0, oft = 0x10; i < 6; i++, oft++)
DM9000_DBG("%02x:", DM9000_ior(oft));
DM9000_DBG("\n");

/* Activate DM9000 */
/* RX enable */
DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
/* Enable TX/RX interrupt mask */
DM9000_iow(DM9000_IMR, IMR_PAR);

i = 0;
while (!(dm9000_phy_read(1) & 0x20)) { /* autonegation complete bit */
udelay(1000);
i++;
if (i == 10000) {
printf("could not establish link\n");
return 0;
}
}

/* see what we've got */
lnk = dm9000_phy_read(17) >> 12;
printf("operating at ");
switch (lnk) {
case 1:
printf("10M half duplex ");
break;
case 2:
printf("10M full duplex ");
break;
case 4:
printf("100M half duplex ");
break;
case 8:
printf("100M full duplex ");
break;
default:
printf("unknown: %d ", lnk);
break;
}
printf("mode\n");
return 0;
}

接着看下NfsStart,首先是获取一些关键参数并打印。设置nfs的一些关键参数,比如要传输的地址,超时等等,设置回调函数NfsHandler,然后NfsSend发送请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// net/nfs.c
void NfsStart(void)
{
debug("%s\n", __func__);
nfs_download_state = NETLOOP_FAIL;

NfsServerIP = NetServerIP;
nfs_path = (char *)nfs_path_buff;

if (nfs_path == NULL) {
net_set_state(NETLOOP_FAIL);
puts("*** ERROR: Fail allocate memory\n");
return;
}

if (BootFile[0] == '\0') {
sprintf(default_filename, "/nfsroot/%02X%02X%02X%02X.img",NetOurIP & 0xFF,(NetOurIP >> 8) & 0xFF,(NetOurIP >> 16) & 0xFF,(NetOurIP >> 24) & 0xFF);
strcpy(nfs_path, default_filename);
printf("*** Warning: no boot file name; using '%s'\n",nfs_path);
} else {
char *p = BootFile;
p = strchr(p, ':');
if (p != NULL) {
NfsServerIP = string_to_ip(BootFile);
++p;
strcpy(nfs_path, p);
} else {
strcpy(nfs_path, BootFile);
}
}

nfs_filename = basename(nfs_path);
nfs_path = dirname(nfs_path);

printf("Using %s device\n", eth_get_name());
printf("File transfer via NFS from server %pI4" "; our IP address is %pI4", &NfsServerIP, &NetOurIP);

/* Check if we need to send across this subnet */
if (NetOurGatewayIP && NetOurSubnetMask) {
IPaddr_t OurNet = NetOurIP & NetOurSubnetMask;
IPaddr_t ServerNet = NetServerIP & NetOurSubnetMask;

if (OurNet != ServerNet)
printf("; sending through gateway %pI4",&NetOurGatewayIP);
}
printf("\nFilename '%s/%s'.", nfs_path, nfs_filename);

if (NetBootFileSize) {
printf(" Size is 0x%x Bytes = ", NetBootFileSize<<9);
print_size(NetBootFileSize<<9, "");
}
printf("\nLoad address: 0x%lx\n" "Loading: *\b", load_addr);

NetSetTimeout(nfs_timeout, NfsTimeout);
net_set_udp_handler(NfsHandler);

NfsTimeoutCount = 0;
NfsState = STATE_PRCLOOKUP_PROG_MOUNT_REQ;

/*NfsOurPort = 4096 + (get_ticks() % 3072);*/
/*FIX ME !!!*/
NfsOurPort = 1000;

/* zero out server ether in case the server ip has changed */
memset(NetServerEther, 0, 6);
NfsSend();
}

接着就是eth_rx,由于我们前面已经初始化过dev->recv = dm9000_rx;所以这个函数中返回也就是调用我们前面初始化的网卡,DM9000的dm9000_rx函数。

也就是下面那一块,在这个里面完成数据的接收。如要查看接收的数据,可以打开 CONFIG_DM9000_DEBUG开关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// net/eth.c
int eth_rx(void)
{
if (!eth_current)
return -1;

return eth_current->recv(eth_current);
}

// drivers/net/dm9000x.c
static int dm9000_rx(struct eth_device *netdev)
{
u8 rxbyte, *rdptr = (u8 *) NetRxPackets[0];
u16 RxStatus, RxLen = 0;
struct board_info *db = &dm9000_info;
/* Check packet ready or not, we must check
the ISR status first for DM9000A */
if (!(DM9000_ior(DM9000_ISR) & 0x01)) /* Rx-ISR bit must be set. */
return 0;

DM9000_iow(DM9000_ISR, 0x01); /* clear PR status latched in bit 0 */

/* There is _at least_ 1 package in the fifo, read them all */
for (;;) {
DM9000_ior(DM9000_MRCMDX); /* Dummy read */

/* Get most updated data,
only look at bits 0:1, See application notes DM9000 */
rxbyte = DM9000_inb(DM9000_DATA) & 0x03;

/* Status check: this byte must be 0 or 1 */
if (rxbyte > DM9000_PKT_RDY) {
DM9000_iow(DM9000_RCR, 0x00); /* Stop Device */
DM9000_iow(DM9000_ISR, 0x80); /* Stop INT request */
printf("DM9000 error: status check fail: 0x%x\n",rxbyte);
return 0;
}

if (rxbyte != DM9000_PKT_RDY)
return 0; /* No packet received, ignore */

DM9000_DBG("receiving packet\n");

/* A packet ready now & Get status/length */
(db->rx_status)(&RxStatus, &RxLen);

DM9000_DBG("rx status: 0x%04x rx len: %d\n", RxStatus, RxLen);

/* Move data from DM9000 */
/* Read received packet from RX SRAM */
(db->inblk)(rdptr, RxLen);

if ((RxStatus & 0xbf00) || (RxLen < 0x40)
|| (RxLen > DM9000_PKT_MAX)) {
if (RxStatus & 0x100) {
printf("rx fifo error\n");
}
if (RxStatus & 0x200) {
printf("rx crc error\n");
}
if (RxStatus & 0x8000) {
printf("rx length error\n");
}
if (RxLen > DM9000_PKT_MAX) {
printf("rx length too big\n");
dm9000_reset();
}
} else {
DM9000_DMP_PACKET(__func__ , rdptr, RxLen);

DM9000_DBG("passing packet to upper layer\n");
NetReceive(NetRxPackets[0], RxLen);
}
}
return 0;
}

我们接着看下,接收完成呢?又该去干什么了?别忘了,接收只是其中一步,我们在bootcmd中一共有三部分,传送内核到内存20000000处,传送设备树到21000000处,使用bootm启动。现在就当已经接收完成,接着看看后面做了什么。清除接收函数,如果接收的数据大小大于0,设置filesize为接收的数字大小,fileaddr为接收的地址。然后跳转到done处,done处又是清除udp、icmp句柄,然后函数就退出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// net/net.c
int NetLoop(enum proto_t protocol)
{
...
net_cleanup_loop();
if (NetBootFileXferSize > 0) {
printf("Bytes transferred = %ld (%lx hex)\n",
NetBootFileXferSize,
NetBootFileXferSize);
setenv_hex("filesize", NetBootFileXferSize);
setenv_hex("fileaddr", load_addr);
}
...
goto done;
done:
#ifdef CONFIG_USB_KEYBOARD
net_busy_flag = 0;
#endif
#ifdef CONFIG_CMD_TFTPPUT
/* Clear out the handlers */
net_set_udp_handler(NULL);
net_set_icmp_handler(NULL);
#endif
return ret;
}

返回到netboot_common中去继续执行,设置参数,刷新缓存,查看是否要自动开始,也就是环境变量中的autostartyes时,然后跳转到do_bootm中,此处就不再分析了。

下面接着分析do_bootm。

do_bootm

首先了解一下uImage 和 zImage的区别。

编译kernel之后,会生成Image或者压缩过的zImage。但是这两种镜像的格式并没有办法提供给uboot的足够的信息来进行load、jump或者验证操作等等。因此,uboot提供了mkimage工具,来将kernel制作为uboot可以识别的格式,将生成的文件称之为uImage。
uboot支持两种类型的uImage,如下

  • Legacy-uImage
    在kernel镜像的基础上,加上64Byte的信息提供给uboot使用。

  • FIT-uImage
    以类似FDT的方式,将kernel、fdt、ramdisk等等镜像打包到一个image file中,并且加上一些需要的信息(属性)。uboot只要获得了这个image file,就可以得到kernel、fdt、ramdisk等等镜像的具体信息和内容。

Legacy-uImage实现较为简单,并且长度较小。但是实际上使用较为麻烦,需要在启动kernel的命令中额外添加fdt、ramdisk的加载信息。
而FIT-uImage实现较为复杂,但是使用起来较为简单,兼容性较好,(可以兼容多种配置)。但是需要的额外信息也较长。

uImage 相较于 zImage 其在头部添加了64bytes image_header用来表示Legacy-uImage的头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct image_header {
__be32 ih_magic; /* Image Header Magic Number */ // 幻数头,用来校验是否是一个Legacy-uImage
__be32 ih_hcrc; /* Image Header CRC Checksum */ // 头部的CRC校验值
__be32 ih_time; /* Image Creation Timestamp */ // 镜像创建的时间戳
__be32 ih_size; /* Image Data Size */ // 镜像数据长度
__be32 ih_load; /* Data Load Address */ // 加载地址
__be32 ih_ep; /* Entry Point Address */ // 入口地址
__be32 ih_dcrc; /* Image Data CRC Checksum */ // 镜像的CRC校验
uint8_t ih_os; /* Operating System */ // 操作系统类型
uint8_t ih_arch; /* CPU architecture */ // 体系
uint8_t ih_type; /* Image Type */ // 镜像类型
uint8_t ih_comp; /* Compression Type */ // 压缩类型
uint8_t ih_name[IH_NMLEN]; /* Image Name */ // 镜像名
} image_header_t;
#define IH_NMLEN 32 /* Image Name Length */

通过比较编译出的uImage和zImage,其大小符合上述我们说的,并且可以通过这64字节得到

  • ih_magic 0x27051956 幻术头

  • ih_size 003fe868 4188264 zImage大小

  • ih_load 0x20008000 数据加载地址

  • ih_ep 0x20008000 入口地址

  • etc

bootm 格式如下

bootm Legacy-uImage加载地址 ramdisk加载地址 dtb加载地址

1
2
3
4
5
6
7
8
9
10
11
12
13
// 假设Legacy-uImage的加载地址是0x20008000,ramdisk的加载地址是0x21000000,fdt的加载地址是0x22000000

// 1.只加载kernel的情况下
bootm 0x20008000

// 2. 加载kernel和ramdisk
bootm 0x20008000 0x21000000

// 3.加载kernel和fdt
bootm 0x20008000 - 0x22000000

// 4.加载kernel、ramdisk、fdt
bootm 0x20008000 0x21000000 0x22000000

看一下do_bootm的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
debug("argc:%d\r\n",argc);
int idx=0;
for(;idx<argc;idx++){
printf("argv[%d]:%s\r\n",idx,argv[idx]);
}
// 在这里,bootm的第一个参数会被去掉。
// 也就是当"bootm 0x20000000 - 0x21000000 "时,
// argc = 3,argv[0] = "0x20000000",argv[1]= "-",argv[2]="0x21000000"
// 当"bootm 0x20000000"时,argc=1,argv[0]="0x20000000"
argc--; argv++;
if (argc > 0) {
char *endp;
simple_strtoul(argv[0], &endp, 16);
if ((*endp != 0) && (*endp != ':') && (*endp != '#')) // 判断是否有子命令,这里我们不管。
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}

return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
}

通过日志可得,其传入的参数也就是我们在bootcmd中配置的bootm及其参数,也就是加载在0x20000000处的kernel和0x21000000处的设备树。

1
2
3
4
5
[File:common/cmd_bootm.c, Line:797, Function:do_bootm] argc:4
argv[0]:bootm
argv[1]:20000000
argv[2]:-
argv[3]:21000000

最后,跳转到 do_bootm_states中,此时标志有

  • BOOTM_STATE_START
  • BOOTM_STATE_FINDOS
  • BOOTM_STATE_FINDOTHER
  • BOOTM_STATE_LOADOS
  • BOOTM_STATE_OS_PREP
  • BOOTM_STATE_OS_FAKE_GO
  • BOOTM_STATE_OS_GO

还有一个全局参数 images的地址,而images的格式是struct bootm_headers

1
2
3
do_bootm
|
--> do_bootm_states

struct bootm_headers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
typedef struct bootm_headers {
/*
* Legacy os image header, if it is a multi component image
* then boot_get_ramdisk() and get_fdt() will attempt to get
* data from second and third component accordingly.
*/
image_header_t *legacy_hdr_os; /* image header pointer */ //Legacy-uImage的镜像头
image_header_t legacy_hdr_os_copy; /* header copy */ //Legacy-uImage的镜像头备份
ulong legacy_hdr_valid; // Legacy-uImage的镜像头是否存在的标记

#if defined(CONFIG_FIT)
const char *fit_uname_cfg; /* configuration node unit name */ // 配置节点名

void *fit_hdr_os; /* os FIT image header */ // FIT-uImage中kernel镜像头
const char *fit_uname_os; /* os subimage node unit name */ // FIT-uImage中kernel的节点名
int fit_noffset_os; /* os subimage node offset */ // FIT-uImage中kernel的节点偏移

void *fit_hdr_rd; /* init ramdisk FIT image header */ // FIT-uImage中ramdisk的镜像头
const char *fit_uname_rd; /* init ramdisk subimage node unit name */ // FIT-uImage中ramdisk的节点名
int fit_noffset_rd; /* init ramdisk subimage node offset */ // FIT-uImage中ramdisk的节点偏移

void *fit_hdr_fdt; /* FDT blob FIT image header */ // FIT-uImage中FDT的镜像头
const char *fit_uname_fdt; /* FDT blob subimage node unit name */ // FIT-uImage中FDT的节点名
int fit_noffset_fdt;/* FDT blob subimage node offset */ // FIT-uImage中ramdisk的节点偏移
#endif

#ifndef USE_HOSTCC
image_info_t os; /* os image info */ // 操作系统信息的结构体
ulong ep; /* entry point of OS */ // 操作系统的入口地址

ulong rd_start, rd_end;/* ramdisk start/end */ // ramdisk在内存上的起始地址和结束地址

char *ft_addr; /* flat dev tree address */ // fdt在内存上的地址
ulong ft_len; /* length of flat device tree */ // fdt在内存上的长度

ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif

int verify; /* getenv("verify")[0] != 'n' */ // 是否需要验证
int state; // 状态标识,用于标识对应的bootm需要做什么操作,具体看下面宏

#ifdef CONFIG_LMB
struct lmb lmb; /* for memory mgmt */ // 内存管理
#endif
} bootm_headers_t;

#define BOOTM_STATE_START (0x00000001) //开始执行bootm的准备动作
#define BOOTM_STATE_FINDOS (0x00000002) // 查找操作系统镜像
#define BOOTM_STATE_FINDOTHER (0x00000004) // 查找操作系统镜像外的其他镜像,比如FDT/ramdisk等

#define BOOTM_STATE_LOADOS (0x00000008) // 加载操作系统
#define BOOTM_STATE_RAMDISK (0x00000010) // 操作ramdisk
#define BOOTM_STATE_FDT (0x00000020) // 操作FDT
#define BOOTM_STATE_OS_CMDLINE (0x00000040) // 操作commandline
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100) // 跳转到操作系统前的准备动作
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) // 伪跳转,一般都能直接跳转到kernel中去
#define BOOTM_STATE_OS_GO (0x00000400) // 跳转到kernel中去

extern bootm_headers_t images;

do_bootm_states

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
static int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], int states, bootm_headers_t *images,
int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
// 更新images->state中的状态标志
images->state |= states;

/*
* Work through the states and see how far we get. We stop on
* any error.
*/
// 判断当前状态是否有BOOTM_STATE_START,也就是是否需要执行前的准备动作,如果需要则调用bootm_start
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
// 判断当前状态是否有BOOTM_STATE_FINDOS,也就是是否需要执行查找OS,如果需要则调用 bootm_find_os,注意此步是紧接着上一步的
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
// 判断当前状态是否有BOOTM_STATE_FINDOTHER,也就是是否需要执行查找其它FDT/ramdisk等,如果需要则调用bootm_find_other,注意此步是紧接着上一步的
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}

// 注意:之前都是查找os,查找fdt等填充images中的结构,下面则是利用images中的结构数据。
/* Load the OS */
// 判断当前状态是否有BOOTM_STATE_LOADOS,也就是是否需要执行加载OS,如果需要则调用bootm_load_os,注意此步是紧接着上一步的
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;

iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load));
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
}

/* Relocate the ramdisk */ // 是否需要重定向ramdinsk,do_bootm流程的话是不需要的
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
if (!ret && (states & BOOTM_STATE_RAMDISK)) {
ulong rd_len = images->rd_end - images->rd_start;

ret = boot_ramdisk_high(&images->lmb, images->rd_start,
rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
setenv_hex("initrd_start", images->initrd_start);
setenv_hex("initrd_end", images->initrd_end);
}
}
#endif
#if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB)
// 是否需要重定向fdt,do_bootm流程的话是不需要的
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif

/* From now on, we need the OS boot function */
if (ret)
return ret;
// 获取对应操作系统的启动函数,存放到boot_fn中
boot_fn = boot_os[images->os.os];
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}

/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T))
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_PREP))// 判断当前状态是否有BOOTM_STATE_OS_PREP,也就是跳转前的最后准备动作,如果需要则调用boot_fn
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

#ifdef CONFIG_TRACE
// 判断当前状态是否有BOOTM_STATE_OS_FAKE_GO,伪跳转到操作系统。
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");

ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif

/* Check for unsupported subcommand. */
if (ret) {
puts("subcommand not supported\n");
return ret;
}

/* Now run the OS! We hope this doesn't return */ // 判断当前状态是否有BOOTM_STATE_OS_GO,也就是现在直接跳转到SO,并且不需要返回
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);

/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();

if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);

return ret;
}

BOOTM_STATE_START

bootm_start,简单看一下,其实也就是初始化bootm_headers_t images,也就是从环境变量中获取 verify对应的value,然后返回0或1,赋值给images.verify。配置images中的内存保留区域,标记images中的当前状态。

实现verify和lmb

LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// common/cmd_bootm.c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
memset((void *)&images, 0, sizeof(images));
images.verify = getenv_yesno("verify");

boot_start_lmb(&images);

bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");
images.state = BOOTM_STATE_START;

return 0;
}
// ----------------------------------------------------------------------------------------------------------
// common/cmd_bootm.c
static void boot_start_lmb(bootm_headers_t *images)
{
ulong mem_start;
phys_size_t mem_size;
// 先初始化images的lmb内存管理,注意cnt为1
lmb_init(&images->lmb);
// 然后从环境变量中获取bootm_low,我们的板子上没有但是定义了CONFIG_SYS_SDRAM_BASE,所以此处返回 CONFIG_SYS_SDRAM_BASE
mem_start = getenv_bootm_low();
mem_size = getenv_bootm_size(); // 从环境变量中获取 bootm_size,但是我们的板子上又没有,所以此函数中tmp为0,然后CONFIG_ARM有定义,所以此处的值为 gd->bd->bi_dram[0].size - tmp,也就是 gd->bd->bi_dram[0].size,也就是 PHYS_SDRAM_1_SIZE
// 此处也就是将 mem_start, mem_size,配置到images->lmb中,在此处会用到cnt=1
lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size);
// 这两个函数的作用暂时不理解
arch_lmb_reserve(&images->lmb);
board_lmb_reserve(&images->lmb);
}

BOOTM_STATE_FINDOS

bootm_find_os,主要是验证内核,从bootm命令及image_header中获取内核信息并更新到images

实现os和ep。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;

/* get kernel image header, start address and length */
// 验证内核,获取kernel起始地址和长度
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
if (images.os.image_len == 0) {
puts("ERROR: can't get kernel image!\n");
return 1;
}

/* get image parameters */
// 更新images中的参数
switch (genimg_get_format(os_hdr)) {
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type(os_hdr);
images.os.comp = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);

images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr);
break;
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
if (fit_image_get_type(images.fit_hdr_os,
images.fit_noffset_os, &images.os.type)) {
puts("Can't get image type!\n");
bootstage_error(BOOTSTAGE_ID_FIT_TYPE);
return 1;
}

if (fit_image_get_comp(images.fit_hdr_os,
images.fit_noffset_os, &images.os.comp)) {
puts("Can't get image compression!\n");
bootstage_error(BOOTSTAGE_ID_FIT_COMPRESSION);
return 1;
}

if (fit_image_get_os(images.fit_hdr_os,
images.fit_noffset_os, &images.os.os)) {
puts("Can't get image OS!\n");
bootstage_error(BOOTSTAGE_ID_FIT_OS);
return 1;
}

images.os.end = fit_get_end(images.fit_hdr_os);

if (fit_image_get_load(images.fit_hdr_os, images.fit_noffset_os,
&images.os.load)) {
puts("Can't get image load address!\n");
bootstage_error(BOOTSTAGE_ID_FIT_LOADADDR);
return 1;
}
break;
#endif
default:
puts("ERROR: unknown image format type!\n");
return 1;
}

/* find kernel entry point */
// 获取内核的entry point
if (images.legacy_hdr_valid) {
images.ep = image_get_ep(&images.legacy_hdr_os_copy);
#if defined(CONFIG_FIT)
} else if (images.fit_uname_os) {
int ret;

ret = fit_image_get_entry(images.fit_hdr_os,
images.fit_noffset_os, &images.ep);
if (ret) {
puts("Can't get entry point property!\n");
return 1;
}
#endif
} else {
puts("Could not find kernel entry point!\n");
return 1;
}

if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}

images.os.start = (ulong)os_hdr;

return 0;
}

BOOTM_STATE_FINDOTHER

bootm_find_other

实现rd_start,rd_end,ft_addr和initrd_end。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static int bootm_find_other(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_KERNEL_NOLOAD) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX ||
images.os.os == IH_OS_VXWORKS)) {
if (bootm_find_ramdisk(flag, argc, argv))
return 1;

#if defined(CONFIG_OF_LIBFDT)
if (bootm_find_fdt(flag, argc, argv))
return 1;
#endif
}

return 0;
}

先看bootm_find_ramdisk,由于我们在bootm中传递的ramfs值为-,也就是忽略ramfs的意思,所以会直接跳过,详见下面的日志。

1
2
3
[File:common/image.c, Line:814, Function:boot_get_ramdisk] ## Skipping init Ramdisk
[File:common/image.c, Line:944, Function:boot_get_ramdisk] ## No init Ramdisk
[File:common/image.c, Line:950, Function:boot_get_ramdisk] ramdisk start = 0x00000000, ramdisk end = 0x00000000

接着看一下bootm_find_fdt,无非就是验证bootm中的第三个参数以及是否可用,并确认设备树的类型,根据日志对应着代码看就可,此处不在展开说。

另外,此处会把

1
2
3
4
5
[File:common/image-fdt.c, Line:271, Function:boot_get_fdt] *  fdt: cmdline image address = 0x21000000
[File:common/image-fdt.c, Line:289, Function:boot_get_fdt] ## Checking for 'FDT'/'FDT Image' at 21000000
[File:common/image-fdt.c, Line:370, Function:boot_get_fdt] * fdt: raw FDT blob
## Flattened Device Tree blob at 21000000
Booting using the fdt blob at 0x21000000

BOOTM_STATE_LOADOS

bootm_load_os

do_bootm中可知uImage header的定义,所以我们可以得出镜像压缩类型值为0,此处也即走IH_COMP_NONE分支,所以会有如下打印

1
2
3
4
   Loading Kernel Image ... OK
[File:common/cmd_bootm.c, Line:481, Function:bootm_load_os] kernel loaded at 0x20008000, end = 0x20406868
[File:common/cmd_bootm.c, Line:486, Function:bootm_load_os] images.os.start = 0x20000000, images.os.end = 0x203fe8a8
[File:common/cmd_bootm.c, Line:488, Function:bootm_load_os] images.os.load = 0x20008000, load_end = 0x20406868

精简一下代码,去掉不走的分支,如下,此处功能就是通过images获得镜像信息,看是否需要解压镜像,验证类型等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// common/cmd_bootm.c
static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
int boot_progress)
{
image_info_t os = images->os;
uint8_t comp = os.comp;
ulong load = os.load;
ulong blob_start = os.start;
ulong blob_end = os.end;
ulong image_start = os.image_start;
ulong image_len = os.image_len;
__maybe_unused uint unc_len = CONFIG_SYS_BOOTM_LEN;
int no_overlap = 0;
void *load_buf, *image_buf;
#if defined(CONFIG_LZMA) || defined(CONFIG_LZO)
int ret;
#endif /* defined(CONFIG_LZMA) || defined(CONFIG_LZO) */

const char *type_name = genimg_get_type_name(os.type);

load_buf = map_sysmem(load, unc_len);
image_buf = map_sysmem(image_start, image_len);
switch (comp) {
case IH_COMP_NONE:
if (load == blob_start || load == image_start) {
printf(" XIP %s ... ", type_name);
no_overlap = 1;
} else {
printf(" Loading %s ... ", type_name);
memmove_wd(load_buf, image_buf, image_len, CHUNKSZ);
}
*load_end = load + image_len;
break;
#ifdef CONFIG_GZIP
case IH_COMP_GZIP:
...
#endif /* CONFIG_GZIP */
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
...
#endif /* CONFIG_BZIP2 */
#ifdef CONFIG_LZMA
case IH_COMP_LZMA:
...
#endif /* CONFIG_LZMA */
#ifdef CONFIG_LZO
case IH_COMP_LZO:
...
#endif /* CONFIG_LZO */
default:
printf("Unimplemented compression type %d\n", comp);
return BOOTM_ERR_UNIMPLEMENTED;
}

flush_cache(load, (*load_end - load) * sizeof(ulong));

puts("OK\n");
debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);
bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);

if (!no_overlap && (load < blob_end) && (*load_end > blob_start)) {
debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",blob_start, blob_end);
debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,*load_end);

/* Check what type of image this is. */
if (images->legacy_hdr_valid) {
if (image_get_type(&images->legacy_hdr_os_copy)== IH_TYPE_MULTI)
puts("WARNING: legacy format multi component image overwritten\n");
return BOOTM_ERR_OVERLAP;
} else {
puts("ERROR: new format image overwritten - must RESET the board to recover\n");
bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);
return BOOTM_ERR_RESET;
}
}

return 0;
}

回到 do_bootm_states函数,调用lmb_reserve对剩余的内存进行管理。

BOOTM_STATE_RAMDISK

接着调用boot_ramdisk_high,然而其中的rd_data为0,所以整个函数退出。

1
2
[File:common/image.c, Line:1003, Function:boot_ramdisk_high] ## initrd_high = 0xffffffff, copy_to_ram = 1
[File:common/image.c, Line:1047, Function:boot_ramdisk_high] ramdisk load start = 0x00000000, ramdisk load end = 0x00000000

BOOTM_STATE_FDT

验证FDT,将代码从21000000重定位到3fe30000处。

1
2
[File:common/image-fdt.c, Line:175, Function:boot_relocate_fdt] ## device tree at 21000000 ... 210063a5 (len=37798 [0x93A6])
Loading Device Tree to 3fe30000, end 3fe393a5 ... OK

接着配置boot_fn,由于是images->os.os为1,也就是linux,所以 boot_fn=do_bootm_linux

由于上面 states 中含有这几个标志,

  • BOOTM_STATE_START
  • BOOTM_STATE_FINDOS
  • BOOTM_STATE_FINDOTHER
  • BOOTM_STATE_LOADOS
  • BOOTM_STATE_OS_PREP
  • BOOTM_STATE_OS_FAKE_GO
  • BOOTM_STATE_OS_GO

当前要配置的状态有

  • BOOTM_STATE_OS_CMDLINE
  • BOOTM_STATE_OS_BD_T
  • BOOTM_STATE_OS_PREP
  • BOOTM_STATE_OS_FAKE_GO
  • BOOTM_STATE_OS_GO

need_boot_fn状态为二者共有的,也即BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO、BOOTM_STATE_OS_GO

所以,接着会调用 do_bootm_linux(BOOTM_STATE_OS_PREP, argc, argv, images);

也就是会调用 boot_jump_linux

看下 boot_jump_linux,我们是32位的ARM,所以删掉64位的代码。直接看剩下的,获取设备id,并从环境变量中获取machid对应的value,打印。

然后定义一个函数指针,有三个参数,其类型分别为 int, int, uint

内核启动之前要求r0为0,r1为machid,r2为atags或设备树地址。

所以对应的为r0,r1,r2。

然后kernel_entry(0, machid, r2)也就是直接执行内核entry point处的代码,换句话说也就是跳转到了kernel中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
...
#else
unsigned long machid = gd->bd->bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

kernel_entry = (void (*)(int, int, uint))images->ep;

s = getenv("machid");
if (s) {
strict_strtoul(s, 16, &machid);
printf("Using machid 0x%lx from environment\n", machid);
}

debug("## Transferring control to Linux (at address %08lx)" "...\n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);

if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;

if (!fake)
kernel_entry(0, machid, r2);
#endif
}

参考

  1. wowo的u-boot启动流程分析(1)平台相关部分_
  2. ooonebook大佬的project-X系列
  3. ooonebook大佬的u-boot系列
  4. S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf
  5. S5PV210_UM_REV1.1.pdf
  6. https://re-eject.gbadev.org/files/GasARMRef.pdf
  7. 第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
  8. 嵌入式Linux学习:u-boot源码分析(1)–AM335X系列的2014.10版
  9. ARM汇编指令集汇总
  10. ARM指令CMP详解
  11. ARM 汇编指令 ADR 与 LDR 使用
  12. 谈谈 U-boot 启动流程
  13. u-boot-2019.10源码分析——init_sequence_f中的函数
  14. uboot环境变量实现分析
  15. LD脚本
  16. u-boot.lds链接文件详解
  17. Arm汇编指令
  18. ARM汇编之解惑条件标志,条件码,条件执行
  19. 从零开始之uboot、移植uboot2017.01 @奔跑的小刺猬
  20. 协处理器CP15介绍—MCR/MRC指令
  21. 网卡DM9000裸机驱动详解
  22. 【详解】如何编写Linux下Nand Flash驱动
  23. u-boot 中的 shell
  24. Uboot命令U_BOOT_CMD分析
  25. u-boot的网络
  26. busybox中hush_doc的说明
  27. ARM64 汇编——寄存器和指令

【源码分析】u-boot-2014.04 分析(未完待续)
https://www.glj0.top/posts/f88e6d17/
作者
gong lj
发布于
2022年3月29日
更新于
2022年6月8日
许可协议