1. 什么是设备树
1.1 背景
嵌入式底层,一般是用汇编或者C语言进行编程,如内存访问、寄存器访问、外设控制等。在linux 3.x之前,linux内核与硬件抽象层相关采用的是C语言的方式描述板级设备信息,一般位于“kernel/arch/arm/mach-xxx”下,这样的方式有个严重弊端就是板级源码与内核耦合在一起,同一个CPU更改PCB或者调整底层设备时,就得修改板级C源码,然后重新编译内核,导致内核存在大量与内核无关的冗余代码。因此,从linux 3.x后引入独立于内核的设备树(Device Tree),来描述板级信息,存放于“/kernel/arch/arm(arm64)/boot/dts”目录下。
1.2 设备树
设备树,顾名思义,分为设备和树,以树的形式描述设备,所有设备都是“挂”在树上,,设备树不限于传统板级描述。设备树以ASCII字符的形式描述板级信息,类似XML、JSON的易读性格式,方便编写和阅读。
- 处理信息,CPU核数和类型、内存地址和大小、内存总线
- 连接板级信息,GPIO、中断
- 连接外设,固定设备、热插拔设备
1.3 设备树组成
设备树包括DTC(device tree compiler)编译器,DTS(device tree source)源文件和DTB(device tree blob)目标文件。DTS与C语言一样,包括源文件DTS和头文件DTSI。DTB是二进制目标文件,DTS和DTSI通过编译后输出,在系统起来后加载使用。
DTS
DTS是设备树源文件,文件为“.dts”后缀,对设备信息的描述。一个系统对应一个DTS,可能存在多个DTS,但最终通过编译都得出一个DTB文件。DTS可以“include”DTS和DTSI,甚至C语言头文件。
#include "rk3399-firefly-core.dtsi"
#include <dt-bindings/input/input.h>
DTSI
DTS头文件,文件为“.dtsi”后缀。一个CPU可能用于多个产品上(板级信息),不同产品存在共同的设备信息,那么可以将这些共同的设备描述保存为一个DTSI文件,通过“include”方式实现复用,减少DTS冗余。类似的,DTSI支持include DTS、DTSI、C语言头文件。
DTC
设备树编译工具,将DTS和DTSI编译为目标DTB文件。
DTB
二进制文件,文件为“.dtb”后缀,存放于板级固定存储地址空间,bootloader在引导内核时,首先读取DTB到内存,由内核解析,执行板级相关操作。
1.4 设备树优点
- 对于程序员,设备树采用ASII字符描述,适合程序员阅读和修改。
- 对于开发效率,板级相关改动只需更改设备树文件,然后编译即可,无需更改驱动源码。
- 对于linux内核管理,释放linux内核板级冗余代码,内核不需经常变动。
- 对于产品开发,便于产品衍生(同一CPU多个产品),只需修改设备树即可满足。
2. 设备树语法
2.1 设备树节点
设备树采用树型结构来描述板级设备信号,每个设备都是一个节点,每个节点都有自己的属性来描述设备,节点和属性就是一对“键值”。因此设备树是由节点和节点属性组成,一个节点可以嵌套多字子节点,子节点还可以包含孙子节点。
【1】节点表示形式
#源自rk3399.dtsi
/ {
compatible = "rockchip,rk3399";
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
aliases {
dsi0 = &dsi;
dsi1 = &dsi1;
ethernet0 = &gmac;
i2c0 = &i2c0;
i2c1 = &i2c1;
i2c2 = &i2c2;
i2c3 = &i2c3;
i2c4 = &i2c4;
i2c5 = &i2c5;
i2c6 = &i2c6;
i2c7 = &i2c7;
i2c8 = &i2c8;
serial0 = &uart0;
serial1 = &uart1;
serial2 = &uart2;
serial3 = &uart3;
serial4 = &uart4;
};
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu-map {
cluster0 {
core0 {
cpu = <&cpu_l0>;
};
core1 {
cpu = <&cpu_l1>;
};
core2 {
cpu = <&cpu_l2>;
};
core3 {
cpu = <&cpu_l3>;
};
};
cluster1 {
core0 {
cpu = <&cpu_b0>;
};
core1 {
cpu = <&cpu_b1>;
};
};
};
uart0: serial@ff180000 {
compatible = "rockchip,rk3399-uart", "snps,dw-apb-uart";
reg = <0x0 0xff180000 0x0 0x100>;
clocks = <&cru SCLK_UART0>, <&cru PCLK_UART0>;
clock-names = "baudclk", "apb_pclk";
interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH 0>;
reg-shift = <2>;
reg-io-width = <4>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_xfer &uart0_cts &uart0_rts>;
status = "disabled";
};
.......
-
根节点,设备树采用树状结构,因此每个设备树文件只有一个根节点(root树根)。根节点以“/”表示,如果一个板级系统由多个设备树组成时,编译后最终也会合并为一个根节点。
-
子节点,子节点命名格式一般为:
node-label:node-name@node-address
注:
node-label,节点标签,可以通过标签索引该节点,可以不填写标签名称
node-name,节点名称,用于描述节点,如i2c、spi、bmp180(具体器件名称)
node-address,表示设备地址、寄存器首地址等,如i2c器件则表示该设备的i2c从地址
2.2 节点属性
设备属性一般是以“属性名+属性值”的键值对存在,属性值的表示方式是多元的,可以为空、数字、字符、字符串等。
值类型 | 举例 |
---|---|
32位无符号整数 | reg = <0x0 > |
32位无符号整型数组 | reg = <0x0 0x2> |
字符串 | compatible = “gpio-leds” |
字符串列表 | compatible = “rockchip,rk3399-firefly-core”, “rockchip,rk3399” |
二进制数组 | local-mac-address = [00 01 12 0a ab ff] |
【1】状态
status ,键值类型为<string>, 设备状态属性,可以支持动态更改,如设备热插拔。
键值 | 描述 |
---|---|
“okay” | 设备正常状态 |
“disable” | 设备当前不可用,设备可能改变为“okay”状态 |
“fail” | 设备不可用,设备出现错误,不可重新改变为“okay”状态 |
“fail-sss” | 与“fail”相同,“sss”表示错误的具体内容 |
【2】兼容属性
compatible ,键值类型为<string list>,兼容属性,用于和设备驱动程序匹配,一般格式为“厂商,器件型号”。如果是根据节点的“compatible”属性,linux内核则匹配该内核是否支持该CPU,如支持才启动内核执。兼容属性键值可以是一组列表,内核匹配时,从左至右边匹配。
compatible = "invensense,mpu6500","mpu65xx"; #先匹配“invensense,mpu6500”,如失败则继续匹配“mpu65xx”
【3】设备信息
model,键值类型为<string list>,与“compatible”类似,描述设备模块信息。
model= "invensense,mpu6500";
【4】 地址
-
reg ,键值类型为<address length>,用于描述设备地址空间资源信息,如外设的寄存器地址范围,字节点名称接着的十六进制地址就是reg属性列表的首地址。reg属性由父节点的“#address-cells”和“#size-cells”的值决定数目列表,可以存在多个列表。特殊情况下,reg也描述器件地址,不需带长度信息,如i2c器件从地址。
-
#address-cells和#size-cells,键值类型为<u32>,用于描述子节点地址(reg属性)信息,#address-cells描述子节点reg属性地址信息的字长,#size-cells描述子节点reg属性地址长度信息所占的字长。
#源自rk3399.dtsi
i2c4: i2c@ff3d0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xff3d0000 0x0 0x1000>;
clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&i2c4_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
#源自rk3399-firefly-port.dtsi
mpu6500: mpu@68 {
status = "disabled";
compatible = "invensense,mpu6500";
reg = <0x68>; #父节点是i2c4
......
【5】 地址映射
ranges,子、父地址映射表,键值类型为<child-bus-address parent-bus-address length>,或者为空。
- child-bus-address,子地址物理空间起始地址。
- parent-bus-address,父地址物理空间起始地址。
- length,字地址空间长度大小,由父节点“#size-cells”属性决定该地址所占的字长。
注
- ranges为空,表示子地址空间和父地址空间相同,不需要进行地址转换。
- ranges不为空,表示把字地址空间起始映射到父地址空间起始,大小为length。
- 含有ranges属性的节点的子节点,其reg都是基于子地址。
- 对于没有ranges属性的节点,代表不是memory map区域。
rk3399设备树文件“range”属性基本为空,表示不需要子、父地址转换,我们单独举个例子说明。
Test: Test@0x0 {
#address-cells = <1>;
#size-cells = <1>;
ranges=<0x0 0x10 0x100> #把字地址空间(0x0——(x0+0x100))映射到父地址空间(0x10——(0x10+0x100))
....
【6】 GPIO
- gpio-controller,空属性,声明该节点为GPIO控制器
- #gpio-cells,键值类型为<u32>,表示需要描述GPIO属性的单位数目,如一个普通GOPIO需要管脚序号和工作模式,则#gpio-cells值为2。
#源自rk3399.dtsi
gpio0: gpio0@ff720000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff720000 0x0 0x100>;
clocks = <&pmucru PCLK_GPIO0_PMU>;
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>; #2 cell个描述
interrupt-controller;
#interrupt-cells = <0x2>;
};
#源自rk3399firefly-port.dtsi
leds {
compatible = "gpio-leds";
work {
label = "firefly:blue:power";
linux,default-trigger = "ir-power-click";
gpios = <&gpio2 27 GPIO_ACTIVE_HIGH>;#与“#gpio-cells”对应,27为管脚序号,GPIO_ACTIVE_HIGH为工作模式
pinctrl-names = "default";
pinctrl-0 = <&led_power>;
default-state = "on";
};
【7】中断
- interrupt-controller ,空属性,声明该节点支持接收中断信息。
- interrupt-parent,表示该节点属于哪一个中断控制器,如果不设置该属性,则默认继承父节点中断控制器。
- #interrupt-cells,键值类型为<u32>,表示需要描述中断属性(interrupts)的单位数目,用来描述子节点中"interrupts"属性使用了父节点中的“interrupts”属性的具体的哪个值。通常,如果父节点interrupt-cells的值为3,则子节点的interrupts的键值分别为<中断域 中断 触发方式> ;如果父节点interrupt-cells的值2,则键值是<中断 触发方式>。
- interrupts,中断标识符列表,与“#interrupt-cells”关联。
- #interrupts-extended,如果设备连接多个中断控制器,则列出这些中断。
###rk3399电源管理设备(rk808)设备节点,父节点为i2c0。###
#源自rk3399-firefly-core.dtsi
rk808: pmic@1b {
compatible = "rockchip,rk808";
reg = <0x1b>;
interrupt-parent = <&gpio1>; #继承GPIO1中断
interrupts = <21 IRQ_TYPE_LEVEL_LOW>;#2个单位中断描述,<中断 触发方式>
.....
#源自rk3399.dtsi
gpio1: gpio1@ff730000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff730000 0x0 0x100>;
clocks = <&pmucru PCLK_GPIO1_PMU>;
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH 0>;
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>; #子节点需2个单位中断描述
};
3. 参考
来源:CSDN
作者:Acuity-
链接:https://blog.csdn.net/qq_20553613/article/details/103838991