设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。

在没有使用设备树的时候,有关板级的硬件信息都被硬编码在内核中,这就导致了内核中描述板级硬件的代码过于庞大,不利于阅读。
随着智能手机的发展,每年新出的 ARM 架构芯片少说都在数十、数百款, Linux 内核下板级信息文件将会成指数级增长!这些板级信息文件都会被硬编码进 Linux 内核中,导致 Linux 内核**“虚胖”**。
当 Linux 之父 linus 看到 ARM 社区向 Linux 内核添加了大量无用、冗余的板级信息文件,不禁的发出了一句This whole ARM thing is a fucking pain in the ass。
从此以后 ARM 社区就引入了 PowerPC 等架构已经采用的设备树(Flattened Device Tree)
这个就是设备树的由来,简而言之就是, Linux 内核中 ARM 架构下有太多的冗余的垃圾板级信息文件,导致 linus 震怒,然后 ARM 社区引入了设备树。
设备树源文件扩展名为.dts,另外DTS是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。
对于设备树文件的编译我们可以使用DTC 工具对其进行编译,DTC工具源码在Linux内核的scripts/dtc目录下。
DTC 工具依赖于 dtc.c、 flattree.c、 fstree.c 等文件,最终编译并链接出 DTC 这
个主机文件。
如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命
令:
make dtbs
Linux设备树文件在内核源码中的 arch/arm/mach-xxx 文件夹和arch/arm/plat-xxx 文件夹中
我们想要使用设备树首先需要修改设备树文件,设备树文件的位置我们上面已经所过了,大家可以去找一下:

我们想要使用设备树首先需要修改设备树文件,我们需要首先找到我们需要修改的设备树文件,如果不知道对应模块的设备树文件在哪个位置的话我上面有讲到,设备树文件都在内核源码中的 arch/arm/mach-xxx 文件夹和arch/arm/plat-xxx 文件夹中。
在文件夹中找到我们芯片或者开发板所对应的DTS文件,例如我使用的开发板是野火公司的I.MX6U开发板,所以我们需要首先找到这个开发板所对应的设备树,我们所有的设备都需要在这个设备树文件中进行描述。如果没有可能是内核版本太老所致。
找到我们需要修改的设备树文件后打开对应的.dts 文件,在根节“/”下创建一个名为“alphaled”的子节点,打开 imx6ull-alientek-emmc.dts 文件,
在根节点“/”最后面输入如下所示内容:
1 alphaled {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-led";
5 status = "okay";
6 reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
7 0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
8 0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
9 0X0209C000 0X04 /* GPIO1_DR_BASE */
10 0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
11 };
2、 3 行:属性#address-cells 和#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。4 行:属性 compatbile 设置 alphaled 节点兼容性为“atkalpha-led”。5 行:属性 status 设置状态为“okay”。6~10 行: reg 属性,非常重要! reg 属性设置了驱动里面所要使用的寄存器物理地址,比如第 6 行:“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。设备树文件修改之后我们需要首先进行编译以下,正如我们编译C语言一样,DTS文件也是需要编译的,我们使用以下命令对设备树文件DTS进行编译。
make dtbs
编译完成以后得到 imx6ull-alientek-emmc.dtb,使用新的 imx6ull-alientek-emmc.dtb 启动Linux 内核。 Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有“alphaled”这个节点,结果如图:
如果没有“alphaled”节点的话请重点下面两点:
alphaled 节点是否为根节点“/”的子节点。可以进入到图 44.3.1 中的 alphaled 目录中,查看一下都有哪些属性文件,结果如图 所示:

大家可以查看一下 compatible、 status 等属性值是否和我们设置的一致。如果不一致修改过来即可。
在编写驱动文件过程中我们只需要关注一下设备树的改动,其他例如申请设备号啥的和原来是一样的,我们现在不需要关注,如果你对字符设备驱动框架的理解还不是很深,你可以看我写的这篇文章:
回归主题,我们现在开始修改设备树相关代码,我们首先在驱动入口处添加以下代码:
/* 获取设备树中的属性数据 */
/* 1、获取设备节点: alphaled */
dtsled.nd = of_find_node_by_path("/alphaled");
if(dtsled.nd == NULL) {
printk("alphaled node not find!\r\n");
return -EINVAL;
} else {
printk("alphaled node find!\r\n");
}
/* 2、获取 compatible 属性内容 */
proper = of_find_property(dtsled.nd, "compatible", NULL);
if(proper == NULL) {
printk("compatible property find failed\r\n");
} else {
printk("compatible = %s\r\n", (char*)proper->value);
}
/* 3、获取 status 属性内容 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!\r\n");
} else {
printk("status = %s\r\n",str);
}
/* 4、获取 reg 属性内容 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!\r\n");
} else {
u8 i = 0;
printk("reg data:\r\n");
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("\r\n");
}
/* 初始化 LED */
#if 0
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif
看到上面的代码是不是感觉加入设备树之后代码结构变得好复杂啊!还要获取那么多设备树属性和数据,这不是妥妥的给自己找不爽吗?
其实设备树的加入主要是为了减少这些板级信息被写入Linux内核中,因为如果不使用设备树的话我们每一个开发板都需要加入到Linux内核中,否者你就无法使用该款开发板,基于此,设备树就此诞生了,让我们每一个开发板都有一棵属于自己的树,板上的硬件相当于树上的分支,这样Linux在启动的时候就会先去找你所对应的设备树,相当于读到了你板子的信息,你就可以使用设备树了!
其实设备树的知识远不止于此,我目前也是囫囵吞枣,也有很多不明白的地方,大家可以多读几篇文章,看看大佬们怎么总结的。