3、设备树(DT)语法入门:节点、属性、reg、interrupts、compatible等核心语法

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来向操作系统(尤其是Linux内核)传递板级硬件信息。在网关驱动加载流程中,内核通过解析设备树来发现CAN控制器、GPIO、时钟、中断控制器等外设,并完成驱动与设备的绑定。本节将系统讲解设备树的核心语法,包括节点定义、属性类型、地址编码、中断映射以及兼容性匹配规则。

学习提示: 设备树语法是嵌入式Linux开发的“硬件语言”。掌握本节内容后,你将能够读懂并编写基本的.dts(设备树源文件),理解内核如何通过设备树找到并初始化CAN控制器。

3.1 设备树节点(Node)基础

设备树由一系列节点(Node)组成,每个节点代表一个硬件设备、总线或控制器。节点通过路径(Path)唯一标识,根节点为“/”。节点可以包含子节点和属性。

节点定义语法:

node-name@unit-address {
    property-name = <value>;
    property-name = "string";
    child-node {
        ...
    };
};
  • node-name:节点名称,通常为设备类型或实例名,如 can0、i2c1。
  • unit-address:可选,用于区分同名的节点,通常为寄存器基地址或设备地址。
  • 属性(Property):键值对,描述设备的配置信息。
示例: 一个简单的CAN控制器节点定义。
/ {
    soc {
        can@0x1c00000 {
            compatible = "vendor,can-controller";
            reg = <0x1c00000 0x1000>;
            interrupts = <0 42 4>;
            clock-frequency = <50000000>;
        };
    };
};
设备树节点层级结构 根节点 "/" soc 节点 can@0x1c00000 节点属性示例 compatible = "vendor,can-controller"; // 兼容性字符串 reg = <0x1c00000 0x1000>; // 寄存器基地址 + 长度 interrupts = <0 42 4>; // 中断号、类型 clock-frequency = <50000000>; // 时钟频率

3.2 核心属性详解

设备树属性是驱动获取硬件参数的主要途径。以下是在CAN控制器初始化过程中最常用的核心属性。

属性名 类型 说明 CAN控制器示例
compatible 字符串列表 设备驱动匹配的关键标识,格式为“厂商,设备型号” "vendor,can-controller"
reg u32数组 描述设备地址空间,格式为(地址, 长度)对 <0x1c00000 0x1000>
interrupts u32数组 描述中断号、触发类型,格式依赖中断控制器 <0 42 4>
interrupt-parent phandle 指定该设备的中断控制器父节点 &gic
clocks phandle列表 引用时钟源,驱动通过时钟框架获取频率 <&clk_can>
status 字符串 设备状态,“okay”表示启用,“disabled”表示禁用 "okay"
常见错误: compatible 属性必须与驱动中的 of_match_table 完全匹配,包括大小写和逗号。例如,驱动中写 {"vendor,can-controller", ...},设备树中必须写 "vendor,can-controller",不能写成 "vendor,can_controller" 或 "VENDOR,can-controller"。

3.3 reg 属性与地址编码

reg 属性用于描述设备在总线上的地址范围。对于内存映射设备(如CAN控制器),它通常包含基地址和地址长度。对于I2C或SPI设备,它包含从设备地址。

语法格式:

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

地址编码规则:

  • #address-cells:指定地址字段占用的u32单元数,通常为1或2。
  • #size-cells:指定长度字段占用的u32单元数,通常为1或0。
示例: 32位地址空间中的CAN控制器。
soc {
    #address-cells = <1>;
    #size-cells = <1>;

    can@1c00000 {
        reg = <0x1c00000 0x1000>;  // 基地址0x1c00000,长度0x1000
    };
};

64位地址示例:

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

    can@300000000 {
        reg = <0x3 0x00000000 0x0 0x10000>;  // 地址0x300000000,长度0x10000
    };
};
reg 属性地址映射关系 系统总线地址空间 CAN 寄存器 0x1C00000 - 0x1C00FFF 其他外设 其他地址区域 reg 属性解析 #address-cells = 1, #size-cells = 1 reg = <0x1C00000 0x1000> → 基地址 0x1C00000, 长度 0x1000 (4KB)

3.4 interrupts 属性与中断映射

中断属性描述了设备如何向中断控制器发送中断请求。中断映射依赖于中断控制器(如GIC)的规范。

标准格式(ARM GIC为例):

interrupts = <interrupt-type interrupt-number flags>;
  • interrupt-type:0 表示SPI(共享外设中断),1 表示PPI(私有外设中断)。
  • interrupt-number:中断号,对于SPI通常从32开始编号。
  • flags:触发类型,如 4 表示高电平触发,1 表示上升沿触发。
示例: CAN控制器使用SPI中断,中断号42,高电平触发。
can@1c00000 {
    compatible = "vendor,can-controller";
    reg = <0x1c00000 0x1000>;
    interrupt-parent = <&gic>;   // 引用GIC中断控制器
    interrupts = <0 42 4>;        // SPI, 中断号42, 高电平触发
};

中断控制器节点示例:

gic: interrupt-controller@2c00000 {
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;       // 每个中断描述占用3个u32
    interrupt-controller;          // 声明本节点为中断控制器
    reg = <0x2c00000 0x1000>;
};
关键点: #interrupt-cells 决定了 interrupts 属性中每个中断描述的长度。对于ARM GIC,通常为3(类型、号、标志)。驱动通过 irq_of_parse_and_map() 函数解析该属性并映射为Linux中断号。

3.5 compatible 属性与驱动匹配

compatible 是设备树中最重要的属性之一,它建立了硬件设备与内核驱动之间的绑定关系。内核在启动时遍历设备树,将每个节点的 compatible 值与驱动注册的 of_match_table 进行匹配。

匹配规则:

  • 字符串列表按优先级从高到低排列,内核依次尝试匹配。
  • 格式通常为 "厂商,设备型号",如 "ti,am335x-can"。
  • 驱动中定义匹配表时,使用 OF_MATCH_TABLE 宏或 of_device_id 结构体。
驱动匹配示例:
// 设备树节点
can@1c00000 {
    compatible = "vendor,can-controller-v2", "vendor,can-controller";
    reg = <0x1c00000 0x1000>;
    interrupts = <0 42 4>;
};

// 内核驱动匹配表
static const struct of_device_id can_of_match[] = {
    { .compatible = "vendor,can-controller-v2", .data = &can_v2_data },
    { .compatible = "vendor,can-controller",    .data = &can_v1_data },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, can_of_match);

匹配优先级: 内核优先匹配列表中的第一个字符串。如果 "vendor,can-controller-v2" 匹配成功,则使用 can_v2_data 作为私有数据;否则尝试匹配 "vendor,can-controller"。

compatible 属性驱动匹配流程 设备树节点 compatible = "v2", "v1" 内核匹配层 遍历 of_match_table CAN驱动 probe() 调用 驱动匹配表 (of_match_table) 1. 尝试匹配 "vendor,can-controller-v2" → 匹配成功,使用 v2 数据 2. 若 v2 不匹配,尝试 "vendor,can-controller" → 匹配成功,使用 v1 数据 3. 若都不匹配,驱动 probe() 不会被调用 4. 匹配成功后,内核调用驱动 probe(),并传入匹配到的私有数据

3.6 其他常用属性

属性 用途 CAN控制器场景
clock-frequency 指定设备工作时钟频率 CAN控制器需要知道时钟频率以计算波特率
pinctrl-names / pinctrl-0 引脚复用配置 将CAN TX/RX引脚配置为CAN功能
status 设备启用状态 "okay" 启用CAN控制器,"disabled" 禁用
label 设备标签,用于用户空间识别 "can0" 或 "can1"
max-speed 最大通信速率 CAN控制器支持的最大波特率
完整CAN控制器设备树节点示例:
&can0 {
    status = "okay";
    compatible = "vendor,can-controller";
    reg = <0x1c00000 0x1000>;
    interrupts = <0 42 4>;
    interrupt-parent = <&gic>;
    clocks = <&clk_can>;
    clock-frequency = <50000000>;
    pinctrl-names = "default";
    pinctrl-0 = <&can0_pins>;
    max-speed = <1000000>;  // 1 Mbps
};

3.7 设备树语法检查与编译

设备树源文件(.dts)需要编译为二进制设备树(.dtb)才能被内核加载。常用工具包括:

  • dtc:设备树编译器,将.dts编译为.dtb。
  • dtc -I dtb -O dts:反编译.dtb为.dts,用于调试。
  • dt-validate:检查设备树语法和绑定约束。

编译命令示例:

# 编译设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 反编译设备树
dtc -I dtb -O dts -o myboard.dts myboard.dtb

# 检查语法
dtc -I dts -O dtb -o /dev/null myboard.dts 2>&1
调试建议: 在开发过程中,如果CAN控制器驱动无法加载,首先检查设备树中 compatible 是否与驱动匹配,其次检查 status 是否为 "okay",最后确认 reg 和 interrupts 地址是否正确。使用 dtc 反编译内核使用的 .dtb 文件,验证实际加载的设备树内容。

3.8 本章小结

设备树语法是嵌入式Linux驱动开发的基础。通过本节学习,你应该掌握:

  • 设备树节点的定义和层级结构。
  • 核心属性 compatible、reg、interrupts 的语法和用途。
  • 地址编码规则(#address-cells、#size-cells)和中断映射方法。
  • 驱动如何通过 compatible 属性与设备树节点匹配。
  • 设备树的编译、反编译和调试工具。