Bootstrap

Linux-设备树及其基本语法

一.什么是设备树

设备树 (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 工具
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 >;
7
8 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 部分是检测到的错误内容。

;