在谷歌的文档中:
编译和验证 | Android 开源项目 | Android Open Source Project
使用 dtc 编译 .dts 时,您必须添加选项 -@ 以在生成的 .dtbo 中添加 symbols 节点。symbols 节点包含所有带标签节点的列表,DTO 库可使用这个列表作为参考。
在kernel的文档中:
Devicetree Overlay Notes — The Linux Kernel documentation
先给出了一个例子
[foo.dts](base dtb)
/* FOO platform */
/dts-v1/;
/ {
compatible = "corp,foo";
/* shared resources */
res: res {
};
/* On chip peripherals */
ocp: ocp {
/* peripherals that are always instantiated */
peripheral1 { ... };
};
};
[bar.dts](overlay dt)
/dts-v1/;
/plugin/;
&ocp {
/* bar peripheral */
bar {
compatible = "corp,bar";
... /* various properties and child nodes */
};
};
[foo+bar.dts] (merge之后)
/* FOO platform + bar peripheral */
/ {
compatible = "corp,foo";
/* shared resources */
res: res {
};
/* On chip peripherals */
ocp: ocp {
/* peripherals that are always instantiated */
peripheral1 { ... };
/* bar peripheral */
bar {
compatible = "corp,bar";
... /* various properties and child nodes */
};
};
};
As a result of the overlay, a new device node (bar) has been created so a bar platform device will be registered and if a matching device driver is loaded the device will be created as expected.
If the base DT was not compiled with the -@ option then the “&ocp” label will not be available to resolve the overlay node(s) to the proper location in the base DT. In this case, the target path can be provided. The target location by label syntax is preferred because the overlay can be applied to any base DT containing the label, no matter where the label occurs in the DT.
是不是和我一样没有看懂呢?看来只有实践才能出真知,我今天使用i.mx8mn evk board做一个探究。
根据上面的描述,猜测-@与设备树中的lable,target path有关。众所周知,dtc使用phandle属性标识lable,每个phandle属性的值在它所属的dts中都是独一无二的,kernel或者uboot通过phandle找到lable对应的target path,但是overlay dt是独立编译的,那么dtc是怎么给overlay dt中引用的lable分配phandle属性的呢?答案是不分配,dtc会给overlay dt分配一个fixups节点,uboot根据这个节点提供的信息在base dtb中寻找相应target path,并给他添加phandle属性。
0、搭建测试环境
给8mn的设备树中添加一个自己的节点my_node,并给它一个my_lable:
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright 2019 NXP
*/
#include <dt-bindings/usb/pd.h>
#include "imx8mn.dtsi"
/ {
chosen {
stdout-path = &uart2;
};
gpio-leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_led>;
status {
label = "yellow:status";
gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
my_lable: my_node {
compatible = "Maximus Sun";
};
...
1、在overlay dt中使用target path
如果想容易点实现dt overlay功能,最简单的办法是在overlay dt中使用target path
写一个overlay dt,这个文件将会merge到我在base dt中创建的节点。注意我这里使用了target path:
[my_overlay_dt-a.dts]
/dts-v1/;
/plugin/;
&{/my_node} {
maximus = "Maximus_Sun";
handsome {
first_name = "Maximus";
second_name = "Sun";
};
};
使用dtc编译,然后dump出来
maximus@maximus-OptiPlex-7090:~/dtbo$ dtc -O dtb -o my_overlay_dt-a.dtb my_overlay_dt-a.dts
maximus@maximus-OptiPlex-7090:~/dtbo$ dtc -O dts -o dtbo_a.dts my_overlay_dt-a.dtb
maximus@maximus-OptiPlex-7090:~/dtbo$ vim dtbo_a.dts
/dts-v1/;
/ {
fragment@0 {
target-path = "/my_node";
__overlay__ {
maximus = "Maximus_Sun";
handsome {
first_name = "Maximus";
second_name = "Sun";
};
};
};
};
注意到dtc添加了一个target-path属性
使用mkdtimg打包
mkdtimg create dtbo.img imx8mn-evk.dtb --id=0 my_overlay_dt-a.dtb --id=1
其中第一个dtb是base dtb,第二个dtb是overlay dtb
将生成的dtbo.img烧进对应的partition即可正常启动,可以在/proc/device-tree/下面找到自己的节点:
2、在overlay dt中使用lable
将overlay dt中的target path更换为label
[my_overlay_dt-a.dts]
/dts-v1/;
/plugin/;
&my_lable {
maximus = "Maximus_Sun";
handsome {
first_name = "Maximus";
second_name = "Sun";
};
};
使用dtc编译,然后dump出来
/dts-v1/;
/ {
fragment@0 {
target = <0xffffffff>;
__overlay__ {
maximus = "Maximus_Sun";
handsome {
first_name = "Maximus";
second_name = "Sun";
};
};
};
__fixups__ {
my_node = "/fragment@0:target:0";
};
};
注意到target-path属性已经被target替代,target属性被赋了一个无效值,并且增加了fixups节点。
重复之前步骤,uboot启动失败了,看一下给出的提示:
failed on fdt_overlay_apply()
base fdt does did not have a /__symbols__ node
make sure you've compiled with -@
Failed to apply overlay 1
所以在编译base dtb的时候需要添加-@参数来生成symbols节点,所以我在AOSP的编译脚本中手动添加了-@参数,重复上面的步骤,成功启动,my_node也出现在设备树节点列表中:
(不少资料都说overlay dt编译时也需要添加-@参数,但是其实是不起作用的,-@并不会给overlay dt添加symbols节点,也不会影响运行)
3、-@做了什么?
首先,对base dt使用-@进行编译,dtc会添加一个symbols节点,它记录了base dt中所有lable对应的target path,这一点可以通过dump base dtb看出来:
__symbols__ {
cpu_pd_wait = "/cpus/idle-states/cpu-pd-wait";
A53_0 = "/cpus/cpu@0";
A53_1 = "/cpus/cpu@1";
A53_2 = "/cpus/cpu@2";
A53_3 = "/cpus/cpu@3";
A53_L2 = "/cpus/l2-cache0";
a53_opp_table = "/opp-table";
resmem = "/reserved-memory";
osc_32k = "/clock-osc-32k";
osc_24m = "/clock-osc-24m";
clk_ext1 = "/clock-ext1";
clk_ext2 = "/clock-ext2";
clk_ext3 = "/clock-ext3";
clk_ext4 = "/clock-ext4";
hsiomix_pd = "/power-domains/hsiomix-pd";
usb_otg1_pd = "/power-domains/usbotg1-pd";
gpumix_pd = "/power-domains/gpumix-pd";
dispmix_pd = "/power-domains/dispmix-pd";
mipi_pd = "/power-domains/mipi-pd";
...
同样可以在下位机中cat symbols节点下面的属性得到证实
evk_8mn:/proc/device-tree/__symbols__ # cat my_lable
/my_nodeevk_8mn:/proc/device-tree/__symbols__ #
继续深究,打开vendor/nxp-opensource/uboot-imx/scripts/dtc/libfdt/fdt_overlay.c中的fdt_overlay_apply函数定义部分
int fdt_overlay_apply(void *fdt, void *fdto)
{
uint32_t delta;
int ret;
FDT_RO_PROBE(fdt);
FDT_RO_PROBE(fdto);
ret = fdt_find_max_phandle(fdt, &delta);
if (ret)
goto err;
ret = overlay_adjust_local_phandles(fdto, delta);
if (ret)
goto err;
ret = overlay_update_local_references(fdto, delta);
if (ret)
goto err;
ret = overlay_fixup_phandles(fdt, fdto);
if (ret)
goto err;
ret = overlay_merge(fdt, fdto);
if (ret)
goto err;
ret = overlay_symbol_update(fdt, fdto);
if (ret)
goto err;
/*
* The overlay has been damaged, erase its magic.
*/
fdt_set_magic(fdto, ~0);
return 0;
err:
/*
* The overlay might have been damaged, erase its magic.
*/
fdt_set_magic(fdto, ~0);
/*
* The base device tree might have been damaged, erase its
* magic.
*/
fdt_set_magic(fdt, ~0);
return ret;
}
看函数名字也可以猜出这里是做什么的,看来uboot在做merge工作的时候大部分工作在处理phandle,打开overlay_fixup_phandles函数定义部分:
static int overlay_fixup_phandles(void *fdt, void *fdto)
{
int fixups_off, symbols_off;
int property;
/* We can have overlays without any fixups */
fixups_off = fdt_path_offset(fdto, "/__fixups__");
if (fixups_off == -FDT_ERR_NOTFOUND)
return 0; /* nothing to do */
if (fixups_off < 0)
return fixups_off;
/* And base DTs without symbols */
symbols_off = fdt_path_offset(fdt, "/__symbols__");
if ((symbols_off < 0 && (symbols_off != -FDT_ERR_NOTFOUND)))
return symbols_off;
fdt_for_each_property_offset(property, fdto, fixups_off) {
int ret;
ret = overlay_fixup_phandle(fdt, fdto, symbols_off, property);
if (ret)
return ret;
}
return 0;
}
uboot找到overlay dtb中的fixups节点,和base dtb的symbols节点,之后的fdt_for_each_property_offset是一个宏定义,其实是在遍历fixups节点中的属性,并根据属性提供的内容在symbols节点中找到正确的target path,
#define fdt_for_each_property_offset(property, fdt, node) \
for (property = fdt_first_property_offset(fdt, node); \
property >= 0; \
property = fdt_next_property_offset(fdt, property))
overlay_fixup_phandle根据前面收集到的信息为overlay dtb创建正确的phandle属性
到这里-@参数的作用就可以弄清楚了,所以说如果我们的overlay dts中使用到了lable,编译base dts的时候就一定要加-@,如果overlay dts仅仅使用target path,就可以不使用-@编译base dts