上一节大致介绍了一下DTS的概念,了解了他的起源,加载过程以及编译方式。
这一节就介绍一下DTS的语法结构。
文件结构
上一节了解过DTS的文件结构,有.dts和.dtsi,其中.dtsi是提炼出来的soc共用的部分,减少冗余。
以高通QSDK的kernel部分的dts为例
ipq5332.dtsi //soc公用部分
ipq5332-mi04.1.dts //单板的特殊内容
ipq5332-mi04.3.dts
ipq5332-memory.dtsi //公用内存配置
......
查看具体文件内容,打开ipq5332-mi04.1.dts,可以在开头看见引用,与C类似,类似于头文件引用。
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* IPQ5332 AP-MI04.1 board device tree source
*
* Copyright (c) 2020-2021 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
*/
/dts-v1/;
#include "ipq5332.dtsi" #这里引用了公用SOC部分
#include "ipq5332-memory.dtsi" #这里引用了公用内存部分
DTS的基础框架
打开.dts可以看到类似如下的基本结构。类似xml或者json的格式。
/dts-v1/;
/ { //根节点
node1{ //node1是节点名,是/的子节点
key=value; //node1的属性
...
node2{ //node2是node1的子节点
key=value; //node2的属性
...
}
} //node1的描述到此为止
node3{
key=value;
...
}
}
从上面的基础结构可以看到 根节点描述为 /{}
并且还有一个声明/dts-v1/;,声明内容为设备树的版本,如果没有这个声明,那么编译dtb时,会报出syntax error的错误,排查起来很麻烦。
设备树使用树状结构描述设备信息,它有以下几种特性:
- 每个设备树文件都有一个根节点,每个设备都是一个节点。
- 节点由 节点名 + 属性 组成。
- 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
- 每个设备的属性都用一组key-value对(键值对)来描述。
- 每个属性的描述用
;结束
节点Node
{}包围起来的结构称之为节点,dts中最开头的/ {},称为根节点。
在节点中,以 key = value 代表节点属性。
/dts-v1/;
/ {
clocks {
sleep_clk: sleep-clk {
compatible = "fixed-clock";
clock-frequency = <32000>;
#clock-cells = <0>;
};
}
blsp1_uart1: serial@78b0000 {
compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
reg = <0x078b0000 0x200>;
};
}
节点Name
/dts-v1/;
/ {
blsp1_uart1: serial@78b0000 {
compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
reg = <0x078b0000 0x200>;
};
}
节点名称:每个节点名格式为:<name>[@<unit_address>]
<name>:设备名,就是一个不超过31位的简单 ascii 字符串,节点的命名应该根据它所体现的是什么样的设备。
<unit_address>:设备地址,用来唯一标识一共节点。没有指定<unit_address>时,同级节点命名必须是唯一的;但只要<unit_address>不同,多个节点也可以使用一样的通用名称。
别名(引用)
以上面代码内容为例blsp1_uart1: serial@78b0000
<name>:设备名 就是 serial 表示串行通信设备
<unit_address>:设备地址 78b0000 设备的唯一地址
那么前面的blsp1_uart1是什么? 这就是别名,或者叫引用。
当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便。所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。标号引用常常还作为节点的重写方式,用于修改节点属性。
例如:/soc@0/apb-bridge@10000000/apb-bus@0/blsp1@10000000/blsp1_uart1@78b0000
这样一个长度的路径明显会为dts的书写与阅读造成困难,所以有了别名。
- 格式:
- 声明别名:
别名 : 节点名 - 访问 : &
别名
- 声明别名:
节点属性Property
属性一般由 key = value; 键值对构成。
Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。
此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr"、"gpio"、"clock"、"power"、"regulator" 等等。
属性一般有以下几种数据类型:
- 字符串
device_type = "network"; - 整数型
reg = <0x39C00000 0x10000>; - 二进制数据(方括号)
local-mac-address = [000000000000]; - 列表类型(用逗号组合)
pins = "gpio10", "gpio11", "gpio12", "gpio13"; - 混合类型(用逗号组合)
mixed-property = "a string", [01 23 45 67], <0x12345678>;
compatible 兼容性
如果一个节点是设备节点,那么它一定要有compatible(兼容性),因为这将作为驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求。
compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。
而根节点的compatible也是非常重要的,一般在系统启动以后,用于识别对应系统一些东西,并由此进行对应的初始化。
wifi1: wifi1@c000000 {
compatible = "qcom,cnss-qcn6122", "qcom,qcn6122-wifi";
qcom,rproc = <&qcom_q6v5_wcss>;
status = "disabled";
};
如上方节点为例,compatible = "qcom,cnss-qcn6122", "qcom,qcn6122-wifi";
compatible属性格式为:compatible = "<manufacturer>,<model>" [, "<manufacturer>,<model>"]
manufacturer表示厂家,这个节点为qcom,表示为高通。
model表示设备型号,这里有两个分别是cnss-qcn6122、qcn6122-wifi,表示了射频模块qcn6122及其wifi配置。
address 地址属性
在DTS中一般以reg属性表示地址,reg是最常见的用于描述硬件设备寄存器基址的属性。
这个属性对于处理器与外设通信至关重要,因为它的存在是为了告诉内核这些设备的控制和状态寄存器在哪里,从而允许驱动程序正确地读写这些寄存器。
除了reg用来描述地址信息,还有#address-cells、#size-cells这两个属性用来约束子节点reg属性的内容格式。
aips-bus@02000000 { /* AIPS1 */
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
rtc: rtc@68 {
compatible = "stm,mt41t62";
reg = <0x68>;
};
};
};
从上面的代码片段可以分析,aips-bus@02000000 是 i2c@021a0000 的父节点;i2c@021a0000 是 rtc@68 的父节点。
首先来看父节点aips-bus@02000000,包含属性#address-cells = <1>;#size-cells = <1>;
所以其子节点i2c@021a0000 的reg属性被约束为reg = <0x021a0000 0x4000>;即reg = <address size>;
而i2c@021a0000 ,包含属性#address-cells = <1>;
#size-cells = <0>;
所以其子节点rtc: rtc@68的reg属性被约束为reg = <0x68>;即reg = <size>;
#address-cells为n,#size-cells为m则子节点reg属性约束为
reg = <address*n size*m>节点名称中的地址如
aips-bus@02000000,这里的@02000000是一种命名约定,用于直观地表明节点所代表的设备或总线的基地址。而reg中的地址则是正式通知驱动,他要操作的设备的寄存器地址。
interrupts 中断属性
中断产生设备用interrupts属性描述中断源(interrupt specifier),因为不同的硬件描述中断源需要的数据量不同,所以interrupts属性的类型也是。
该属性引入了以下属性
interrupt-controller一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。#interrupt-cells中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符interrupt-parent标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的interrupts一个中断标识符列表,表示每一个中断输出信号。
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;//指定依附的中断控制器是intc
serial@101f0000 { //子节点:串口设备
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
intc: interrupt-controller@10140000 { //intc中断控制器
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;//定义为中断控制器设备
#interrupt-cells = <2>;
};
}
#interrupt-cells:为了明确表示一个中断由几个u32表示,引入了#interrupt-cells属性,#interrupt-cells属性的类型是整数型
在ARM GIC(Generic Interrupt Controller)中,interrupts属性一般有如下表示:
当#interrupt-cells为3时:
- interrupts = <中断类型 中断触发信号 中断触发标志>
- 中断类型: 0 表示SPI中断,1 表示PPI中断
- 中断触发信号: PPI中断【0-15】、SPI中断类型【0-987】
- 中断触发标志:
- bits [ 3 :0 ]
- 1 = 低- 至- 高边沿触发
- 2 = 高- 到- 低边沿触发
- 4 = 活跃的高水平 - 敏感
- 8 = 低电平有效 - 敏感
- bits [ 15 :8 ]
- PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。
- bits [ 3 :0 ]
当#interrupt-cells为2时:
- interrupts = <中断触发信号 中断触发标志>
- 中断触发信号:SGI中断即软件触发中断 【0-15】、PPI中断即私有外设中断【16-31】、SPI中断即公用外设中断【32-1019】
- 中断触发标志:
- bits [ 3 :0 ]
- 1 = 低- 至- 高边沿触发
- 2 = 高- 到- 低边沿触发
- 4 = 活跃的高水平 - 敏感
- 8 = 低电平有效 - 敏感
- bits [ 15 :8 ]
- PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。
- bits [ 3 :0 ]
interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
GPIO属性
GPIO即通用输入输出口。常见与定义CPU的GPIO接口。
该属性一般有以下几种:
gpio-controller空字段,用来表示这个节点是GPIO控制器#gpio-cells用来描述GPIO使用节点的属性
tlmm: pinctrl@1000000 {
compatible = "qcom,ipq5018-pinctrl";
reg = <0x1000000 0x300000>;
interrupts = <0x0 0xd0 0x0>;
gpio-controller;
#gpio-cells = <0x2>; #表示GPIO引脚需要2个单元格的数据
interrupt-controller;
#interrupt-cells = <0x2>;
};
&soc {
gpio_keys {
compatible = "gpio-keys";
/* pinctrl-0 = <&button_pins>;
pinctrl-names = "default"; */
button@1 {
/* label = "wps";
linux,code = <KEY_WPS_BUTTON>; */
gpios = <&tlmm 34 GPIO_ACTIVE_LOW>; #使用控制器tlmm GPIO34 低电平触发
linux,input-type = <1>;
debounce-interval = <60>;
};
};
};
上面的案例可以看到GPIO控制器tlmm的约束值#gpio-cells为2,表示需要两个单元格的数据。后面在button节点也可以看到,当引用控制器后,后面跟着两个值分别为GPIO序列、工作模式。
结语
这一篇文章主要内容就是介绍下,dts的文件结构了解其如何组成dtb,以及具体的编写方式,从根节点出发,到子节点,以及一些特殊节点和其相关的特殊属性的了解。其实这里只列出了一些较通用的节点和属性。实际上在真正的复杂板级结构上制作dts,有时候需要自己编写额外的dts解析源码,这样会引入一些自己定义的key。相对私有化,故不在本篇介绍了。
其实看完第二节基础框架后,去看真实的dts,哪怕有些地方看不懂,猜也能猜个大概了。