3、设备树常用属性详解:compatible属性、reg属性和地址编码、interrupts属性、status属性

好,咱们今天来啃设备树里最常用的几个属性。说实话,我刚入行那会儿,看着设备树里一堆属性名,头都大了。后来做多了才发现,真正天天打交道的,其实就这几个核心属性。掌握了它们,你就能看懂八成以上的设备树节点。

3.1 compatible属性:设备与驱动的“身份证”

compatible属性,说白了就是告诉内核:“我这个设备是谁,该用哪个驱动来伺候我”。它是设备与驱动之间匹配的桥梁。

它的格式很简单:

compatible = "厂商,设备型号";

比如:

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

这里有两个字符串,内核匹配驱动时会按顺序尝试。先找"ti,am335x-uart"对应的驱动,找不到再找"ns16550"的。

匹配规则:内核遍历设备树时,会拿compatible里的每个字符串去和驱动里of_match_table中的字符串比对。只要有一个匹配上,就算成功。

我个人习惯,写新驱动时一定会把最具体的型号放在最前面,通用的放后面。这样既能支持特定硬件的优化,又能兼容通用驱动。

我在项目中遇到过一个问题:某款芯片的UART在旧内核上跑得好好的,升级内核后突然不工作了。查了半天,发现是compatible字符串写反了顺序,内核先匹配到了一个不兼容的通用驱动。嗯,顺序真的很重要。

3.2 reg属性和地址编码:硬件的“门牌号”

reg属性描述设备的地址空间信息。你想想看,CPU要访问一个外设,总得知道它映射到哪个地址段吧?reg就是干这个的。

基本格式:

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

举个例子:

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

这里表示UART的寄存器基地址是0x44e09000,地址范围是0x2000字节。

小技巧:地址和长度的位数由根节点的#address-cells和#size-cells决定。如果#address-cells=<1>,地址就是32位;如果是<2>,就是64位。size-cells同理。

我记得有一次调试一个PCIe设备,reg属性里写了三个地址段:配置空间、MMIO空间、IO空间。当时没注意#address-cells是2,结果地址解析全乱了。后来老老实实数了一遍cells,才把问题定位到。

多段reg的场景也很常见:

reg = <0x10000000 0x10000    /* 配置空间 */
       0x20000000 0x100000   /* MMIO空间 */
       0x30000000 0x1000>;   /* IO空间 */

3.3 interrupts属性:设备的“门铃”

设备不能总让CPU轮询吧?那太浪费了。interrupts属性就是告诉内核:我这个设备用哪个中断号,怎么触发。

基本格式:

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

触发类型常用的有:

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

实际例子:

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

注意:中断号的编码方式取决于中断控制器。有些控制器用全局中断号,有些用相对编号。一定要看对应中断控制器的文档。

我曾经踩过一个坑:在ARM平台上,GIC中断控制器用SPI(共享外设中断)和PPI(私有外设中断)两种编号方式。SPI中断号从32开始,而设备树里写的是硬件中断号,两者差32。当时怎么调都不对,后来查了GIC手册才明白。

3.4 status属性:设备的“开关”

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

常用值:

  • okay:设备正常,可以启用
  • disabled:设备禁用,内核不初始化
  • reserved:保留,不用于Linux
  • fail:设备检测失败

实际应用场景:

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

&uart1 {
    status = "disabled";  /* 这个串口不用 */
};

最佳实践:在dtsi(设备树包含文件)中,所有外设默认设为disabled。然后在具体的板级dts文件中,根据需要逐个使能。这样既清晰又安全。

我见过一些新手直接把所有外设都设成okay,结果系统启动时一堆驱动去抢资源,各种冲突。嗯,这里要注意:不用的设备一定要关掉,省电又省心。

3.5 综合示例:一个完整的设备节点

把上面几个属性串起来,就是一个完整的设备节点:

spi@10000000 {
    compatible = "ti,am335x-spi", "ti,omap4-spi";
    reg = <0x10000000 0x1000>;
    interrupts = <0 65 4>;
    status = "okay";
    
    #address-cells = <1>;
    #size-cells = <0>;
    
    spidev@0 {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <10000000>;
    };
};

这个节点包含了我们今天讲的所有核心属性。你看,compatible告诉内核用哪个驱动,reg告诉地址在哪,interrupts告诉中断怎么用,status告诉设备是否启用。

我的建议:刚开始写设备树时,先照着已有的模板改。把compatible、reg、interrupts、status这四个属性搞明白,其他属性慢慢学。别想一口吃成胖子。

好了,这节的内容就到这。下节课我们聊聊设备树里那些“看不见”的属性——它们不直接出现在节点里,但影响整个设备树的解析。到时候见。