uboot的最终目的是用来启动内核的,它读取bootenv,获取bootcmd并执行它,bootcmd执行将kernel拷贝到ram中,并将bootargs传递给kernel,然后启动内核。
实际工作中uboot开关,主要涉及:修改启动方式、修改分区表(bootargs)、验签、tf卡烧录等。
uboot启动主要分为两个阶段,主要在cpu目录下的start.S文件中:
第一阶段:硬件的初始化,包括:
1、设置处理器模式为SVC模式(管理员)
2、关闭看门狗:避免reset MCU,此外uboot阶段也用不到watchdog
3、屏蔽中断:此时用不到中断,同时避免因意外中断导致uboot启动失败
4、设置时钟:为了之后配置外设
5、初始化sdram:为了将内核拷贝之sdram中运行
6、设置栈
7、清除bss段:bss段是用来存储静态变量,全局变量的。
然后程序跳转到start_arm_boot函数,宣告第一阶段的结束。
第二阶段:
1.从flash中读出内核
2.启动内核:执行start_arm_boot,主要流程为:
进入main_loop,执行s=getenv(“bootcmd”)和run_command(s,0),它会根据bootcmd环境变量的内容来启动内核,bootcmd环境变量中指示了从某个flash地址读取内核到启动的内存地址,然后启动它,再执行bootm。
uboot启动的内核为uImage,这种格式的内核是由两部分组成:真正的内核和内核头部组成,头部中包括内核中的一些信息,比如内核的加载地址,入口地址。
uboot在接受到启动命令后,要做的主要是,1,读取内核头部,2,移动内核到合适的加载地址,3,启动内核,执行do_bootm_linux
reset/re: 重启系统
printenv: 打印当前系统环境变量
setenv: 设置环境变量, 格式: setenv name value, 表示将 name 变量设置成 value 值;如果没有value, 表示删除该变量。
saveenv: 保存环境变量到 flash 中。
fatls: 显示 SD 卡的文件, 格式 fatls mmc 0 。
示例:
isvp_t40# printenv
bootargs=console=ttyS1,115200n8 32M@0x0 rmem=32M@0x2000000
init=/linuxrc rootfstype=squashfs root=/dev/mtdblock2 rw
mtdparts=jz_sfc:256k(boot),2560k(kernel),2048k(root),-(appfs)
bootcmd=sf probe;sf read 0x80600000 0x40000 0x280000; bootm 0x80600000
bootdelay=1
ipaddr=193.169.4.151
serverip=193.169.4.2
bootargs 内核通过 bootargs 找到文件系统位置,并挂载它。
● console=ttyS1,115200n8 设置串口号为 ttyS1, 波特率为 115200
● mem=32M@0x0 rmem=32M@0x2000000 分配内存,@为从0x2000000开始分配32M给rmem。
● init=/linuxrc 系统首先运行/linuxrc 文件
● rootfstype=squashfs 指定文件系统类型,例如jffs2、ext4等
● root=/dev/mtdblock2 rw 告诉内核根文件从 mtd 分区 3 挂载, 可读写
● mtdparts=jz_sfc:256k(boot),2560k(kernel),2048k(root),-(appfs) mtd :指定分区大小,有些bootargs中没有声明该字段。
uboot启动kernel所执行的命令
● sf probe;sf read 0x80600000 0x40000 0x280000; bootm 0x80600000 从nor flash启动
● ipaddr 193.169.4.151 板子的 IP
● serverip=193.169.4.2 服务器 IP 地址
在uboot命令行中,执行help,查看具体命令的说明
这里拷贝nor flash使用sf命令,有时也用sfcnor命令,具体平台具体分析
sf probe: 使能spi flash.
sf read 0x80600000 0x40000 0x280000: 从flash的0x40000开始,读取0x280000长度的数据,拷贝到内存0x80600000这个位置。
其中,0x280000就是kernel(2560k)的长度,0x40000是boot(4*64 = 256k)的长度。
一般对外发布补丁文件,通过打补丁的方式将自己的代码写入对应版本的uboot中
有时厂家只提供补丁文件,和uboot发行版本号,需要自己打patch,进行编译。
1、patch -px < 补丁文件
命令说明:patch -px < 补丁文件
x:根据补丁文件中的描述,来确定的数字
例如:如果补丁文件中内容如下:
— u-boot-1.1.6/board/100ask24x0/100ask24x0.c 1970-01-01 08:00:00.000000000 +0800
如果我们在u-boot-1.1.6目录下打补丁,则需要忽略补丁文件中的第一个“u-boot-1.1.6”目录,因此这里为“-p1”
1、查看顶层目录下boards.cfg
内容如下:
编译命令 架构 芯片名称 开发板名字 厂商 soc名称 功能定义
halley2_uImage_sfc_nand mips xburst halley2 ingenic x1000 halley2:SPL_SFC_NAND,MTD_SFCNAND
首先根据soc和开发板名字确定大体范围,然后根据功能定义确定自己需要的编译命令。
编译命令: make halley2_uImage_sfc_nand ==> 生成image
功能定义:
开头的字符串:例如上述的halley2,说明,halley2_uImage_sdc_nand对应的配置文件名是:halley2.h,可以在include/configs下找到
包含nor =》nor flash启动,一般是soc内的nor flash,速度快,但是flash容量小
包含nand => nand flash 启动,看芯片规格,是否已经有nand?是内置nand、还是外置nand?
包含emmc => mmc设备启动,一般是sd卡启动、tf卡启动等。
修改工具链:顶层目录Makefile中,修改CROSS_COMPILE变量
make clean; make halley2_uImage_sfc_nand
在顶层目录下生成.bin文件
bootcmd中指定了,从flash中的哪个位置拷贝多长数据,至内存,例如:
bootcmd=sfcnor read 0x40000 0x340000 0x80800000 ;bootm 0x80800000
从nor flash的0x40000(4*64 = 256k)位置,拷贝0x340000(3M + 4 *64 k = 3M + 256kb)数据,至内存0x8080000处
当,内核增加一些功能配置,导致内核数据变大了,大于bootcmd中指定的内核大小时,就需要改bootcmd
法1:在boot cmd中setenv修改,不推荐
法2:修改配置文件
通过boards.cfg找到编译命令对应的功能定义,其中第一个字段就是配置文件名。
vi include/configs/配置文件名.h
修改CONFIG_BOOTCOMMAND即可。
1、查看tf卡文件名:fatls mmc 0
2、单烧分区:
单烧rootfs,其他分区的烧类似
fatload mmc 0 0x80650000 rootfs.squashfs ;sf probe;sf erase 0x50000 0x230000;sf write 0x80650000 0x50000 0x230000
把tf卡的rootfs.squashfs拷贝到mmc(内存)的0x80650000中,该地址是从uboot的起始地址偏移到rootfs分区的起始地址,得到的。
board_r.c
-》
common/main.c中
/Makefile:
LIBS-y
例如在common下加自己的文件:
修改common/Makefile,加COBJS-XXX += *.c
以atmel的sama5d3x为例:
配置命令:make CROSS_COMPILE=arm-vfpv4-linux-gnueabi- sama5d3xek_nandflash_defconfig
由make xxx_config可知:
顶层makefile中一定有其对应的规则,例如config或%config,在Makfile中查找可得:
./Makefile:
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter config %config,$(MAKECMDGOALS)),)
config-targets := 1
ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
mixed-targets := 1
endif
endif
endif
···
ifeq ($(config-targets),1)
KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG
config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
分析:
( K B U I L D E X T M O D ) 没 有 被 定 义 , 则 i f e q ( (KBUILD_EXTMOD)没有被定义,则ifeq ( (KBUILDEXTMOD)没有被定义,则ifeq((KBUILD_EXTMOD),)成立
( f i l t e r c o n f i g (filter config %config, (filterconfig(MAKECMDGOALS)):
$(MAKECMDGOALS):保存执行make时所带的命令行参数
filter: $(filter PATTERN …, TEXT),取出TEXT中符合PATTREN模式的内容
因此,得到config-targets := 1
由上述可知config目标的三个前置条件为:
scripts_basic
1、scripts_basic
./Makefile:
$(srctree)/scripts/Kbuild.include: ;
include $(srctree)/scripts/Kbuild.include
···
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
分析:
其中 ( b u i l d ) 在 (build)在 (build)在(srctree)/scripts/Kbuild.include文件中定义
Kbuild.include:
build := -f $(srctree)/scripts/Makefile.build obj
则,scripts_basic目标执行了:make -f ./scripts/Makefile.build obj=scripts/basic
即-f执行特定的makefile(执行不规则的makefile文件前需要添加-f),执行./scripts/Makefile.build,并传入参数obj=scripts/basic
2、./scripts/Makefile.build
prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif
PHONY := __build
__build:
分析:
$(patsubst ( p r e f i x ) / (prefix)/%,%, (prefix)/(obj)), 将 ( o b j ) 中 的 (obj)中的 (obj)中的(prefix)/%替换为%。
因为obj = scripts/basic,其中没有tpl或spl,因此,不作替换。即src = scripts/basic
./scripts/Makefile.build中没有obj目标,因此选择默认目标作为目标
./scripts/Makefile.build:
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
如果scripts/basic目录下有Kbuild,就包含该Kbuild,否则包含scripts/basic目录下的Makefile,得到include scripts/basic/Makefile。
scripts/basic/Makefile:
hostprogs-y := fixdep
always := $(hostprogs-y)
./scripts/Makefile.build:
ifneq ($(hostprogs-y)$(hostprogs-m),)
include scripts/Makefile.host
endif
...
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
include scripts/Makefile.host
__build:依赖于$(always),即scripts/basic/Makefile中定义的always:=fixdep
则需要先编译fixdep,它的编译规则在scripts/Makefile.host中指定。
scripts/Makefile.host:
quiet_cmd_host-csingle = HOSTCC $@
cmd_host-csingle = $(HOSTCC) $(hostc_flags) -o $@ $< \
$(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))
$(host-csingle): $(obj)/%: $(src)/%.c FORCE
$(call if_changed_dep,host-csingle)
最后在scripts/basic/下生成fixdep文件,fixdep工具用于更新编译文件
3、%config的执行命令:
( Q ) (Q) (Q)(MAKE) $(build)=scripts/kconfig $@
同上,首先进入./scripts/Makefile.build,并传入参数obj=scripts/kconfig xxx_config
即:make -f ./scripts/Makefile.build obj=scripts/kconfig sama5d3xek_nandflash_defconfig
包括以下几步:
a.在./scripts/Makefile.build里面包含scripts/kconfig/Makefile
b.include scripts/Makefile.host,在Makefile.host制定编译规则
c.生成scripts/kconfig/conf二进制文件
d.执行如下命令:
scripts/kconfig/conf --defconfig=arch/…/configs/sama5d3xek_nandflash_defconfig Kconfig
即scripts/kconfig/conf --defconfig=configs/sama5d3xek_nandflash_defconfig Kconfig
该命令引入了所有Kconfig和sama5d3xek_nandflash_defconfig中定义的CONFIG_XXX变量,最后输出到.config中。
至此,make sama5d3xek_nandflash_defconfig完成。
make xxx_config:
a.执行script/makefile.build,生成fixdep工具,用于处理编译中间文件
b.在scripts/kconfig/目录下生成conf工具,用于处理配置过程中的参数
c.将Kconfig文件中的定义和xxx_config文件中定义的CONFIG_XXX变量,输出到.config中。
1、构建include/config/auto.conf
-include include/config/auto.conf
-include include/config/auto.conf.cmd
$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
@# If the following part fails, include/config/auto.conf should be
@# deleted so "make silentoldconfig" will be re-run on the next build.
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
{ rm -f include/config/auto.conf; false; }
@# include/config.h has been updated after "make silentoldconfig".
@# We need to touch include/config/auto.conf so it gets newer
@# than include/config.h.
@# Otherwise, 'make silentoldconfig' would be invoked twice.
$(Q)touch include/config/auto.conf
该include/config/auto.conf文件并不存在,因此include/config/auto.conf成为目标
为了执行-include include/config/auto.conf,需要构建include/config/auto.conf目标
即include/config/%.conf。
Makefile:
.config include/config/auto.conf.cmd: ;
include/config/%.conf: .config include/config/auto.conf.cmd
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
@# If the following part fails, include/config/auto.conf should be
@# deleted so "make silentoldconfig" will be re-run on the next build.
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
{ rm -f include/config/auto.conf; false; }
@# include/config.h has been updated after "make silentoldconfig".
@# We need to touch include/config/auto.conf so it gets newer
@# than include/config.h.
@# Otherwise, 'make silentoldconfig' would be invoked twice.
$(Q)touch include/config/auto.conf
将include/configs/xxx.h、.config、include的Makefile,将其中所有的CONFIG_XXX变量包含进来
2、链接CONFIG_XXX
Makefile:
libs-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 += drivers/dma/
libs-y += drivers/gpio/
libs-y += drivers/i2c/
libs-y += drivers/mmc/
libs-y += drivers/mtd/
libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
libs-y += drivers/mtd/onenand/
libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
libs-y += drivers/mtd/spi/
libs-y += drivers/net/
libs-y += drivers/net/phy/
libs-y += drivers/pci/
libs-y += drivers/power/ \
drivers/power/fuel_gauge/ \
drivers/power/mfd/ \
drivers/power/pmic/ \
drivers/power/battery/
libs-y += drivers/spi/
libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/
libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
libs-y += drivers/serial/
libs-y += drivers/usb/eth/
libs-y += drivers/usb/gadget/
libs-y += drivers/usb/host/
libs-y += drivers/usb/musb/
libs-y += drivers/usb/musb-new/
libs-y += drivers/usb/phy/
libs-y += drivers/usb/ulpi/
libs-y += common/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
libs-y := $(sort $(libs-y))
u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
根据CONFIG_XXX决定编译哪些lib,并最终链接成uboot
3、编译终极目标
make没有指定目标,默认构建终极目标_all。_all依赖all,all又依赖$(ALL-y)
$(ALL-y)展开后为:checkarmreloc RKLoader-uboot.bin u-boot.srec u-boot.bin System.map binary_size_check.
4、include/config/uboot.release
define filechk_uboot.release
echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
endef
# Store (new) UBOOTRELEASE string in include/config/uboot.release
include/config/uboot.release: include/config/auto.conf FORCE
$(call filechk,uboot.release)
调用filechk_uboot.release
5、构建$(u-boot-dirs)
$(u-boot-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
其中, ( u − b o o t − d i r s ) 由 x x x d e f c o n f i g 中 的 : C O N F I G S Y S A R C H = " a r m " C O N F I G S Y S C P U = " a r m v 7 " 决 定 , 即 (u-boot-dirs)由xxx_defconfig中的: CONFIG_SYS_ARCH="arm" CONFIG_SYS_CPU="armv7" 决定,即 (u−boot−dirs)由xxxdefconfig中的:CONFIGSYSARCH="arm"CONFIGSYSCPU="armv7"决定,即(u-boot-dirs):= arch/arm/cpu/armv7
最终,包含arch/arm/cpu/armv7/Makefile,该Makefile中定义obj-y
上述obj-y,就是需要编译的文件,将它们编译生成的.o文件链接为arch/arm/cpu/armv7/build-in.o文件
6、uboot.lds
uboot.lds:arch/arm/cpu/u-boot.lds
uboot编译出来的第一个链接脚本就是执行u-boot.lds链接脚本,u-boot.lds中指定了uboot起始函数为:
ENTRY(_start)
7、_start
_start:在arch/arm/lib/vectors.S中定义,它主要定义了异常向量表,及其操作函数。
_start会跳去执行reset
reset:在arch/arm/cpu/armv7/start.S中定义
8、board.c
board.c:arch/arm/lib
uboot的移植工作主要在此处,包括网卡初始化,串口初始化,中断初始化,中断使能,环境变量的初始化等工作。
board_init_r():
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop();
}
进入死循环,main_loop()定义在common/main.c下
8、main.c
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifndef CONFIG_SYS_GENERIC_BOARD
puts("Warning: Your board does not use generic board. Please read\n");
puts("doc/README.generic-board and take action. Boards not\n");
puts("upgraded by the late 2014 may break or be removed.\n");
#endif
modem_init();
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
}
9、添加自己的命令
在common/下创建文件cmd_hello.c
参考cmd_help.c拷贝头文件,写入如下内容:
#include
#include
static int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int i;
printf("hello world!\n");
for(i = 0; i < argc; i++)
{
printf("argv[%d]:%s\n", i, argv[i]);
}
return 0;
}
U_BOOT_CMD(
hello, CONFIG_SYS_MAXARGS, 1, do_hello,
"hello uboot,-just for test",
"\n"
" - hello long help ....\n"
);
修改Makefile,添加对cmd_hello.c的编译。
分析:
U_BOOT_CMD是一个宏定义,在command.h中定义如下:
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_MKENT_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(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
展开就是:
cmd_tbl_t _u_boot_list_2_do_bootm_2_bootm __aligned(4) __attribute__((unused,section(".u_boot_list_2_"#_list"_2_"#_name)))
={ bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,"boot application image from memory", bootm_help_text, NULL}
即:U_BOOT_CMD的作用就是定义一个结构体变量(struct cmd_tbl_s),并将其存放在uboot的没有被占用的section段中。
在uboot中输入的内容,uboot会查找uboot相应的section段,执行相应的命令。
10、uboot分区
a.include/configs/sama5d3xek.h
该文件中包含了:
#include “at91-sama5_common.h”
b.include/at91-sama5_common.h
#define CONFIG_BOOTARGS \
"console=ttyS0,115200 earlyprintk " \
"mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro," \
"256K(env),256k(env_redundent),256k(spare)," \
"512k(dtb),6M(kernel)ro,-(rootfs) " \
"rootfstype=ubifs ubi.mtd=7 root=ubi0:rootfs"