前言
在之前的文章中已经对设备树的基本概念作了讲解, 操作系统(例如在 Android 中使用的 Linux 内核)会使用 DT 来支持 Android 设备使用的各种硬件配置。硬件供应商(ODM)会提供自己的 DT 源文件,接下来 Linux 会将这些文件编译到引导加载程序使用的设备树 Blob (DTB) 文件中。
Android在原有的DT基础上增加了设备树叠加层的处理方式。进一步的对于芯片产品的DT和开发者(ODM/OEM/产品开发者)的DT做了解耦。
设备树叠加层 (DTO) 可让主要的(ODM)设备树 Blob (DTB) 叠加在Soc设备树上。使用 DTO 的引导加载程序可以维护系统芯片 (SoC) DT,并动态叠加针对特定设备(ODM)的 DT,从而向树中添加节点并对现有树中的属性进行更改。
本篇文章主要讲述如下内容:
- DT的专有名词
- Bootloader 加载DT的基本流程
- Android 下DTO的实现以及原理
- 实际案例-MT8167 平台DTS的加载完整流程
DT相关的专有名词
DT | Device Tree |
DTB | Device Tree Blob |
DTBO | Device Tree Blob for Overlay |
DTC | Device Tree Compiler |
DTO | Device Tree Overlay |
DTS | Device Tree Source |
FDT | Flattened Device Tree, a binary format contained in a .dtb blob file |
Bootloader 加载DT的基本流程
如上图所示,系统加载DT主要包含: DTS源文件编译, .dtb分区以及对应镜像文件生成, bootloader运行将.dtb 分区的文件加载到内存中, 将对应的内存地址通过寄存器传递到kernel。
在支持DTO的Android下, DT 是由以下两个部分组成:
- Main DT, 主要是Soc-only的部分以及默认的系统配置,例如cpu配置/内存相关配置等,soc的供应商提供, 本文所用的MT8167 那么这个Main DT就是由MTK提供的.
- Overlay DT, 该Soc所对应的产品需要的特定配置, 主要是由ODM/OEM提供,这里可以理解为开发者自己定义(当然目前在MT8167上MTK也提供了基本的Oerlay DT基本模板)
- 编译阶段
- 通过dtc(device tree compiler)将Main DT dts源文件编译为.dtb文件.
- 通过dtc(device tree compiler)将Overlay DT的dts源文件编译为后缀名为.dtbo的文件.
这里需要注意的是, .dtb和.dtbo 的文件格式是相同的都是FDT. 后缀名不同只是为了区分.
- dtb分区
- 在MT8167这一平台上是将dtbo划分为了独立的分区, 具体的分析在实际案例章节会详细说明.
- 运行
- 在bootloader中将.dtb文件读取到内存中
- 在bootloader中将.dtbo文件从dtbo分区(emmc指定分区)读取到内存中.
- 将.dtb 和 .dtbo 合并
- 在bootloader跳转启动kernel时,将该内存地址通过寄存器传递给到kernel
关于这个环节的详细流程在实际案例章节会详细说明.
实际案例-MT8167 平台DTS的加载完整流程
1. MT8167 平台的Main DT和Overlay DT一览
截取了MT8167平台Main DT的部分内容, 关于Main DT 和 Oerlay DT 在上一章节"Bootloader 加载DT的基本流程"已描述.
# kernel-4.4/arch/arm/boot/dts/mt8167.dtsi
/ {
model = "MT8167";
compatible = "mediatek,mt8167";
interrupt-parent = <&sysirq>;
#address-cells = <2>;
#size-cells = <2>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x0>;
enable-method = "psci";
cpu-idle-states = <&CLUSTER_SLEEP_0 &CLUSTER_SLEEP_0>,
<&CPU_SLEEP_0_0 &CPU_SLEEP_0_0 &CPU_SLEEP_0_0>;
clocks = <&infracfg CLK_IFR_MUX1_SEL>,
<&topckgen CLK_TOP_MAINPLL_D2>,
<&apmixedsys CLK_APMIXED_ARMPLL>;
clock-names = "cpu", "intermediate", "armpll";
operating-points-v2 = <&cluster0_opp>;
clock-frequency = <1300000000>;
};
...
# kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts
/dts-v1/;
#include "mt8167.dtsi"
#include "mt6392.dtsi"
/ {
/* chosen */
chosen {
bootargs = "console=ttyS0,921600n1 root=/dev/ram initrd=0x44000200,0x200000";
};
firmware: firmware {
android: android {
compatible = "android,firmware";
fstab: fstab {
compatible = "android,fstab";
};
};
};
odm: odm {
compatible = "simple-bus";
/* reserved for overlay by odm */
};
};
基于上述截取的部分内容可以看到,针对MT8167平台+Android O Main DT由以下两个文件构成,
kernel-4.4/arch/arm/boot/dts/mt8167.dtsi
# 而mt8167.dtsi 是以include的方式包含在mt8167_dtbo.dts, 类似C语言中的头文件包含
kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts
继续看Overlay DT,
# kernel-4.4/arch/arm/boot/dts/apollo.dts
#include <generated/autoconf.h>
/dts-v1/;
#ifdef CONFIG_MTK_DTBO_FEATURE
/plugin/;
#define ROOT_NODE &odm
ROOT_NODE {
mtcpufreq {
compatible = "mediatek,mt8167-cpufreq";
};
mt8167_audio_codec: mt8167_audio_codec {
compatible = "mediatek,mt8167-codec";
clocks = <&topckgen CLK_TOP_AUDIO>;
clock-names = "bus";
mediatek,afe-regmap = <&afe>;
mediatek,apmixedsys-regmap = <&apmixedsys>;
mediatek,dmic-wire-mode = <1>; /* 0(ONE_WIRE) 1(TWO_WIRE) */
mediatek,headphone-cap-sel = <1>; /* 0(10UF) 1(22UF) 2(33UF) 3(47UF) */
};
...
这里需要注意的是,在apollo.dts 中ROOT_NODE 实际的定义是&odm, &符号在dts语法中是引用的意思,这里暂时可以理解为:
# mt8167_dtbo.dts 中odm 最终在merge apollo.dts时会将apollo.dts下的odm相关定义覆盖mt8167_dtbo.dts中的odm相关内容
# 最终合并mt8167_dtbo.dts apollo.dts,部分内容如下.
# 当然这里所指的合并 具体是如何处理的接下来的文章中会详细说明
/dts-v1/;
#include "mt8167.dtsi"
#include "mt6392.dtsi"
/ {
/* chosen */
chosen {
bootargs = "console=ttyS0,921600n1 root=/dev/ram initrd=0x44000200,0x200000";
};
firmware: firmware {
android: android {
compatible = "android,firmware";
fstab: fstab {
compatible = "android,fstab";
};
};
};
odm: odm {
compatible = "simple-bus";
mtcpufreq {
compatible = "mediatek,mt8167-cpufreq";
};
mt8167_audio_codec: mt8167_audio_codec {
compatible = "mediatek,mt8167-codec";
clocks = <&topckgen CLK_TOP_AUDIO>;
clock-names = "bus";
mediatek,afe-regmap = <&afe>;
mediatek,apmixedsys-regmap = <&apmixedsys>;
mediatek,dmic-wire-mode = <1>; /* 0(ONE_WIRE) 1(TWO_WIRE) */
mediatek,headphone-cap-sel = <1>; /* 0(10UF) 1(22UF) 2(33UF) 3(47UF) */
};
/* reserved for overlay by odm */
};
};
2. Main DT 和 Overlay DT的构建
在分析之前提前揭晓下答案:
Main DT
-> mt8167_dtbo.dts & mt8167_dtbo.dts
-> DTC - mt8167_dtbo.dtb
-> CAT mt8167_dtbo.dtb && zImage > zImage-dtb中
也就是Main DT最终会包含在zImage-dtb.img中
Overlay DT
-> apollo.dts
-> DTC - apollo.dtb
-> mkimage apollo.dtb -> odmdtbo.img
也就是Overlay DT最终会包含在odmdtbo.img,作为独立的分区文件写入设备的指定分区
下面来看各自的构建流程.
Main DT,
相关文件以及路径:
# 源文件
kernel-4.4/arch/arm/boot/dts/mt8167.dtsi
kernel-4.4/arch/arm/boot/dts/mt8167_dtbo.dts
# 相关Makefile
arm/boot/Makefile
scripts/Makefile.lib
下面来看下具体的构建过程,
在arm/boot/Makefile中有如下相关代码,
# arm/boot/Makefile
# 在kernel的配置文件apollo_defconfig中将CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES 定义为了mt8167_dtbo
# 也就是这里DTB_NAMES := mt8167_dtbo
DTB_NAMES := $(subst $\",,$(CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE_NAMES))
ifneq ($(DTB_NAMES),)
# DTB_LIST := mt8167_dtbo.dtb
DTB_LIST := $(addsuffix .dtb,$(DTB_NAMES))
else
DTB_LIST := $(dtb-y)
endif
DTB_OBJS := $(addprefix $(obj)/dts/,$(DTB_LIST))
targets += $(addprefix dts/,$(DTB_LIST))
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@$(kecho) ' Kernel: $@ is ready'
# Documentation/kbuild/makefiles.txt -> --- 6.7 Commands useful for building a boot image if_changed
$(obj)/zImage-dtb: $(obj)/zImage $(DTB_OBJS) FORCE
$(call if_changed,cat)
@echo ' Kernel: $@ is ready'
如上图所截取的部分Makfile, 在构建zImage-dtb时, 首先会构建zImage 和 $(DTB_OBJS)-mt8167_dtbo.dtb。
在构建构建生成zImage和mt8167_dtbo.dtb会调自定义的cat指令将mt8167_dtbo.dtb追加到zImage尾部.
# Kernel源码目录: Documentation/kbuild/makefiles.txt -> --- 6.7 Commands useful for building a boot image if_change
$(obj)/zImage-dtb: $(obj)/zImage $(DTB_OBJS) FORCE
# 而cat是在scripts/Makefile.lib中定义的.
$(call if_changed,cat)
@echo ' Kernel: $@ is ready'
# scripts/Makefile.lib
# cat
# ---------------------------------------------------------------------------
# Concatentate multiple files together
quiet_cmd_cat = CAT $@
# 这里可以看出,dtb是直接加载在kernel image 后
# $(filter-out FORCE,$^) 获取到的是 zImage 和 mt8167_dtbo.dtb,$@为zImage-dtb
cmd_cat = (cat $(filter-out FORCE,$^) > $@) || (rm -f $@; false)
以上描述了Main DT的构建以及如何追加到zImage.
Overlay DT
相关文件以及路径:
# 源文件
arch/arm/boot/dts/apollo.dts
# Makefile
arch/arm/boot/dts/Makefile
scripts/drvgen/drvgen.mk
arch/arm/boot/Makefile
下面来看下具体的构建过程,
在arch/arm/Makefile有如下代码:
boot := arch/arm/boot
# 当make 目标zImage-dtb时,会先构建以赖的dtbs,然后是$(DTB_OVERLAY_IMAGE_TAGERT)
zImage-dtb: vmlinux scripts dtbs $(DTB_OVERLAY_IMAGE_TAGERT)
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
# 构建dtbs
dtbs: prepare scripts
# 通过debug以及上下文分析得知如下编译指令是:
# make arch/arm/boot/dts
# 也就是会执行arch/arm/boot/dts/下的Makefile
$(Q)$(MAKE) $(build)=$(boot)/dts
继续执行kernel-4.4/arch/arm/boot/dts/下的Makefile
# kernel-4.4/arch/arm/boot/dts/Makefile
# CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE是在arch/arm/configs/apollo_defconfig中定义的,在我这个案例中为CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE_NAMES="apollo"
ifeq ($(strip $(CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE)), y)
DTB_LIST += $(addsuffix .dtb, $(subst $\",,$(CONFIG_BUILD_ARM_DTB_OVERLAY_IMAGE_NAMES)))
endif
# 将构建Overlay dts的目标添加到targets中进行apollo.dts的构建, 输出目标为apollo.dtb
targets += $(DTB_LIST)
看到这里你可能会有疑问? 为什么赋值给targets 关于这一点kernel官方文档有做了初步的说明,
# 对于dtc构建dts的规则做了基本的说明
dtc
Create flattened device tree blob object suitable for linking
into vmlinux. Device tree blobs linked into vmlinux are placed
in an init section in the image. Platform code *must* copy the
blob to non-init memory prior to calling unflatten_device_tree().
To use this command, simply add *.dtb into obj-y or targets, or make
some other target depend on %.dtb
A central rule exists to create $(obj)/%.dtb from $(src)/%.dts;
architecture Makefiles do no need to explicitly write out that rule.
Example:
targets += $(dtb-y)
clean-files += *.dtb
DTC_FLAGS ?= -p 1024
# 而dtc自定义是在scripts/Makefile.lib中实现
quiet_cmd_dtc = DTC $@
cmd_dtc = mkdir -p $(dir ${dtc-tmp}) ; \
$(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
$(srctree)/scripts/dtc/dtc_overlay -@ -O dtb -o $@ -b 0 \
-i $(dir $<) $(DTC_FLAGS) \
-d $(depfile).dtc.tmp $(dtc-tmp) ; \
cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)
$(obj)/%.dtb: $(src)/%.dts FORCE
$(call if_changed_dep,dtc)
dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp)
以上的构建只是将apollo.dts编译生成了apollo.dtb还是并不是最终烧录到dtb分区的Overlay DT.
下面来继续看下中的dtb image是如何生成的.
# arch/arm/Makefile
# 前面的内容已经讲述了dtbs的构建生成了apollo.dtb,下面继续看下$(DTB_OVERLAY_IMAGE_TAGERT)的构建
zImage-dtb: vmlinux scripts dtbs $(DTB_OVERLAY_IMAGE_TAGERT)
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
# DTB_OVERLAY_IMAGE_TAGERT是在scripts/drvgen/drvgen.mk中定义
ifeq ($(strip $(CONFIG_MTK_DTBO_FEATURE)), y)
DTB_OVERLAY_IMAGE_TAGERT := $(DRVGEN_OUT)/odmdtbo.img
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_DTB_OVERLAY_OBJ:=$(DTB_FILES)
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ:=$(DRVGEN_OUT)/$(MTK_PROJECT).mdtb
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_IMG:=$(DRVGEN_OUT)/$(MTK_PROJECT).mimg
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MULTIPLE_DTB_OVERLAY_HDR:=$(srctree)/scripts/multiple_dtbo.py
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MKIMAGE_TOOL:=$(srctree)/scripts/mkimage
$(DTB_OVERLAY_IMAGE_TAGERT) : PRIVATE_MKIMAGE_CFG:=$(srctree)/scripts/odmdtbo.cfg
$(DTB_OVERLAY_IMAGE_TAGERT) : $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) dtbs $(PRIVATE_MKIMAGE_TOOL) $(PRIVATE_MKIMAGE_CFG) $(PRIVATE_MULTIPLE_DTB_OVERLAY_HDR)
@echo Singing the generated overlay dtbo.
# Singing the generated overlay dtbo.
# cat ./arch/arm/boot/dts/apollo.dtb > ./arch/arm/boot/dts/apollo.mdtb || (rm -f ./arch/arm/boot/dts/apollo.mdtb; false)
cat $(PRIVATE_DTB_OVERLAY_OBJ) > $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) || (rm -f $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ); false)
# python kernel-4.4/scripts/multiple_dtbo.py ./arch/arm/boot/dts/apollo.mdtb ./arch/arm/boot/dts/apollo.mimg
python $(PRIVATE_MULTIPLE_DTB_OVERLAY_HDR) $(PRIVATE_MULTIPLE_DTB_OVERLAY_OBJ) $(PRIVATE_MULTIPLE_DTB_OVERLAY_IMG)
# mkimage apollo.mimg odmdtbo.cfg > odmdtbo.img
$(PRIVATE_MKIMAGE_TOOL) $(PRIVATE_MULTIPLE_DTB_OVERLAY_IMG) $(PRIVATE_MKIMAGE_CFG) > $@
.PHONY: odmdtboimage
odmdtboimage : $(DTB_OVERLAY_IMAGE_TAGERT) dtbs
endif
上述截取的部分构建代码只是展示了下在MT8167+Kernel 4.4中是如何构建Overlay DT的, 对于具体的构建过程不同平台会有所差异,但核心逻辑是类似的.
但可以看下scripts/multiple_dtbo.py中实现,
# scripts/multiple_dtbo.py
# dtbo的构建规则可以查看google官方给出的规则
# https://source.android.com/devices/architecture/dto/partitions DTB/DTBO Partitions Format
def parse_dtb(input):
dtb_list = []
with open(input, 'rb') as f:
img_data = f.read()
img_size = f.tell()
dtb_offset = 0
while dtb_offset <= img_size - 8:
dtb_magic = struct.unpack('>I', img_data[dtb_offset : dtb_offset+4])[0]
if dtb_magic == 0xD00DFEED:
dtb_size = struct.unpack('>I', img_data[dtb_offset+4 : dtb_offset+8])[0]
dtb_list.append(dtb_offset)
dtb_offset = dtb_offset + dtb_size
else:
dtb_offset = dtb_offset + 1
print('{}.'.format(dtb_list))
f.closed
return dtb_list
def write_header(output_file, input_file, dtb_list):
head[0] = struct.pack('I',0xdeaddead) #Magic number
head[1] = struct.pack('I', os.path.getsize(input_file))#dtbo size without header
head[2] = struct.pack('I', 512) #Header Size
head[3] = struct.pack('I', 2) #Header version
head[4] = struct.pack('I', len(dtb_list)) #number of dtbo
head[5] = struct.pack('I', 0xffffffff) #Reserved
head[6] = struct.pack('I', 0xffffffff) #Reserved
head[7] = struct.pack('I', 0xffffffff) #Reserved
i = 0
for offset in dtb_list:
head[8 + i] = struct.pack('I', offset)
i = i + 1
with open(output_file, 'w') as fo:
for item in head:
fo.write("%s" % item)
with open(input_file, 'r') as fi:
for line in fi.readlines():
fo.write(line)
fi.close
fo.close
def main(argv):
if len(argv) < 2:
print("Usage: python post_process_dtbs.py odmdtbs.dtb odmdtbo.img")
sys.exit(1)
input_img = argv[1]
output_img = argv[2]
dtb_list = parse_dtb(input_img)
if len(dtb_list) > 111 :
print("ERROR: Append too much DTBO")
sys.exit(1)
write_header(output_img, input_img, dtb_list)
if __name__ == '__main__':
main(sys.argv)
通过以上分析可以得出Overlay DT的构建过程如下:
1. 通过dtc编译器将apollo.dts编译为apollo.dtb
2. 调用python脚本multiple_dtbo.py添加dtbo的规定格式.
3. 调用mkimage生成最终的烧录到dtb分区的odmdtbo.img
3. bootloader中如何加载dtb和dtbo
针对MT8167+Android O平台, bootloader 是由preloader和lk两部分构成,在这里就不做过多的描述。
我们直接来看下在lk中是如何加载相关DTB内容的.
在lk中是如何加载Main DT的,
// vendor/mediatek/proprietary/bootable/bootloader/lk/app/mt_boot/mt_boot.c
// 在跳转到kernel前需要先加载相关的dtb到内存中
int boot_linux_fdt(void *kernel, unsigned *tags,
unsigned machtype,
void *ramdisk, unsigned ramdisk_sz)
{
void *fdt = tags;
int ret = 0;
int offset;
char tmpbuf[TMPBUF_SIZE];
//当前使用的kernel是32bit
if (g_is_64bit_kernel) {
...
}
else {
dprintf(INFO, "32 bits kernel\n");
zimage_size = *(unsigned int *)((unsigned int)kernel + 0x2c) - *
(unsigned int *)((unsigned int)kernel + 0x28);
//在“Main DT和Overlay DT的构建”这一章节中已经做了说明,在这一平台Main DT是直接跟在zImage后面.
// 获取到dtb_addr(这里的前提是lk已经把zImage-dtb分区的内容全部加载到内存了)
dtb_addr = (unsigned int)kernel + zimage_size;
wake_up_iothread();
wait_for_iothread();
}
//在获取到dtb_addr后,需要校验下dtb的FD_Magic,FD_Magic=0xd00dfeed
if (fdt32_to_cpu(*(unsigned int *)dtb_addr) == FDT_MAGIC) {
#if CFG_DTB_EARLY_LOADER_SUPPORT
dtb_size = fdt32_to_cpu(*(unsigned int *)(fdt + 0x4));
#else
dtb_size = fdt32_to_cpu(*(unsigned int *)(dtb_addr + 0x4));
#endif
} else {
dprintf(CRITICAL, "Can't find device tree. Please check your kernel image\n");
while (1)
;
}
//将加载到内存的Main DT memcpy 到fdt(内存指针)
memcpy(fdt, (void *)dtb_addr, dtb_size);
...
// 将Overlay DT(odmdtbo.img)从dtbo分区加载到内存中并与Main DT合并,合并后的dtb存储在指针:g_fdt
/*The memory pointed to by "g_fdt" is the location that the Linux kernel expects to find the device tree, and it is at least a few mega-bytes free. The merged device tree is therefore copied to that space.
*/
// dtb_overlay函数的具体实现这里就不在展开了,相关代码比较好理解.
bool rtn = dtb_overlay(fdt, dtb_size);
...
if (platform_atag_append) {
//将fdt追加到atag中,最终通过r2寄存器传递给kernel
ret = platform_atag_append(fdt);
if (ret) {
assert(0);
return FALSE;
}
}
}
4. kernel中是如何使用获取到合并后的DT
在kernel-4.4/arch/arm/kernel/setup.c中,
// arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
/* arch/arm/kernel/head-common.S中赋值的.
.long __atags_pointer @ r6
str r2, [r6] @ Save atags pointer
而r2是lk(bootloader)用于与内存通信的媒介,传递了atags的内存地址.
r2包含了dtb pointer.
*/
mdesc = setup_machine_fdt(__atags_pointer);
}
参考链接
Google官方文档-Device Tree Overlays 【Device Tree】Device Tree 基础概念【Device Tree】Kernel中的gpio driver在DTS下是如何初始化的