设备树语法入门:节点、属性、标签、包含文件

设备树(Device Tree)说白了就是一块板卡的“硬件说明书”。你想想看,Linux 内核要驱动一块板子,总得知道 CPU 是什么、内存多大、I2C 控制器挂在哪、GPIO 怎么映射吧?这些信息以前都硬编码在代码里,换一块板子就得重新编译内核。设备树就是来解决这个问题的——把硬件描述从内核代码里抽出来,变成一份独立的数据文件。

我个人习惯把设备树比作“乐高说明书”。它不关心你怎么拼,只告诉你有哪些零件、零件之间怎么连接。内核拿到这份说明书,就知道怎么初始化硬件了。好,咱们直接进入正题。

2.1 节点(Node)——设备树的基本单元

节点是设备树里最核心的概念。每个节点代表一个硬件设备或总线,比如 CPU、内存、I2C 控制器、UART 端口。节点用一对花括号 { } 包裹,里面可以嵌套子节点。

/ {
    cpu {
        compatible = "arm,cortex-a7";
    };
    memory {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;
    };
};

根节点 / 是整棵树的起点,所有设备都挂在它下面。我在项目中遇到过有人把节点名写成了 CPU(大写),结果内核死活认不出来——设备树节点名是大小写敏感的,这一点千万记住。

命名规范:节点名一般用全小写,单词之间用短横线连接。比如 i2c-controller、serial-port。不要用下划线,那是老式设备树的写法,现在不推荐了。

2.2 属性(Property)——描述节点的“身份证”

属性就是节点的特征描述。每个属性都有一个名字和一个值。常见的属性类型有字符串、整数数组、二进制数据等。

属性名 含义 示例
compatible 设备兼容性标识,驱动匹配的关键 "vendor,device"
reg 寄存器地址和大小 <0x10000000 0x1000>
interrupts 中断号 <0 31 4>
status 设备状态(okay/disabled) "okay"

这里我要特别强调 compatible 属性。它就像设备的“身份证号”,驱动通过它来匹配设备。格式一般是 "厂商,设备型号",比如 "fsl,imx6ul-uart"。我曾经调试过一个网卡驱动,怎么都加载不上,最后发现是 compatible 字符串里多了一个空格——内核匹配是精确字符串比较,多一个字符都不行。

避坑指南:属性值里的尖括号 <> 表示 32 位整数数组,方括号 [] 表示字节数组,双引号 "" 表示字符串。别搞混了,否则编译出来的 dtb 内核根本解析不了。

2.3 标签(Label)——给节点起个“外号”

标签就是节点的别名。你想想看,如果一个节点嵌套得很深,比如 /soc/i2c@021a0000/rtc@68,每次引用它都要写这么一长串,多麻烦。标签就是用来解决这个问题的。

&i2c1 {
    status = "okay";
    clock-frequency = <100000>;
};

标签用 & 符号引用。我习惯在写设备树时,给所有重要的控制器都加上标签,比如 uart1、i2c2、spi0。这样后续添加子设备时,直接通过标签引用,代码清晰很多。

注意:标签不能重复,也不能以数字开头。我曾经见过有人把标签写成 1st-uart,结果编译直接报错。标签的命名规则和 C 语言变量名一样:字母或下划线开头,后面跟字母、数字、下划线。

2.4 包含文件(Include)——模块化你的设备树

一个复杂的板卡可能有几十个设备,全写在一个文件里会非常臃肿。设备树支持 #include 预处理指令,可以把公共部分拆成单独的文件。

#include "imx6ul.dtsi"
#include "imx6ul-pinfunc.h"

/ {
    model = "My Custom Board";
    compatible = "my,imx6ul-board";
};

.dtsi 文件是设备树的“头文件”,通常存放 SoC 的公共定义。比如某个芯片厂商会提供 imx6ul.dtsi,里面定义了所有内置外设的节点框架。板卡厂商只需要在自己的 .dts 文件里包含它,然后覆盖或补充自己的配置。

嗯,这里要注意:#include 和 C 语言的 #include 是同一个预处理机制。所以你也可以用 #define 定义宏,在设备树里做条件编译。不过我个人不太推荐这么干——设备树应该保持简洁,宏用多了反而难调试。

我的习惯:把 SoC 级定义放 .dtsi,板卡级配置放 .dts,外设模块单独拆成 .dtsi。比如 lcd-panel.dtsi、wifi-module.dtsi,这样换屏幕或换 Wi-Fi 模块时,只需要改一行包含路径。

2.5 知识体系总览

说了这么多,咱们用一张图把设备树语法的核心关系串起来。你一看就明白了。

设备树语法核心结构 根节点 / 子节点 cpu 子节点 memory 子节点 i2c@021a0000 属性 compatible 属性 reg 属性 status 标签 &i2c1 → 引用节点 #include "imx6ul.dtsi" 核心关系 节点包含属性 标签引用节点 包含文件组织模块

从这张图可以看得很清楚:根节点下面挂子节点,每个节点带若干属性;标签是节点的快捷引用方式;包含文件则把公共定义抽离出来。这四个要素组合在一起,就构成了设备树的完整语法体系。

2.6 实战要点总结

  • 节点:每个设备一个节点,嵌套关系反映总线拓扑。根节点 / 是唯一入口。
  • 属性:compatible 是驱动匹配的关键,reg 描述地址空间,status 控制设备启用/禁用。
  • 标签:用 & 引用,避免长路径。命名规则同 C 变量。
  • 包含文件:.dtsi 放公共定义,.dts 放板级配置。模块化是王道。
常见错误:忘记在属性值末尾加分号、标签名用了数字开头、compatible 字符串多空格、#include 路径写错。这些错误编译时都会报错,但错误信息有时候不太直观。我的建议是:写一行编译一行,别攒了一大堆再调试。

好了,设备树语法入门就讲到这里。这四个概念你掌握了,后面写设备树就跟搭积木一样简单。记住:节点是骨架,属性是血肉,标签是快捷方式,包含文件是模块化工具。把这四样用好,任何板卡的设备树你都能驾驭。