2. 设备树基础语法(一):节点与属性的概念,根节点、子节点、标签的写法

好,咱们正式开始啃设备树的语法了。

说实话,我第一次接触设备树的时候,看着那一堆花括号和分号,心里直犯嘀咕——这不就是个配置文件吗?后来被硬件折腾了几回,才明白这东西远不止配置文件那么简单。它其实是操作系统和硬件之间的「翻译官」。

2.1 节点(Node)是什么?

节点,说白了就是设备树里的「文件夹」。每个节点代表一个硬件设备、一个总线、或者一个功能模块。

我习惯把节点想象成硬件电路板上的一个芯片。芯片有引脚,节点有属性。芯片有名字,节点也有名字。嗯,这个类比很直观。

节点的基本语法长这样:

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};

注意看:节点名后面跟花括号,花括号里放属性和子节点。最后用分号结尾。这个分号特别容易漏,我当年就因为这个,调试了整整一个下午。

⚠️ 我曾经踩过的坑: 节点结尾的分号不能省!还有,花括号里的每一行属性声明,末尾也要加分号。少一个分号,整个设备树编译就报错。

2.2 属性(Property)是什么?

属性就是节点的「参数」。它告诉内核:这个设备用哪个中断号、内存地址是多少、时钟频率多快。

属性有三种常见写法:

属性类型 示例 说明
整型(32位) reg = <0x10000000 0x1000>; 尖括号包起来,多个值用空格隔开
字符串 compatible = "ti,am335x-gpio"; 双引号包起来,逗号分隔厂商和设备名
布尔值 status = "disabled"; 用字符串表示,常见的有 "okay" 和 "disabled"

我在项目中遇到过最常用的属性就是 compatible。内核就是靠这个字符串找到对应的驱动。你想想看,如果这个字符串写错了,驱动就加载不上,设备就罢工了。

2.3 根节点(Root Node)

每个设备树文件有且只有一个根节点。它用斜杠 / 表示,是整个设备树的起点。

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
    
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  // 256MB RAM
    };
    
    cpus {
        cpu@0 {
            compatible = "arm,cortex-a8";
        };
    };
};

根节点里一般放什么呢?板子型号、内存信息、CPU信息、还有各种总线。说白了,整个板子的「骨架」都在根节点里。

💡 我个人习惯: 根节点里只放全局性的东西。比如内存大小、CPU配置、板子型号。具体的外设,比如I2C、SPI、UART,我习惯放在对应的总线节点下面,这样结构更清晰。

2.4 子节点(Child Node)

子节点就是节点里面再套节点。这跟文件系统的目录结构一模一样。

/ {
    soc {
        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart", "ti,omap3-uart";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
        };
        
        i2c0: i2c@44e0b000 {
            compatible = "ti,omap4-i2c";
            reg = <0x44e0b000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;
            
            eeprom@50 {
                compatible = "atmel,24c32";
                reg = <0x50>;
            };
        };
    };
};

你看,soc 是根节点的子节点,uart0i2c0soc 的子节点,eeprom 又是 i2c0 的子节点。这种层级关系,直接反映了硬件上的连接关系。

2.5 标签(Label)的写法

标签是个好东西。它相当于给节点起了一个「别名」,方便在其他地方引用。

标签的写法很简单:在节点名前加一个名字,后面跟冒号。

uart0: serial@44e09000 {
    // 节点内容
};

这里的 uart0 就是标签。有了标签,我可以在其他地方这样引用:

&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
};

注意那个 & 符号,它表示「引用标签为 uart0 的节点」。这种写法在设备树中特别常见,尤其是当你需要修改某个节点的时候,不用把整个节点重新写一遍。

🔑 核心要点: 标签的作用就是「解耦」。你可以在一个 .dtsi 文件里定义节点,然后在另一个 .dts 文件里通过标签来修改它。这样代码复用性就大大提高了。

2.6 知识体系结构图

下面这张图,是我自己画的知识结构。你看一眼,就能把今天讲的内容串起来。

根节点 / 子节点 soc 子节点 memory 标签: uart0 serial@44e09000 标签: i2c0 i2c@44e0b000 eeprom@50 属性示例 compatible = "ti,am3352-uart"; reg = <0x44e09000 0x2000>; interrupts = <72>; status = "okay"; 根节点 子节点 带标签的节点 叶子节点 属性

2.7 避坑指南

最后,分享几个我实际项目中遇到的坑:

  • 节点名不要用特殊字符:只能用字母、数字、连字符和下划线。我见过有人用点号,编译直接报错。
  • 标签不能重复:整个设备树里,标签必须是唯一的。重复了,编译会报「duplicate label」错误。
  • 根节点没有标签:根节点不能用标签引用。想修改根节点?直接在根节点花括号里改。
  • 属性值类型要匹配:整型用尖括号,字符串用双引号。混用了,内核解析会出问题。

嗯,今天就讲这么多。节点和属性是设备树的基石,搞懂了这两个概念,后面的内容就水到渠成了。

专注资料整理