Linux 内核:设备树中的特殊节点
背景
在解析设备树dtb格式的时候,发现了这个,学习一下。
参考:
介绍
常见的特殊节点有
-
aliases
:用于定义别名,目的就是为了方便访问节点 -
chosen
:chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少
以我之前调试过的zynq平台为例。
/ {
model = "ZynqMP ZCU104 RevA";
compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
aliases {
ethernet0 = &gem3;
gpio0 = &gpio;
i2c0 = &i2c1;
mmc0 = &sdhci1;
rtc0 = &rtc;
serial0 = &uart0;
serial1 = &uart1;
serial2 = &dcc;
spi0 = &qspi;
usb0 = &usb0;
};
chosen {
bootargs = "earlycon";
stdout-path = "serial0:115200n8";
};
memory@0 {
device_type = "memory";
reg = <0x0 0x0 0x0 0x80000000>;
};
};
aliases 子节点
单词 aliases 的意思是“别名”,因此 aliases
节点用于定义别名,目的就是为了方便访问节点。
不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。
/ {
model = "ZynqMP ZCU104 RevA";
compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
aliases {
// ...
spi0 = &qspi;
};
// ...
};
// ...
&qspi {
status = "okay";
flash@0 {
compatible = "m25p80", "spi-flash"; /* n25q512a 128MiB */
#address-cells = <1>;
#size-cells = <1>;
reg = <0x0>;
spi-tx-bus-width = <1>;
spi-rx-bus-width = <4>;
spi-max-frequency = <108000000>; /* Based on DC1 spec */
partition@qspi-fsbl-uboot { /* for testing purpose */
label = "qspi-fsbl-uboot";
reg = <0x0 0x100000>;
};
partition@qspi-linux { /* for testing purpose */
label = "qspi-linux";
reg = <0x100000 0x500000>;
};
partition@qspi-device-tree { /* for testing purpose */
label = "qspi-device-tree";
reg = <0x600000 0x20000>;
};
partition@qspi-rootfs { /* for testing purpose */
label = "qspi-rootfs";
reg = <0x620000 0x5E0000>;
};
};
};
chosen 子节点
chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。
一般.dts 文件中 chosen 节点通常为空或者内容很少, imx6ull-alientekemmc.dts 中 chosen 节点内容如下所示:
/ {
model = "ZynqMP ZCU104 RevA";
compatible = "xlnx,zynqmp-zcu104-revA", "xlnx,zynqmp-zcu104", "xlnx,zynqmp";
chosen {
bootargs = "earlycon";
stdout-path = "serial0:115200n8";
};
// ...
};
从上面中可以看出, chosen 节点设置了
- “
stdout-path
”,表示标准输出使用serial0
。 -
bootargs
,表示用于Linux
的启动参数
uboot、linux与bootargs
在支持设备树的嵌入式系统中,实际上:
- uboot基本上可以不通过显式的
bootargs=xxx
来传递给内核,而是在env
拿出,并存放进设备树中的chosen
节点中 - Linux也开始在设备树中的
chosen
节点中获取出来,
这样子就可以做到针对uboot与Linux在bootargs
传递上的统一。
uboot 与 chosen
结论:uboot 会自己在chosen
节点里面添加了 bootargs
属性!并且设置 bootargs 属性的值为 bootargs环境变量的值。
因为在启动 Linux 内核之前,只有 uboot 知道 bootargs 环境变量的值,并且 uboot也知道.dtb 设备树文件在 DRAM 中的位置,所以uboot可以这样子做。
// common/fdt_support.c
int fdt_chosen(void *fdt)
{
int nodeoffset;
int err;
char *str; /* used to set string properties */
err = fdt_check_header(fdt);
if (err < 0) {
printf("fdt_chosen: %s\n", fdt_strerror(err));
return err;
}
/* find or create "/chosen" node. */
// 从设备树(.dtb)中找到 chosen 节点,
// 如果没有找到的话就会自己创建一个 chosen 节点
nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
if (nodeoffset < 0)
return nodeoffset;
// 读取 uboot 中 bootargs 环境变量的内容。
str = getenv("bootargs");
if (str) {
// 向 chosen 节点添加 bootargs 属性,并且 bootargs 属性的值就是环境变量 bootargs 的内容
err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
strlen(str) + 1);
if (err < 0) {
printf("WARNING: could not set bootargs %s.\n",
fdt_strerror(err));
return err;
}
}
return fdt_fixup_stdout(fdt, nodeoffset);
}
调用流程:
bootz
do_bootz()
do_bootm_states()
boot_selected_os()
boot_fn() -> do_bootm_linux
// 准备启动Linux之前的一些工作
boot_prep_linux()
image_setup_linux()
image_setup_libfdt()
fdt_chosen()
上图中框起来的部分就是函数 do_bootm_linux
函数的执行流程,也就是说do_bootm_linux
函数会通过一系列复杂的调用,最终通过 fdt_chosen
函数在 chosen 节点中加入了 bootargs
属性。
这样子,Linux内核在启动的时候,就可以根据bootargs
来做自己要做的事情。
linux与 chosen
以arm架构为例。
Linux会根据dtb中的chosen
中的bootargs
属性来重写cmd_lines
。
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
unsigned long l;
char *p;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
if (depth != 1 || !data ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
early_init_dt_check_for_initrd(node);
/* Retrieve command line */
// 找到设备树中的的chosen节点中的bootargs,并作为cmd_line
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
// ...
pr_debug("Command line is: %s\n", (char*)data);
/* break now */
return 1;
}
流程如下:
start_kernel
setup_arch(&command_line);
setup_machine_fdt();
early_init_dt_scan_nodes();
early_init_dt_scan_chosen();