3. 设备树节点与属性详解:compatible属性、reg属性、interrupts属性、status属性、自定义属性的使用规范

设备树(Device Tree)是嵌入式Linux系统中描述硬件拓扑结构的核心机制。对于网关驱动部署而言,正确理解并配置设备树节点与属性,是确保CAN控制器、SPI总线、GPIO中断等外设正常工作的前提。本章将逐一拆解五大核心属性的语法规范、使用场景与常见陷阱。

学习目标:掌握设备树节点的基础结构,精通compatible、reg、interrupts、status及自定义属性的编写规范,能够独立为CAN控制器编写正确的设备树节点。

3.1 设备树节点基础结构

设备树由一系列节点(node)和属性(property)组成,每个节点代表一个硬件设备或总线,属性则描述设备的配置参数。节点的基本语法如下:

/dts-v1/;

/ {
    model = "Gateway CAN Board";
    compatible = "vendor,gateway-can-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        can0: can@1c00000 {
            compatible = "vendor,can-controller";
            reg = <0x1c00000 0x1000>;
            interrupts = <0 55 4>;
            status = "okay";
            clock-frequency = <8000000>;
        };
    };
};

节点路径由根节点(/)开始,通过层级关系定位。节点名称通常采用“设备名@地址”的格式,如 can@1c00000。每个节点可以包含子节点和属性,属性值可以是字符串、整数、数组或二进制数据。

关键概念:节点名称中的“@地址”部分用于区分同一层级中相同设备名称的不同实例,地址通常对应设备的基地址或片选号。

下图展示了设备树节点的层级结构与属性挂载关系:

设备树节点层级结构与属性挂载 根节点 / model = "Gateway CAN Board" soc 节点 #address-cells = <1> can0: can@1c00000 compatible = "vendor,can-controller" reg = <0x1c00000 0x1000> interrupts = <0 55 4> 节点命名规范 • 格式:设备名@地址 • 设备名:如 can、spi、uart • 地址:基地址或片选号 • 标签(label):如 can0,用于引用 属性挂载规则 • 每个节点可挂载多个属性 • 属性名 = 属性值; • 值类型:字符串、整数、数组 • 子节点继承父节点地址空间

3.2 compatible 属性:驱动匹配的核心

compatible 属性是设备树中最重要的属性之一,它建立了硬件设备与内核驱动之间的绑定关系。内核通过设备树中的 compatible 字符串,在驱动注册表中查找匹配的驱动。

3.2.1 语法规范

compatible = "manufacturer,device-model", "generic-device";

属性值是一个或多个字符串,按优先级从高到低排列。内核优先使用第一个匹配的驱动,若未找到则尝试后续字符串。

组成部分 格式要求 示例 说明
厂商前缀 小写字母、数字、连字符 ti, nxp, microchip 标识设备制造商
设备型号 小写字母、数字、连字符 m_can, flexcan, sja1000 具体芯片或IP核型号
通用兼容 小写字母、数字、连字符 can-controller, serial 通用设备类别,用于回退匹配

3.2.2 CAN控制器兼容性配置示例

/* MCP2515 SPI转CAN控制器 */
can0: can@0 {
    compatible = "microchip,mcp2515", "microchip,mcp251x", "can-controller";
    reg = <0>;
    clocks = <&clk16m>;
    interrupt-parent = <&gpio1>;
    interrupts = <3 2>;
    spi-max-frequency = <10000000>;
    status = "okay";
};

/* NXP FlexCAN控制器 */
can1: can@2090000 {
    compatible = "fsl,imx6q-flexcan", "fsl,imx6ul-flexcan", "fsl,flexcan";
    reg = <0x02090000 0x4000>;
    interrupts = <0 110 4>;
    clocks = <&clks 1>, <&clks 2>;
    clock-names = "ipg", "per";
    status = "okay";
};
常见错误:compatible 字符串中厂商前缀与设备型号之间必须使用英文逗号加空格分隔,如 "microchip,mcp2515"。缺少逗号或使用中文逗号会导致驱动匹配失败。

下图展示了 compatible 属性驱动匹配的完整流程:

compatible 属性驱动匹配流程 设备树节点 compatible = "microchip,mcp2515" 内核OF匹配表 of_match_table 字符串匹配 strcmp 逐项比较 匹配成功 调用 probe 函数 驱动初始化硬件 匹配失败 尝试下一个字符串 或返回 -ENODEV 匹配优先级说明 1. 内核从 compatible 字符串列表的第一个元素开始匹配 2. 匹配成功则调用对应驱动的 probe 函数,不再继续匹配 3. 匹配失败则尝试下一个字符串,直到列表末尾 4. 全部匹配失败则返回 -ENODEV,设备无法被驱动

3.3 reg 属性:地址空间与资源映射

reg 属性用于描述设备在总线上的地址空间,包括基地址和地址范围。对于内存映射设备,它定义了寄存器区域的起始地址和大小;对于SPI/I2C设备,它定义了片选号或从设备地址。

3.3.1 语法规范

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

每个地址-长度对由 #address-cells 和 #size-cells 属性决定其单元大小。这两个属性通常在父节点中定义。

场景 #address-cells #size-cells reg 示例 含义
32位内存映射 1 1 <0x1c00000 0x1000> 基地址0x1c00000,范围0x1000字节
64位内存映射 2 2 <0x0 0x1c00000 0x0 0x1000> 64位基地址,64位范围
SPI设备 1 0 <0> 片选号0
I2C设备 1 0 <0x50> 从设备地址0x50

3.3.2 CAN控制器reg配置示例

/* 内存映射CAN控制器 - 32位地址 */
can0: can@1c00000 {
    compatible = "vendor,can-controller";
    reg = <0x1c00000 0x1000>;  /* 基地址0x1c00000,寄存器范围4KB */
    interrupts = <0 55 4>;
    status = "okay";
};

/* SPI连接CAN控制器 - 片选地址 */
&spi1 {
    status = "okay";
    
    can0: can@0 {
        compatible = "microchip,mcp2515";
        reg = <0>;  /* SPI片选0 */
        spi-max-frequency = <10000000>;
        interrupts = <3 2>;
        status = "okay";
    };
};
实践建议:对于内存映射设备,reg 的地址范围应精确对应芯片手册中寄存器区域的起始地址和大小。过大的范围可能与其他设备地址冲突,过小的范围可能导致寄存器访问越界。

3.4 interrupts 属性:中断信号绑定

interrupts 属性描述设备的中断请求信号,包括中断控制器、中断号和触发类型。中断系统是CAN控制器实时响应报文的关键,正确配置中断属性至关重要。

3.4.1 语法规范

interrupts = <interrupt-specifier>;
interrupt-parent = <&interrupt-controller>;

interrupt-specifier 的格式由中断控制器的 #interrupt-cells 属性决定。常见的中断控制器类型如下:

中断控制器 #interrupt-cells 中断说明符格式 示例
GIC (ARM通用中断控制器) 3 <中断类型 中断号 触发类型> <0 55 4>
GPIO控制器 2 <GPIO引脚 触发类型> <3 2>
简单中断控制器 1 <中断号> <10>

3.4.2 GIC中断类型与触发类型

对于ARM GIC控制器,中断说明符的三个字段含义如下:

字段 值 含义
中断类型 0 SPI(共享外设中断),用于外设
中断类型 1 PPI(私有外设中断),用于CPU内部
触发类型 1 上升沿触发
触发类型 2 下降沿触发
触发类型 4 高电平触发
触发类型 8 低电平触发

3.4.3 CAN控制器中断配置示例

/* GIC中断控制器 - SPI中断 */
interrupt-parent = <&gic>;
interrupts = <0 55 4>;  /* SPI中断号55,高电平触发 */

/* GPIO中断控制器 - 外部中断 */
interrupt-parent = <&gpio1>;
interrupts = <3 2>;  /* GPIO1_3引脚,下降沿触发 */

/* 多中断源配置 */
interrupts = <0 55 4>, <0 56 4>;  /* 两个中断源 */
interrupt-names = "rx", "tx";       /* 中断名称,用于驱动识别 */
常见错误:中断号在设备树中通常从0开始编号,但某些平台(如ARM)的中断号在设备树中需要减去32(SPI中断偏移)。务必参考平台芯片手册确认中断号映射关系。

3.5 status 属性:设备启用控制

status 属性控制设备的启用状态,是设备树中实现设备热插拔、电源管理、调试控制的重要机制。

3.5.1 可选值及含义

值 含义 使用场景
"okay" 设备启用,驱动正常加载 正常部署的设备
"disabled" 设备禁用,驱动不加载 未使用的硬件、调试阶段
"reserved" 设备保留,不由Linux管理 由固件或安全世界管理的设备
"fail" 设备检测失败 硬件故障或检测错误

3.5.2 使用规范

/* 在dtsi文件中默认禁用 */
&can0 {
    status = "disabled";
};

/* 在板级dts文件中启用 */
&can0 {
    status = "okay";
    /* 其他板级特定配置 */
    clock-frequency = <8000000>;
};

/* 条件编译示例 - 通过宏控制 */
#ifdef CONFIG_CAN_SUPPORT
&can0 {
    status = "okay";
};
#else
&can0 {
    status = "disabled";
};
#endif
最佳实践:在SoC级dtsi文件中将外设默认设为"disabled",在板级dts文件中按需启用。这种分层设计避免了不同板卡之间的配置冲突,也便于维护。

3.6 自定义属性:扩展硬件描述能力

设备树允许开发者定义自定义属性,用于描述芯片特有的配置参数。自定义属性是设备树灵活性的重要体现,但必须遵循命名规范和使用约束。

3.6.1 命名规范

规则 说明 正确示例 错误示例
厂商前缀 自定义属性应包含厂商前缀 vendor,clock-frequency clock-frequency(标准属性除外)
小写连字符 使用小写字母和连字符 can-bus-speed can_bus_speed / canBusSpeed
避免标准属性名 不要覆盖标准属性含义 vendor,can-mode compatible(标准属性)

3.6.2 常见自定义属性示例

/* CAN控制器自定义属性 */
can0: can@1c00000 {
    compatible = "vendor,can-controller";
    reg = <0x1c00000 0x1000>;
    interrupts = <0 55 4>;
    status = "okay";
    
    /* 自定义属性:CAN总线速率 */
    vendor,can-bus-speed = <500000>;        /* 500Kbps */
    
    /* 自定义属性:采样点位置 */
    vendor,sample-point = <875>;            /* 87.5% */
    
    /* 自定义属性:工作模式 */
    vendor,operation-mode = "loopback";      /* 回环模式 */
    
    /* 自定义属性:滤波器配置 */
    vendor,filter-config = <0x12345678 0x0FFFFFFF>;
    
    /* 自定义属性:DMA支持 */
    vendor,dma-enable;
};

3.6.3 驱动中读取自定义属性

/* 内核驱动中读取自定义属性示例 */
#include <linux/of.h>
#include <linux/of_device.h>

static int can_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    u32 bus_speed, sample_point;
    const char *mode;
    int ret;
    
    /* 读取整数属性 */
    ret = of_property_read_u32(np, "vendor,can-bus-speed", &bus_speed);
    if (ret) {
        dev_err(&pdev->dev, "Failed to read can-bus-speed\n");
        return ret;
    }
    
    /* 读取字符串属性 */
    ret = of_property_read_string(np, "vendor,operation-mode", &mode);
    if (!ret) {
        if (strcmp(mode, "loopback") == 0) {
            /* 配置回环模式 */
        }
    }
    
    /* 读取布尔属性 */
    if (of_property_read_bool(np, "vendor,dma-enable")) {
        /* 启用DMA */
    }
    
    return 0;
}
关键原则:自定义属性应遵循“先标准后自定义”的原则。优先使用设备树标准属性描述通用硬件特性,仅在标准属性无法满足需求时使用自定义属性。自定义属性必须在驱动文档中明确说明其含义和取值范围。

3.7 属性配置综合示例:CAN控制器完整设备树节点

以下是一个完整的CAN控制器设备树节点,综合运用了本章介绍的所有属性:

/* 完整CAN控制器设备树节点 - MCP2515 SPI转CAN */
/ {
    /* 根节点属性 */
    model = "Industrial Gateway CAN Board";
    compatible = "vendor,gateway-v2", "vendor,gateway";
    
    /* 时钟源 */
    clk16m: oscillator {
        compatible = "fixed-clock";
        #clock-cells = <0>;
        clock-frequency = <16000000>;
    };
    
    /* SPI总线 */
    &spi1 {
        status = "okay";
        #address-cells = <1>;
        #size-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_spi1>;
        
        /* CAN控制器节点 */
        can0: can@0 {
            compatible = "microchip,mcp2515", "microchip,mcp251x", "can-controller";
            reg = <0>;                          /* SPI片选0 */
            clocks = <&clk16m>;                 /* 外部时钟 */
            interrupt-parent = <&gpio1>;        /* 中断控制器 */
            interrupts = <3 2>;                 /* GPIO1_3,下降沿触发 */
            spi-max-frequency = <10000000>;     /* SPI最大频率 */
            status = "okay";                     /* 设备启用 */
            
            /* 自定义属性 */
            microchip,oscillator-frequency = <16000000>;
            microchip,can-bus-speed = <500000>;
            microchip,sample-point = <875>;
            microchip,operation-mode = "normal";
        };
    };
    
    /* 内存映射CAN控制器 */
    can1: can@2090000 {
        compatible = "fsl,imx6q-flexcan", "fsl,flexcan";
        reg = <0x02090000 0x4000>;             /* 寄存器基地址和范围 */
        interrupts = <0 110 4>;                /* GIC SPI中断110,高电平触发 */
        clocks = <&clks 1>, <&clks 2>;
        clock-names = "ipg", "per";
        status = "okay";
        
        /* 自定义属性 */
        fsl,stop-mode = <&gpr 0x34 28>;
        fsl,clk-source = <0>;
    };
};

下图展示了CAN控制器设备树节点与内核驱动之间的完整数据流:

CAN控制器设备树节点与驱动数据流 设备树节点 compatible = "microchip,mcp2515" reg = <0> interrupts = <3 2> status = "okay" ← 上一章 📖 返回目录 下一章 →