一、设备树概念
设备树被用来描述板级信息,包括:CPU的数量和类别、内存基地址和大小、总线和桥、外设链接、中断控制器和中端使用情况、GPIP控制器和GPIO使用情况、Clock控制器和Clock使用情况等。
设备树信息被保存在一个ASCII文本文件中,适合人类的阅读习惯,类似于xml文件,在ARM Linux中,一个.dts文件对应一个ARM的machine,放置在内核的arch/arm(arm64)/boot/dts中。
设备树是一种数据结构,用于描述设备信息的语言,具体而言,是用于操作系统中描述硬件,使得不需要对设备的信息进行硬编码(hard code)。
设备树由一系列被命名的**节点(node)和属性(property)**组成,而节点本身可包含子节点。所谓属性,其实就是成对出现的键值对(key-value)。
设备树源文件dts被编译成dtb二进制文件,在bootloader运行时传递给操作系统,操作系统对其进行展开(Flattened),从而产生一个硬件设备的拓扑图,有这个拓扑图,在编码的过程中就可以直接通过系统提供的接口获取到设备树的节点和属性信息。
二、DTS、DTB和DTC
.dts文件通过DTC工具编译为.dtb,可以理解为.c文件通过GCC工具编译为.o文件。
待补充:
DTC工具源码及其Makefile;
加载及编译设备树文件;
三、DTS语法
目的是会写一个小型设备树,设备树详细语法规则请参考《》
3.1 .dtsi头文件
在.dts文件中使用“#include”来引用“imx6ull.dtsi”这个.dtsi 头文件。
//示例代码 43.3.1.1 imx6ull-alientek-emmc.dts 文件代码段
#include "imx6ull.dtsi"
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范
围,比如 UART、IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息
的。
/*
* 参考源码为imx6ul.dtsi,在 imx6ul.dtsi 文件中不仅仅描述了cpu0 这一个节点信息,
* 还将I.MX6ULL 这颗SOC的外设都描述了,比如ecspi1~4、uart1~8、usbphy1~2和i2c1~4等等。
*/
/ {
#address-cell = <1>;
#size-cell = <1>;
chosen {};
aliases {
ethernet0 = &fec1; //别名
};
cpus {
address-cell = <1>;
address-cell = <0>;
cpu0: cpu@0 {
...
};
};
...
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
ocram: sram@900000 {
};
...
dma_apbh: dma-apbh@1804000 {
};
aips1: aips-bus@2000000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
ranges;
spba-bus@2000000 {
compatible = "fsl,spba-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x40000>;
ranges;
ecspi1: ecspi@2008000 {
};
ecspi2: ecspi@200c000 {
};
ecspi3: ecspi@2010000 {
};
};
pwm1: pwm@2080000 {
};
pwm2: pwm@2084000 {
};
};
aips2: aips-bus@2100000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02100000 0x100000>;
ranges;
adc1: adc@2198000 {
};
i2c1: i2c@21a0000 {
};
i2c2: i2c@21a4000 {
};
uart5: serial@21f4000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart";
reg = <0x021f4000 0x4000>;
interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART5_IPG>,
<&clks IMX6UL_CLK_UART5_SERIAL>;
clock-names = "ipg", "per";
status = "disabled";
};
};
};
};
3.2 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键值对。
/ {
aliases {
can0 = &flexcan1;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatilble = "arm,cortex-a7";
device_type = "cpu";
};
};
intc: interrupt-controller@00a01000 {
/*
* 设备就是节点,叫做设备节点
* 有属性就意味着是设备节点,设备节点便是具体设备是吗?
* 可以理解为有键值对就是具体设备吗?
* 在dtsi中应该不是,因为是头文件的共性信息,具体设备应该是在dts文件中
* dtsi中应该是设备节点及其信息。
*/
compatilble = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x1000>;
};
};
aliases、cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:node-name@unit-address
其中“node-name”是节点名字,为 ASCII 字符串。“unit-address”一般表示设备的地址或寄
存器首地址,如“interrupt-controller@00a01000”。
label: node-name@unit-address
intc: interrupt-controller@00a01000
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过
&cpu0 就可以访问“cpu@0”这个节点。
3.2.1 字符串
compatible = "arm,cortex-a7";
上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。
3.2.2 32 位无符号整数
reg = <0>;
上述代码设置 reg 属性的值为 0,reg 的值也可以设置为一组值,比如:
reg = <0 0x123456 100>;
3.2.3 字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。
3.3 标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以
自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux 下的很多外设驱动都会使用
这些标准属性。
3.3.1 compatible 属性
compatible 属性用于将设备和驱动绑定起来。一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
待补充:
内核函数和compatible属性的逻辑调用关系;
3.3.2 model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的。
3.3.3 status 属性
status属性描述设备状态信息。
值 | 描述 |
---|---|
“okay” | 表明设备是可操作的。 |
“disabled” | 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。 |
“fail” | 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。 |
“fail-sss” | 含义和“fail”相同,后面的 sss 部分是检测到的错误内容。 |
3.3.4 #address-cells 和#size-cells 属性
两个属性的值都是无符号 32 位整形,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位),#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg 属性的格式一般为:
reg = <address1 length1 address2 length2 address3 length3......>
每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用
的字长。
用以下代码举个实例说明:
aips3: aips-bus@02200000 {
compatible = "fsl,aips-bus", "simple-bus";
#adress-cells = <1>; //节点起始地址 长度所占字节为1
#size-cells = <0>; //地址 长度所占字节为1
dcp: dcp@02280000 {
compatible = "fsl,imx6sl-dcp";
//adress = 0x02280000,length = 0x4000
//相当于起始地址为0x02280000,地址长度为0x4000
reg = <0x02280000 0x4000>;
};
};
3.3.5 reg 属性
reg 属性的值一般是(address,length)对。reg 属性一般用于描
述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息
3.3.6 ranges 属性
3.3.7 name 属性
3.3.8 device_type 属性
3.4 根节点 compatible 属性
Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。
3.4.1 使用设备树之前的设备匹配方法
3.4.1 使用设备树之后的设备匹配方法
对这个流程没有自己追一追的话,始终差点感觉。
待补充:
追代码流程;
3.5 向节点追加或修改内容
硬件修改了,我们就要同步的修改设备树文件。
在开发板的.dts文件中进行数据追加:比如在i2c1 节点追加一个名为 fxls8471 的子节点
//访问i2c1这个label对应的节点,也即i2c1@021a0000
&i2c1 {
clock-frequency = <100000>; //表示时钟为100KHZ
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay"; //status 属性的值由原来的 disabled 改为 okay
//NXP官方开发板在I2C1上接了一个磁力计芯片mag3110
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
//NXP官方开发板在I2C1上接了六轴芯片fxls8471
fxls8471@le {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};
四、设备树demo
五、设备树在系统中的体现
六、设备树特殊节点
aliases子节点
aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。
aliases {
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;
sai1 = &sai1;
sai2 = &sai2;
sai3 = &sai3;
spi0 = &ecspi1;
spi1 = &ecspi2;
spi2 = &ecspi3;
spi3 = &ecspi4;
usbphy0 = &usbphy1;
usbphy1 = &usbphy2;
};
chosen子节点
chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般.dts 文件中 chosen 节点通常为空或者内容很少。
chosen函数调用流程:
七、内核解析DTB文件
在 start_kernel 函数中完成了设备树节点解析的工作,最终实际工作的函数为 unflatten_dt_node。
八、绑定信息文档.txt
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 property 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".
- scl-gpios: specify the gpio related to SCL pin
- sda-gpios: specify the gpio related to SDA pin
- pinctrl: add extra pinctrl to configure i2c pins to gpio function for i2c
bus recovery, call it "gpio" state
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";
pinctrl-names = "default", "gpio";
pinctrl-0 = <&pinctrl_i2c1>;
pinctrl-1 = <&pinctrl_i2c1_gpio>;
scl-gpios = <&gpio5 26 GPIO_ACTIVE_HIGH>;
sda-gpios = <&gpio5 27 GPIO_ACTIVE_HIGH>;
};
有时候使用的一些芯片在 Documentation/devicetree/bindings 目录下找不到对应的文档,这个时候就要咨询芯片的提供商,让他们给你提供参考的设备树文件。
九、设备树常用OF操作函数
待补充:
各具体定义及其使用;
9.1 查找节点的OF函数
设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中,定义如下:
struct device_node {
const char *name; //节点名字
const char *type; //设备类型
phandle phandle;
const char *full_name; //节点全名
struct fwnode_handle fwnode;
struct property *properties; //属性
struct property *deadprops; /* removed properties */
struct device_node *parent; //父节点
struct device_node *child; //子节点
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
9.2 查找父/子节点的OF函数
9.3 提取属性值的OF函数
9.4 其他常用的OF函数
参考文章:
https://blog.youkuaiyun.com/qq_35031421/article/details/105002645#comments_16910375