u-boot分析与使用
u-boot分析与使用
零、U-boot使用
uboot 命令中输入的数字均为十六进制,而非十进制,注意!!!
0.1信息查询命令
buinfo:查看板子信息
printenv:查看环境变量(直接输入print也行)
version:查看uboot版本号
0.2环境变量相关操作
- 
setenv:新建、删除、设置或者修改环境变量的值
1
setenv [变量名] [变量值]
如果变量值中有空格,可以用单引号
'括起来:1
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
 - 
saveenv:保存修改后的环境变量
 
0.3内存操作
md
内存查看命令,用于显示内存值
1  | md[.b, .w, .l] address [# of objects]  | 
命令中的[.b .w .l]对应 byte、 word 和 long,即分别以 1 个字节、 2 个字节、 4 个字节来显示内存值。 address 就是要查看的内存起始地址, [# of objects]表示要查看的数据长度,这个长度和你所选择的显示格式有关。
nm
修改指定地址的内存值
1  | nm[.b, .w, .l] address  | 
同样以.b、 .w 和.l 来指定操作格式。 address 就是要修改的内存起始地址。 值得注意的是该命令修改完后地址不会自增
mm
修改指定地址的内存值
1  | mm[.b, .w, .l] address  | 
与nm命令类似,区别就是修改完后地址会自增
mw
使用一个指定的数据填充一段内存
1  | mw[.b, .w, .l] address value [count]  | 
以.b、 .w 和.l 来指定操作格式。 address 表示要填充的内存起始地址,value为要填充的数据, count是填充的长度。
cp
数据拷贝命令,用于将 DRAM 中的数据从一段内存拷贝到另一段内存中,或者把 Nor Flash 中的数据拷贝到 DRAM 中
1  | cp[.b, .w, .l] source target count  | 
以.b、 .w 和.l 来指定操作格式。source 为源地址, target 为目的地址, count 为拷贝的长度
cmp
比较命令,用于比较两段内存的数据是否相等
1  | cmp[.b, .w, .l] addr1 addr2 count  | 
以.b、 .w 和.l 来指定操作格式。addr1 为第一段内存首地址, addr2 为第二段内存首地址, count 为要比较的长度。
0.4网络操作
该部分命令需要有以下环境变量的支持:
1  | #板卡ip地址  | 
ping
验证网络是否正常,不做过多解释
注意!只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot(uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 会失败)
dhcp
dhcp会自动获取 IP 地址,同时还会通过 TFTP 来启动 linux 内核,输入? dhcp可查看uboot中 dhcp 命令详细的信息,这里一般用于自动获取ip地址
nfs
通过nfs(Network File System,网络文件系统)使得板卡能够直接从服务器(服务器需要提前布置好NFS服务与NFS文件目录)上下载相关资源到DRAM中(如zImage、文件系统、设备树等)
1  | nfs [loadAddress] [[hostIPaddr:]bootfilename]  | 
其中 loadAddress 是要保存的 DRAM 地址, [[hostIPaddr:]bootfilename]是要下载的文件地址(需要输入服务器中的完整路径)
tftp
与nfs类似,均用于通过网络下载文件到 DRAM 中(服务器需要提前布置好TFTP服务与TFTP文件目录)
1  | tftp [loadAddress] [[hostIPaddr:]bootfilename]  | 
loadAddress 是 文 件 在 DRAM 中 的存 放 地 址 ,[[hostIPaddr:]bootfilename]是要从 Ubuntu 中下载的文件(这里不需要输入服务器中的完整路径,直接从TFTP文件目录中获取)
0.5EMMC和SD卡操作
mmc info
mmc info命令和mmcinfo命令相同,均为输出当前选中的设备信息,不做过多解释
mmc rescan
用于扫描当前开发板上所有的 MMC 设备,包括 EMMC 和 SD 卡
mmc list
查看当前开发板一共有几个 MMC 设备,通过该命令可以查看当前选中的设备(一般来说0为SD卡,1为EMMC)
mmc dev
切换当前 MMC 设备
1  | mmc dev [dev] [part]  | 
[dev]用来设置要切换的 MMC 设备号,[part]是分区号(可以不写,默认为分区 0)
mmc part
查看选中的设备对应的分区信息
mmc read
读取当前选中 mmc 设备的数据
1  | mmc read addr blk# cnt  | 
addr 是数据读取到 DRAM 中的地址, blk# 是要读取的块起始地址(十六进制),一个块是 512字节,这里的块和扇区是一个意思,在 MMC 设备中我们通常说扇区, cnt 是要读取的块数量(十六进制)。
mmc write
将数据写入到当前选中 mmc 设备中
1  | mmc write addr blk# cnt  | 
addr 是数据写入到 DRAM 中的地址, blk# 是要写入的块起始地址(十六进制),一个块是 512字节,这里的块和扇区是一个意思,在 MMC 设备中我们通常说扇区, cnt 是要写入的块数量(十六进制)。
mmc erase
擦除 MMC 设备的指定块,最好别用
1  | mmc erase blk# cnt  | 
blk# 为要擦除的起始块, cnt 是要擦除的数量。
0.6FAT文件系统操作
需要在 uboot 中对 SD 卡或者 EMMC 中存储的文件进行操作时需要用到该部分操作命令(只支持FAT格式)
fatinfo
查询指定 MMC 设置指定分区的文件系统信息
1  | fatinfo <interface> [<dev[:part]>]  | 
interface 表示接口,比如 mmc。dev 是查询的设备号, part 是要查询的分区。
fatls
查询 FAT 格式设备的目录和文件信息
1  | fatls <interface> [<dev[:part]>] [directory]  | 
interface 是要查询的接口,比如 mmc。dev 是要查询的设备号,part 是要查询的分区,directory是要查询的目录(默认为根目录/)
fstype
查看 MMC 设备某个分区的文件系统格式
1  | fstype <interface> <dev>:<part>  | 
interface 是要查询的接口,比如 mmc。dev 是要查询的设备号,part 是要查询的分区
fatload
将指定的文件读取到 DRAM 中
1  | fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]  | 
interface 为接口,比如 mmc。 dev 是设备号, part 是分区, addr 是保存在 DRAM 中的起始地址, filename 是要读取的文件名字。 bytes 表示读取多少字节的数据(0或者省略表示读取整个文件)。pos 是要读的文件相对于文件首地址的偏移(0或者省略表示从文件首地址开始读取)
fatwrite
uboot 默认没有使能 fatwrite 命令,需要修改板子配置头文件来使能宏CONFIG_FAT_WRITE。该命令用于将 DRAM 中的数据写入到 MMC 设备中
1  | fatwrite <interface> <dev[:part]> <addr> <filename> <bytes>  | 
interface 为接口,比如 mmc, dev 是设备号, part 是分区, addr 是要写入的数据在 DRAM中的起始地址, filename 是写入的数据文件名字, bytes 表示要写入多少字节的数据
0.7EXT文件系统操作
uboot 有 ext2 和 ext4 这两种格式的文件系统的操作命令,常用的就四个命令,分别为:ext2load、 ext2ls、 ext4load、 ext4ls 和 ext4write。  这些命令与FAT文件系统的类似,在此不做过多叙述。
0.8BOOT操作
bootz
用于启动zImage镜像文件
1  | bootz [addr [initrd[:size]] [fdt]]  | 
addr 是 Linux 镜像文件在 DRAM 中的位置, initrd 是 initrd 文件在DRAM 中的地址(不使用该参数可以用-代替), fdt 就是设备树文件在 DRAM 中的地址。
bootm
与上述命令类似,但是是启动uImage镜像文件,如果不使用设备树,则命令为:
1  | bootm addr  | 
addr 是 uImage 镜像在 DRAM 中的首地址。如果要使用设备树则和bootz命令一致
boot
通过读取环境变量 bootcmd 来启动 Linux 系统
例子:
1  | setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'  | 
0.9其他命令
reset
复位命令,不做过多解释
go
用于运行裸机程序
1  | go addr [arg ...]  | 
addr 是应用在 DRAM 中的首地址,arg是传入的参数
run
用于运行环境变量中定义的命令
1  | setenv mybootemmc 'fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ullalientek-emmc.dtb;bootz 80800000 - 83000000'  | 
一、Makefile结构分析
编译u-boot时,一般需要采取以下命令:
1  | make V=1 xxx_config # V=1输出全部内容  | 
下面依次介绍各命令的作用:
其中会用到以下命令,提前熟悉下:
 1
2 #搜索文件内的变量名
grep -nR "变量名"
1、make xxx_config
该命令会执行顶层Makefile中的目标%config,具体如下所示:
1  | %config: scripts_basic outputmakefile FORCE  | 
其中一些变量在其他地方定义过,总结如下:
- 
相关参数值:
1
2
3
4
5
6
7#使用参数V=1时
Q = # 输出详细信息
#不使用参数V=1时
Q = @ # 此时输出信息为精简版
MAKE = make
build = -f ./scripts/Makefile.bulid obj - 
outputmakefile为空,scripts_basic:
1
2
3scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount展开后:
1
2
3scripts_basic:
make -f ./scripts/Makefile.bulid obj=scripts/basic
rm -f .tmp_quiet_recordmcount - 
FORCE表示会让该依赖每次都执行 
最后实际上运行的命令为:
1  | #依赖中:  | 
通过上述命令,可以看出都是调用./scripts/Makefile.bulid,该文件也可以视为Makefile,下面分析上述两条命令:
1、命令make -f ./scripts/Makefile.bulid obj=scripts/basic:
未指定目标,即采取./scripts/Makefile.bulid文件中PHONY指定的默认目标:__build:
1  | __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \  | 
利用echo输出各变量,有:
1  | #顶层Makefile中:  | 
综上,有:
1  | __build: /scripts/basic/fixdep  | 
所以这条命令只是编译可执行文件/scripts/basic/fixdep
2、命令make -f ./scripts/Makefile.bulid obj=scripts/kconfig xxx_defconfig:
在Makefile.bulid中通过include $(kbuild-file),即调用include ./scripts/kconfig/Makefile,在这里边指定目标:
1  | %_defconfig: $(obj)/conf  | 
这里替换掉各个变量后有:
1  | %_defconfig: scripts/kconfig/conf  | 
即有:
- 编译生成软件scripts/kconfig/conf
 - 调用编译好的软件scripts/kconfig/conf生成配置输出文件
.config 
2、make
未指定目标,即采取Makefile文件中PHONY指定的默认目标,这里PHONY依赖有点多,但是重心为以下依赖:
PHONY->_all->$(ALL-y)
其中$(ALL-y)中的依赖也很多,但是重点是u-boot.bin依赖,且u-boot.bin依赖的文件也比较多,只需关注以下依赖即可:
1  | u-boot.bin: u-boot-nodtb.bin FORCE  | 
- 
libs-y会包括很多,但是基本上均为各种源码所对应的.o文件1
2
3
4
5
6
7
8
9libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
...
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))- 最后的
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))调用了patsubst函数,将libs-y中的/替换为/built-in.o,即将原来的各个目录替换为了对应目录下的built-in.o文件 built-in.o文件是通过相同文件夹下.built-in.o.cmd文件生成的
 - 最后的
 
综上,最后简化后的makefile如下:
1  | u-boot: arch/arm/cpu/armv7/start.o lib/built-in.o ...(大量的built-in.o文件) u-boot.lds FORCE  | 
- u-boot.lds为编译出的链接脚本(真正的链接脚本来自
arch/arm/cpu/u-boot.lds) 
3、链接脚本
链接地址
通过make V=1 -j8编译U-boot时,最后会输出Entry Point: xxxxxxxx,此地址即为链接地址
该地址可以通过编译输出时的log信息发现,在最后链接时,会加入-Ttext xxxxxxxx,即在链接过程中即指定了链接地址
通过grep -nR "xxxxxxxx"可以发现最后的链接地址来自于include/configs/mx6_common.h中的**CONFIG_SYS_TEXT_BASE**宏
脚本分析
编译成功后会生成一个u-boot.map文件,其中包括了各段的具体地址,结合该.lds文件分析可以得到各段的地体地址
1  | OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")  | 
二、初始化流程
详见思维导图:U-Boot启动流程.xmind

三、u-boot命令实现
- 
所有命令通过结构体
cmd_tbl_t进行封装(在include/command.h中定义)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
char *help; /* Help message (long) */
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
};
typedef struct cmd_tbl_s cmd_tbl_t; - 
所有命令通过结构体中的
name成员进行查找 - 
这些结构体存在一个uboot自定义的段内:
1
2
3
4
5
6
7
8SECTIONS
{
...
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*))); //uboot命令存放段
}
...
} - 
通过宏
U_BOOT_CMD可以快速定义这些命令结构体: 
1  | U_BOOT_CMD(  | 
例子:
 1
2
3
4
5
6
7
8
9
10
11
12
13
14 U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
);
四、一般的移植思路
- 芯片厂商或者uboot官网找到uboot的源码
 - 直接在
configs/下复制类似芯片或板卡的xxx_defconfig默认配置文件 - 在
include/configs下找到类似芯片或板卡的配置头文件,直接复制 - 在
board/下找到类似芯片或板卡的板级文件夹,直接复制 

