2. 设备树节点与属性

好,咱们今天聊聊设备树里最核心的两个东西——节点和属性。

说白了,设备树就是一棵树。每个硬件设备就是一个节点,节点上挂着的各种信息就是属性。你想想看,这跟咱们文件系统的目录结构是不是很像?根目录下面挂子目录,子目录下面再挂文件。设备树也是这么个玩法。

2.1 节点定义:给硬件设备上户口

每个节点代表一个硬件设备。节点怎么定义?看个例子你就明白了:

/ {
    soc {
        uart0: serial@10000000 {
            compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
            reg = <0x10000000 0x4000>;
            interrupts = <0 58 4>;
            status = "okay";
        };
    };
};

这里 serial@10000000 就是一个节点。@后面的数字是它的地址,用来区分同级别的节点。我刚开始学的时候,老觉得这个@是多余的,后来发现——没有它,多个串口设备就乱套了。

节点名一般用设备类型+地址的形式。比如 i2c@30000000、gpio@2090000。嗯,这里要注意:节点名不能随便起,最好跟内核里已有的命名规范保持一致。

2.2 属性类型:节点的身份证信息

属性就是节点的详细信息。每个属性都有名字和值。常用的属性类型我列一下:

属性名 作用 值的格式
compatible 告诉内核这个设备是啥型号 字符串列表
reg 描述设备的地址空间 地址+长度对
interrupts 描述中断信息 中断号+触发方式
status 设备是否启用 "okay" 或 "disabled"
clocks 时钟源 phandle+时钟索引

2.2.1 compatible:最关键的属性

compatible 是设备树的灵魂。内核通过它来匹配驱动。格式是 "厂商,型号",比如 "fsl,imx6q-uart"。

我个人习惯写两个值:第一个是具体型号,第二个是兼容型号。这样如果内核没有针对这个具体型号的驱动,还能用通用的驱动顶上。

重要:compatible 的匹配顺序是从左到右。内核会先尝试匹配第一个字符串,不行再试第二个。

2.2.2 reg:告诉驱动你的地盘在哪

reg 描述设备的寄存器地址和长度。格式是 <地址 长度>。如果有多个地址段,就写多组。

reg = <0x10000000 0x4000>;  // 起始地址0x10000000,长度0x4000

我在项目中遇到过一个问题:有个同事把 reg 的地址写错了,结果驱动一访问寄存器就死机。查了两天才发现是设备树里地址少写了一个0。嗯,这种坑踩过一次就记住了。

2.2.3 interrupts:中断怎么玩

interrupts 描述设备的中断信息。格式一般是 <中断控制器编号 中断号 触发方式>。

interrupts = <0 58 4>;
// 0: 中断控制器编号
// 58: 中断号
// 4: 触发方式(高电平触发)

触发方式的值:1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发。这个数字我老是记混,后来干脆写了个小纸条贴在显示器上。

2.2.4 status:设备开关

status 就两个值:"okay" 表示启用,"disabled" 表示禁用。默认不写的话,内核会当成 "okay" 处理。

小技巧:调试的时候,可以把暂时不用的设备设成 "disabled",这样内核就不会去初始化它,省得干扰你排查问题。

2.3 节点生命周期:从编译到使用

一个设备树节点从诞生到被驱动使用,大概经历这么几个阶段:

  1. 编写阶段:你在 .dts 文件里写节点和属性
  2. 编译阶段:dtc 工具把 .dts 编译成 .dtb 二进制文件
  3. 加载阶段:bootloader 把 .dtb 传给内核
  4. 解析阶段:内核解析 .dtb,构建出 device_node 树
  5. 匹配阶段:内核遍历 device_node,根据 compatible 匹配驱动
  6. 使用阶段:驱动通过 of_xxx 系列 API 读取属性,初始化硬件

我曾经在调试一个网卡驱动时,发现设备树里明明写了节点,但驱动就是匹配不上。查了半天,原来是 bootloader 加载的 .dtb 文件路径错了,加载了个旧的版本。嗯,这种问题最坑人——代码看着都对,就是跑不起来。

注意:设备树编译成 .dtb 后,里面的字符串会被压缩。所以你在内核里看到的 device_node 的 name 可能跟 .dts 里写的不完全一样。调试时别被这个迷惑了。

2.4 知识体系结构图

下面这张图帮你理清节点和属性的关系:

根节点 / soc uart0: serial@10000000 compatible "fsl,imx6q-uart" "fsl,imx21-uart" 字符串列表 reg <0x10000000 0x4000> 地址 + 长度 32位整数对 interrupts <0 58 4> 控制器 号 触发 中断描述 status "okay" 或 "disabled" 开关状态 节点生命周期 编写 .dts → 编译 .dtb → 加载到内核 → 解析 device_node → 匹配驱动 → 驱动通过 of_xxx API 读取属性 → 初始化硬件

这张图左边是节点和属性的关系,右边是节点从编写到被驱动使用的完整生命周期。你想想看,一个硬件设备从你在 .dts 里写一行代码,到最终被驱动初始化,中间要经过这么多步骤。任何一个环节出问题,设备都起不来。

调试建议:如果设备没起来,先检查 /sys/firmware/devicetree/base/ 目录下有没有你的节点。没有的话,说明 .dtb 没加载对。有的话,再看属性值对不对。这个目录是内核解析完设备树后导出的,相当于设备树的"运行快照"。

好了,节点和属性的基础知识就这些。记住一句话:节点是骨架,属性是血肉。骨架搭好了,血肉填对了,驱动才能跑起来。

可可精品资料库