Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree 目录下根据节点名字创建不同文件夹,如图所示:
上图就是目录/proc/device-tree 目录下的内容,/proc/device-tree 目录下是根节点“/”的所有属性和子节点,我们依次来看一下这些属性和子节点。
1、根节点“/”各个属性
在图中,根节点属性属性表现为一个个的文件(图中细字体文件),比如图 中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设备树中就是根节点的5 个属性。既然是文件那么肯定可以查看其内容,输入cat 命令来查看model和 compatible 这两个文件的内容,结果如图所示:
从图中可以看出,文件 model 的内容是“Freescale i.MX6 ULL 14x14 EVK Board”,文件 compatible 的内容为“fsl,imx6ull-14x14-evkfsl,imx6ull”。打开文件 imx6ull-alientek-emmc.dts查看一下,这不正是根节点“/”的 model 和 compatible 属性值吗!
2、根节点“/”各子节点
图中各个文件夹(途中粗字体文件夹)就是根节点“/”的各个子节点,比如“aliases”、 “backlight”、“chosen”和“clocks”等等。大家可以查看一下 imx6ull-alientek-emmc.dts 和imx6ull.dtsi 这两个文件,看看根节点的子节点都有哪些,看看是否和图中的一致。/proc/device-tree 目录就是设备树在根文件系统中的体现,同样是按照树形结构组织的,进入/proc/device-tree/soc 目录中就可以看到 soc 节点的所有子节点,如图所示:
和根节点“/”一样,图中的所有文件分别为 soc 节点的属性文件和子节点文件夹。大家可以自行查看一下这些属性文件的内容是否和 imx6ull.dtsi 中 soc 节点的属性值相同,也可以进入“busfreq”这样的文件夹里面查看 soc 节点的子节点信息。
在根节点“/”中有两个特殊的子节点:aliases 和 chosen,我们接下来看一下这两个特殊的子节点。
3、 aliases 子节点
打开 imx6ull.dtsi 文件,aliases 节点内容如下所示:
18 aliases { 19 can0 = &flexcan1; 20 can1 = &flexcan2; 21 ethernet0 = &fec1; 22 ethernet1 = &fec2; 23 gpio0 = &gpio1; 24 gpio1 = &gpio2; ...... 42 spi0 = &ecspi1; 43 spi1 = &ecspi2; 44 spi2 = &ecspi3; 45 spi3 = &ecspi4; 46 usbphy0 = &usbphy1; 47 usbphy1 = &usbphy2; 48 };
单词 aliases 的意思是“别名”,因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上label,然后通过&label 来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。
4、 chosen 子节点
chosen 并不是一个真实的设备,chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少,imx6ull-alientek-emmc.dts 中 chosen 节点内容如下所示:
18 chosen { 19 stdout-path = &uart1; 20 };
从示例代码中可以看出,chosen 节点仅仅设置了属性“stdout-path”,表示标准输出使用 uart1。但是当我们进入到/proc/device-tree/chosen 目录里面,会发现多了 bootargs 这个属性,如图所示:
输入 cat 命令查看bootargs 这个文件的内容,结果如图所示:
从图中可以看出,bootargs 这个文件的内容为“console=ttymxc0,115200……”,这个不就是我们在uboot 中设置的 bootargs 环境变量的值吗?现在有两个疑点:
①、我们并没有在设备树中设置 chosen 节点的 bootargs 属性,那么图中 bootargs这个属性是怎么产生的?
②、为何 bootargs 文件的内容和 uboot 中 bootargs 环境变量的值一样?它们之间有什么关系?
前面讲解uboot 的时候说过,uboot 在启动Linux 内核的时候会将bootargs 的值传递给Linux内核,bootargs 会作为Linux 内核的命令行参数,Linux 内核启动的时候会打印出命令行参数(也就是 uboot 传递进来的bootargs 的值),如图所示:
既然 chosen 节点的 bootargs 属性不是我们在设备树里面设置的,那么只有一种可能,那就是 uboot 自己在 chosen 节点里面添加了 bootargs 属性!并且设置 bootargs 属性的值为 bootargs环境变量的值。因为在启动 Linux 内核之前,只有 uboot 知道 bootargs 环境变量的值,并且 uboot也知道.dtb 设备树文件在 DRAM 中的位置,因此 uboot 的“作案”嫌疑最大。在 uboot 源码中全局搜索“ chosen ” 这个字符串, 看看能不能找到一些蛛丝马迹。果然不出所料, 在common/fdt_support.c 文件中发现了“chosen”的身影,fdt_support.c 文件中有个 fdt_chosen 函数,此函数内容如下所示:
275 int fdt_chosen(void *fdt) 276 { 277 int nodeoffset; 278 int err; 279 char *str; /* used to set string properties */ 280 281 err = fdt_check_header(fdt); 282 if (err < 0) { 283 printf("fdt_chosen: %s\n", fdt_strerror(err)); 284 return err; 285 } 286 287 /* find or create "/chosen" node. */ 288 nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen"); 289 if (nodeoffset < 0) 290 return nodeoffset; 291 292 str = getenv("bootargs"); 293 if (str) { 294 err = fdt_setprop(fdt, nodeoffset, "bootargs", str, 295 strlen(str) + 1); 296 if (err < 0) { 297 printf("WARNING: could not set bootargs %s.\n", 298 fdt_strerror(err)); 299 return err; 300 } 301 } 302 303 return fdt_fixup_stdout(fdt, nodeoffset); 304 }
第 288 行,调用函数 fdt_find_or_add_subnode 从设备树(.dtb)中找到 chosen 节点,如果没有找到的话就会自己创建一个 chosen 节点。
第 292 行,读取 uboot 中 bootargs 环境变量的内容。
第 294 行,调用函数 fdt_setprop 向 chosen 节点添加 bootargs 属性,并且 bootargs 属性的值就是环境变量bootargs 的内容。
证据“石锤”了,就是 uboot 中的 fdt_chosen 函数在设备树的chosen 节点中加入了 bootargs属性,并且还设置了 bootargs 属性值。接下来我们顺着 fdt_chosen 函数一点点的抽丝剥茧,看看都有哪些函数调用了 fdt_chosen,一直找到最终的源头。这里我就不卖关子了,直接告诉大家整个流程是怎么样的,见下图:
图中框起来的部分就是函数 do_bootm_linux 函数的执行流程, 也就是说do_bootm_linux 函数会通过一系列复杂的调用,最终通过 fdt_chosen 函数在 chosen 节点中加入了 bootargs 属性。而我们通过 bootz 命令启动 Linux 内核的时候会运行 do_bootm_linux 函数,至此,真相大白,一切事情的源头都源于如下命令:
bootz 80800000 – 83000000
当我们输入上述命令并执行以后,do_bootz函数就会执行,然后一切就按照图中所示的流程开始运行。