4. 设备树节点与属性详解:核心属性

设备树里那些属性,说白了就是硬件和驱动之间的「沟通语言」。我刚开始学的时候,看着一堆属性名也头疼。后来发现,其实核心的就那么几个,搞懂了它们,大部分设备树你都能看懂。

今天咱们就一个一个来拆解。我会结合我实际项目中踩过的坑,帮你把这些属性吃透。

4.1 compatible 属性:你是谁?

compatible 属性,是设备树里最重要的属性,没有之一。它的作用就是告诉内核:「我是哪个设备,用哪个驱动来管我」。

格式很简单,就是一个字符串列表。比如:

compatible = "vendor,device", "generic-device";

这里有个讲究:越具体的名字放前面,越通用的放后面。内核匹配驱动时,会从左到右依次尝试。

核心规则:compatible 的值通常写成 "厂商名,设备名" 的形式。比如 "ti,am335x-uart"、"fsl,imx6q-uart"。

我在项目中遇到过一个问题:某款 WiFi 芯片,设备树里只写了 "broadcom,bcm4330",结果内核找不到驱动。后来发现,驱动里匹配的是 "brcm,bcm4330"。厂商缩写不一致,折腾了我半天。所以,写 compatible 时一定要和驱动代码里的一模一样,大小写、标点都不能错。

我的习惯:写设备树前,先去内核源码里搜一下对应驱动的 of_match_table,看看它到底匹配什么字符串。这样最保险。

4.2 reg 属性:你在哪?

reg 属性描述设备的地址空间信息。说白了,就是告诉驱动:「你的寄存器在哪个地址,占多大地方」。

它的格式是:

reg = <address1 length1 address2 length2 ...>;

每个地址和长度组成一个「地址段」。一个设备可能有多个地址段,比如控制寄存器和数据缓冲区分开的情况。

来看个实际例子:

uart@44e09000 {
    compatible = "ti,am335x-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
};

这个 UART 设备的寄存器基地址是 0x44e09000,长度是 0x2000(8KB)。

我曾经踩过的坑:有一次我写 reg 属性时,地址写对了,但长度写小了。结果驱动访问寄存器时,只映射了部分地址空间,读写某些寄存器直接导致系统崩溃。嗯,这个错误排查起来特别痛苦。

关于 reg 的地址位数,由父节点的 #address-cells#size-cells 决定。比如:

soc {
    #address-cells = <1>;  // 地址用 1 个 32 位整数表示
    #size-cells = <1>;     // 长度用 1 个 32 位整数表示
    
    uart@44e09000 {
        reg = <0x44e09000 0x2000>;  // 地址 0x44e09000,长度 0x2000
    };
};

如果是 64 位地址,#address-cells 就是 2:

bus {
    #address-cells = <2>;
    #size-cells = <2>;
    
    device@100000000 {
        reg = <0x1 0x00000000 0x0 0x1000>;  // 地址 0x100000000,长度 0x1000
    };
};

4.3 interrupts 属性:怎么通知你?

设备需要和 CPU 通信,最常见的方式就是中断。interrupts 属性就是描述设备用哪个中断号、什么触发方式。

格式:

interrupts = <中断号 触发类型>;

触发类型常用的有:

含义
0 上升沿触发
1 下降沿触发
2 高电平触发
3 低电平触发
4 上升沿和下降沿都触发

实际例子:

gpio-keys {
    compatible = "gpio-keys";
    
    button@1 {
        label = "Power";
        gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
        interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
    };
};

这里中断号是 3,触发方式是下降沿触发。

我建议:中断触发类型尽量和硬件实际信号一致。比如按键按下是低电平,就用下降沿或低电平触发。选错了,可能会出现按一次触发两次,或者松手才触发的问题。

另外,如果设备有多个中断,可以这样写:

interrupts = <10 IRQ_TYPE_EDGE_RISING>, <11 IRQ_TYPE_LEVEL_HIGH>;

驱动里通过 platform_get_irq() 按索引获取。

4.4 status 属性:设备能用吗?

status 属性控制设备的启用状态。说白了就是告诉内核:「这个设备现在能不能用」。

常用的值:

含义
okay 设备可用,正常启用
disabled 设备禁用,内核不会去初始化它
reserved 保留,通常用于特殊用途
fail 设备检测失败

这个属性在板级设备树中特别有用。比如 SoC 默认把所有外设都打开,但具体板子上可能只用一部分:

// 在 SoC 的 dtsi 文件中
uart1: uart@44e09000 {
    compatible = "ti,am335x-uart";
    reg = <0x44e09000 0x2000>;
    status = "disabled";  // 默认禁用
};

// 在板级 dts 文件中
&uart1 {
    status = "okay";  // 这个板子用 UART1,启用它
};

重要:status 属性是可选的。如果不写,默认就是 "okay"(设备可用)。但为了代码清晰,我建议显式写出来。

我记得有一次调试,某个 I2C 设备死活不工作。查了半天,发现是 status 被写成了 "ok"(少了个 a)。内核解析时没识别出来,直接当 disabled 处理了。嗯,这种低级错误,犯过一次就记住了。

4.5 其他常用属性

除了上面四个核心属性,还有几个也经常见到:

  • label:给设备起个名字,方便人识别。比如 "label = "Power Button"。
  • clock-frequency:指定设备的工作频率。比如 I2C 控制器常用。
  • pinctrl-0pinctrl-names:引脚复用配置,后面章节会详细讲。
  • dmasdma-names:DMA 通道配置。

4.6 知识体系图

下面这张图,帮你理清这几个核心属性的关系:

设备树节点 compatible 驱动匹配的关键 reg 地址空间描述 interrupts 中断配置 status 设备启用状态 核心属性作用总结 compatible → 驱动匹配 | reg → 地址映射 | interrupts → 中断绑定 | status → 设备开关

4.7 实战小技巧

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

  1. 先看 datasheet:写 reg 和 interrupts 前,一定先看芯片手册,确认寄存器基地址和中断号。
  2. 参考现成的 dts:内核源码里有很多现成的设备树文件,找同款芯片的参考一下,比自己从头写快得多。
  3. 编译检查:写完设备树,用 dtc -I dts -O dtb 编译一下,语法错误能提前发现。
  4. 查看 /proc/device-tree:系统启动后,去这个目录看看,设备树解析成什么样一目了然。

特别注意:设备树里不要写驱动逻辑。它只描述硬件,不描述行为。比如「按键按下时做什么」是驱动的活,设备树只管「这个按键接在哪个 GPIO 上」。

好了,核心属性就这些。你想想看,搞懂了 compatible、reg、interrupts、status,大部分设备树你都能读懂了。下次看到一段设备树,试着按这个思路去分析,很快就能上手。


专注资料整理