2. 设备树基础语法:节点、属性、值的定义与规范

设备树,说白了就是一套描述硬件信息的「说明书」。内核通过它来知道你的板子上接了哪些外设、内存多大、中断怎么连。我刚开始接触设备树时,觉得它不过是个配置文件,后来踩了几个坑才明白——语法细节没搞对,内核可能连启动都起不来。

这一章,我们就来拆解设备树最核心的三个要素:节点属性。搞懂了它们,你就能自己写一个简单的设备树了。

2.1 节点:设备树的骨架

节点是设备树的基本组成单元。每个节点代表一个硬件设备或总线。你可以把节点想象成文件系统里的目录,一层套一层。

节点的语法很简单:

/dts-v1/;

/ {
    node1 {
        child-node {
            ...
        };
    };
    node2 {
        ...
    };
};

根节点用 / 表示,所有设备都挂在它下面。每个节点用花括号 {} 包裹,最后用分号 ; 结束。

节点命名规范:

  • 节点名由字母、数字、逗号、点、短横线、下划线组成
  • 推荐格式:name@address,比如 uart@fe001000
  • @address 部分通常写设备在总线上的基地址

我在项目中遇到过有人把节点名写成 UART@fe001000(大写),结果内核解析时报错。记住,节点名是区分大小写的,统一用小写最保险。

2.2 属性:描述设备的特征

属性就是节点的「参数」。每个属性都有一个名字和一个值。属性写在节点内部,格式是:

property-name = <value>;

常见的属性类型有:

属性类型 示例 说明
字符串 compatible = "vendor,device"; 用于匹配驱动,格式固定
32位整数 reg = <0xfe001000 0x1000>; 地址和长度,用尖括号包裹
64位整数 reg = <0x00000000 0x80000000>; 高32位在前,低32位在后
布尔值 status = "disabled"; 用字符串表示,常见值:okay/disabled
字节数组 local-mac-address = [00 11 22 33 44 55]; 用方括号包裹,每个字节用空格隔开

我的习惯:compatible 属性时,一定要遵循「厂商,设备」的格式。比如 compatible = "fsl,imx6q-uart", "ns16550";。这样内核驱动匹配时,会先找精确匹配,找不到再回退到通用驱动。

2.3 值的表示方法

设备树里的值有几种写法,每种都有它的使用场景。我刚开始时经常搞混,后来总结了一个规律:

  • 尖括号 <>:放整数,一个或多个。比如 <0x1000 0x2000>
  • 方括号 []:放字节数据。比如 [01 02 03]
  • 双引号 "":放字符串。比如 "okay"
  • & 符号:引用其他节点。比如 &uart1

举个例子,一个完整的 UART 节点可能是这样的:

uart1: serial@fe001000 {
    compatible = "fsl,imx6q-uart", "ns16550";
    reg = <0xfe001000 0x1000>;
    interrupts = <0 26 4>;
    status = "disabled";
};

这里 uart1: 是节点的标签(label),方便其他地方引用。比如在根节点里可以写 &uart1 { status = "okay"; }; 来使能它。

我曾经踩过的坑:reg 属性时,地址和长度的顺序搞反了。比如 reg = <0x1000 0xfe001000>;,内核会认为基地址是 0x1000,长度是 0xfe001000,这显然不对。记住:先地址,后长度

2.4 特殊属性:标准化的约定

设备树规范定义了一些特殊属性,它们有固定的含义。我挑几个最常用的说说:

  • #address-cells#size-cells:定义子节点中 reg 属性的地址和长度用几个 32 位整数表示。比如 #address-cells = <1>; #size-cells = <1>; 表示地址和长度各占一个 32 位整数。
  • model:描述设备型号,比如 model = "Freescale i.MX6 Quad SABRE Board";
  • phandle:节点的唯一标识符,通常由编译器自动生成,用于节点间的引用。

你想想看,如果没有 #address-cells,内核怎么知道 reg 里的数字哪个是地址哪个是长度?这就是规范的重要性。

2.5 知识体系图

下面这张图总结了设备树语法的核心结构,我建议你把它存下来,写设备树时对照着看:

根节点 / 子节点 node1 子节点 node2 属性:描述设备特征 compatible = "vendor,device"; reg = <0xfe001000 0x1000>; interrupts = <0 26 4>; 值的表示方法 尖括号 <> :整数,如 <0x1000> 方括号 [] :字节,如 [01 02 03] 双引号 "" :字符串,如 "okay" & 符号 :引用节点,如 &uart1 设备树语法核心:节点 + 属性 + 值

2.6 实战:写一个简单的设备树片段

光说不练假把式。我们写一个实际点的例子——描述一个 I2C 控制器和它下面挂载的温度传感器:

/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;

    i2c@fe050000 {
        compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
        reg = <0xfe050000 0x4000>;
        interrupts = <0 33 4>;
        #address-cells = <1>;
        #size-cells = <0>;

        temp-sensor@48 {
            compatible = "ti,tmp102";
            reg = <0x48>;
        };
    };
};

注意看,I2C 控制器节点里又定义了 #address-cells#size-cells,这是因为 I2C 子节点的地址只有 7 位,所以 #size-cells = <0>。嗯,这里要特别注意——每个总线节点都要重新定义这些属性,因为它们的作用域是局部的。

我的建议:写设备树时,先画一张硬件拓扑图,把每个设备的地址、中断号、依赖关系列清楚。然后按「根节点 → 总线节点 → 设备节点」的顺序逐层写。这样不容易漏掉东西。

2.7 常见错误与避坑

最后,我总结几个新手最容易犯的错误:

  • 忘记分号:每个属性声明和节点结束都要有 ;。少一个分号,整个设备树解析失败。
  • 花括号不匹配:节点用 {} 包裹,一定要成对出现。我建议用带缩进的编辑器写,一眼就能看出括号是否对齐。
  • 地址长度单位搞错reg 里的地址和长度单位是 32 位整数,不是字节。比如 reg = <0x1000 0x2>; 表示地址 0x1000,长度是 2 个 32 位整数(8 字节),而不是 2 字节。
  • 引用节点时标签写错:用 &uart1 引用节点时,确保标签 uart1: 已经定义过。

我曾经在一个项目里,因为少写了一个分号,导致内核启动时直接 panic。查了整整半天才找到问题。从那以后,我写设备树都会用 dtc -I dts -O dtb test.dts 先编译一遍,检查语法错误。

好了,这一章的内容就到这里。设备树的语法其实不复杂,但细节很多。多写几遍,自然就熟练了。


公众号:蓝海资料掘金营,微信 deep3321