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

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

[一、DTS 文件的整体结构](#一、DTS 文件的整体结构)

[二、.dtsi 头文件](#二、.dtsi 头文件)

三、设备节点

3.1、设备节点整体结构

3.2、节点命名规则

3.3节点属性

四、标准属性

[4.1. compatible 属性](#4.1. compatible 属性)

4.1.1、基本格式

[4.2、model 属性](#4.2、model 属性)

[4.3、status 属性](#4.3、status 属性)

[4.4、#address-cells 和#size-cells 属性](#address-cells 和#size-cells 属性)

[4.5、reg 属性](#4.5、reg 属性)

[4.6、ranges 属性](#4.6、ranges 属性)

总结



前言

上一期博客我们初步介绍了一下设备树的概念,这一期博客我们来介绍一下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 子总线地址空间的起始物理地址 由父节点 #address-cells 定义
parent-bus-address 父总线地址空间的起始物理地址(子地址要映射到的目标地址) 由父节点 #address-cells 定义
length 本次映射的地址空间长度(子地址空间的有效范围) 由父节点 #size-cells 定义

总结

本期博客主要介绍了DTS的语法情况

相关推荐
济6172 小时前
ARM Linux 驱动开发篇---Linux 设备树简介-- Ubuntu20.04
linux·arm开发·嵌入式linux驱动开发
CHENG-JustDoIt1 天前
嵌入式开发 | ARM Cortex-M 系列中M3、M4、M23 和 M33四款处理器的深度对比分析
arm开发·单片机·嵌入式硬件·arm
invicinble1 天前
对于linux形成整体性的认识
linux·运维·arm开发
济6171 天前
ARM Linux 驱动开发篇---新版led驱动实验程序编写-- Ubuntu20.04
嵌入式·嵌入式linux驱动开发
『往事』&白驹过隙;1 天前
在ARM开发中 volatile与const关键字的关键用途
c语言·arm开发·mcu·物联网·学习·iot
『往事』&白驹过隙;2 天前
浅谈PC开发中的设计模式搬迁到ARM开发
linux·c语言·arm开发·设计模式·iot
济6173 天前
ARM Linux 驱动开发篇--嵌入式 Linux LED 驱动开发实验(2)--Linux 下 LED 灯驱动开发代码编写-- Ubuntu20.04
linux·arm开发·驱动开发
还在忙碌的吴小二5 天前
OEC设备刷写飞牛NAS ARM版(RK3566)超详细教程
arm开发
还在忙碌的吴小二5 天前
飞牛NAS ARM版升级全指南+性能深度解析|低成本盘活闲置设备,功耗与体验双突破
arm开发·人工智能