3、设备树常用属性详解:compatible、reg、interrupts、status、clocks

设备树里的属性,说白了就是给驱动看的“说明书”。每个属性告诉内核:这个设备是谁、怎么访问它、什么时候能用。我刚开始学设备树时,总觉得属性太多记不住。后来发现,真正常用的其实就那几个。今天咱们就把它们掰开揉碎了讲清楚。

3.1 compatible —— 设备的名字和身份证

compatible 是设备树里最重要的属性,没有之一。它用来匹配驱动。内核通过它找到对应的驱动程序。

格式很简单:

compatible = "厂商,型号";

比如:

compatible = "ti,am335x-rtc", "ti,da830-rtc";

这里有两个字符串,内核会按顺序尝试匹配。第一个最精确,第二个是兼容型号。我建议你永远把最具体的型号写在前面。

我的经验: 写 compatible 时,一定要遵循“厂商,型号”的命名规范。别自己瞎编。我曾经见过有人写 "my_led" 这种名字,结果内核根本匹配不上,折腾了半天才发现是命名不规范。

匹配规则其实很简单:驱动里会声明一个 of_device_id 表,内核遍历设备树节点,找到 compatible 字符串匹配的驱动,就绑定上去了。

3.2 reg —— 告诉驱动去哪里找硬件

reg 属性描述设备的地址空间。它告诉驱动:你的寄存器在哪个物理地址,占多大范围。

格式:

reg = <地址1 长度1 地址2 长度2 ...>;

举个例子:

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

这表示 UART 的寄存器从 0x44e09000 开始,占用 0x2000 字节。驱动拿到这个信息后,调用 ioremap() 就能访问了。

注意: reg 里的地址是物理地址,不是虚拟地址。驱动里需要自己映射。另外,地址和长度的位数由 #address-cells 和 #size-cells 决定。默认是 1 个 cell(32位),64位系统可能是 2 个 cell。

我在项目中遇到过一个问题:某款芯片的 SPI 控制器有两个寄存器区域,一个控制寄存器,一个数据缓冲区。当时只写了一个 reg,结果驱动只能访问到控制寄存器,数据读写全乱套了。后来加上第二个 reg 才解决。

3.3 interrupts —— 让设备会“喊人”

没有中断的设备,驱动只能轮询,效率低得可怜。interrupts 属性就是告诉内核:这个设备用哪个中断号、怎么触发。

格式:

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

比如:

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

触发类型常用的有:

宏定义 值 含义
IRQ_TYPE_EDGE_RISING 1 上升沿触发
IRQ_TYPE_EDGE_FALLING 2 下降沿触发
IRQ_TYPE_LEVEL_HIGH 4 高电平触发
IRQ_TYPE_LEVEL_LOW 8 低电平触发

你想想看,如果中断类型选错了会怎样?比如设备是下降沿触发,你配成了电平触发,那中断可能会一直触发,CPU 直接跑死。嗯,我当年就犯过这个错,调试了一整天。

关键点: 中断号是相对于中断控制器而言的。如果设备挂在多个中断控制器下,需要用 interrupt-parent 指定父控制器。

3.4 status —— 设备的“开关”

status 属性控制设备是否启用。说白了就是一个开关。

常用值:

  • okay —— 设备可用,驱动会加载
  • disabled —— 设备禁用,驱动不会加载
  • fail —— 设备检测失败

比如:

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

我习惯在板级设备树里把用不到的接口都设成 disabled。比如某个开发板上有 6 个 UART,但实际只用了 2 个,剩下的就关掉。这样能省点资源,也避免驱动误加载。

避坑指南: 我曾经在调试时发现某个设备怎么都 probe 不了,查了半天,发现 status 写成了 "ok" 而不是 "okay"。内核只认 "okay",少一个字母都不行。这种低级错误最坑人。

3.5 clocks —— 给设备提供“心跳”

现代 SoC 里,每个外设都需要时钟才能工作。clocks 属性就是告诉驱动:你的时钟源在哪、频率多少。

格式:

clocks = <&时钟控制器 时钟ID>;

比如:

i2c@44e0b000 {
    compatible = "ti,am335x-i2c";
    reg = <0x44e0b000 0x1000>;
    interrupts = <70>;
    clocks = <&clk_24mhz>;
    clock-names = "fck";
};

clock-names 是给时钟起个名字,驱动里通过名字来获取时钟句柄。这样代码可读性更好。

我遇到过最头疼的问题:某个外设的时钟频率配错了,导致通信速率不对。比如 I2C 配成了 100KHz 的时钟,但实际给了 400KHz,结果数据全是乱码。后来用示波器一看,才发现是时钟问题。

总结一下: clocks 属性不只是配个时钟源,还涉及到时钟使能、频率设置、时钟树管理。驱动里通常用 clk_get() 和 clk_prepare_enable() 来操作。

3.6 其他常用属性一览

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

属性 作用 示例
pinctrl-0 引脚复用配置 pinctrl-0 = <&uart0_pins>;
dmas DMA 通道配置 dmas = <&edma 0>;
power-domains 电源域管理 power-domains = <&pd_core>;
resets 复位信号 resets = <&rst 1>;

这些属性虽然不常用,但遇到特定硬件时,缺了它们设备就是跑不起来。

3.7 知识体系图

下面这张图帮你理清这些属性的关系:

设备树常用属性核心逻辑 设备树节点 compatible 驱动匹配 reg 地址空间 interrupts 中断配置 status 启用/禁用 clocks 时钟源 每个属性解决一个核心问题:你是谁?你在哪?你怎么通知我?你开没开?你用什么时钟?

这五个属性,基本覆盖了设备树里 80% 的配置场景。你只要把它们搞明白了,大部分设备树问题都能自己解决。

可可精品资料库