【Device Tree】Android DTS 加载流程
前言在之前的文章中已经对设备树的基本概念作了讲解, 操作系统(例如在 Android 中使用的 Linux 内核)会使用 DT 来支持 Android 设备使用的各种硬件配置。硬件供应商(ODM)会提供自己的 DT 源文件,接下来 Linux 会将这些文件编译到引导加载程序使用的设备树 Blob (DTB) 文件中。Android在原有的DT基础上增加了设备树叠加层的处理方式。进一步的对于芯片产品
前言
在之前的文章中已经对设备树的基本概念作了讲解, 操作系统(例如在 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下是如何初始化的
更多推荐
所有评论(0)