第二节 设备树基础语法:节点、属性、值

好,咱们今天来聊聊设备树最核心的三个概念——节点、属性和值。说实话,我刚接触设备树那会儿,看着满屏的{};=,心里直犯嘀咕:这玩意儿跟C语言的结构体有啥区别?后来写多了才发现,它其实比结构体灵活得多。

一、节点(Node)——设备树的骨架

节点是什么?说白了就是设备树里的一个"容器"。每个节点代表一个设备、一个总线、或者一个功能模块。我习惯把节点想象成一个文件夹——它可以包含子节点,也可以有自己的属性。

节点的写法很简单:

/dts-v1/;

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

    cpu@0 {
        compatible = "arm,cortex-a7";
        reg = <0x00 0x100>;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };
};

看到没?根节点是/,里面包了两个子节点:cpu@0memory@80000000。每个节点都用花括号{}括起来,最后用分号;结束。

我的小习惯:节点名后面的@地址不是必须的,但如果有reg属性,我建议加上。这样一眼就能看出这个节点对应哪个地址空间。

二、属性(Property)——节点的"身份证"

属性就是描述节点特征的东西。你可以把它理解成C语言里的变量,但写法更自由。属性值可以是字符串、数字、数组,甚至二进制数据。

常见的属性类型有这些:

属性类型 写法示例 说明
字符串 compatible = "arm,cortex-a7"; 用双引号括起来
32位整数 reg = <0x80000000 0x40000000>; 尖括号里放数字
64位整数 reg = <0x00000000 0x80000000>; 用两个32位拼一个64位
字符串列表 compatible = "ti,am335x-uart", "ns16550"; 逗号分隔多个字符串
二进制数据 mac-address = [00 11 22 33 44 55]; 方括号里放十六进制字节
混合类型 foo = <0x1234>, "bar"; 用逗号分隔不同类型

嗯,这里要注意:compatible属性是最重要的,没有之一。内核就是靠它来匹配驱动程序的。我在项目中遇到过有人把compatible写反了顺序,结果驱动死活加载不上——因为内核是按优先级匹配的,第一个匹配到就停了。

三、值(Value)——属性的具体内容

值就是属性后面的那一串东西。别看它简单,里面有不少门道。

先看整数值:

interrupts = <0 31 4>;  // 三个32位整数
clock-frequency = <24000000>;  // 24MHz

再看字符串值:

model = "TI AM335x BeagleBone Black";
status = "okay";  // 或者 "disabled"

还有phandle引用:

clocks = <&clk_uart>;  // 引用另一个节点
避坑指南:我曾经在调试一个I2C设备时,发现设备树里reg地址写对了,但设备就是枚举不出来。查了半天,原来是#address-cells#size-cells没配对。这两个属性决定了reg里每个地址和长度占几个32位字。你想想看,如果父节点说地址占2个cell,但你只写了1个,那解析出来的地址就全乱了。

四、节点与属性的组织规则

设备树不是随便写的,它有一套约定俗成的规则。我总结了几点:

  • 根节点必须存在——所有设备都挂在/下面
  • 每个设备节点必须有compatible——否则内核不认识它
  • 地址相关的节点要有reg——告诉内核它占哪段地址空间
  • 中断控制器节点要有interrupt-controller——这是个空属性,只起标记作用

举个例子,一个完整的UART节点长这样:

uart0: serial@44e09000 {
    compatible = "ti,am335x-uart", "ns16550";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    clock-frequency = <48000000>;
    status = "okay";
};

这里uart0:是标签(label),方便其他地方引用。比如在clocks属性里就可以写&uart0来引用这个节点。

注意:标签名不能跟节点名冲突。我见过有人把标签写成uart0,节点名也写成uart0,结果编译报错。其实标签和节点名是两套命名空间,但为了可读性,我建议标签用全小写加下划线,节点名用全小写加@地址。

五、实战小技巧

最后分享几个我写设备树时的习惯:

  1. 先搭骨架,再填细节——先把所有节点列出来,再逐个加属性
  2. 多用status = "disabled"——调试时先关掉不用的设备,减少干扰
  3. 善用#include——把通用的部分抽成.dtsi文件,避免重复劳动
  4. 编译前先检查语法——用dtc -I dts -O dtb test.dts编译,有错会直接报

说实话,设备树语法本身不难,难的是理解硬件和内核之间的映射关系。但只要你把节点、属性、值这三个概念吃透了,后面学中断映射、时钟绑定什么的,就会轻松很多。

下一节咱们聊聊设备树的地址映射——reg属性到底怎么用,为什么有时候写一个数,有时候写两个数。到时候我会拿一个实际项目里的例子来讲,保证你一听就懂。