47 KiB
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 包含的硬件完整打桩信息被提取为一个二进制文件 DTB,bootloader 则需要加载 kernel 镜像(uImage 或 zImage)以及 DTB(arch/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 源文件编译成 DTB,bootloader 再将 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 文件中加入
#define CONFIG_OF_LIBFDT
当我们将 DTB 文件在 Uboot 里加载到内存中后,通过 fdt addr 0xnnnnnnnn 命令来设置 DTB 文件对应地址,这样就可以使用 fdt resize、fdt print 等命令对 DTB 文件进行操作了。对于ARM,使用 bootz kernel_addr initrd_addr dtb_addr 命令来启动 kernel,dtb_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 属性项匹配。
节点的使用:
[label]:<node-name>[@<unit-address>] {}
的格式来定义。挂到内存空间的设备,其 unit-address 一般是 node 中“reg”属性描述的开始地址。label 为可选项,如果存在,则在 DTS 的其他地方可以通过“&label”来引用该节点。
2.3 属性
属性必须包含在节点中,每个设备的属性都用一组 key-value 对(键值对)来描述,每个属性的描述用英文“;“结束。例如,使用:
<property-name>=<value>;
来定义一个属性。属性值可以为整数或字符串。如果为整数则用“<>”括起来,“<>”中可以有多个单元,称为 cell,cell 间使用空格隔开,不同的 cell 可以有不同的含义。字符串使用英文双引号括起来。同一属性的多个值可以使用英文“,”进行分割,例如:
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>;
2.4 标准属性
2.4.1 compatible
“compatible”属性通常用来匹配 device 和 driver,一旦 Device Tree 中 compatible 属性值与 driver 的 compatible 字段一致,则触发该 driver 的 probe 函数。推荐 compatible 属性值使用如下格式:
compatible = ”manufacturer,model”;
Value type: stringlist
Example:
compatible = "fsl,mpc8641", "ns16550";
2.4.2 model
“model”属性只是简单的表示型号,root 节点用其来传递值给 machine_desc_str。
Value type: stringlist
Example:
model = "fsl,MPC8349EMITX";
2.4.3 phandle
“phandle”属性通过一个唯一的 id 来标识一个 Node,在 property 可以使用这个 id 来引用 Node。
Value type: u32
Example:
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:
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”字段构成,如:
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 Nexus,phandle 指相对应的 node,如果没有指定,则继承父节点的 interrupt-parent 配置。
interrupt 和 interrupts 都根据其 interrupt-parent node 中定义的“#interrupt-cells”来解析。比如 #interrupt-cells=2,那根据 2 个 cells 为单位来解析 interrupt/interrupts 属性。理论上每个 cell 的具体含义,一般由驱动决定,但一般而言 interrupt/interrupts 属性后面,会有两个或三个固定参数。
两个的时候一般是这样出现:
interrupt-parent = <&gpio2>;
interrupts = <29 0>;
表明:中断控制器是 GPIO2,然后使用它的 29 号中断。(这里的 29 号,就是指 29 号引脚),0 是指触发的方式(上升沿、下降沿等)。
三个的时候一般是这样出现:
interrupts = <0 37 1>;
interrupts = <GIC_SPI 37 1>;
interrupts = <GIC_PPI 37 1>;
第一个参数表示是 IPI、PPI、SPI、SGI 其中的一个。第二个参数表示:是第一个参数里面的第几个。第三个参数表示:中断触发的类型(上升沿、下降沿等)。
IPI、PPI、SPI、SGI 是 ARM 规范的中断,含义如下:
- IPI:inter-processer interrupt 中断号0~15
- PPI:per processor interrupts 中断号16~31
- SPI:shared processor interrupts 中断号 32 ~32+224
- SGI:software generated interrupts (SGI).
中断类型的值如下:
#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”指定。
举例:
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”,比如:
interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;
2.5 标准节点
2.5.1 Root
每个 Device Tree 只有一个根节点,用“/{}”表示,如果在 dts/dtsi 文件中写了多个根节点,则在编译后被组合成一个根节点。根节点需要有以下必备属性:
- #address-cells:u32, Specifies the number of <u32> cells to represent the address in the reg property in children of root;
- #size-cells:u32, Specifies the number of <u32> cells to represent the size in the reg property in children of root;
- model:string, Specifies a string that uniquely identifies the model of the system board. The recommended format is “manufacturer,model-number”;
- compatible:stringlist, 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:
/{
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 的缩写:
aliases {
serial0 = "/simple-bus@fe000000/serial@llc500";
ethernet0 = "/simple-bus@fe000000/ethernet@31c000";
};
2.5.3 memory
用来传递内存布局:
- device_type:string,Value shall be “memory”;
- reg:prop-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-area:prop-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).
举例:
// 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 传递。
bootargs:string,用来传递 cmdline 参数; linux,initrd-start:用来传递 initrd 的开始地址; stdout-path:string,用来指定标准输出设备; stdin-path:string,用来指定标准输入设备。
举例:
/* chosen */
chosen {
bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram";
};
2.5.5 cpus
cpus 节点也是必须的,下面举个具体例子:
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 文件示例如下:
/ {
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 中,定义如下:
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 结构体来表示属性,定义如下:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 表示从根节点开始查找整个设备树;
matches:of_device_id 匹配表,也就是在此匹配表里面查找节点;
match:找到的匹配的 of_device_id。
返回值:
成功则返回找到的节点,失败返回 NULL。
3.3.4 of_find_matching_node 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
inline struct device_node *of_find_node_by_path(const char *path);
说明:
通过路径来查找指定的节点。
参数:
path:带有全路径的节点名。
返回值:
成功则返回找到的节点,失败返回 NULL。
3.3.6 of_get_parent 函数
头文件:
#include <linux/of.h>
函数原型:
struct device_node *of_get_parent(const struct device_node *node);
说明:
用于获取指定节点的父节点(如果有父节点的话)。
参数:
node:子节点。
返回值:
成功则返回找到的父节点,失败返回 NULL。
3.3.7 of_get_next_child 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
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:要读取的属性名字;
index:index of the string in the list of strings
out_string:读取到的字符串值。
返回值:
0:读取成功;
负值:读取失败。
3.3.22 of_property_read_bool 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of.h>
函数原型:
int of_n_addr_cells(struct device_node *np);
说明:
用于获取“#address-cells“属性值。
参数:
np:设备节点。
返回值:
获取到的“#address-cells”属性值。
3.3.24 of_n_size_cells 函数
头文件:
#include <linux/of.h>
函数原型:
int of_n_size_cells(struct device_node *np);
说明:
用于获取“#size-cells“属性值。
参数:
np:设备节点。
返回值:
获取到的“#size-cells”属性值。
3.3.25 of_device_is_compatible 函数
头文件:
#include <linux/of.h>
函数原型:
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 函数
头文件:
#include <linux/of_address.h>
函数原型:
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 函数
头文件:
#include <linux/of_address.h>
函数原型:
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 函数
头文件:
#include <linux/of_address.h>
函数原型:
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 函数
头文件:
#include <linux/of_address.h>
函数原型:
void __iomem *of_iomap(struct device_node *np, int index);
说明:
通过设备结点的“reg”字段直接进行设备内存区间的 ioremap()。
参数:
np:设备节点;
index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值:
成功则返回经过内存映射后的虚拟内存首地址,失败返回 NULL。
3.3.30 of_io_request_and_map 函数
头文件:
#include <linux/of_address.h>
函数原型:
void __iomem *of_io_request_and_map(struct device_node *np, int index, const char *name);
说明:
提取 I/O 口地址并申请 I/O 资源及映射成虚拟地址。
参数:
np:设备节点;
index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0;
name:设备名,申请 I/O 地址时使用。
返回值:
成功:映射好虚拟地址;
失败:NULL
3.3.31 of_get_named_gpio 函数
头文件:
#include <linux/of_gpio.h>
函数原型:
int of_get_named_gpio(struct device_node *np, const char *propname, int index);
说明:
从设备树中提取gpio口。
参数:
dev:设备节点;
propname:属性名;
index:gpio 口引脚标号。
返回值:
成功:得到 GPIO 口编号;
失败:负数,绝对值是错误码。
3.3.32 irq_of_parse_and_map 函数
头文件:
#include <linux/of_irq.h>
函数原型:
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 函数
头文件:
#include <linux/of_irq.h>
函数原型:
int of_irq_count(struct device_node *np);
说明:
从设备树中提取中断的数量。
参数:
np:设备节点。
返回值:
成功:大于等于0,实际中断数量;
0:表示没有中断。
3.3.34 of_irq_get 函数
头文件:
#include <linux/of_irq.h>
函数原型:
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 函数
头文件:
#include <linux/of_net.h>
函数原型:
void *of_get_mac_address(struct device_node *np);
说明:
从设备树中提取 MAC 地址。
参数:
np:设备节点;
返回值:
成功:MAC(6字节)的首地址;
失败:NULL。
3.3.36 of_find_device_by_node 函数
头文件:
#include <linux/of_platform.h>
函数原型:
struct platform_device *of_find_device_by_node(struct device_node *np);
说明:
获取与节点对应的 platform_device。
参数:
node:设备节点。
返回值:
成功则返回 platform_device 指针,失败返回 NULL。