NotePublic/Software/Development/System/Linux/Kernel/Modules/DeviceTree/DeviceTree.md

1916 lines
48 KiB
Markdown
Raw Normal View History

# Device Tree
## 1.概述
### 1.1.设备树的起源
linux 2.6 及之前,大量板级信息被硬编码到内核里,十分庞大,大量代码冗余。
linux 2.6 之后,引入了设备树。设备树源于 OpenFirmware描述硬件的数据结构。由一些列节点node和属性property组成通常包括下列信息
* CPU 数量和类别
* 内存基地址和大小
* 总线和桥
* 外设连接
* 中断控制器和中断使用情况
* GPIO 控制器和 GPIO 使用情况
* 时钟控制器和时钟使用情况
本质上是画一棵 CPU、总线、设备组成的树Linux 内核会把设备树展开成 platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、中断等资源,也会传递个内核,内核会将这些资源绑定给展开的相应设备。
不使用 DT 时kernel 包含了硬件的完整描述信息bootloader 加载单独的一个二进制文件kernel 镜像文件 uImage 或 zImage并执行它bootloader 通过寄存器 r2 传递 ATAGS为一些附加信息如 RAM 大小和地址、cmdline 等)给 kernel通过寄存器 r1 传递一个机器类型machine type用于告诉内核将启动哪一款板卡整数给 kernel。
使用 DT 时kernel 包含的硬件完整打桩信息被提取为一个二进制文件 DTBbootloader 则需要加载 kernel 镜像uImage 或 zImage以及 DTBarch/arm/boot/dts/ 目录下的 DTS 文件<一个板卡一个 dts 文件>通过 DTC 编译成 DTB 文件bootloader 通过寄存器 r2 传递 DTB 文件(该文件也包含了 RAM 信息、cmdline 等信息)所在地址给 kernel而原先传递板卡类型整数的 r1 则不需要再关注了。
### 1.2.DTSI/DTS/DTC/DTB
dtsi可被 #include 的设备树源文件;
dts设备树源文件适合阅读
dtc编译 dts 和 dtsi 后得到的设备树文件dts 及 dtsi 中的内容被组合或覆盖该文件为源码形式Linux 内核无法识别;
dtb即 Device Tree Blob编译 dtc 后得到的二进制设备树文件适合内核进行处理Linux 内核可加载和识别其中的内容。
如果谋 dts 文件引用了谋 dtsi 文件,可以在 dts 中覆盖 dtsi 中的部分内容。
### 1.3.Binding
对于 Device Tree 中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为“.txt”。这些文档位于内核的 Documentation/devicetree/bindings 目录,其下又分为很多子目录。
### 1.4.DTB 的编译
DTB(Devicetree Blob) 是 DTS 的二进制文件格式Kernel 使用 DTC 工具将 DTS 源文件编译成 DTBbootloader 再将 DTB 文件传递给 Kernel 解析。
不遵守标准书写的 DTS 文件在编译的时候会报错。
### 1.5.Bootloader
在系统启动的时候bootloader如 Uboot 可以将保存在 flash 中的 DTB copy 到内存(还可以通过 bootloader 的交互式命令加载 DTB并把 DTB 的起始地址传递给 kernel。
Uboot mainline 从 v1.1.3 开始支持 Device Tree其对 ARM 的支持则是和 ARM 内核支持 Device Tree 同期完成。为了使能 Device Tree需要编译 Uboot 的时候在 config 文件中加入
```cpp
#define CONFIG_OF_LIBFDT
```
当我们将 DTB 文件在 Uboot 里加载到内存中后,通过 fdt addr 0xnnnnnnnn 命令来设置 DTB 文件对应地址,这样就可以使用 fdt resize、fdt print 等命令对 DTB 文件进行操作了。对于ARM使用 bootz kernel_addr initrd_addr dtb_addr 命令来启动 kerneldtb_addr 作为 bootz 或 bootm 最后一个参数,第一个参数为内核镜像的地址,第二个参数为 initrd 的地址,如不存在,使用“-“代替。
### 1.6.设备节点到 platform device 的转换
设备树中的节点有些能被转换为内核里的 platform_device转换条件如下
* 根节点下含有 compatile 属性的子节点,会转换为 platform_device
* 含有特定 compatile 属性的节点的子节点,会转换为 platform_device
* 如果一个节点的 compatile 属性,它的值是这 4 者之一:"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus",那么它的子结点(需含 compatile 属性)也可以转换为 platform_device。
* 总线 I2C、SPI 节点下的子节点:不转换为 platform_device
* 某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。
## 2.DTS 语法
### 2.1.注释
DTS 中使用“//”进行行注释或“/**/”进行块注释。
### 2.2.节点
设备树包含一个根节点和多个子节点,子节点可嵌套,形成父子关系,这样就可以方便的描述设备间的关系。节点会被展开为 device其 compatible 属性用于与 driver 的 compatible 属性项匹配。
节点的使用:
```dts
[label]:<node-name>[@<unit-address>] {}
```
的格式来定义。挂到内存空间的设备,其 unit-address 一般是 node 中“reg”属性描述的开始地址。label 为可选项,如果存在,则在 DTS 的其他地方可以通过“&label”来引用该节点。
### 2.3.属性
属性必须包含在节点中,每个设备的属性都用一组 key-value 对(键值对)来描述,每个属性的描述用英文“;“结束。例如,使用:
```dts
<property-name>=<value>;
```
来定义一个属性。属性值可以为整数或字符串。如果为整数则用“<>”括起来,“<>”中可以有多个单元,称为 cellcell 间使用空格隔开,不同的 cell 可以有不同的含义。字符串使用英文双引号括起来。同一属性的多个值可以使用英文“,”进行分割,例如:
```dts
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>;
```
按 property-name 可以表述的数据类型,可以分为:
* 字符串string-prop = “a string”;
* 字符串列表string-list = “hi”,”str”,”list”;
* 整型数据cell-prop = <0xaa>;
* 二进制数据binary-prop = [aa bb f8];
* 混合数据mixed-prop = “str”,<0x123>,[ff dd];
* 布尔类型bool-porp;
* 引用类型ref-prop = <&other-node>
### 2.4.标准属性
#### 2.4.1.compatible
“compatible”属性通常用来匹配 device 和 driver一旦 Device Tree 中 compatible 属性值与 driver 的 compatible 字段一致,则触发该 driver 的 probe 函数。推荐 compatible 属性值使用如下格式:
```dts
compatible = ”manufacturer,model”;
```
Value type: stringlist
Example:
```dts
compatible = "fsl,mpc8641", "ns16550";
```
#### 2.4.2.model
“model”属性只是简单的表示型号root 节点用其来传递值给 machine_desc_str。
Value type: stringlist
Example:
```dts
model = "fsl,MPC8349EMITX";
```
#### 2.4.3.phandle
“phandle”属性通过一个唯一的 id 来标识一个 Node在 property 可以使用这个 id 来引用 Node。
Value type: u32
Example:
```dts
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>;
};
```
在 DeviceTree 中通过另一种方式进行 phandle 的定义和引用更加常见:
* 定义一个“label”来引用Node在编译是系统会自动为node生成一个phandle属性。
* 使用”&”来引用“label”即是引用phandle。
#### 2.4.4.ranges
“ranges”属性用来做当前 node 和父 node 之间的地址映射,格式为(child-bus-address, parentbus-address, length)。其中 child-bus-address 的解析长度受当前 node 的 #address-cells 属性控制parentbus-address 的解析长度受父 node 的 #address-cells 属性控制 length 的解析长度受当前 node 的 #size-cells 属性控制。
Value type: empty or prop-encoded-array encoded as an arbitrary number of (child-bus-address, parentbus-address, length) triplets.
Example:
```dts
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;
serial {
device_type = "serial";
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
```
The soc node specifies a ranges property of <0x0 0xe0000000 0x00100000>;
This property value specifies that for an 1024KB range of address space, a child node addressed at physical 0x0 maps to a parent address of physical 0xe0000000. With this mapping, the serial device node can be addressed by a load or store at address 0xe0004600, an offset of 0x4600 (specified in reg) plus the 0xe0000000 mapping specified in ranges.
#### 2.4.5.reg 属性和 address-cells、size-cells
子节点的“reg”属性用于标记 iomem由“address”字段和“size”字段构成
```dts
reg = <address1 size1 [address2 size2] [address3 size3]...>;
```
属性“#address-cells”描述了子节点的“reg”属性的“address”字段为几个 32bit 的整型数据。
属性“#size-cells”描述了子节点的“reg”属性的“size”字段为几个 32bit 整型数据,如果为 0则没有 lenth 字段。
#### 2.4.6.interrupt
和中断相关的 node 可以分成3种
* Interrupt Generating Devices中断发生设备这种设备可以发生中断
* Interrupt Controllers中断控制器处理中断
* Interrupt Nexus中断联结路由中断给中断控制器。
##### 2.4.6.1.Interrupt Generating Devices
主要有如下属性:
* interrupt属性用来定义设备的中断解析一般包含中断号、触发方法等。
* interrupts属性用来定义设备的中断解析一般包含中断号、触发方法等。
* interrupt-parent属性用来指定当前设备的 Interrupt Controllers/Interrupt Nexusphandle 指相对应的 node如果没有指定则继承父节点的 interrupt-parent 配置。
interrupt 和 interrupts 都根据其 interrupt-parent node 中定义的“#interrupt-cells”来解析。比如 #interrupt-cells=2那根据 2 个 cells 为单位来解析 interrupt/interrupts 属性。理论上每个 cell 的具体含义,一般由驱动决定,但一般而言 interrupt/interrupts 属性后面,会有两个或三个固定参数。
两个的时候一般是这样出现:
```dts
interrupt-parent = <&gpio2>;
interrupts = <29 0>;
```
表明:中断控制器是 GPIO2然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚)0 是指触发的方式(上升沿、下降沿等)。
三个的时候一般是这样出现:
```dts
interrupts = <0 37 1>;
interrupts = <GIC_SPI 37 1>;
interrupts = <GIC_PPI 37 1>;
```
第一个参数表示是 IPI、PPI、SPI、SGI 其中的一个。第二个参数表示:是第一个参数里面的第几个。第三个参数表示:中断触发的类型(上升沿、下降沿等)。
IPI、PPI、SPI、SGI 是 ARM 规范的中断,含义如下:
* IPIinter-processer interrupt 中断号015
* PPIper processor interrupts 中断号1631
* SPIshared processor interrupts 中断号 32 32+224
* SGIsoftware generated interrupts (SGI).
中断类型的值在 \<linux kernel source\>/include/irq.h 中,部分定义如下:
```cpp
#define IRQ_TYPE_NONE 0
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8
```
##### 2.4.6.2.Interrupt Controllers
主要有如下属性:
* #interrupt-cells用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度
* interrupt-controller用来声明当前 node 为中断控制器。
一个SoC中可能有不止一个中断控制器。
##### 2.4.6.3.Interrupt Nexus
主要有如下属性:
* #interrupt-cells用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度。
* interrupt-map用来描述 interrupt nexus 设备对中断的路由。解析格式为 5 元素序列child unit address, child interrupt specifier, interrupt-parent, parent unit address, parent interrupt specifier。其中“child unit address”的 cells 长度由子节点的 “#address-cells” 指定; “child interrupt specifier”的 cells 长度由子节点的“#interrupt-cells”指定 “interrupt-parent” phandle 指向 interrupt controller 的引用; “parent unit address”的 cells 长度由父节点的“#address-cells”指定 “parent interrupt specifier”的 cells 长度由父节点的“#interrupt-cells”指定。
举例:
```dts
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
open-pic {
clock-frequency = <0>;
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <2>;
};
pci {
#interrupt-cells = <1>;
#size-cells = <2>;
#address-cells = <3>;
interrupt-map-mask = <0xf800 0 0 7>;
interrupt-map = <
/* IDSEL 0x11 - PCI slot 1 */
0x8800 0 0 1 &open-pic 2 1 /* INTA */
0x8800 0 0 2 &open-pic 3 1 /* INTB */
0x8800 0 0 3 &open-pic 4 1 /* INTC */
0x8800 0 0 4 &open-pic 1 1 /* INTD */
/* IDSEL 0x12 - PCI slot 2 */
0x9000 0 0 1 &open-pic 3 1 /* INTA */
0x9000 0 0 2 &open-pic 4 1 /* INTB */
0x9000 0 0 3 &open-pic 1 1 /* INTC */
0x9000 0 0 4 &open-pic 2 1 /* INTD */
>;
};
};
```
For example, the first row of the interrupt-map table specifies the mapping for INTA of slot 1. The components of that row are shown here
child unit address: 0x8800 0 0
child interrupt specifier: 1
interrupt parent: &open-pic
parent unit address: (empty because #address-cells = <0> in the open-pic node)
parent interrupt specifier: 2 1
##### 2.4.6.4.Interrupts Extended
一个“interrupts-extended”属性就可以既指定“interrupt-parent”也指定“interrupts”比如
```dts
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
```
### 2.5.标准节点
#### 2.5.1.Root
每个 Device Tree 只有一个根节点,用“/{}”表示,如果在 dts/dtsi 文件中写了多个根节点,则在编译后被组合成一个根节点。根节点需要有以下必备属性:
* #address-cellsu32, Specifies the number of \<u32\> cells to represent the address in the reg property in children of root;
* #size-cellsu32, Specifies the number of \<u32\> cells to represent the size in the reg property in children of root;
* modelstring, Specifies a string that uniquely identifies the model of the system board. The recommended format is “manufacturer,model-number”;
* compatiblestringlist, Specifies a list of platform architectures with which this platform is compatible. This property can be used by operating systems in selecting platform specific code. The recommended form of the property value is:"manufacturer,model".
For example:
```dts
/{
compatible = "fsl,mpc8572ds"
}
```
#### 2.5.2.aliases
由于 Device tree 是树状结构,当要引用一个 node 的时候要指明相对于 root node 的full path例如“/node-name-1/node-name-2/node-name-N“。如果多次引用每次都要写这么复杂的字符串多少是有些麻烦因此可以在 aliases 节点定义一些设备节点 full path 的缩写:
```dts
aliases {
serial0 = "/simple-bus@fe000000/serial@llc500";
ethernet0 = "/simple-bus@fe000000/ethernet@31c000";
};
```
#### 2.5.3.memory
用来传递内存布局:
* device_typestringValue shall be “memory”;
* regprop-encoded-array, Consists of an arbitrary number of address and size pairs that specify the physical address and size of the memory ranges;
* initial-mapped-areaprop-encoded-array, Specifies the address and size of the Initial Mapped Area Is a prop-encoded-array consisting of a triplet of (effective address, physical address, size). The effective and physical address shall each be 64-bit (\<u64\> value), and the size shall be 32-bits (\<u32\> value).
举例:
```dts
// RAM: starting address 0x0, length 0x80000000 (2GB)
// RAM: starting address 0x100000000, length 0x100000000 (4GB)
\ {
#address-cells = <2>;
#size-cells = <2>;
memory@0 {
device_type = "memory";
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};
}
```
#### 2.5.4.chosen
chosen node 主要用来描述由系统 firmware 指定的 runtime parameter。如果存在 chosen 这个 node其 parent node 必须是名字是“/”的根节点。原来通过 tag list 传递的一些 linux kernel 的运行时参数可以通过 Device Tree 传递。
bootargsstring用来传递 cmdline 参数;
linux,initrd-start用来传递 initrd 的开始地址;
stdout-pathstring用来指定标准输出设备
stdin-pathstring用来指定标准输入设备。
举例:
```dts
/* chosen */
chosen {
bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram";
};
```
#### 2.5.5.cpus
cpus 节点也是必须的,下面举个具体例子:
```dts
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a35";
reg = <0x000>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1248000000>;
};
cpu1: cpu@001 {
device_type = "cpu";
compatible = "arm,cortex-a35";
reg = <0x001>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1248000000>;
};
cpu2: cpu@002 {
device_type = "cpu";
compatible = "arm,cortex-a35";
reg = <0x002>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1248000000>;
};
cpu3: cpu@003 {
device_type = "cpu";
compatible = "arm,cortex-a35";
reg = <0x003>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1248000000>;
};
cpu4: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x100>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1378000000>;
};
cpu5: cpu@101 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x101>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1378000000>;
};
cpu6: cpu@102 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x102>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1378000000>;
};
cpu7: cpu@103 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0x103>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1378000000>;
};
cpu8: cpu@200 {
device_type = "cpu";
compatible = "arm,cortex-a73";
reg = <0x200>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1638000000>;
};
cpu9: cpu@201 {
device_type = "cpu";
compatible = "arm,cortex-a73";
reg = <0x201>;
enable-method = "psci";
cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
<&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
cpu-release-addr = <0x0 0x40000200>;
clock-frequency = <1638000000>;
};
cpu-map {
cluster0 {
core0 {
cpu = <&cpu0>;
};
core1 {
cpu = <&cpu1>;
};
core2 {
cpu = <&cpu2>;
};
core3 {
cpu = <&cpu3>;
};
};
cluster1 {
core0 {
cpu = <&cpu4>;
};
core1 {
cpu = <&cpu5>;
};
core2 {
cpu = <&cpu6>;
};
core3 {
cpu = <&cpu7>;
};
};
cluster2 {
core0 {
cpu = <&cpu8>;
};
core1 {
cpu = <&cpu9>;
};
};
};
idle-states {
entry-method = "arm,psci";
LEGACY_MCDI: legacy-mcdi {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0000001>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
LEGACY_SODI: legacy-sodi {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0000002>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
LEGACY_SODI3: legacy-sodi3 {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0000003>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
LEGACY_DPIDLE: legacy-dpidle {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0000004>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
LEGACY_SUSPEND: legacy-suspend {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0000005>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
MCDI: mcdi {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0010001>;
entry-latency-us = <600>;
exit-latency-us = <600>;
min-residency-us = <1200>;
};
SODI: sodi {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010002>;
entry-latency-us = <800>;
exit-latency-us = <1000>;
min-residency-us = <2000>;
};
SODI3: sodi3 {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010003>;
entry-latency-us = <800>;
exit-latency-us = <1000>;
min-residency-us = <2000>;
};
DPIDLE: dpidle {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010004>;
entry-latency-us = <800>;
exit-latency-us = <1000>;
min-residency-us = <2000>;
};
SUSPEND: suspend {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x1010005>;
entry-latency-us = <800>;
exit-latency-us = <1000>;
min-residency-us = <2000>;
};
};
};
```
### 2.6.DTS 示例
一个简单的 dts 文件示例如下:
```dts
/ {
    compatible = "acme,coyotes-rev"
    #address-cells = <1>;  // 描述下一级子节点的数据属性
    #size-cells = <1>;
    interrupt-parent = <&intc>;
    cpus {
      #address-cells = <1>;
      #size-cells = <0>;
      cpu@0 {
        compatible = "arm,cortex-a9";   // device兼容性用于与driver匹配
        reg = <0>;
      };
      cpu@1 {
        compatible = "arm,cortex-a9";
        reg = <1>;
      };
   };
   serial@101f0000 {        // 地址
     compatible = "arm,pl011";
     reg = <0x101f0000 0x1000 >;
     interrupts = < 1 0 >;
   };
   serial@101f2000 {
     compatible = "arm,pl011";
     reg = <0x101f2000 0x1000 >;
     interrupts = < 2 0 >;
   };
   gpio@101f3000 {
     compatible = "arm,pl061";
     reg = <0x101f3000 0x1000
     0x101f4000 0x0010>;
     interrupts = < 3 0 >;
   };
   intc: interrupt-controller@10140000 {
     compatible = "arm,pl190";
     reg = <0x10140000 0x1000 >;
     interrupt-controller;
     #interrupt-cells = <2>;
   };
   spi@10115000 {
     compatible = "arm,pl022";
     reg = <0x10115000 0x1000 >;
     interrupts = < 4 0 >;50
   };
   external-bus {
     #address-cells = <2>
     #size-cells = <1>;
     ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
           1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
           2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
     ethernet@0,0 {
       compatible = "smc,smc91c111";
       reg = <0 0 0x1000>;
       interrupts = < 5 2 >;
     };
     i2c@1,0 {
       compatible = "acme,a1234-i2c-bus";
       #address-cells = <1>;
       #size-cells = <0>;
       reg = <1 0 0x1000>;
       interrupts = < 6 2 >;
       rtc@58 {
         compatible = "maxim,ds1338";
         reg = <58>;
         interrupts = < 7 3 >;
       };
    };
    flash@2,0 {
      compatible = "samsung,k8f1315ebm", "cfi-flash";
      reg = <2 0 0x4000000>;
    };
  };// end of external-bus
};
```
## 3.Device Tree 的解析
device 包含 “of_node” 属性其类型为“struct device_node*”,通过 dev_of_node(device) 可获得该 of_node。of_node 代表了该设备在设备树中所对应的节点。各 driver 可以使用内核 API 结合 of_node 属性自行解析设备树中对应的资源。
### 3.1.device_node 结构体
Linux 内核中使用 device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 中,定义如下:
```cpp
struct device_node {
const char *name; /* 节点名字 */
const char *type; /* 设备类型 */
phandle phandle;
const char *full_name; /* 节点全名 */
struct fwnode_handle fnode;
struct property *properties; /* 属性 */
struct property *deadprops; /* removes 属性 */
struct device_node *parend; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling;
struct kobject kobj;
...
};
```
### 3.2.property 结构体
Linux 内核中使用 struct property 结构体来表示属性,定义如下:
```cpp
struct property {
char *name; //属性名字
int length; //属性长度
void *value; //属性值
struct property *next;//下一个属性
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
```
### 3.3.常用 OF 函数
#### 3.3.1.of_find_node_by_name 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
```
**说明:**
通过节点名字查找指定的节点。
**参数:**
from开始查找的节点如果为 NULL 表示从根节点开始查找整个设备树;
name要查找的节点名字。
**返回值:**
成功则返回找到的节点,失败返回 NULL。
#### 3.3.2.of_find_node_by_type 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
```
**说明:**
通过 device_type 属性查找指定的节点。
**参数:**
from开始查找的节点如果为 NULL 表示从根节点开始查找整个设备树;
type要查找的节点对应的字符串也就是 device_type 属性值。
**返回值:**
成功则返回找到的节点,失败返回 NULL。
#### 3.3.3.of_find_compatible_node 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_find_compatible_node(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match);
```
**说明:**
根据 device_type 和 compatible 这两个属性查找指定的节点。
**参数:**
from开始查找的节点如果为 NULL 表示从根节点开始查找整个设备树;
matchesof_device_id 匹配表,也就是在此匹配表里面查找节点;
match找到的匹配的 of_device_id。
**返回值:**
成功则返回找到的节点,失败返回 NULL。
#### 3.3.4.of_find_matching_node 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_find_matching_node(struct device_node *from, const struct of_device_id *matches);
```
**说明:**
通过 compatible 属性查找指定节点。
**参数:**
from指向开始路径的节点如果为NULL则从根节点开始
matches指向设备ID表注意ID表必须以NULL结束。
**返回值:**
成功:得到节点的首地址;
失败NULL。
#### 3.3.5.of_find_node_by_path 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
inline struct device_node *of_find_node_by_path(const char *path);
```
**说明:**
通过路径来查找指定的节点。
**参数:**
path带有全路径的节点名。
**返回值:**
成功则返回找到的节点,失败返回 NULL。
#### 3.3.6.of_get_parent 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_get_parent(const struct device_node *node);
```
**说明:**
用于获取指定节点的父节点(如果有父节点的话)。
**参数:**
node子节点。
**返回值:**
成功则返回找到的父节点,失败返回 NULL。
#### 3.3.7.of_get_next_child 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
```
**说明:**
查找谋节点的子节点。
**参数:**
node父节点
prev前一个子节点也就是从哪一个子节点开始的下一个子节点如果为 NULL 表示从第一个子节点开始。
**返回值:**
成功则返回找到的下一个子节点,失败返回 NULL。
#### 3.3.8.of_find_property 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
```
**说明:**
通过属性名称查找指定的属性
**参数:**
np设备节点
name属性名字
lenp返回属性值的字节数。
**返回值:**
成功则返回找到的属性,失败返回 NULL。
#### 3.3.9.of_get_property 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
const void *of_get_property(const struct device_node *np, const char *name, int *lenp);
```
**说明:**
是 of_find_property 函数的扩展,直接返回 property 的 value 属性。
**参数:**
np设备节点
name属性名字
lenp返回属性值的字节数。
**返回值:**
成功则返回找到的 value失败返回 NULL。
#### 3.3.10.of_property_count_elems_of_size 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_count_elems_of_size(const struct device_node *np, const char *propname int elem_size);
```
**说明:**
用于获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小。
**参数:**
np设备节点
proname需要统计元素数量的属性名字
elem_size元素长度。
**返回值:**
得到的属性元素数量。
#### 3.3.11.of_property_read_u32_index 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value);
```
**说明:**
用于从属性中获取指定标号的 u32 类型数据值(无符号 32 位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值。
**参数:**
np设备节点
proname要读取的属性名字
index要读取的值标号
out_value读取到的值。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.12.of_property_read_u8 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value);
```
**说明:**
用于读取属性只有一个 u8 数据的值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.13.of_property_read_u16 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value);
```
**说明:**
用于读取属性只有一个 u16 数据的值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.14.of_property_read_u32 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value);
```
**说明:**
用于读取属性只有一个 u32 数据的值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.15.of_property_read_u64 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value);
```
**说明:**
用于读取属性只有一个 u64 数据的值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.16.of_property_read_u8_array 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u8_array(const struct device_node *np,
const char *propname,
u8 *out_values,
size_t sz);
```
**说明:**
以 u8 数组形式读取属性值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值
sz要读取的数组元素数量。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.17.of_property_read_u16_array 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u16_array(const struct device_node *np,
const char *propname,
u16 *out_values,
size_t sz);
```
**说明:**
以 u16 数组形式读取属性值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值
sz要读取的数组元素数量。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.18.of_property_read_u32_array 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz);
```
**说明:**
以 u32 数组形式读取属性值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值
sz要读取的数组元素数量。
**返回值:**
0读取成功
负值:读取失败。-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.19.of_property_read_u64_array 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz);
```
**说明:**
以 u64 数组形式读取属性值。
**参数:**
np设备节点
proname要读取的属性名字
out_value读取到的数组值
sz要读取的数组元素数量。
**返回值:**
0读取成功
负值:读取失败,-EINVAL 表示属性不存在。-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
#### 3.3.20.of_property_read_string 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string);
```
**说明:**
用于读取属性中字符串值。
**参数:**
np设备节点
proname要读取的属性名字
out_string读取到的字符串值。
**返回值:**
0读取成功
负值:读取失败。
#### 3.3.21.of_property_read_string_index 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_property_read_string_index(const struct device_node *np,
const char *propname,
int index, const char **output);
```
**说明:**
Find and read a string from a multiple strings property.
**参数:**
np设备节点
proname要读取的属性名字
indexindex of the string in the list of strings
out_string读取到的字符串值。
**返回值:**
0读取成功
负值:读取失败。
#### 3.3.22.of_property_read_bool 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname);
```
**说明:**
用于读取属性中布尔类型数据的值。
**参数:**
np设备节点
proname要读取的属性名字。
**返回值:**
属性存在则返回 true否则返回 false。
#### 3.3.23.of_n_addr_cells 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_n_addr_cells(struct device_node *np);
```
**说明:**
用于获取“#address-cells“属性值。
**参数:**
np设备节点。
**返回值:**
获取到的“#address-cells”属性值。
#### 3.3.24.of_n_size_cells 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_n_size_cells(struct device_node *np);
```
**说明:**
用于获取“#size-cells“属性值。
**参数:**
np设备节点。
**返回值:**
获取到的“#size-cells”属性值。
#### 3.3.25.of_device_is_compatible 函数
**头文件:**
```cpp
#include <linux/of.h>
```
**函数原型:**
```cpp
int of_device_is_compatible(const struct device_node *device,
const char *compat);
```
**说明:**
用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性。
**参数:**
device设备节点
compat要查看的字符串。
**返回值:**
0节点的 compatible 属性中不包含 compat 指定的字符串;
正数:节点的 compatible 属性中包含 compat 指定的字符串。
#### 3.3.26.of_get_address 函数
**头文件:**
```cpp
#include <linux/of_address.h>
```
**函数原型:**
```cpp
const __be32 *of_get_address(struct device_node *dev,
int index,
u64 *size,
unsigned int *flags);
```
**说明:**
用于获取地址相关属性主要是“reg”或者“assigned-addresses”属性值。
**参数:**
dev设备节点
index要读取的地址标号
size地址长度
flags参数比如 IORESOURCE_IO、IORESOURCE_MEM 等。
**返回值:**
成功则返回读取到的地址数据首地址,失败返回 NULL。
#### 3.3.27.of_translate_address 函数
**头文件:**
```cpp
#include <linux/of_address.h>
```
**函数原型:**
```cpp
u64 of_translate_address(struct device_node *dev,const __be32 *in_addr);
```
**说明:**
将从设备树读取到的地址转换为物理地址。
**参数:**
dev设备节点
in_addr要转换的地址。
**返回值:**
成功则返回转换得到的物理地址,失败返回 OF_BAD_ADDR。
#### 3.3.28.of_address_to_resource 函数
**头文件:**
```cpp
#include <linux/of_address.h>
```
**函数原型:**
```cpp
int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r);
```
**说明:**
根据索引号从设备树中获取“reg”这一类的地址资源。
**参数:**
dev设备节点
index地址资源索引号
r得到的 resource 类型的资源值。
**返回值:**
0成功
负值:失败。
#### 3.3.29.of_iomap 函数
**头文件:**
```cpp
#include <linux/of_address.h>
```
**函数原型:**
```cpp
void __iomem *of_iomap(struct device_node *np, int index);
```
**说明:**
通过设备结点的“reg”字段直接进行设备内存区间的 ioremap()。
**参数:**
np设备节点
indexreg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
**返回值:**
成功则返回经过内存映射后的虚拟内存首地址,失败返回 NULL。
#### 3.3.30.of_io_request_and_map 函数
**头文件:**
```cpp
#include <linux/of_address.h>
```
**函数原型:**
```cpp
void __iomem *of_io_request_and_map(struct device_node *np, int index, const char *name);
```
**说明:**
提取 I/O 口地址并申请 I/O 资源及映射成虚拟地址。
**参数:**
np设备节点
indexreg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0
name设备名申请 I/O 地址时使用。
**返回值:**
成功:映射好虚拟地址;
失败NULL
#### 3.3.31.of_get_named_gpio 函数
**头文件:**
```cpp
#include <linux/of_gpio.h>
```
**函数原型:**
```cpp
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
```
**说明:**
从设备树中提取gpio口。
**参数:**
dev设备节点
propname属性名
indexgpio 口引脚标号。
**返回值:**
成功:得到 GPIO 口编号;
失败:负数,绝对值是错误码。
#### 3.3.32.irq_of_parse_and_map 函数
**头文件:**
```cpp
#include <linux/of_irq.h>
```
**函数原型:**
```cpp
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
```
**说明:**
从设备树的 interrupt 字段解析出中断号,如果有多个中断则通过 index 来指定。
**参数:**
node设备节点
index用于指定要解析中断的段如果只有一段的话就设置为 0。
**返回值:**
成功则返回解析的中断号,失败返回 0。
#### 3.3.33.of_irq_count 函数
**头文件:**
```cpp
#include <linux/of_irq.h>
```
**函数原型:**
```cpp
int of_irq_count(struct device_node *np);
```
**说明:**
从设备树中提取中断的数量。
**参数:**
np设备节点。
**返回值:**
成功大于等于0实际中断数量
0表示没有中断。
#### 3.3.34.of_irq_get 函数
**头文件:**
```cpp
#include <linux/of_irq.h>
```
**函数原型:**
```cpp
int of_irq_get(struct device_node *np, int index);
```
**说明:**
从设备树中提取中断号。如果你的设备节点既不能转换为 platform_device它也不是 I2C/SPI 设备,那么在驱动程序中可以自行调用 of_irq_get 函数去解析设备树,得到中断号。
**参数:**
np设备节点
index要提取的中断号的标号。
**返回值:**
成功:中断号;
失败:负数,其绝对值是错误码。
#### 3.3.35.of_get_mac_address 函数
**头文件:**
```cpp
#include <linux/of_net.h>
```
**函数原型:**
```cpp
void *of_get_mac_address(struct device_node *np);
```
**说明:**
从设备树中提取 MAC 地址。
**参数:**
np设备节点
**返回值:**
成功MAC6字节的首地址
失败NULL。
#### 3.3.36.of_find_device_by_node 函数
**头文件:**
```cpp
#include <linux/of_platform.h>
```
**函数原型:**
```cpp
struct platform_device *of_find_device_by_node(struct device_node *np);
```
**说明:**
获取与节点对应的 platform_device。
**参数:**
node设备节点。
**返回值:**
成功则返回 platform_device 指针,失败返回 NULL。
## 4.外部参考资料
1. [Linux 驱动学习笔记 - 设备树常用 OF 函数(五)](https://blog.csdn.net/tyustli/article/details/105444648)
2. [Device Tree 详解](http://kernel.meizu.com/device-tree.html)
3. [ARM Linux 3.x的设备树Device Tree](https://blog.csdn.net/21cnbao/article/details/8457546)
4. [Linux Kernel DT(Device Tree)](https://www.jianshu.com/p/923b380366bb)