一、/proc/device-tree
Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/deive-tree 目录下根据节点名字创建不同的文件夹,如图所示:
/proc/device-tree 目录下是根节点 “/” 的所有属性和子节点。
二、根节点各个属性
根节点属性表现为一个个的文件:
- #address-cells
- #size-cells
- compatible
- model
- name
既然是文件,那么可以查看其内容:
cat compatible
输出
三、根节点各个子节点
/proc/device-tree/ 下各个文件夹就是根节点 “/” 的各个子节点,比如:
- soc
- backlight
- chosen
- clocks
/proc/device-tree/ 目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/soc 目录中就可以看到soc 节点的所有子节点。
四、根节点下的特殊节点
在根节点 “/” 中有两个特殊的子节点:aliases 和 chosen。
4.1 aliases 子节点
打开imx6ull.dtsi 文件,aliases 节点内容如下所示:
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
serial3 = &uart4;
serial4 = &uart5;
serial5 = &uart6;
serial6 = &uart7;
serial7 = &uart8;
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
usbphy0 = &usbphy1;
usbphy1 = &usbphy2;
};
单词aliases 的意思是 “别名” ,因此aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候加上label,然后通过&label 来访问节点,这样也很方便,而且设备树里面有大量的使用&label 的形式来访问节点。
4.2 chosen 子节点
chosen 并不是一个真实的设备,chosen 节点主要是为了uboot 向linux 内核传递数据,重点是bootargs 参数。一般 .dts 文件中chosen 节点通常为空或者内容很少,imx6ull-alientek-emmc.dts 中chosen 节点内容如下所示:
chosen {
stdout-path = &uart1;
};
从上述代码可以看出,chosen 节点仅仅设置了属性 “stdout-path” , 表示标准输出使用 uart1 。但是当我们进入到/proc/device-tree/chosen 目录里面,会发现多了bootargs 这个属性。
输入cat 命令,查看bootargs 这个文件的内容,结果如下:
console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.10.100:/home/ljy/Linux/nfs/rootfs,proto=tcp rw ip=192.168.10.50:192.168.10.100:192.168.10.1:255.255.255.0::eth0:off
这个就是我们在uboot 中设置的bootargs 环境变量的值。现在有两个疑点:
- 我们并没有在设备树中设置chosen 节点的bootargs 属性,那么这个属性是怎么产生的?
- 为何bootargs 文件的内容和uboot 中bootargs 环境变量的值一样?它们之间有什么关系?
uboot 在启动Linux 内核的时候会将bootargs 的值传递给Linux 内核,bootargs 会作为Linux 内核的命令行参数,Linux 内核启动的时候会打印出命令行参数。
uboot 自己在chosen 节点里面添加了bootargs 属性,并且设置bootargs 属性的值为bootargs 环境变量的值。
在启动Linux 内核之前,只有uboot 知道bootargs 环境变量的值,并且uboot 也知道.dtb 设备树文件在DRAM 中的位置。
Uboot 源码中的实现
uboot 源码中,common/fdt_support.c 文件中发现了“chosen” 的处理,如下所示:
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. */
nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
if (nodeoffset < 0)
return nodeoffset;
str = getenv("bootargs");
if (str) {
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);
}
- 调用函数fdt_find_or_add_subnode 从设备树(.dtb) 中找到chosen 节点,如果没有找到的话就会子创建一个chosen 节点。
- 调用fdt_setprop 向chosen 节点添加bootargs 属性,并且bootargs 属性的值就是环境变量bootargs 的内容。
uboot 加入bootargs 属性的流程
uboot 在设备树的chosen 节点加入bootargs 属性的流程如下:
五、内核解析DTB 文件
Linux 内核在启动的时候会解析DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树根节点文件。Linux 内核解析DTB 文件的流程如下:
在start_kernel 函数中完成设备树节点解析的工作,最终实际工作的函数为unflatten_dt_node。