一、U-boot功能
uboot(启动内核)=裸机
1.uboot源码如何组织
芯片架构(arm、x86…)->soc厂商相同架构不同的芯片产品->板卡厂商相同芯片不同的板卡产品
uboot目的是启动内核,所以uboot需要先初始化内存、时钟和其他硬件,将内核从Flash拷贝到内存中。
内核的作用是去启动APP。APP在Flash中,其中内核需要能读写Flash(驱动程序,包括U盘、网络、LCD、输入设备)、读写文件(文件系统)、找到并启动APP。
2.驱动如何使用设备树
烧录的u-boot=uboot.bin+某个dtb(配置文件)
dtb中保存了配置文件,保证uboot.bin不臃肿。uboot启动后会去读dtb配置文件。相较于内核的设备树文件,uboot的设备树文件时简化版本。
二、Makefile使用技巧
# 将在make过程中使用到的Makefile中的规则和变量写入1.txtmake-p>1.txtmakefile的remake功能
当 make 运行过程中,检测到自己所依赖的 Makefile/include 文件被更新,就自动重新加载 Makefile、重新运行 make。下例中引用test文件时并未创建test文件,后续创建test文件后自动重新make。
三、默认配置的过程
工具及源码位置:
执行默认配置命令后会生成conf工具用于将默认配置写入根目录下的 .config 文件,工具及源码位置如上图。
在相关的 Makefile 中的依赖关系如下
# 上图第一条Makefile命令用conf工具生成.config相当于 scripts/kconfig/conf --defconfig=configs/mx6ull_14x14_evk_deconfig Kconfigscripts/kconfig/conf--defconfig=arch/../configs/mx6ull_14x14_evk_deconfig Kconfig分析scripts/kconfig/conf.c代码总结以下配置过程:
defconfig_file = "arch/../configs/mx6ull_14x14_evk_deconfig"; name = "Kconfig" conf_paarse(name); //解析uboot根目录下的Kconfig conf_read(defconfig_file); //读配置文件 conf_set_all_new_symbols(def_default); //设置new_symbols为默认值 conf_write(NULL); //写到.config重要:
对于.config中存在,但 defconfig 默认配置文件中没有显式设置的配置选项,来源于整套 Kconfig 树,根 Kconfig 为入口,各子模块 Kconfig 定义具体选项,(从顶层 Kconfig 入口,递归 source 所有子目录 Kconfig,读取全部配置项):一部分通过select被已开启配置自动选中;另一部分采用 Kconfig 定义的default默认值。defconfig 只保存用户显式指定的配置,其余配置项由 Kconfig 的依赖、选择、默认值规则推导生成最终的.config。defconfig和Kconfig都由conf工具解析。
四、make过程
1.过程解读
1.根据 .config 自动生成头文件:include/config.h、include/autoconf.h,还有 u‑boot.cfg
- config.h / autoconf.h:把.config里的CONFIG_XXX=y转成 C 宏#define CONFIG_XXX 1,C 代码#include <config.h>,就可以直接使用#ifdef CONFIG_XXX条件编译。
- u‑boot.cfg:给构建系统、脚本使用的配置文本,不是给 C 代码用。
- 关键点:include/config.h 是自动生成的文件,源码仓库里原本没有,make 才会生成。
2.制作工具(编译主机端工具)
- U‑Boot 会先编译运行在 PC 主机上工具:tools/下面的 mkimage、dtc 等;后续打包 u‑boot 镜像会依赖这些主机工具,必须先编译出来。
3.交叉编译各个 C / 汇编源文件
- 根据.config的各个 CONFIG 宏,Kbuild/Makefile 决定编译哪些目录、哪些.c/.S文件;
- .c文件#include <config.h>,通过宏拿到.config的配置项,做条件编译;
- 注意:.c 不能直接读取.config,必须经过 config.h 头文件中转。
在编译过程中生成的u-boot.cfg作用:
Kconfig + 板级头文件所有生效 CONFIG_xxx 的全集,调试查看用,编译过程不会读取它。移植时,想确认某个宏到底有没有生效,直接看 u‑boot.cfg。.config看不到板头文件里#define CONFIG_XXX,但 u‑boot.cfg 可以看到。
在Makefile中会根据.config中的配置设置变量obj如下,之后会进入obj变量指定的目录中的Makefile决定编译某个文件。
# 等于目录 obj -y += xxx/ # 等于.o文件 obj -y += xxx.o # -某个配置项等于某个目录 obj -$(CONFIG_XXX) += X_dir/ # 某个配置项等于某个.o文件 obj -$(CONFIG_XXX) += X_file.o
例如你想编译 env_dataflash.o 在配置时就要将 CONFIG_ENV_IS_IN_DATAFLASH 配置为 y ,在编译的时候变量$(CONFIG_ENV_IS_IN_DATAFLASH) 就会为 y 即 obj-y += env_dataflash.o。
2.make过程中生成的各类配置文件
执行make xxx_defconfig只完成初步配置,生成顶层.config;真正执行 make 编译时,还会再跑一遍 silentoldconfig,等价执行make include/config/auto.conf,完成二次配置。
include/config/auto.conf:Kconfig 输出,.config的副本,存放所有 Kconfig 的 CONFIG_* 变量,被顶层 Makeinclude 包含,给 Make 读取 Kconfig 的配置项。
1.生成链接文件
在include/config/下创建符号链接,把板子对应的配置头文件链接到include/config.h。2.生成include/config.h
C语言代码使用的配置头文件,将把 Kconfig 中所有CONFIG_*选项全部转成 C 宏定义。C 代码#include <config.h>就可以直接拿到开发板板级配置。3.生成u-boot.cfg
完整配置文本快照文件,把全部 CONFIG 配置项原样输出保存,用于调试、查看当前完整配置,不参与编译过程。4.生成include/autoconf.mk
Makefile 使用的配置文件,非常关键。把 CONFIG_xxx 转换成 Makefile 变量,供各个子目录 Makefile 使用。顶层config.mk会‑include include/autoconf.mk,子目录中COBJS‑$(CONFIG_CMD_NET) += net.o就依靠这个文件的变量来控制编译哪些源文件。5.生成include/autoconf.mk.dep
依赖关系文件,记录autoconf.mk依赖哪些源文件(Kconfig、板头文件等)。make 工具读取.dep文件:当依赖的 Kconfig / 头文件被修改时,会自动重新生成autoconf.mk,实现增量编译;如果没有这个 dep,修改 Kconfig 后 make 不会自动重跑配置,会出现配置不更新的 bug。
u-boot编译过程总结
MX_6ULL为例
1.链接u-boot得到一个elf格式的u-boot
2.生成u-boot-nodtb.bin是imx文件依赖之一
3.编译设备树是imx文件依赖之二,dt.dtb是由imxull-14x14-evk.dtb复制来的。
4.拼接dt.dtb和u-boot-nodtb.bin生成u-boot-dtb.bin
5.制作u-boot-dtb.imx文件(这一步是芯片相关处理,对于6ull会添加DDR等配置文件,对于stm32mp157则直接将u-boot-dtb.bin拷贝为u-boot.bin再添加一个头部生成u-boot.stm32)
五、核心脚本
以/lib目录作为所要编译的目录为例
顶层Makefile
#在 arch/arm/Makefile 中指定了第一个文件 head-y := arch/arm/cpu/$(CPU)/start.o #顶层Makefile中定义了要编译哪些目录 u-boot-init := $(head-y) 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/domain/ \ drivers/power/fuel_gauge/ \ drivers/power/mfd/ \ drivers/power/pmic/ \ drivers/power/battery/ \ drivers/power/regulator/ libs-y += drivers/spi/ #顶层Makefile中定义了如何编译子目录,重点在https://100ask.org/course/52scripts/Makefile.build脚本 #下面的脚本使用 **Makefile.build** 递归编译子目录,生成子目录/build-in.o ,再链接start.o与各个子目录的build-in.o $(u-boot-dirs): prepare scripts # make -f $(scrtree)/scripys/Makefile.build obj=arch/arm/cpu/arm7 $(Q)$(MAKE) $(build)=$@lib/Makefile
lib/Makefile内容如下:
ifndef CONFIG_SPL_BUILD # 若想将efi编译进u-boot,在配置的时候就要将CONFIG_EFI设置成y obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_EFI_LOADER) += efi_loader/ obj-$(CONFIG_LZMA) += lzma/ obj-$(CONFIG_LZO) += lzo/ obj-$(CONFIG_ZLIB) += zlib/ obj-$(CONFIG_BZIP2) += bzip2/ obj-$(CONFIG_TIZEN) += tizen/ obj-$(CONFIG_FIT) += libfdt/ obj-$(CONFIG_CMD_DHRYSTONE) += dhry/ obj-$(CONFIG_AVB_SUPPORT) += avb/ obj-$(CONFIG_IMX_TRUSTY_OS) += trusty/ql-tipc/sysdeps/ obj-$(CONFIG_AES) += aes.o obj-$(CONFIG_USB_TTY) += circbuf.o obj-y += crc7.o obj-y += crc8.olib/Makefile 提供待编译文件清单(obj‑y 系列),scripts/makefile.build 解析该清单并生成实际编译目标。
scripts/makefile.build
makefile.build如何编译某个目录
以lib目录为例:
在顶层Makefile中传入的参数时lib即:
make -f $(scrtree)/scripys/Makefile.build obj=lib1.确定 src 源路径
# Modified for U-Boot prefix := tpl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := spl src := $(patsubst $(prefix)/%,%,$(obj)) ifeq ($(obj),$(src)) prefix := .2.包含Makefile
在这里会去包含要编译子目录中的Makefile.
# The filename Kbuild has precedence over Makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) # 查找 lib/ 下存在 Kbuild 还是 Makefile 赋给变量 kbuild-file kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) # 此时 $(kbuild-file) 就等于 lib/Makefle 下面的语句将其包含进来 include $(kbuild-file)2.编译.o
# 编译.o执行部分 # Built-in and composite module parts $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c)3.编译子目录
# 编译子目录执行部分 # To build objects in subdirs, we need to descend into the directories $(sort $(subdir-obj-y)): $(subdir-ym) ;4.链接build-in.o得到u-boot
# Makefile.build的第一个目标 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: # builtin-target 的依赖 $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) # 链接命令 $(LD) cmd_link_o_target = $(if $(strip $(obj-y)),\ $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)__build是Makefile.build脚本的第一个目标,是子目录构建入口。
lib子目录编译总结
顶层Makefile → make -f scripts/makefile.build obj=lib → makefile.build载入lib/Makefile,解析obj‑y/subdir‑y等 → 执行脚本第一个目标 __build → 依赖 builtin‑target(lib/built‑in.o)、subdir‑ym、always → built‑in.o又依赖lib目录下各个obj‑y的*.o → *.o规则由makefile.build内部的%.o自动规则生成 → subdir‑ym触发更深子目录再次调用makefile.build六、设备树编译
1.在 auto.conf 中会指定设备树文件如下。
CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk"2.编译
arch/arm/dts/imx6ull-14x14-evk.dts ⬇ arch/arm/dts/imx6ull-14x14-evk.dtb ⬇ 复制.dtb文件到根目录下的 dts/dt.dtb最终 dt.dtb 与 u-boot-nodtb.bin 合并得到 u-boot-dtb.bin 。
七、启动过程
XIP设备
一上电CPU必定从XIP设备中执行第一条指令。对于支持Nand启动、USB启动、UART启动、SD卡启动的芯片,里面一定有BootROM进行硬件初始化、把程序从非XIP设备复制到RAM中执行。
RAM 不属于 XIP 设备,它本身就是 CPU 可直接寻址的内存。XIP 设备一般指支持原地执行的 NOR‑Flash,该 Flash 被硬件映射到 CPU 系统地址空间,CPU 可以直接读取 Flash 中的指令并运行,不需要把程序拷贝到 RAM;但运行时栈、全局变量等数据仍然需要存放在 RAM 中。而 SD 卡属于非 XIP 块设备,CPU 无法直接对其地址取指访问,必须通过 SD/eMMC 主机控制器把程序读取复制到 RAM 之后,CPU 才可以执行。
板子上电并不是直接运行uboot,而是运行BROM(BootROM)中固化的程序,将Falsh或SD卡中的uboot拷贝到内存中再去运行uboot。
之后根据这个流程分析源码
uboot启动流程(分为XIP设备启动与非XIP设备启动两种情况)
情况一、对于在XIP设备中启动(例:uboot是存储在XIP设备上的,对于Nor Flash就是XIP设备)
1.上电复位,BootROM 配置地址映射,CPU 复位向量指向 Nor,SPL 直接在 Nor 上 XIP 执行(无拷贝)
2.SPL 做极早期初始化:时钟、DDR 时序 / 训练、基础串口
3.SPL 从 Nor 把 U-Boot proper 拷贝到 DDR,跳转 DDR(DDR是易失性存储设备,而Nand Flash属于非易失性存储设备)
4.U-Boot proper 在 DDR 运行:外设初始化、环境变量、命令行
5.读 Flash 把内核 + dtb 拷贝到 DDR
6.启动内核
情况二、对于在非XIP设备中启动(例:uboot存储在SD卡这样的非XIP设备)
1.上电复位,先跑 BootROM(本身 XIP)
2.BootROM 通过块设备驱动,把SPL 读取拷贝到片上 SRAM,跳转到 SRAM 执行(核心区别)
3.SPL 在 SRAM 中初始化 DDR
4.SPL 再从 SD/eMMC/NAND 读取完整 U-Boot proper 拷贝到 DDR,跳转 DDR
5.U-Boot proper 在 DDR 运行,外设初始化
6.加载内核到 DDR
7.启动内核
SPL (Secondary Program Loader,二级程序加载器,是 U-Boot 体系里的精简先导引导程序,**非XIP设备的头部信息**) U-Boot porper中有重定位程序,在将 U-Boot porper 复制到DDR后运行 U-Boot porper 首先会进行重定位。重定位的两种方式: 1.修改程序,将原来的地址都改为新地址。2.将程序复制到新地址。BootROM的作用:
硬件初始化、将程序从非XIP设备复制到RAM中执行。
BootROM如何选择启动设备?
a.通过芯片的bootpin引脚选择某个设备。例:6ull SD卡、EMMC或USB启动。
b.通过芯片的bootpin引脚选择某个顺序。例:全志 从 SD卡->SPI->Nand 或 SPI->Nand->SD 依次尝试。
U-Boot porper中有重定位程序,在将 U-Boot porper 复制到DDR后运行 U-Boot porper 首先会进行重定位。重定位的两种方式:
1.修改程序,将原来的地址都改为新地址。2.将程序复制到新地址。