一、U-boot功能uboot启动内核裸机1.uboot源码如何组织芯片架构arm、x86…-soc厂商相同架构不同的芯片产品-板卡厂商相同芯片不同的板卡产品uboot目的是启动内核所以uboot需要先初始化内存、时钟和其他硬件将内核从Flash拷贝到内存中。内核的作用是去启动APP。APP在Flash中其中内核需要能读写Flash驱动程序包括U盘、网络、LCD、输入设备、读写文件文件系统、找到并启动APP。2.驱动如何使用设备树烧录的u-bootuboot.bin某个dtb配置文件dtb中保存了配置文件保证uboot.bin不臃肿。uboot启动后会去读dtb配置文件。相较于内核的设备树文件uboot的设备树文件时简化版本。二、Makefile使用技巧# 将在make过程中使用到的Makefile中的规则和变量写入1.txtmake-p1.txtmakefile的remake功能当 make 运行过程中检测到自己所依赖的 Makefile/include 文件被更新就自动重新加载 Makefile、重新运行 make。下例中引用test文件时并未创建test文件后续创建test文件后自动重新make。三、默认配置的过程工具及源码位置执行默认配置命令后会生成conf工具用于将默认配置写入根目录下的 .config 文件工具及源码位置如上图。在相关的 Makefile 中的依赖关系如下# 上图第一条Makefile命令用conf工具生成.config相当于 scripts/kconfig/conf --defconfigconfigs/mx6ull_14x14_evk_deconfig Kconfigscripts/kconfig/conf--defconfigarch/../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.cfgconfig.h / autoconf.h把.config里的CONFIG_XXXy转成 C 宏#define CONFIG_XXX 1C 代码#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.confKconfig 输出.config的副本存放所有 Kconfig 的 CONFIG_* 变量被顶层 Makeinclude 包含给 Make 读取 Kconfig 的配置项。1.生成链接文件在include/config/下创建符号链接把板子对应的配置头文件链接到include/config.h。2.生成include/config.hC语言代码使用的配置头文件将把 Kconfig 中所有CONFIG_*选项全部转成 C 宏定义。C 代码#include config.h就可以直接拿到开发板板级配置。3.生成u-boot.cfg完整配置文本快照文件把全部 CONFIG 配置项原样输出保存用于调试、查看当前完整配置不参与编译过程。4.生成include/autoconf.mkMakefile 使用的配置文件非常关键。把 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-boot2.生成u-boot-nodtb.bin是imx文件依赖之一3.编译设备树是imx文件依赖之二dt.dtb是由imxull-14x14-evk.dtb复制来的。4.拼接dt.dtb和u-boot-nodtb.bin生成u-boot-dtb.bin5.制作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 objarch/arm/cpu/arm7 $(Q)$(MAKE) $(build)$lib/Makefilelib/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.buildmakefile.build如何编译某个目录以lib目录为例在顶层Makefile中传入的参数时lib即make -f $(scrtree)/scripys/Makefile.build objlib1.确定 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 objlib → 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_TREEimx6ull-14x14-evk2.编译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 复位向量指向 NorSPL 直接在 Nor 上 XIP 执行无拷贝2.SPL 做极早期初始化时钟、DDR 时序 / 训练、基础串口3.SPL 从 Nor 把 U-Boot proper 拷贝到 DDR跳转 DDRDDR是易失性存储设备而Nand Flash属于非易失性存储设备4.U-Boot proper 在 DDR 运行外设初始化、环境变量、命令行5.读 Flash 把内核 dtb 拷贝到 DDR6.启动内核情况二、对于在非XIP设备中启动例uboot存储在SD卡这样的非XIP设备1.上电复位先跑 BootROM本身 XIP2.BootROM 通过块设备驱动把SPL 读取拷贝到片上 SRAM跳转到 SRAM 执行核心区别3.SPL 在 SRAM 中初始化 DDR4.SPL 再从 SD/eMMC/NAND 读取完整 U-Boot proper 拷贝到 DDR跳转 DDR5.U-Boot proper 在 DDR 运行外设初始化6.加载内核到 DDR7.启动内核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.将程序复制到新地址。