在Linux3.x
之前的内核源码中,存在大量对板级细节信息描述的代码。这些代码充斥在/arch/arm/plat-xxx
和/arch/arm/mach-xxx
目录中。每年新出的 ARM
架构芯片少说都有数十、数百款,每一款芯片又会有很多款基于该芯片的板子,这些板子上的设备又不相同,每一块板子都要有自己的板级信息文件,所以Linux 内核下板级信息文件将会成指数级增长!关键是这些板级信息文件还都是.c 或.h 文件,它们都会被编译进 Linux 内核中,导致 Linux 内核“虚胖”。因此为了解决这个问题,引入设备树。
设备树(Device Tree
),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source)
,这个 DTS
文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU
数量、 内存基地址、IIC
接口上接了哪些设备、SPI
接口上接了哪些设备等等,如下图所示:
在上图中,树的主干就是系统总线,IIC
控制器、GPIO
控制器、SPI
控制器等都是接到系统主线上的分支。IIC
控制器有分为 IIC1
和 IIC2
两种,其中IIC1
上接了FT5206
和 AT24C02
这两个 IIC
设备,IIC2
上只接了 MPU6050
这个设备。DTS
文件的主要功能就是按照上图所示的结构来描述板子上的设备信息,DTS
文件描述设备信息是有相应的语法规则要求的。
所以总结来说就是
:设备树是一种描述硬件资源的数据结构。它通过bootloader
将硬件资源传给内核,使得内核和硬件资源描述相对独立,也即设备树文件不需要编译进内核。
设备树的主要优势
:在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。而引入设备树之后,对于同一SOC
的不同主板,只需更换设备树文件.dtb
即可实现不同主板的无差异支持,而无需更换内核文件。
dts
文件是对Device Tree
的描述,放置在内核的/arch/arm/boot/dts
目录。一个*.dts
文件对应一个ARM
的machine
。dts
文件描述了一个板子的硬件资源。以前写在mach-xxx
文件中的内容被转成了dts
文件。
: dtb
是编译*.dts
生成的二进制文件(.dtb
),bootloader
在引导内核时,会预先读取.dtb
到内存,进而由内核解析。
dtc
为编译工具,它可以将.dts
文件编译成.dtb
文件。DTC
工具源码在 Linux 内核的 scripts/dtc
目录下。如果要编译 .dts
文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:make all
或者make dtbs
。make all
命令是编译 Linux 源码中的所有东西,包括 zImage
,.ko
驱动模块以及设备树
,如果只是编译设备树的话建议使用make dtbs
命令。还可以直接指定要编译的文件,如xxx.dtb
,意思是将xxx.dts
编译成dtb
。注意我们编译的时候只会编译修改的文件,没有修改的dts
文件是不会被再次编译的。
一个 SOC
可以作出很多不同的板子,这些不同的板子肯定是有共同的信息,将这些共同的信息提取出来作为一个通用的文件,其他的.dts
文件直接引用这个通用文件即可,这个通用文件就是.dtsi
文件,类似于C
语言中的头文件。一般.dts
描述板级信息(也就是开发板上有哪些 IIC
设备、SPI
设备等),.dtsi
描述 SOC
级信息(也就是 SOC
有几个CPU
、主频是多少、各个外设控制器信息等)。
设备树用树状结构描述设备信息,它有以下几种特性
key-value
对(键值对)来描述。;
结束/*
所以,一个设备树的基本框架可以写成下面这个样子,一般来说,/表示板子,
它的子节点node1表示SoC上的某个控制器,控制器中的子节点node2表示
挂接在这个控制器上的设备(们)。
*/
/{ //根节点
node1{ //node1是节点名,是/的子节点
key=value; //node1的属性
...
node2{ //node2是node1的子节点
key=value; //node2的属性
...
}
} //node1的描述到此为止
node3{
key=value;
...
}
}
下面通过一个具体的dtsi
文件来看一下设备树文件的框架是怎样的。注意,dts
和dtsi
虽然是不同的文件,但是他们只是描述的信息不同,结构都是一样的。
10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16
17 / {
18 aliases {
19 can0 = &flexcan1;
......
48 };
49
50 cpus {
51 #address-cells = <1>;
52 #size-cells = <0>;
53
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
......
89 };
90 };
91
92 intc: interrupt-controller@00a01000 {
93 compatible = "arm,cortex-a7-gic";
94 #interrupt-cells = <3>;
95 interrupt-controller;
96 reg = <0x00a01000 0x1000>,
97 <0x00a02000 0x100>;
98 };
99
100 clocks {
101 #address-cells = <1>;
102 #size-cells = <0>;
103
104 ckil: clock@0 {
105 compatible = "fixed-clock";
106 reg = <0>;
107 #clock-cells = <0>;
108 clock-frequency = <32768>;
109 clock-output-names = "ckil";
110 };
......
135 };
136
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
143
144 busfreq {
145 compatible = "fsl,imx_busfreq";
......
162 };
197
198 gpmi: gpmi-nand@01806000{
199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
200 #address-cells = <1>;
201 #size-cells = <1>;
202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
......
216 };
......
1177 };
1178 };
示例代码中第 54~89
行就是 cpu0
这个设备节点
信息,这个节点信息描述了SOC
所使用的 CPU
信息,比如架构是 cortex-A7
,频率支持 996MHz、792MHz、 528MHz、396MHz 和 198MHz
等等。在文件中不仅仅描述了 cpu0
这一个节点信息,这颗 SOC
所有的外设都描述的清清楚楚,比如 ecspi1-4
、uart1-8
、usbphy1-2
、i2c1-4
等等。
Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree
目录下根据节点名字创建不同文件夹,如下图所示:
首先需要注意的是,既然你的板子可以跑这个系统,那么这个系统肯定是已经对你的板子做了适配,因此当你到此文件夹下的时候,里面的各个属性和子节点肯定是针对的运行该系统的这个板子,也即对应的是你这个板子的dts
文件。当然这是在已经完全适配的情况下,在此之前我们可以自己往里面添加属性和子节点。
我们要知道的是,在目录下,文件对应属性,文件夹对应子节点,每个子节点又可以有属性和子节点,就这样一级一级循环下去。因为属性有值,而且对应的是文件,我们就可以通过cat
命令来查看文件内容,也就是属性的值。
设备树是用来描述板子上的设备信息的,不同的设备其信息不同,反映到设备树中就是属
性不同。那么我们在设备树中添加一个硬件对应的节点的时候从哪里查阅相关的说明呢?在Linux 内核源码中有详细的.txt
文档描述了如何添加节点,这些.txt
文档叫做绑定文档,路径为:Linux 源码目录/Documentation/devicetree/bindings
比如我们现在要想在I.MX6ULL
这颗SOC
的 I2C
下添加一个节点,那么就可以查看
Documentation/devicetree/bindings/i2c/i2c-imx.txt
,此文档详细的描述了I.MX
系列的SOC
如何在设备树中添加 I2C
设备节点,文档内容如下所示:
* Freescale Inter IC (I2C) and High Speed Inter IC (HS-I2C) for i.MX
Required properties:
- compatible :
- "fsl,imx1-i2c" for I2C compatible with the one integrated on i.MX1 SoC
- "fsl,imx21-i2c" for I2C compatible with the one integrated on i.MX21 SoC
- "fsl,vf610-i2c" for I2C compatible with the one integrated on Vybrid vf610 SoC
- reg : Should contain I2C/HS-I2C registers location and length
- interrupts : Should contain I2C/HS-I2C interrupt
- clocks : Should contain the I2C/HS-I2C clock specifier
Optional properties:
- clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz.
The absence of the propoerty indicates the default frequency 100 kHz.
- dmas: A list of two dma specifiers, one for each entry in dma-names.
- dma-names: should contain "tx" and "rx".
Examples:
i2c@83fc4000 { /* I2C2 on i.MX51 */
compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
reg = <0x83fc4000 0x4000>;
interrupts = <63>;
};
i2c@70038000 { /* HS-I2C on i.MX51 */
compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
reg = <0x70038000 0x4000>;
interrupts = <64>;
clock-frequency = <400000>;
};
i2c0: i2c@40066000 { /* i2c0 on vf610 */
compatible = "fsl,vf610-i2c";
reg = <0x40066000 0x1000>;
interrupts =<0 71 0x04>;
dmas = <&edma0 0 50>,
<&edma0 0 51>;
dma-names = "rx","tx";
};
当然关于设备树还有很多重要的知识点,比如节点中各个属性的含义、DTS的语法规则、设备树常用的OF操作函数等等,因为比较重要,所以我们之后单独说。
参考文章:https://www.cnblogs.com/xiaojiang1025/p/6131381.html