3. 设备树常用属性:compatible、reg、interrupts、status等核心属性详解

好,咱们今天聊聊设备树里最常用的几个属性。说实话,我刚开始接触设备树时,看着那一堆属性名也挺懵的。但用久了你会发现,真正天天打交道的,其实就那么几个。今天我就把这几张「王牌」给你讲透。

3.1 compatible —— 灵魂匹配属性

这个属性,说白了就是告诉内核:「我是谁」。内核通过它来找到对应的驱动。

它的格式很简单:"厂商,型号"。比如:

compatible = "ti,am335x-uart", "ns16550";

这里有个细节我特别想强调:顺序很重要。内核匹配驱动时,是从左往右匹配的。第一个最精确,后面的作为备选。我在项目中遇到过,有人把通用型号写在前面,结果内核永远匹配不到精确驱动,折腾了半天。

核心要点:

  • 第一个值要尽量精确,包含厂商和具体型号
  • 后面的值可以写兼容的通用型号
  • 匹配失败时,内核会依次尝试后面的值

我的习惯:写compatible时,我通常会先查芯片手册,确认官方推荐的字符串。实在找不到,就参考Linux内核里已有的写法,保持风格一致。

3.2 reg —— 地址与空间描述

reg属性描述设备的地址空间信息。它由一组组的地址长度组成。

看个例子:

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

这里0x44e09000是基地址,0x2000是地址范围。嗯,要注意的是,这个长度不是随便写的。我见过有人把长度写成0x1000,结果驱动访问到后面寄存器时直接崩溃。后来一查,原来这个UART模块实际占用了0x2000的空间。

如果设备有多个地址段,可以这样写:

reg = <0x44e09000 0x2000>, <0x44e0b000 0x1000>;

每个地址段对应一个#address-cells#size-cells组合。这两个属性决定了地址和长度的位数。

属性 含义 常见值
#address-cells 地址占几个32位单元 1(32位地址)或2(64位地址)
#size-cells 长度占几个32位单元 1或0(无长度信息)

避坑指南:我曾经在移植U-Boot时,发现某个外设怎么都访问不到。查了两天才发现,是父节点的#address-cells和#size-cells没配对。子节点的reg解析完全错了。记住:子节点的reg格式,由父节点的这两个属性决定。

3.3 interrupts —— 中断配置

中断属性描述设备使用的中断资源。它通常包含中断控制器、中断号和触发类型。

基本写法:

interrupts = <0 56 4>;

这三个数字分别代表:

  • 第一个:中断类型(0=SPI共享中断,1=PPI私有中断)
  • 第二个:中断号
  • 第三个:触发类型(1=上升沿,2=下降沿,4=高电平,8=低电平)

你想想看,为什么要有触发类型?因为不同的外设,中断信号的行为不一样。比如按键按下是电平变化,而DMA传输完成是脉冲信号。

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

interrupts = <0 56 4>, <0 57 4>;

每个中断对应一个interrupt-names属性中的名字:

interrupt-names = "rx", "tx";

我的经验:中断号千万别搞错。我建议你直接看芯片手册里的中断向量表,或者参考官方设备树文件。我曾经因为中断号写错一位,导致系统一运行就死机,排查了整整一个下午。

3.4 status —— 设备状态控制

status属性控制设备是否启用。它虽然简单,但用好了能省不少事。

常用值:

  • okay:设备启用
  • disabled:设备禁用
  • reserved:保留,不用于系统
  • fail:设备检测失败

实际项目中,我经常这样用:

&uart0 {
    status = "okay";
    pinctrl-0 = &uart0_pins;
    pinctrl-names = "default";
};

为什么要在板级文件里单独写status?因为SoC级设备树里,很多外设默认是disabled的。到了具体的板子上,再根据需要打开。这样设计很灵活,你想想看,一个SoC可能用在几十种板子上,每个板子用的外设不一样,总不能改SoC级文件吧?

实用技巧:调试时,如果某个外设不工作,先检查status是不是"okay"。我遇到过好几次,明明驱动没问题,就是status忘了改,白白浪费了时间。

3.5 其他常用属性

除了上面四个,还有几个属性我经常用到:

pinctrl-*:引脚复用配置。这个在嵌入式开发里太重要了。一个引脚可能同时是GPIO、UART、I2C,你得告诉系统当前用哪个功能。

pinctrl-0 = &uart0_pins;
pinctrl-names = "default";

clocks:时钟配置。很多外设需要时钟才能工作,这个属性指定了时钟源和频率。

clocks = <&clk_uart0 0>;

dmas:DMA配置。对于需要高速数据传输的设备,DMA几乎是标配。

dmas = <&edma 0 2>, <&edma 1 2>;
dma-names = "rx", "tx";

我的建议:刚开始学设备树时,别想着把所有属性都记住。先把compatible、reg、interrupts、status这四个吃透,其他的用到时再查。我到现在写设备树,也经常翻参考手册。

3.6 实战:组合使用这些属性

最后,咱们看一个完整的例子。这是一个I2C控制器在设备树中的描述:

i2c0: i2c@44e0b000 {
    compatible = "ti,am335x-i2c", "ti,omap4-i2c";
    reg = <0x44e0b000 0x1000>;
    interrupts = <0 70 4>;
    status = "disabled";
    #address-cells = <1>;
    #size-cells = <0>;
    clocks = <&clk_i2c0>;
    pinctrl-0 = <&i2c0_pins>;
    pinctrl-names = "default";
};

然后在板级文件里:

&i2c0 {
    status = "okay";
    clock-frequency = <400000>;

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };
};

你看,每个属性各司其职。compatible告诉内核用哪个驱动,reg告诉驱动寄存器在哪,interrupts告诉中断怎么用,status控制设备开关。配合起来,一个完整的设备描述就出来了。

嗯,今天就先聊到这。这些属性看着简单,但用好了真能省不少事。下次咱们聊聊设备树里更高级的用法,比如中断映射和时钟管理。