一.什么是设备树
设备树
(Device Tree)
,将这个词分开就是“设备”和“树”,描述设备树的文件叫做
DTS(Device
Tree Source)
,这个
DTS
文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如
CPU
数量、 内存基地址、
IIC
接口上接了哪些设备、
SPI
接口上接了哪些设备等等,如图所示
Linux使用DTS文件代替了之前的.c文件描述。简而言之,就是用一种新的文件来描述板子之间的设备信息,而这个描述方式是以树形结构的,文件的扩展名为.dts,一个平台或者机器对应一个dts。
二.DTS、DTB和DTC的关系
.dts相当于.c,就是DTS源码文件,即设备树源码文件
DTC工具相当于gcc编译器,将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb
DTC工具相当于gcc编译器,将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb
需要用到
DTC
工具
DTC工具将.dts编译为.dtb,.dtb相当于bin文件,或可执行文件
如果要编译
DTS
文件的话只需要进入到
Linux
源码根目录下,然后执行如下命
令:
make all
或者:
make dtbs
“make all”命令是编译 Linux 源码中的所有东西,包括 uImage,.ko 驱动模块以及设备
树;
如果只是编译设备树的话建议使用“
make dtbs
”命令,“
make dtbs
”会编译选中的所有设
备树文件。,比如 ST 官方编写的“stm32mp157d-ed1.dts”,可以输入如下命令:
make stm32mp157d-ed1.dtb
三.DTS基本语法
3.1 .dtsi 头文件
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范 围,比如 UART、IIC 等等。如果一个系列里有多个 SOC 就会把相同内部外设信息提炼到一 个.dtsi 文件里,这样为了减少代码的冗余
和C语言一样,DTS也有头文件,拓展名为.dtsi
8 #include "stm32mp157.dtsi"
9 #include "stm32mp15xd.dtsi"
10 #include "stm32mp15-pinctrl.dtsi"
11 #include "stm32mp15xxaa-pinctrl.dtsi"
12 #include "stm32mp157-m4-srm.dtsi"
13 #include "stm32mp157-m4-srm-pinctrl.dtsi"
第 8~14 行,使用“#include”来引用“stm32mp15*.dtsi”这些.dtsi 头文件。
设备树里面除了可以通过“
#include
”来引用
.dtsi
文件,也可以引用
.h 文件头文件,如
#include < dt - bindings / rtc / rtc - stm32 . h >
.dts文件不仅可以应用C语言里面的.h头文件,甚至也可以引用.dts文件,如
#include <dt-bindings/rtc/rtc-stm32.h>
3.2 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
1 / { #根节点
2 #address-cells = <1>;
3 #size-cells = <1>;
4
5 aliases { #子节点
6 serial0 = &uart4;
7 };
8
9 cpus { #子节点
10 #address-cells = <1>;
11 #size-cells = <0>;
12
13 cpu0: cpu@0 { #cpus的子节点
14 compatible = "arm,cortex-a7";
15 device_type = "cpu";
16 reg = <0>;
17 clocks = <&scmi0_clk CK_SCMI0_MPU>;
18 clock-names = "cpu";
19 operating-points-v2 = <&cpu0_opp_table>;
20 nvmem-cells = <&part_number_otp>;
21 nvmem-cell-names = "part_number";
22 #cooling-cells = <2>;
23 };
24 };
25
26 soc { #子节点
27 compatible = "simple-bus";
28 #address-cells = <1>;
29 #size-cells = <1>;
30 interrupt-parent = <&intc>;
31 ranges;
32
33 sram: sram@10000000 { #soc的子节点
34 compatible = "mmio-sram";
35 reg = <0x10000000 0x60000>;
36 #address-cells = <1>;
37 #size-cells = <1>;
38 ranges = <0 0x10000000 0x60000>;
39 };
40 };
41 };
在设备树中节点命名格式如下:
node-name@unit-address
其中“
node-name
”是节点名字,为
ASCII
字符串,节点名字应该能够清晰的描述出节点的
功能,比如“
uart1
”就表示这个节点是
UART1
外设。
“
unit-address
”一般表示设备的地址或寄存器首地址,有时是I2C的设备地址,具体节点具体分析。 如果某个节点没有地址或者寄存器的话“unit-address
”可以不要,比如“
cpu@0
”、 “sram@10000000
”、“
soc
”。
但在第十三行代码的节点命名却为
cpu0:cpu@0
上述命令并不是“
node-name@unit-address
”这样的格式,而是用“:”隔开成了两部分,“:”
前面是节点标签
(label)
,“:”后面的才是节点名字,格式如下所示:
label: node-name@unit-address
引入
label
的目的就是为了方便访问节点,可以直接通过
&label
来访问这个节点,比如通过
&cpu0
就可以访问“
cpu@0
”这个节点,而不需要输入完整的节点名字。再比如节点 “
sram:
sram@10000000
”,节点
label
是
sram
,而节点名字就很长了,为“
sram@10000000
”。很明显通
过
&sram
来访问“
sram@10000000
”这个节点要方便很多!
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任
意的字节流。设备树源码中常用的几种数据形式如下所示:
1、字符串
compatible = "arm,cortex-a7";
上述代码设置
compatible
属性的值为字符串“
arm,cortex-a7
”。
2、32 位无符号整数
reg = <0>;
上述代码设置
reg
属性的值为
0
,
reg
的值也可以设置为一组值,比如:
reg = <0 0x123456 100>;
3
、字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“
,
”隔开,如下所示:
compatible = "st,stm32mp157d-atk", "st,stm32mp157";
上述代码设置属性
compatible
的值为“
st,stm32mp157d-atk
”和“
st,stm32mp157
”。
3.3 标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以
自定义属性。除了用户自定义属性,有很多属性是标准属性,
Linux
下的很多外设驱动都会使用
这些标准属性。
3.3.1 compatible 属性
compatible
属性也叫做“兼容性”属性,这是非常重要的一个属性,
compatible
属性的值是
一个字符串列表,
compatible
属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要
使用的驱动程序,
compatible
属性的值格式如下所示:
"manufacturer,model"
其中
manufacturer
表示厂商,
model
一般是模块对应的驱动名字。比如
stm32mp15xx-dkx.dtsi
中有一个音频设备节点,这个节点的音频芯片采用的
Cirrus Logic
公司出品的
cs42l51
,
compatible
属性值如下:
compatible = "cirrus,cs42l51";
属性值为“
cirrus,cs42l51
”,其中‘
cirrus
’表示厂商是
Cirrus Logic
,“
cs42l51
”表示驱动模
块名字。
compatible
也可以多个属性值。比如:
compatible = "cirrus,my_cs42l51","cirrus,cs42l51";
这样我们的设备就有两个属性值,这个设备首先使用第一个兼容值在
Linux
内核里面查找,
看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查,以此类推,
直到查找完
compatible
属性中的所有值。
一般驱动程序文件都会有一个
OF
匹配表,此
OF
匹配表保存着一些
compatible
值,如果设
备节点的
compatible
属性值和
OF
匹配表中的任何一个值相等,那么就表示设备可以使用这个
驱动。比如
799 const struct of_device_id cs42l51_of_match[] = { #匹配表
800 { .compatible = "cirrus,cs42l51", }, #匹配值
801 { }
802 };
数组
cs42l51_of_match
就是
cs42l51.c
这个驱动文件的匹配表,此匹配表只有一个匹配值
“
cirrus,cs42l51
”。如果在设备树中有哪个节点的
compatible
属性值与此相等,那么这个节点就
会使用此驱动文件。
3.3.2 model 属性
model
属性值也是一个字符串,一般
model
属性描述开发板的名字或者设备模块信息,比
如名字什么的,比如:
model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";
3.3.3 ranges 属性
ranges
属性值可以为空或者按照
(child-bus-address,parent-bus-address,length)
格式编写的数字
矩阵,
ranges
是一个地址映射
/
转换表,
ranges
属性每个项目由子地址、父地址和地址空间长度
这三部分组成:
child-bus-address
:子总线地址空间的物理地址,由父节点的
#address-cells
确定此物理地址
所占用的字长。
parent-bus-address
:
父总线地址空间的物理地址,同样由父节点的
#address-cells
确定此物
理地址所占用的字长。
length
:
子地址空间的长度,由父节点的
#size-cells
确定此地址长度所占用的字长。
如果
ranges
属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,
对于我们所使用的
stm32mp157
来说,子地址空间和父地址空间完全相同,因此会在
stm32mp151.dtsi
中找到大量的值为空的
ranges
属性,如下所示:
192 soc {193 compatible = "simple-bus" ;194 #address - cells = < 1 >;195 #size - cells = < 1 >;196 interrupt - parent = <& intc >;197 ranges ;......1968 };
第
197
行定义了
ranges
属性,但是
ranges
属性值为空。
ranges
属性不为空的示例代码如下所示:
1 soc {2 compatible = "simple-bus" ;3 #address - cells = < 1 >;4 #size - cells = < 1 >;5 interrupt - parent = <& intc >;6 ranges = < 0 0x10000000 0x100000 >;78 sram : sram@10000000 {9 compatible = "mmio-sram" ;10 reg = < 0x0 0x60000 >;11 #address - cells = < 1 >;12 #size - cells = < 1 >;13 ranges = < 0 0x10000000 0x60000 >;14 };15 };
第
6
行,节点
soc
定义的
ranges
属性,值为
<0 0x10000000 0x100000>
,此属性值指定了一
个
1024KB(0x100000)
的地址范围,子地址空间的物理起始地址为
0
,父地址空间的物理起始地
址为
0x10000000
。
第
8
行,
sram
是
STM32MP1
内部
RAM
节点,
reg
属性定义了
sram
设备寄存器的起始地址
为
0
,寄存器长度为
0x60000
。经过地址转换,
sram
设备可以从
0x10000000
开始进行读写操作,
0x10000000=0x0 + 0x10000000
。
3.3.4 reg属性
reg
属性前面已经提到过了,
reg
属性的值一般是
(address
,
length)
对。
reg
属性一般用于描
述设备地址空间资源信息或者设备地址信息,比如某个外设的寄存器地址范围信息,或者
IIC
器件的设备地址等,比如在
stm32mp151.dtsi
中有如下内容:
576 uart5 : serial@40011000 {577 compatible = "st,stm32h7-uart" ;578 reg = < 0x40011000 0x400 >;579 interrupts - extended = <& exti 31 IRQ_TYPE_LEVEL_HIGH >;580 clocks = <& rcc UART5_K >;581 resets = <& rcc UART5_R >;582 wakeup - source ;583 power - domains = <& pd_core >;584 dmas = <& dmamux1 65 0x400 0x5 >,585 <& dmamux1 66 0x400 0x1 >;586 dma - names = "rx" , "tx" ;587 status = "disabled" ;588 };
uart5
节点描述了
stm32mp1
系列芯片的
UART5
相关信息,重点是第
578
行的
reg
属性。由
于
uart5
的父节点“
soc
”设置了
#address-cells = <1>
、
#size-cells = <1>
,因此
reg
属性中
address=0x40011000
,
length=0x400
。查阅《
STM32MP157
参考手册》可知,
stm32mp157
芯片的 UART5 寄存器首地址为
0x40011000
,但是
UART5
的地址长度
(
范围
)
并没有
0x400
这么多,这 里我们重点是获取 UART5
寄存器首地址。
3.3.5 #address-cells 和#size-cells 属性
这两个属性的值都是无符号
32
位整形,
#address-cells
和
#size-cells
这两个属性可以用在任
何拥有子节点的设备中,用于描述子节点的地址信息。
#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
这个数据所占用
的字长
3.3.6 status 属性
status
属性看名字就知道是和设备状态有关的,
status
属性值也是字符串,字符串是设备的
状态信息,可选的状态如表
所示:
值 | 描述 |
“
okay
”
|
表明设备是可操作的。
|
“
disabled
”
|
表明设备当前是不可操作的,但是在未来可以变为可操作的
|
“
fail
”
|
表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
|
“
fail-sss
”
|
含义和“
fail
”相同,后面的
sss
部分是检测到的错误内容。
|