3. 设备树语法入门:根节点、子节点、属性的基本写法

好,咱们今天正式进入设备树的语法世界。说实话,我刚接触设备树那会儿,看着一堆花括号和分号,第一反应是——这玩意儿到底是代码还是配置文件?后来做多了才发现,它其实就是一个描述硬件信息的树形结构。你把它想象成一棵倒着长的树,根在最上面,树枝往下分,每个树枝上挂着各种属性标签。

我个人习惯把设备树比作一张硬件清单。内核拿到这张清单,就知道板子上接了哪些外设、内存多大、中断怎么连。嗯,咱们今天就先把这张清单的格式搞清楚。

3.1 根节点:整棵树的起点

每个设备树文件有且只有一个根节点。它用斜杠 / 表示,是整个设备树的入口。我见过不少新手在根节点上犯低级错误,比如少写分号,或者把根节点名字写错。

根节点的标准写法是这样的:

/ {
    // 这里是根节点的内容
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am335x";
};

注意看,根节点用花括号包裹,最后一定要加分号。这里面的 modelcompatible 就是属性。model 描述板子名称,compatible 告诉内核这个板子兼容哪些平台。我曾经在调试一块新板子时,就是因为 compatible 写错了,内核死活不认我的板子,折腾了大半天。

警告:根节点不能有 reg 属性。它代表整个系统,不需要地址。如果你在根节点里写 reg,编译会直接报错。

3.2 子节点:描述具体设备

子节点就是挂在根节点下面的设备。每个子节点描述一个具体的硬件模块,比如串口、I2C控制器、GPIO控制器等。子节点的命名格式是 节点名@地址,其中地址部分是可选的,但如果有地址,一定要写。

来看一个实际例子:

/ {
    model = "My Board";
    
    cpu@0 {
        compatible = "arm,cortex-a9";
        reg = <0>;
    };
    
    serial@44e09000 {
        compatible = "ti,omap3-uart";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
    };
};

这里 cpu@0serial@44e09000 都是子节点。@后面的数字是设备的起始地址或编号。你想想看,如果板子上有两个串口,一个地址是 0x44e09000,另一个是 0x44e0b000,通过地址就能区分开。

子节点里还可以嵌套子节点,形成多层结构。比如 I2C 控制器下面挂多个 I2C 设备:

i2c@44e0b000 {
    compatible = "ti,omap4-i2c";
    reg = <0x44e0b000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
    
    rtc@68 {
        compatible = "dallas,ds1307";
        reg = <0x68>;
    };
};

这种嵌套结构在实际项目中非常常见。我记得有一次调试一个音频编解码芯片,它在 I2C 总线上,地址是 0x1a。结果我忘了在父节点里设置 #address-cells#size-cells,导致子节点地址解析失败。嗯,这两个属性后面会详细讲,你先记住它们很重要。

3.3 属性:描述设备特征

属性就是键值对,用来描述设备的各种特征。常见的属性类型有字符串、整数、数组、布尔值等。我整理了一个表格,方便你对照:

属性类型 写法示例 说明
字符串 compatible = "ti,omap3-uart"; 用双引号包裹
32位整数 reg = <0x44e09000>; 用尖括号包裹
整数数组 reg = <0x44e09000 0x2000>; 多个值用空格隔开
布尔值 status = "okay"; 用字符串表示,okay 或 disabled
字节数组 local-mac-address = [00 11 22 33 44 55]; 用方括号包裹,十六进制

这里我要特别强调一下 compatible 属性。它是设备树里最重要的属性之一,说白了就是告诉内核:这个设备用什么驱动来匹配。格式是 "厂商,型号",比如 "ti,omap3-uart"。内核驱动里会有一个 of_match_table,里面列了一串 compatible 字符串,设备树里的值和驱动里的值对上,驱动就加载了。

我曾经踩过一个坑:compatible 字符串里厂商名和型号之间用了下划线而不是逗号,结果驱动死活匹配不上。后来查了半天才发现是这里的问题。所以写 compatible 的时候,一定要严格按照 "厂商,型号" 的格式。

3.4 标准属性速览

除了上面提到的,还有几个标准属性你必须要掌握:

  • reg:描述设备的地址空间,格式是 <地址 长度>。如果有多个地址段,可以写多组。
  • interrupts:描述中断号,具体格式取决于中断控制器。
  • status:设备状态,常用 "okay""disabled"。调试时很有用。
  • label:给节点起个别名,方便在其他地方引用。
  • phandle:节点的唯一标识符,通常由编译器自动生成,你不需要手动写。

来看一个综合例子,把这些属性串起来:

/ {
    model = "My Custom Board";
    compatible = "mycompany,myboard";
    
    gpio@4804c000 {
        compatible = "ti,omap4-gpio";
        reg = <0x4804c000 0x200>;
        interrupts = <29>;
        status = "okay";
        label = "GPIO1";
        
        #gpio-cells = <2>;
        gpio-controller;
    };
};

注意看 gpio-controller 这个属性,它没有值,只有名字。这种叫空属性,表示这个节点是一个 GPIO 控制器。类似的还有 interrupt-controller。说白了,就是给节点打一个标签,告诉内核这个节点有什么特殊功能。

小技巧:调试设备树时,可以用 dtc -I dtb -O dts 命令把编译好的 dtb 反编译成 dts,看看实际解析出来的内容是什么。我经常用这招来检查属性有没有写对。

3.5 本章知识结构图

下面这张图帮你理清设备树语法的核心逻辑。从根节点出发,子节点层层嵌套,属性附着在节点上描述硬件特征。你把这个结构记在脑子里,写设备树就有方向了。

根节点 / model = "My Board" compatible = "my,board" cpu@0 compatible = "arm,cortex-a9" reg = <0> serial@44e09000 compatible = "ti,uart" reg = <0x44e09000 0x2000> interrupts = <72> i2c@44e0b000 eeprom@50 rtc@68 图例 根节点 子节点 属性 属性关联

这张图展示了设备树的核心结构:根节点在最顶层,下面挂载子节点,每个节点可以有自己的属性。子节点还可以继续嵌套子节点,形成树形结构。你写设备树的时候,就按照这个思路来:先确定根节点,再添加子节点,最后给每个节点填上必要的属性。

核心要点:设备树语法其实不复杂,记住三件事就行——根节点是 /,子节点描述设备,属性描述特征。写的时候注意分号和花括号的配对,compatible 字符串的格式,以及 reg 属性的地址长度。把这几点掌握好,大部分设备树你都能看懂、能修改。

好了,这一章的内容就到这里。设备树的基本语法其实不复杂,关键是多写多练。我建议你找一块开发板的设备树文件,打开看看,对照着本章的内容逐行分析。看得多了,自然就熟了。

专注资料整理