ARM Linux 驱动开发篇---Linux 设备树(DTS)语法-- Ubuntu20.04

🎬 渡水无言:个人主页渡水无言
❄专栏传送门: 《linux专栏》 《嵌入式linux驱动开发》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录
4.4、#address-cells 和#size-cells 属性
前言
上一期博客我们初步介绍了一下设备树的概念,这一期博客我们来介绍一下DTS语法。
一、DTS 文件的整体结构
设备树源文件(.dts)采用一种类 C 语言的语法格式,以树形结构组织硬件信息。下面我们逐步解析其核心语法元素。
一个完整的 DTS 文件遵循树形层级结构,核心分为三部分,具体结构如下图所示:

代码如下:
// 1. 头文件/include引用(可选) #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/arm-gic.h> #include <dt-bindings/input/input.h> #include "imx6ull.dtsi" // 2. /dts-v1/ 版本声明(必须) /dts-v1/; // 设备树根节点,所有硬件描述都在这个节点内 / { // -------------------------- // 子节点:aliases(别名节点) // 作用:为其他节点提供简短别名,方便引用 // -------------------------- aliases { // 定义 can0 为 flexcan1 节点的别名,后续可通过 &can0 引用该CAN控制器 can0 = &flexcan1; }; // -------------------------- // 子节点:cpus(CPU集合节点) // 作用:管理系统中所有CPU核心的描述 // -------------------------- cpus { // 子节点reg属性中,地址部分占用1个32位整数 #address-cells = <1>; // 子节点reg属性中,大小部分占用0个32位整数(CPU节点不需要描述地址范围) #size-cells = <0>; // -------------------------- // 孙节点:cpu@0(CPU0核心节点) // 标签为 cpu0,方便后续引用 // -------------------------- cpu0: cpu@0 { // 兼容属性,用于内核匹配对应的CPU驱动 compatible = "arm,cortex-a7"; // 设备类型,明确该节点代表CPU设备 device_type = "cpu"; // 寄存器地址,对应CPU的ID(这里为0号CPU) reg = <0>; }; }; // -------------------------- // 子节点:interrupt-controller@00a01000(中断控制器节点) // 标签为 intc,方便后续引用 // 物理基地址为 0x00a01000 // -------------------------- intc: interrupt-controller@00a01000 { // 兼容属性,用于内核匹配对应的GIC中断控制器驱动 compatible = "arm,cortex-a7-gic"; // 引用该中断控制器时,每个中断描述占用3个32位整数 #interrupt-cells = <3>; // 标识该节点是一个中断控制器(布尔属性,存在即为真) interrupt-controller; // 寄存器地址范围,描述GIC的两个寄存器块: // 第一个块:基地址 0x00a01000,长度 0x1000 // 第二个块:基地址 0x00a02000,长度 0x100 reg = <0x00a01000 0x1000>, <0x00a02000 0x100>; }; };二、.dtsi 头文件
和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在 imx6ull-alientek-emmc.dts 中有如下所示内容:
include <dt-bindings/input/input.h> include "imx6ull.dtsi"可以看到:
使用“#include”来引用“input.h”这个.h 头文件。
使用“#include”来引用“imx6ull.dtsi”这个.dtsi 头文件。
在.dts 设备树文件中,可以通过 “#include”来引用.h、.dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息的。
三、设备节点
3.1、设备节点整体结构
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从 imx6ull.dtsi 文件中缩减出来的设备树文件内容:
// 设备树根节点,所有硬件描述都在这个节点内 / { // -------------------------- // 子节点:aliases(别名节点) // 作用:为其他节点提供简短别名,方便引用 // -------------------------- aliases { // 定义 can0 为 flexcan1 节点的别名,后续可通过 &can0 引用该CAN控制器 can0 = &flexcan1; }; // -------------------------- // 子节点:cpus(CPU集合节点) // 作用:管理系统中所有CPU核心的描述 // -------------------------- cpus { // 子节点reg属性中,地址部分占用1个32位整数 #address-cells = <1>; // 子节点reg属性中,大小部分占用0个32位整数(CPU节点不需要描述地址范围) #size-cells = <0>; // -------------------------- // 孙节点:cpu@0(CPU0核心节点) // 标签为 cpu0,方便后续引用 // -------------------------- cpu0: cpu@0 { // 兼容属性,用于内核匹配对应的CPU驱动 compatible = "arm,cortex-a7"; // 设备类型,明确该节点代表CPU设备 device_type = "cpu"; // 寄存器地址,对应CPU的ID(这里为0号CPU) reg = <0>; }; }; // -------------------------- // 子节点:interrupt-controller@00a01000(中断控制器节点) // 标签为 intc,方便后续引用 // 物理基地址为 0x00a01000 // -------------------------- intc: interrupt-controller@00a01000 { // 兼容属性,用于内核匹配对应的GIC中断控制器驱动 compatible = "arm,cortex-a7-gic"; // 引用该中断控制器时,每个中断描述占用3个32位整数 #interrupt-cells = <3>; // 标识该节点是一个中断控制器(布尔属性,存在即为真) interrupt-controller; // 寄存器地址范围,描述GIC的两个寄存器块: // 第一个块:基地址 0x00a01000,长度 0x1000 // 第二个块:基地址 0x00a02000,长度 0x100 reg = <0x00a01000 0x1000>, <0x00a02000 0x100>; }; };“/”是根节点,每个设备树文件只有一个根节点。
注意:imx6ull.dtsi 和 imx6ull-alientek-emmc.dts 这两个文件都有一个“/”根节点,这样不会出错吗?不会的,因为这两个“/”根节点的内容会合并成一个根节点。
节点层级说明
根节点:/ —— 整个设备树的顶层容器。
一级子节点:
aliases:别名定义节点
cpus:CPU 集合节点
intc: interrupt-controller@00a01000:中断控制器节点
二级子节点(孙节点):
cpu0: cpu@0:CPU0 核心节点,是cpus的子节点。
3.2、节点命名规则
aliases、cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:
node-name@unit-address其中“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能。
“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话。“unit-address”可以不要。
但是我们在3.1中的代码我们看到的节点命名却如下所示:
cpu0:cpu@0上述命令并不是“node-name@unit-address”这样的格式,而是用“:”隔开成了两部分。“:” 前面的是节点标签(label)。“:”后面的才是节点名字。格式如下所示:
label: node-name@unit-address引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。
3.3节点属性
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:
①、字符串
compatible = "arm,cortex-a7";上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。
②、32 位无符号整数
reg = <0>;上述代码设置 reg 属性的值为 0,reg 的值也可以设置为一组值,比如:
reg = <0 0x123456 100>;③、字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。
四、标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用这些标准属性,本节我们就来学习一下几个常用的标准属性。
4.1. compatible 属性
compatible 属性是设备树中最核心、最重要的标准属性,没有之一!它也被称为 “兼容性属性”,核心作用是将硬件设备节点与 Linux 内核中的驱动程序完成绑定匹配。
4.1.1、基本格式
compatible 属性的值是一个字符串列表,每个字符串都遵循统一的命名规范:
compatible = "manufacturer,model";manufacturer:设备厂商标识(如 fsl 代表飞思卡尔、rockchip 代表瑞芯微);
model:设备型号 / 对应的驱动模块名称,内核会通过该字段匹配具体驱动。
例子:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";该属性包含两个兼容值,内核会按顺序匹配:
优先使用第一个值 fsl,imx6ul-evk-wm8960 在内核中查找匹配的驱动;
若未找到,则使用第二个值 fsl,imx-audio-wm8960 继续查找。
这种 “多值列表” 的设计,既可以适配专属驱动,也能兼容通用驱动,是设备树兼容性设计的核心思路。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
4.2、model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:
model = "wm8960-audio";4.3、status 属性
status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,可选的状态如表所示:
| 值 | 描述 |
|---|---|
| "okay" | 表明设备是可操作的。 |
| "disabled" | 表明设备当前是不可操作的,但在未来可以变为可操作的(如热插拔设备插入后)。具体含义还要看设备的绑定文档。 |
| "fail" | 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。 |
| "fail-sss" | 含义和 "fail" 相同,后面的 sss 部分是检测到的错误内容。 |
4.4、#address-cells 和#size-cells 属性
#address-cells 和 #size-cells 是设备树中用于描述子节点地址信息的关键属性,它们的值均为无符号 32 位整数。这两个属性可以作用于任何拥有子节点的设备节点,用于规范其子节点 reg 属性的格式。
核心作用
#address-cells:决定子节点 reg 属性中,起始地址(address) 部分所占用的 32 位字长。
#size-cells:决定子节点 reg 属性中,地址长度(length) 部分所占用的 32 位字长。
#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性
都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg 属性的格式一为:
reg = <address1 length1 address2 length2 address3 length3……>每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长
度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用
的字长,比如:
aips3: aips-bus@02200000 { compatible = "fsl,aips-bus", "simple-bus"; #address-cells = <1>; // 子节点地址占1个32位字 #size-cells = <1>; // 子节点长度占1个32位字 dcp: dcp@02280000 { compatible = "fsl,imx6sl-dcp"; reg = <0x02280000 0x4000>; // 地址: 0x02280000, 长度: 0x4000 }; };子节点 dcp@02280000 的 reg = <0x02280000 0x4000> 严格遵循了这一规则,清晰地定义了该设备的寄存器基地址为 0x02280000,地址空间大小为 0x4000 字节。
4.5、reg 属性
reg 属性前面已经提到过了,reg 属性的值一般是(address,length)对。reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
4.6、ranges 属性
ranges 是设备树中用于描述子总线与父总线地址映射关系的标准属性,核心作用是实现地址空间的转换或映射。该属性的值有两种形式:
空值(ranges;):表示子总线地址空间与父总线地址空间完全一致,无需做地址转换;
数字矩阵:按 (child-bus-address, parent-bus-address, length) 格式编写,每一组数据对应一段地址映射关系。
ranges 属性的每一组映射关系包含三个核心参数,其字长由父节点的 #address-cells 和 #size-cells 决定:
参数 | 含义 | 字长规则 |
|---|---|---|
child-bus-address | 子总线地址空间的起始物理地址 | 由父节点 |
parent-bus-address | 父总线地址空间的起始物理地址(子地址要映射到的目标地址) | 由父节点 |
length | 本次映射的地址空间长度(子地址空间的有效范围) | 由父节点 |
总结
本期博客主要介绍了DTS的语法情况