什么是 U-Boot

熟悉嵌入式开发的应该都听过它,U-boot 就是启动系统前的一段引导程序,虽然是引导程序,但是功能非常强大。

这一篇主要讲解如何从无到有运行 U-Boot,关于 U-Boot 引导 Linux 的部分放在另外一篇文章讲解。

U-Boot 之前的版本以版本号命名如:0.1.0, 0.2.0 这几年改为了以时间和日期命名:U-Boot 2016.03。

使用 git 获得 U-Boot 的源码:

1


git clone git://git.denx.de/u-boot.git


目前我使用的是 2016.02 的版本。

MLO 及其启动过程

上一篇文章,我们了解了 BeagleBone 有个 SPL 过程,就在这个时候读取 MLO 文件,MLO 文件其实是个精简版的 U-Boot,也是由 U-Boot 生成,但是功能有限,只初始化了部分资源如 DDR,然后启动 U-Boot。

MLO 文件是如何编译出来的

分析 MLO 的编译过程之前需要知道编译原理和 Makefile 等相关知识。

我们先找找 Makefile 看看能不能找到什么。建议使用 Sublime 编辑器。用全局查找功能查找 MLO 关键字。

找到 ​​u-boot/scripts/Makefile.spl​​ 文件 ​117行​:

u-boot/scripts/Makefile.spl

1
2


MLO MLO.byteswap: $(obj)/u-boot-spl.bin FORCE
$(call if_changed,mkimage)


可以看到 MLO 文件是由 ​​u-boot-spl.bin​​ 文件通过 ​​mkimage​​ 命令生成的。

再查到 ​​u-boot/Makefile​​ 文件 ​1310 行​:

u-boot/Makefile

1
2
3
4


spl/u-boot-spl.bin: spl/u-boot-spl
@:
spl/u-boot-spl: tools prepare $(if $(CONFIG_OF_SEPARATE),dts/dt.dtb)
$(Q)$(MAKE) obj=spl -f $(srctree)/scripts/Makefile.spl all


​u-boot-spl.bin​​ 文件是还是由 ​​u-boot/scripts/Makefile.spl​​ 文件生成。

文件 ​​u-boot/scripts/Makefile.spl​​ ​168 行​ 定义了 ​​u-boot-spl.bin​​ 的生成:

u-boot/scripts/Makefile.spl

1
2
3
4
5
6
7
8
9
10
11


ifeq ($(CONFIG_SPL_OF_CONTROL),y)
$(obj)/$(SPL_BIN)-dtb.bin: $(obj)/$(SPL_BIN)-nodtb.bin $(obj)/$(SPL_BIN)-pad.bin \
$(obj)/$(SPL_BIN).dtb FORCE
$(call if_changed,cat)

$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-dtb.bin FORCE
$(call if_changed,copy)
else
$(obj)/$(SPL_BIN).bin: $(obj)/$(SPL_BIN)-nodtb.bin FORCE
$(call if_changed,copy)
endif


因为 ​​SPL_BIN​​ 在 ​第32行​ 定义为 ​​u-boot-spl​​:

u-boot/scripts/Makefile.spl

1
2
3
4
5


ifeq ($(CONFIG_TPL_BUILD),y)
SPL_BIN := u-boot-tpl
else
SPL_BIN := u-boot-spl
endif


由 ​168 行​ 上面的定义可以知道 ​​u-boot-spl.bin​​ 和 ​​u-boot-spl-nodtb.bin​​ 有关系。

接着查找到第223行

u-boot/scripts/Makefile.spl

1
2


$(obj)/$(SPL_BIN)-nodtb.bin: $(obj)/$(SPL_BIN) FORCE
$(call if_changed,objcopy)


 

​u-boot-spl-nodtb.bin​​ 是通过 objcopy 命令由 ​​u-boot-spl​​ 生成。

再看第246行:

u-boot/scripts/Makefile.spl

1
2


$(obj)/$(SPL_BIN): $(u-boot-spl-init) $(u-boot-spl-main) $(obj)/u-boot-spl.lds FORCE
$(call if_changed,u-boot-spl)


所以​​u-boot-spl​​ 是由 ​​u-boot-spl.lds​​ 链接文件生成的 ,但是目录下面有几个​​u-boot-spl.lds​​文件,到底是哪个 lds 文件呢,上面是 ​​$(obj)/u-boot-spl.lds​​, ​​obj​​ 在 ​1310 行​ 编译 ​​u-boot-spl.bin​​ 的时候赋值为 ​​obj=spl​​,所以我们需要看 ​​u-boot/spl/u-boot-spl.lds​​ 这个文件,但是如果你之前没有编译过这个文件是没有的。这个文件是如何生成的呢?我们稍后再看,先看 lds 文件的内容:

u-boot/spl/u-boot-spl.lds

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


MEMORY { .sram : ORIGIN = 0x402F0400, LENGTH = (0x4030B800 - 0x402F0400) }
MEMORY { .sdram : ORIGIN = 0x80a00000, LENGTH = 0x80000 }
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.text :
{
__start = .;
*(.vectors)
arch/arm/cpu/armv7/start.o (.text)
*(.text*)
} >.sram
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
. = ALIGN(4);
.data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
} >.sram
. = ALIGN(4);
__image_copy_end = .;
.end :
{
*(.__end)
} >.sram
.bss :
{
. = ALIGN(4);
__bss_start = .;
*(.bss*)
. = ALIGN(4);
__bss_end = .;
} >.sdram
}


链接文件里面说明了内存布局,​​arch/arm/cpu/armv7/start.o​​ 代码段都放在 SRAM 中,所以 ​​arch/arm/cpu/armv7/start.S​​ 就是我们要找的东西了。

lds 链接文件的生成

​u-boot/spl/u-boot-spl.lds​​ 这个文件的生成在 ​​u-boot/scripts/Makefile.spl​​ 有解释:

u-boot/scripts/Makefile.spl

1
2


$(obj)/u-boot-spl.lds: $(LDSCRIPT) FORCE
$(call if_changed_dep,cpp_lds)


​LDSCRIPT​​ 的定义:

u-boot/scripts/Makefile.spl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


# Linker Script
ifdef CONFIG_SPL_LDSCRIPT
# need to strip off double quotes
LDSCRIPT := $(addprefix $(srctree)/,$(CONFIG_SPL_LDSCRIPT:"%"=%))
endif

ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot-spl.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
$(error could not find linker script)
endif


 

可见 ​​Makefile.spl​​ 文件中先是判断有没有指定的 lds 文件,如果没有指定的,就查找 board 文件夹中目标板目录下面有没有 lds 文件,如果没有就查找相应的 cpu 目录,因为我们目标器件是 am335x,所以发现有 ​​u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds​​ 再通过 ​​cpp_lds​​ 命令编译成,​​cpp_lds​​ 是一组命令的集合,具体定义还是在 ​​Makefile.spl​​ 文件中,我们查看 ​​u-boot/arch/arm/cpu/armv7/am33xx/u-boot-spl.lds​​ 也发现 MLO 文件代码是在 ​​start.S​​ 文件中。

MLO 程序分析

查看 ​​start.S​​ 分析下 MLO 程序具体的执行流程,MLO 的 makefile 会根据 ​​CONFIG_SPL_BUILD​​ 编译不同的源文件,同样的在源码内也通过 ​​CONFIG_SPL_BUILD​​ 控制不同的代码执行,前面一部分 MLO 文件和 U-Boot 是类似的,进入到 ​​_main​​ 函数中两个程序的功能就开始出现差异了:

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


reset //(arch/arm/cpu/armv7/start.S)
save_boot_params_ret //(arch/arm/cpu/armv7/start.S)
|- disable interrupts
|- cpu_init_cp15 //(arch/arm/cpu/armv7/start.S)
| |- Invalidate L1 I/D
| |- disable MMU stuff and caches
|- cpu_init_crit //(arch/arm/cpu/armv7/start.S)
| |- lowlevel_init //(arch/arm/cpu/armv7/lowlevel_init.S)
| |- Setup a temporary stack
| |- Set up global data
| |- s_init //(arch/arm/cpu/armv7/am33xx/board.c)
| |- watchdog_disable
| |- set_uart_mux_conf
| |- setup_clocks_for_console
| |- uart_soft_reset
|- _main //(arch/arm/lib/crt0.S)

|(MLO)如果是 MLO 文件
|- board_init_f //(arch/arm/cpu/armv7/am33xx/board.c)
| |- board_early_init_f //(arch/arm/cpu/armv7/am33xx/board.c)
| | |- prcm_init
| | |- set_mux_conf_regs
| |- sdram_init //(board/ti/am335x/board.c) 初始化 DDR
|- spl_relocate_stack_gd
|- board_init_r //(common/spl/spl.c)
|- ...
|- spl_load_image //根据不同的启动方式加载 u-boot 镜像,
|- jump_to_image_no_args //进入u-boot代码运行


|(U-Boot)如果是U-Boot 镜像
|- board_init_f //(common/board_f.c)
| |- ...
| |- initcall_run_list(init_sequence_f)
| |- ...
|
|- relocate_code //(arch/arm/lib/relocate.S) 代码重定位
|- relocate_vectors //(arch/arm/lib/relocate.S) 向量表重定义
|- Set up final (full) environment
|- board_init_r //(common/board_r.c)
|- initcall_run_list(init_sequence_r)//初始化各种外设
|- main_loop()


当 U-Boot 重定位好代码、向量表之后,运行 ​​board_init_r​​ 函数,此函数会调用 ​​init_sequence_r​​ 列表里面的函数初始化各种外设驱动,最后在 ​​main_loop()​​ 函数中运行,U-Boot 有个 ​​bootdelay​​ 延时启动,如果不手动停止 U-Boot 会自动运行 ​​bootcmd​​ 包含的命令。

内核引导这部分放在另外一篇文章详细讲解。

U-Boot 编译

编译 U-Boot

编译 U-Boot 前我们需要安装交叉编译器:

1


# sudo apt-get install gcc-arm-linux-gnueabihf


下载 U-Boot 源码:

1


# git clone git://git.denx.de/u-boot.git


因为 U-Boot 官方已经支持了 Beaglebone Black 所以配置文件也已经自带了,编译输入如下命令:

1
2
3


# make distclean
# make am335x_boneblack_defconfig
# ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make


片刻后会生成 ​​MLO​​ 和 ​​u-boot.img​​ 文件。

配置 U-Boot 参数

有两种方式可以配置 U-Boot 的一些参数,分别是 ​​uEnv.txt​​ 和 ​​boot.src​​ 文件。

U-Boot 启动的时候会在启动分区寻找这两个文件。

boot.scr: This file is a U-Boot script. It contains instructions for U-Boot. Using these instruction, the kernel is loaded into memory, and (optionally) a ramdisk is loaded. boot.scr can also pass parameters to the kernel. This file is a compiled script, and cannot be edited directly. In some cases, boot.scr loads further instructions and configuration parameters from a text file.

uEnv.txt: A file with additional boot parameters. This file can be read by boot.scr, or by the boot sequence if there is no script file. uEnv.txt is a regular text file that can be edited. This file should have Unix line ending, so a compatible program must be used when editing this file.

U-Boot 启动的时候如果不打断会调用 ​​bootcmd​​ 包含的命令来执行,通常 ​​bootcmd​​ 会调用 bootscript 脚本也就是 ​​boot.scr​​ 里面的命令进行执行, ​​boot.scr​​ 通常也会先读取 ​​uEnv.txt​​ 确定额外参数,因为 ​​boot.src​​文件必须通过 ​​boot.cmd​​ 文件编译而来, ​​uEnv.txt​​ 则是可以任意编辑,这样可配置性就大大提高了。如果没有 ​​boot.src​​ 文件,U-Boot 有默认配置的 ​​bootcmd​​ 命令。

在 Beagelbone Black 中我们不需要额外的 ​​boot.scr​​ 文件,用默认的命令即可,默认的命令为:

1
2
3


#define CONFIG_BOOTCOMMAND \
"run findfdt; " \
"run distro_bootcmd"


​run distro_bootcmd​​ 最终会调用 ​​run mmcboot​​ 命令加载 ​​uEnv.txt​​ 文件,并且会运行 ​​uEnv.txt​​ 文件里面 ​​uenvcmd​​ 指代的命令。

​uEnv.txt​​ 从网络启动例子:

1
2
3
4
5
6
7


console=ttyO0,115200n8
ipaddr=192.168.23.2
serverip=192.168.23.1
rootpath=/exports/rootfs
netargs=setenv bootargs console=${console} ${optargs} root=/dev/nfs nfsroot=${serverip}:${rootpath},${nfsopts} rw ip=${ipaddr}:${serverip}:192.168.23.1:255.255.255.0:beaglebone:eth0:none:192.168.23.1
netboot=echo Booting from network ...; tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; run netargs; bootz ${loadaddr} - ${fdtaddr}
uenvcmd=run netboot


制作 U-Boot 的 SD 启动卡

制作 SD 启动卡之前首先需要为 SD 卡分区, ROM Code 启动的时候如果是从 MMC 设备加载启动代码,ROM Code 会从第一个活动分区寻找名为 “MLO” 的文件,并且此分区必须为 FAT文件系统。所以制作 U-Boot 的启动卡只需要一个带有 MLO 和 U-Boot 镜像的 FAT 格式的 SD 卡,如果需要启动 Linux 内核还需要别的分区,我们以后再讲。

有两种方式可以制作包含 U-Boot 的可启动的 SD 卡,一种是用 RAW Mode 的方式,还有一种是用 FTA 的方式。

FTA 模式下只要建立一个 FTA 分区再把 MLO 和 uboot.img 文件拷贝进去即可。

我是使用的 USB 读卡器,插入后 Linux ​​/dev/​​ 目录会显示 ​​/dev/sd*​​ 设备,我这里多出两个设备分别显示 ​​/dev/sdb​​ 和 ​​/dev/sdb1​​ ,其中 ​​/dev/sdb​​ 表示一整个物理磁盘, ​​/dev/sdb1​​ 表示的是具体的分区。

使用命令 ​​sudo fdisk /dev/sdb​​ 管理磁盘:

​a​​ : toggle a bootable flag(设置或取消启动表示)

​b​​ : edit bsd disklabel(编辑 bsd disklabel)

​c​​ : toggle the dos compatibility flag

​d​​ : delete a partition (删除一个分区)

​l​​ : list known partition types (列出已知的分区类型)

​m​​ : print this menu (打印次列表)

​n​​ : add a new partition (增加一个新分区)

​o​​ : create a new empty DOS partition table (建立一个新的空 DOS 分区表)

​p​​ : print the partition table (打印分区表)

​q​​ : quit without saving changes (不保存退出)

​s​​ : create a new empty Sun disklabel

​t​​ : change a partition’s system id

​u​​ : change display/entry units

​v​​ : verify the partition table (验证分区表)

​w​​ : write table to disk and exit (把分区表写入磁盘)

​x​​ : extra functionality (experts only) (额外的功能)

新建启动分区:

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


Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Device Boot Start End Blocks Id System
/dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA)

Command (m for help): m
Command action
a toggle a bootable flag
b edit bsd disklabel
c toggle the dos compatibility flag
d delete a partition
l list known partition types
m print this menu
n add a new partition
o create a new empty DOS partition table
p print the partition table
q quit without saving changes
s create a new empty Sun disklabel
t change a partition's system id
u change display/entry units
v verify the partition table
w write table to disk and exit
x extra functionality (experts only)

Command (m for help): d
Selected partition 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Device Boot Start End Blocks Id System

Command (m for help): n
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p
Partition number (1-4, default 1):
Using default value 1
First sector (2048-15130623, default 2048):
Using default value 2048
Last sector, +sectors or +size{K,M,G} (2048-15130623, default 15130623):
Using default value 15130623

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Device Boot Start End Blocks Id System
/dev/sdb1 2048 15130623 7564288 83 Linux

Command (m for help): t
Selected partition 1
Hex code (type L to list codes): c
Changed system type of partition 1 to c (W95 FAT32 (LBA))

Command (m for help): a
Partition number (1-4): 1

Command (m for help): p

Disk /dev/sdb: 7746 MB, 7746879488 bytes
24 heads, 20 sectors/track, 31522 cylinders, total 15130624 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Device Boot Start End Blocks Id System
/dev/sdb1 * 2048 15130623 7564288 c W95 FAT32 (LBA)

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: If you have created or modified any DOS 6.x
partitions, please see the fdisk manual page for additional
information.
Syncing disks.


 

建立好新的分区之后需要命名并格式化:

1


# sudo mkfs.vfat -F 32 -n boot /dev/sdb1


格式化之后挂载磁盘并把 ​​MLO​​ 文件和 ​​u-boot.img​​ 文件拷贝进去:

1
2
3
4
5
6
7
8
9


# sudo mount /dev/sdb1 /media/jg/boot 
# sudo cp MLO /media/jg/boot/MLO
# ls /media/jg/boot
MLO
# sudo cp u-boot.img /media/jg/boot/u-boot.img
# ls /media/jg/boot
MLO u-boot.img
# sync
# sudo umount /media/jg/boot


接着把 SD 卡插入 Beaglebone Black 并且按着 S2 按钮上电,从串口打印出的信息我们可以看到 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


U-Boot SPL 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20)
Trying to boot from MMC
Card doesn't support part_switch
MMC partition switch failed
*** Warning - MMC partition switch failed, using default environment

reading u-boot.img
reading u-boot.img

U-Boot 2016.03-rc2-00084-g595af9d (Feb 29 2016 - 22:21:20 +0800)

Watchdog enabled
I2C: ready
DRAM: 512 MiB
MMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1
*** Warning - bad CRC, using default environment

Net: <ethaddr> not set. Validating first E-fuse MAC
cpsw, usb_ether
Press SPACE to abort autoboot in 2 seconds
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
reading boot.scr
** Unable to read file boot.scr **
reading uEnv.txt
** Unable to read file uEnv.txt **
** File not found /boot/zImage **
switch to partitions #0, OK
mmc1(part 0) is current device
Scanning mmc 1:1...
switch to partitions #0, OK
mmc1(part 0) is current device
SD/MMC found on device 1
reading boot.scr
** Unable to read file boot.scr **
reading uEnv.txt
** Unable to read file uEnv.txt **
** File not found /boot/zImage **
## Error: "bootcmd_nand0" not defined
cpsw Waiting for PHY auto negotiation to complete......... TIMEOUT !
BOOTP broadcast 1
BOOTP broadcast 2
BOOTP broadcast 3
BOOTP broadcast 4


启动之后,前面一段打印信息是 MLO 程序打印出来的,读取 U-Boot 之后开始运行完整的 U-Boot,之后程序扫描各个设备读取 ​​boot.scr​​ 和 ​​uEnv.txt​​ 文件,接着再读取是否有 Linux 内核可以运行。