简介
由来
platform
的弊端:假如soc
不变,我们每换一个平台,都要修改C
文件,并且还要重新编译。
- 而且会在
arch/arm/plat-xxx
和arch/arm/mach-xxx
下面留下大量的关于板级细节的代码。 - 这些代码相对于
Linux
内核来说就是垃圾代码。
设备树的引入是为了剔除相对于内核的垃圾代码。
- 用设备树文件来描述这些设备信息,也就是代替
device.c
文件,platform
匹配上基本不变。 - 使用设备树不仅可以去掉大量“垃圾代码”,并且采用文本格式,方便阅读和修改。
- 如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过
bootloader
传递给内核。 - 内核在对其进行解析和展开得到一个关于硬件的拓扑图。我们通过内核提供的接口获取设备树的节点和属性就可以。
- 即内核对于同一
soc
的不同主板,只需更换设备树文件dtb
即可实现不同主板的无差异支持,而无需更换内核文件。
设备树定义
Device Tree
是一种描述硬件的数据结构,由一系列被命名的节点(node
)和属性(property
)组成
- 节点本身可包含子节点。
- 所谓属性,其实就是成对出现的
name
和value
。
- 在
Device Tree
中,可描述的信息包括:CPU
的数量和类别,内存基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况,GPIO
控制器和GPIO
使用情况,Clock
控制器和Clock
使用情况。 - 设备树基本上就是画一棵电路板上由
CPU
、总线、设备组成的树,Bootloader
会将这棵树传递给内核 - 内核可以识别这棵树,并根据它展开出
Linux
内核中的platform_device
、i2c_client
、spi_device
等设备,而这些设备用到的内存、IRQ
等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
DTS 、DTC 和 DTB
文件
.dts
是一种ASCII文件格式设备树描述,在Linux
中,一个.dts
文件对应一个ARM
设备,一般放置在arch/arm/boot/dts
目录下。
dtb
文件是dts
文件被编译后生成的二进制文件,由Linux
内核解析- 有了设备树文件就可以在不改动
Linux
内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts
文件。 dtc
是将dts
编译为dtb
的工具。在Linux
内核下可以单独编译设备树文件
名称 | .dts | ⇒dtc ⇒ | .dtb |
---|---|---|---|
类型 | ascii 文件 | 编译工具 | 二进制文件 |
在内核源码
/linux/linux-imx/arch/arm64/boot/dts/freescale/Makefile
中可以看出,当选中某一个选项后,所有使用到设备树文件会被编译为.dtb。
- 如果我们配置新的开发板,只需要新建一个此开发板对应的
.dts
文件,然后将对应的.dtb
文件名添加到dtb- $(CONFIG_ARCH_ROCKCHIP)
下,这样在编译设备树的时候就会将对应的.dts
编译为二进制的.dtb
文件。
DTS 设备树语法结构
- 一般情况下,我们不会从头编写一个完整的
dts
文件,SOC
厂商一般会直接提供一个有着基本框架的dts
文件,当需要添加自己的板子设备树文件时,基于厂商提供的dts
文件修改即可。所以我们要了解dts
设备树文件的语法,这样我们才清楚如何添加我们自己的设备。
dtsi
头文件
device tree source include(dtsi)
是更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码。
- 由于一个
SOC
可能对应多个ARM
设备,这些dts
文件势必包含许多共同的部分,Linux
内核为了简化,把SOC
公用的部分或者多个设备共同的部分提炼为.dtsi
文件,类似于C
语言的头文件。 .dtsi
文件也可以包含其他的.dtsi
。#include "imx8mm-evk.dtsi"
用#include
关键字来引用了imx8mm-evk.dtsi
文件,也可以像C语言那样来引用.h
文件- 一般
.dtsi
文件用于描述SOC
的内部外设信息,比如CPU
架构、主频、外设寄存器地址范围,比如UART
、IIC
等等。 - 例如
fsl-imx8mm.dtsi
就是描述iMX8MM
这个SOC
内部外设情况信息
#include "fsl-imx8-ca53.dtsi"
#include <dt-bindings/clock/imx8mm-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/pinctrl/pins-imx8mm.h>
#include <dt-bindings/thermal/thermal.h>
/ {
compatible = "fsl,imx8mm";
interrupt-parent = <&gpc>;
#address-cells = <2>;
#size-cells = <2>;
aliases {
ethernet0 = &fec1;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
spi0 = &flexspi0;
usb0 = &usbotg1;
usb1 = &usbotg2;
};
cpus {
idle-states {
entry-method = "psci";
CPU_SLEEP: cpu-sleep {
compatible = "arm,idle-state";
arm,psci-suspend-param = <0x0010033>;
local-timer-stop;
entry-latency-us = <1000>;
exit-latency-us = <700>;
min-residency-us = <2700>;
wakeup-latency-us = <1500>;
};
};
};
memory@40000000 {
device_type = "memory";
reg = <0x0 0x40000000 0 0x80000000>;
};
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* global autoconfigured region for contiguous allocations */
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0 0x28000000>;
alloc-ranges = <0 0x40000000 0 0x80000000>;
linux,cma-default;
};
};
gic: interrupt-controller@38800000 {
compatible = "arm,gic-v3";
reg = <0x0 0x38800000 0 0x10000>, /* GIC Dist */
<0x0 0x38880000 0 0xC0000>; /* GICR (RD_base + SGI_base) */
#interrupt-cells = <3>;
interrupt-controller;
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&gic>;
};
timer {
compatible = "arm,armv8-timer";
interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Physical Secure */
<GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Physical Non-Secure */
<GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Virtual */
<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>; /* Hypervisor */
clock-frequency = <8000000>;
interrupt-parent = <&gic>;
};
clocks {
#address-cells = <1>;
#size-cells = <0>;
osc_32k: clock@0 {
compatible = "fixed-clock";
reg = <0>;
#clock-cells = <0>;
clock-frequency = <32768>;
clock-output-names = "osc_32k";
};
osc_24m: clock@1 {
compatible = "fixed-clock";
reg = <1>;
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc_24m";
};
clk_ext1: clock@2 {
compatible = "fixed-clock";
reg = <3>;
#clock-cells = <0>;
clock-frequency = <133000000>;
clock-output-names = "clk_ext1";
};
clk_ext2: clock@3 {
compatible = "fixed-clock";
reg = <4>;
#clock-cells = <0>;
clock-frequency = <133000000>;
clock-output-names = "clk_ext2";
};
clk_ext3: clock@4 {
compatible = "fixed-clock";
reg = <5>;
#clock-cells = <0>;
clock-frequency = <133000000>;
clock-output-names = "clk_ext3";
};
clk_ext4: clock@5 {
compatible = "fixed-clock";
reg = <6>;
#clock-cells = <0>;
clock-frequency= <133000000>;
clock-output-names = "clk_ext4";
};
};
mipi_pd: gpc_power_domain@0 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <0>;
domain-name = "MIPI_PD";
};
pcie0_pd: gpc_power_domain@1 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <1>;
domain-name = "PCIE0_PD";
};
usb_otg1_pd: gpc_power_domain@2 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <2>;
domain-name = "USB_OTG1_PD";
};
usb_otg2_pd: gpc_power_domain@3 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <3>;
domain-name = "USB_OTG2_PD";
};
gpu_2d_pd: gpc_power_domain@4 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <4>;
domain-name = "GPU_2D_PD";
};
gpu_mix_pd: gpc_power_domain@5 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <5>;
domain-name = "GPU_MIX_PD";
};
vpu_mix_pd: gpc_power_domain@6 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <6>;
domain-name = "VPU_MIX_PD";
};
disp_mix_pd: gpc_power_domain@7 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <7>;
domain-name = "DISP_MIX_PD";
};
vpu_g1_pd: gpc_power_domain@8 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <8>;
domain-name = "VPU_G1_PD";
};
vpu_g2_pd: gpc_power_domain@9 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <9>;
domain-name = "VPU_G2_PD";
};
vpu_h1_pd: gpc_power_domain@10 {
compatible = "fsl,imx8mm-pm-domain";
#power-domain-cells = <0>;
domain-id = <10>;
domain-name = "VPU_H1_PD";
};
gpio1: gpio@30200000 {
compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
reg = <0x0 0x30200000 0x0 0x10000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio2: gpio@30210000 {
compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
reg = <0x0 0x30210000 0x0 0x10000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
gpio3: gpio@30220000 {
compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
reg = <0x0 0x30220000 0x0 0x10000>;
interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
..................
设备节点信息
设备树从根节点开始,每个设备都是一个节点。
- 根节点就相当于树根。节点和节点之间可以互相嵌套,形成父子关系。
- 设备的属性用
key-value
对(键值对)来描述,每个属性用分号结束。
设备树结构模板
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>;
child-node1 {
};
};
};
- 一个单独的根节点:
/
- 两个子节点:
node1
和node2
- 两个
node1
的子节点:“child-node1
”和“child-node2
” - 一些分散在树里的属性,属性是最简单的键-值对,它的值可以为空或者包含一个任意的字节流。
设备树源文件中仍有几个基本的数据表示形式
/ {
*节点示例*
node1 {
*文本字符串*
a-string-property = "A string";
*cells*
cell-property = <0xbeef 123 0xabcd1234>;
*二进制数据*
binary-property = [0x01 0x23 0x45 0x67];
*混合数据*
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
*字符串列表*
string-list = "red fish", "blue fish";
};
};
- 文本字符串 (
a-string-property
):表示一个简单的字符串。 - Cells (
cell-property
):表示一系列的32
位无符号整数。 - 二进制数据 (
binary-property
):表示一组二进制数据。 - 混合数据 (
mixed-property
):包含字符串、二进制数据和整数的组合。 - 字符串列表 (
string-list
):表示一个由逗号分隔的字符串列表。
设备节点及lable
命名
- 格式:
<名称>[@<设备地址>]
<设备地址>
就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,主要用来区分用。- 同一级的节点只要地址不一样,名字是可以不唯一的。
- 设备地址是一个可选选项。
- 节点路径必须使用完整路径,可以使用别名来简化书写:
别名:<名称>[@<设备地址>]
,后续直接通过别名
访问。 - 一般是通过引用向节点中添加内容
&uart8 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okay";
};
注意事项:编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写
- 使用引用可以避免四处找节点。如
dts
和dtsi
里面都有根节点,但最终会合并成一个根节点。
标准属性
address-cells
和size-cells
属性- 不同的平台,不同的总线,地址位长度可能不同,规范规定一个
32
位的长度为一个cell
。 #address-cells
属性用来表示总线地址需要几个cell
表示,该属性本身是u32
类型的。#size-cells
属性用来表示子总线地址空间的长度需要几个cell
表示,属性本身的类型也是u32
。- 这两个属性不可以继承,未定义这两个属性的时候,不会继承更高一级父节点的设置
- 内核默认认为
#address-cells
为2
,#size-cells
为1
。
spi4 { // 主节点 spi4
compatible = "spi-gpio"; // 兼容性字符串,指示这是一个 SPI GPIO 设备
#address-cells = <1>; // 地址由一个32位单元表示
#size-cells = <0>; // 由于是 SPI 设备,不需要指定大小
gpio_spi: gpio_spi@0 { // 子节点 gpio_spi
compatible = "fairchild,74hc595"; // 兼容性字符串,指示这是一个特定的 SPI 设备
reg = <0>; // SPI 设备的地址,这里用一个单元表示
};
};
aips3: aips-bus@02200000 { // 主节点 aips-bus
compatible = "fsl,aips-bus", "simple-bus"; // 兼容性字符串,指示这是一个 AIPS 总线设备
#address-cells = <1>; // 地址由一个32位单元表示
#size-cells = <1>; // 大小也由一个32位单元表示
dcp: dcp@02280000 { // 子节点 dcp
compatible = "fsl,imx6sl-dcp"; // 兼容性字符串,指示这是一个特定的 DCP 设备
reg = <0x02280000 0x4000>; // 地址为 0x02280000,大小为 0x4000
};
};
reg属性
reg
属性用来表示节点地址资源的,比如常见的就是寄存器的起始地址及大小。要想表示一块连续地址,必须包含起始地址和空间大小两个参数,如果有多块地址,那么就需要多组这样的值表示。
- 对于
reg
属性,每个元素是一个二元组,包含起始地址和大小。 - 地址和大小用几个 u32 表示呢?由父节点的
address-cells
和size-cells
属性决定。
uart1: serial@02020000 { // 主节点 uart1
compatible = "fsl,imx6ul-uart", // 兼容性字符串,指示这是一个 Freescale i.MX6 UltraLite UART 设备
"fsl,imx6q-uart", // 兼容性字符串,指示这是一个 Freescale i.MX6 Quad UART 设备
"fsl,imx21-uart"; // 兼容性字符串,指示这是一个 Freescale i.MX21 UART 设备
reg = <0x02020000 0x4000>; // 地址为 0x02020000,大小为 0x4000
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>; // 中断信息,使用 GIC_SPI 中断控制器,中断号为 26,触发方式为高电平
clocks = <&clks IMX6UL_CLK_UART1_IPG>, // IPG 时钟,引用名为 clks 的时钟节点
<&clks IMX6UL_CLK_UART1_SERIAL>; // 串行时钟,引用名为 clks 的时钟节点
clock-names = "ipg", "per"; // 时钟名称,分别为 IPG 时钟和 PER 时钟
status = "disabled"; // 设备状态为禁用
};
compatible属性
compatible
属性是操作系统用来决定设备和驱动绑定的关键因素。
- 设备树中的每个表示一个设备的节点都需要一个
compatible
属性 compatible
属性也叫做兼容性属性,属性的值是一个字符串列表,用于表示是何种设备,可以在代码中进行匹配。
compatible = "manufacturer,model";
- 例
sound
节点的compatible
属性值如下:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
- 属性值有两个,分别为
fsl,imx6ul-evk-wm8960
和fsl,imx-audio-wm8960
,其中fsl
表示厂商是 飞思卡尔,imx6ul-evk-wm8960
和imx-audio-wm8960
表示设备驱动的名字。 sound
这个设备首先使用第一个兼容值在Linux
内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个Linux
内核也没有找到对应的驱动。
status属性
status
属性用来表示节点的状态,其实就是硬件的状态,用字符串表示。
okay
表示硬件正常工作disable
表示当前硬件不可用fail
表示因为出错不可用fail-sss
表示某种原因出错不可用,sss
表示具体出错的原因。- 实际中,基本只用
okay
和disabl
。