提供相对完整的 Device Tree 资料.

Signed-off-by: rick.chan <chenyang@autoai.com>
This commit is contained in:
rick.chan 2020-06-08 11:58:18 +08:00
parent bcbf2e3eb1
commit 6713147451
1 changed files with 454 additions and 14 deletions

View File

@ -1,6 +1,8 @@
# Device Tree
## 设备树的起源
## 概述
### 设备树的起源
linux 2.6 及之前,大量板级信息被硬编码到内核里,十分庞大,大量代码冗余。
@ -16,7 +18,11 @@ linux 2.6 之后,引入了设备树。设备树源于 OpenFirmware描述硬
本质上是画一棵 CPU、总线、设备组成的树Linux 内核会把设备树展开成 platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、中断等资源,也会传递个内核,内核会将这些资源绑定给展开的相应设备。
## DTSI/DTS/DTC/DTB
不使用 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则不需要再关注了。
### DTSI/DTS/DTC/DTB
dtsi可被 #include 的设备树源文件;
@ -24,10 +30,30 @@ dts设备树源文件
dtc编译 dts 和 dtsi 后得到的设备树文件dts 及 dtsi 中的内容被组合或覆盖该文件为源码形式Linux 内核无法识别;
dtb编译 dtc 后得到的二进制设备树文件Linux 内核可加载和识别其中的内容。
dtb即 Device Tree Blob编译 dtc 后得到的二进制设备树文件Linux 内核可加载和识别其中的内容。
如果谋 dts 文件引用了谋 dtsi 文件,可以在 dts 中覆盖 dtsi 中的部分内容。
### Binding
对于 Device Tree 中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为“.txt”。这些文档位于内核的 Documentation/devicetree/bindings 目录,其下又分为很多子目录。
### DTB 的编译
DTB(Devicetree Blob) 是 DTS 的二进制文件格式Kernel 使用 DTC 工具将 DTS 源文件编译成 DTBbootloader 再将 DTB 文件传递给 Kernel 解析。
### Bootloader
不遵守标准书写的 DTS 文件在编译的时候会报错。
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 的地址,如不存在,使用“-“代替。
## DTS 语法
设备树包含一个根节点和多个子节点,如果在 dts/dtsi 文件中写了多个根节点,则在编译后被组合成一个根节点。子节点可嵌套。 节点会被展开为 device其 compatible 属性用于与 driver 的 compatible 属性项匹配,如果匹配成功则调用该 driver 的 probe 函数。
@ -39,10 +65,10 @@ DTS 中使用“//”进行行注释或“/**/”进行块注释。
节点使用:
```dts
[label]:<name>[@<unit-address>] {}
[label]:<node-name>[@<unit-address>] {}
```
的格式来定义。挂到内存空间的设备,其 unit-address 一般是内存地址。别的地方可以通过“&label”来引用该节点。
的格式来定义。挂到内存空间的设备,其 unit-address 一般是 node 中“reg”属性描述的开始地址。label 为可选项,如果存在,则在 DTS 的其他地方可以通过“&label”来引用该节点。
### 属性
@ -87,7 +113,7 @@ model = "fsl,MPC8349EMITX";
#### phandle
“phandle”属性通一个唯一的 id 来标识一个 Node在 property 可以使用这个 id 来引用 Node。
“phandle”属性通一个唯一的 id 来标识一个 Node在 property 可以使用这个 id 来引用 Node。
Value type: u32
@ -152,12 +178,19 @@ reg = <address1 size1 [address2 size2] [address3 size3]...>;
#### interrupt
* interrutt-controller属性为空表明“我是中断控制器”
* #interrupt-cells表明连接此中断控制器的设备的中断属性的 cell 大小,也就是 interrupt = <> 属性的 cell 大小;
* interrupt-parent设备节点通过这个关键字指定其依附的中断控制器 phandle如果没有指定则继承父节点的 interrupt-parent 配置;
* interrupt设备节点里使用一般包含中断号、触发方法等。具体有多少个 cell#interrupt-cells 决定,每个 cell 的具体含义,一般由驱动决定。
和中断相关的 node 可以分成3种
多个中断可以用 interrupts 描述。interrupts 属性后面,会有不同的参数,有时是两个,有时是三个。
* Interrupt Generating Devices中断发生设备这种设备可以发生中断
* Interrupt Controllers中断控制器处理中断
* Interrupt Nexus中断联结路由中断给中断控制器。
##### Interrupt Generating Devices Property
* 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 属性后面,会有两个或三个固定参数。
两个的时候一般是这样出现:
@ -166,7 +199,7 @@ interrupt-parent = <&gpio2>;
interrupts = <29 0>;
```
一般这样表明:中断控制器是 GPIO2然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚)0 是指触发的方式(上升沿、下降沿等)。
表明:中断控制器是 GPIO2然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚)0 是指触发的方式(上升沿、下降沿等)。
三个的时候一般是这样出现:
@ -196,21 +229,386 @@ IPI、PPI、SPI、SGI 是 ARM 规范的中断,含义如下:
#define IRQ_TYPE_LEVEL_LOW 8
```
##### Interrupt Controllers Property
* #interrupt-cells用来规定连接到该中断控制器上的设备的”interrupts”属性的解析长度
* interrupt-controller用来声明当前 node 为中断控制器。
##### Interrupt Nexus Property
* #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
### 标准节点
#### Root
每个 DeviceTree 只有一个根节点。根节点需要有以下必备属性:
* #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"
```
#### 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";
};
```
#### 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>;
};
}
```
#### 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";
};
```
#### cpus
####
cpus 节点也是必须的,下面举个具体例子:
// TODO: <http://kernel.meizu.com/device-tree.html><https://blog.csdn.net/21cnbao/article/details/8457546><https://www.jianshu.com/p/923b380366bb>
```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>;
};
};
};
```
### DTS 示例
@ -986,6 +1384,42 @@ out_string读取到的字符串值。
负值:读取失败。
#### 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读取成功
负值:读取失败。
#### of_property_read_bool 函数
**头文件:**
@ -1426,3 +1860,9 @@ node设备节点。
**返回值:**
成功则返回 platform_device 指针,失败返回 NULL。
## 参考资料
* [Device Tree 详解](http://kernel.meizu.com/device-tree.html)
* [ARM Linux 3.x的设备树Device Tree](https://blog.csdn.net/21cnbao/article/details/8457546)
* [Linux Kernel DT(Device Tree)](https://www.jianshu.com/p/923b380366bb)