设备树在Linux系统内核中的体现

2019-11-25 08:44发布


Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree 目录下根据节点名字创建不同文件夹,如图所示:

111


根节点“/”的属性以及子节点

上图就是目录/proc/device-tree 目录下的内容,/proc/device-tree 目录下是根节点“/”的所有属性和子节点,我们依次来看一下这些属性和子节点。

1、根节点“/”各个属性

在图中,根节点属性属性表现为一个个的文件(图中细字体文件),比如图 中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设备树中就是根节点的5 个属性。既然是文件那么肯定可以查看其内容,输入cat 命令来查看model和 compatible 这两个文件的内容,结果如图所示:

222


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 节点的所有子节点,如图所示:

333


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 这个属性,如图所示:

444


chosen 节点目录

输入 cat 命令查看bootargs 这个文件的内容,结果如图所示:

555


bootargs 文件内容

从图中可以看出,bootargs 这个文件的内容为“console=ttymxc0,115200……”,这个不就是我们在uboot 中设置的 bootargs 环境变量的值吗?现在有两个疑点:

①、我们并没有在设备树中设置 chosen 节点的 bootargs 属性,那么图中 bootargs这个属性是怎么产生的?

②、为何 bootargs 文件的内容和 uboot 中 bootargs 环境变量的值一样?它们之间有什么关系?

前面讲解uboot 的时候说过,uboot 在启动Linux 内核的时候会将bootargs 的值传递给Linux内核,bootargs 会作为Linux 内核的命令行参数,Linux 内核启动的时候会打印出命令行参数(也就是 uboot 传递进来的bootargs 的值),如图所示:

666


命令行参数

既然 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,一直找到最终的源头。这里我就不卖关子了,直接告诉大家整个流程是怎么样的,见下图:

777


fdt_chosen 函数调用流程

图中框起来的部分就是函数 do_bootm_linux 函数的执行流程, 也就是说do_bootm_linux 函数会通过一系列复杂的调用,最终通过 fdt_chosen 函数在 chosen 节点中加入了 bootargs 属性。而我们通过 bootz 命令启动 Linux 内核的时候会运行 do_bootm_linux 函数,至此,真相大白,一切事情的源头都源于如下命令:

bootz 80800000 – 83000000

当我们输入上述命令并执行以后,do_bootz函数就会执行,然后一切就按照图中所示的流程开始运行。