U-Boot flat device tree(fdt)
在Linux系统中,设备树Device Tree的概念已经广泛使用了,启动相关代码和驱动代码中随处可见设备树的身影。U-Boot中也引入了设备树的内容,通过设备树信息在运行时进行动态配置,使得U-Boot对板级信息的适配和描述更加灵活,一个单一的U-Boot二进制能够支持多个板子。U-Boot中使用flat device tree(fdt)扁平设备树,前面的文章中已经分析过,在代码中U-Boot通过gd->fdt_blob
来指向内存中的设备树信息,阅读本文前,请思考
U-Boot加载设备树文件的形式是什么?是只有一种方式还是有多种方式?它们各自是什么原理?
在编译阶段,如何告诉U-Boot使用哪个设备树文件?U-Boot对设备树文件做了什么?
指定设备树文件
在U-Boot中,有3种方式向U-Boot指定设备树文件,其中前三种是在编译时指定,后一种是在运行时指定
CONFIG_DEFAULT_DEVICE_TREE
make DEVICE_TREE
make EXT_DTB
env fdtcontroladdr
通过CONFIG_DEFAULT_DEVICE_TREE
宏定义直接指定设备树源文件dts路径,该选项位于板级配置文件中,在configs文件夹下可以看到很多板子的设置
1 | grep "CONFIG_DEFAULT_DEVICE_TREE" * -Rn |
注意这里输入的文件不带后缀.dts,并且文件必须放在arch/\
2、3两种方式是通过make命令跟参数的形式,make DEVICE_TREE和CONFIG_DEFAULT_DEVICE_TREE
宏定义是一个道理,都是指定设备树源文件,且路径都是确定在arch/
这3种方式的处理都可以在U-Boot的dts/Makefile中看到
1 | DEVICE_TREE ?= $(CONFIG_DEFAULT_DEVICE_TREE:"%"=%) |
可以看到,DEVICE_TREE用CONFIG_DEFAULT_DEVICE_TREE
赋值是使用了”?=”,如果make命令没有指定DEVICE_TREE的话,就是用宏定义的值,如果命令指定了,就用命令中的,也就是说make DEVICE_TREE的优先级高于CONFIG_DEFAULT_DEVICE_TREE
宏定义
如果make命令指定了EXT_DTB,就直接使用EXT_DTB指定的dtb,否则将DEVICE_TREE指定的dts后缀替换为dtb,就是Makefile的DTB目标,因此EXT_DTB的优先级又高于DEVICE_TREE
最后一种指定方式是通过设置环境变量”fdtcontroladdr”来指定一个内存中的地址作为设备树的地址,这里的地址格式必须是16进制的,这个选项一般不使用,调试过程为了灵活性会使用这种方式。因为是运行时指定,因此这种方式优先级是最高的
加载设备树的形式
U-Boot加载设备树有很灵活的形式,要使用fdt功能,首先需要在配置中设置CONFIG_OF_CONTROL
选项,该选项是一个使能选项,使能后U-Boot才支持fdt功能。在使能CONFIG_OF_CONTROL
的前提下,U-Boot可以通过编译选项的方式指定设备树的不同加载形式
CONFIG_OF_EMBED:嵌入方式,设备树文件将编译到U-Boot镜像内部
CONFIG_OF_SEPARATE:设备树被编译为u-boot.dtb文件,并以
cat
的形式与U-Boot镜像合并CONFIG_OF_BOARD:使用板级特定的代码来加载设备树文件,把加载工作交给用户处理,用户需要实现
board_fdt_blob_setup
函数
嵌入U-Boot镜像
在U-Boot顶层Makefile中,顶层Makefile目标是all,all依赖$(ALL-y)
,而$(ALL-y)
又依赖u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check这些目标,其中最主要的是U-Boot镜像”u-boot.bin”
1 | all: $(ALL-y) |
u-boot.bin的生成规则与CONFIG_OF_SEPARATE
有关,如果定义了CONFIG_OF_SEPARATE
,则u-boot.bin依赖u-boot-nodtb.bin和dts/dt.dtb,否则只依赖u-boot-nodtb.bin
1 | ifeq ($(CONFIG_OF_SEPARATE),y) |
而u-boot-nodtb.bin依赖于u-boot,u-boot又依赖于$(u-boot-init)
和$(u-boot-main)
和u-boot.lds,u-boot-main依赖于$(libs-y)
1 | u-boot-nodtb.bin: u-boot FORCE |
如果定义了CONFIG_OF_EMBED
,会将dts路径添加到$(libs-y)
1 | libs-$(CONFIG_OF_EMBED) += dts/ |
在dts/Makefiles中,定义了第二目标为dt.dtb.S汇编文件,然后如果定义了嵌入设备树选项,会把dt.dtb.o添加到$(obj-y)
中
1 | .SECONDARY: $(obj)/dt.dtb.S |
dt.dtb.S的生成是在scripts/Makefile.lib中
1 | # Generate an assembly file to wrap the output of the device tree compiler |
这里定义了一个cmd,即依赖dtb生成dtb.S,选择一个支持CONFIG_OF_EMBED选项的board进行配置并编译,会在dts目录下生成dt.dtb.S汇编文件,该文件内容如下
1 | .section .dtb.init.rodata,"a" |
声明了一个只读的数据段”.dtb.init.rodata”,前后进行了对齐,其内容就是dt.dtb文件,前后用__dtb_dt_begin
和__dtb_dt_end
标签标记,方便程序中进行索引
该汇编文件会在编译时被编译生成dt.dtb.o然后链接到u-boot-nodtb.bin中。因此,嵌入方式本质上是通过一个汇编文件声明数据段的方式将外部文件链接到U-Boot镜像中
与U-Boot拼接
由以上分析可知,如果定义了CONFIG_OF_SEPARATE
,u-boot.bin依赖u-boot-nodtb.bin和dts/dt.dtb,而dts/dt.dtb依赖u-boot,且会进入dtbs目录执行Makefile
1 | ifeq ($(CONFIG_OF_SEPARATE),y) |
dtbs目录下的Makefile负责生成dtb文件,然后u-boot-dtb.bin是由u-boot-nodtb.bin和dts/dt.dtb通过cat来生成的,命令cat的定义如下
1 | quiet_cmd_cat = CAT $@ |
本质上就是以下命令
1 | cat u-boot-dtb.bin dts/dt.dtb > u-boot-dtb.bin |
可以看到实质上是进行了文件内容拼接操作
运行时加载设备树
运行时加载设备树文件的操作在board_f.c文件中的init_sequence_f数组中的fdtdec_setup,该函数定义在lib/fdtdec.c中。通过代码可以看出,首先是在init_sequence_f数组定义的位置,如果未定义CONFIG_OF_EMBED
,是不会编译fdtdec_setup函数的。在fdtdec_setup函数内部,如果判断是嵌入形式的设备树,则指针直接指向__dtb_dt_begin
,如果是拼接方式,则指向__bss_end
,是U-Boot镜像尾部,也就是设备树头部
1 | int fdtdec_setup(void) |