2、设备树基础回顾:设备树语法、节点、属性、reg、interrupts等核心概念

设备树(Device Tree,DT)是嵌入式 Linux 中用于描述硬件拓扑结构的数据结构,它将硬件信息从内核源码中剥离,以树形节点+属性的方式传递给内核。掌握设备树语法是编写时钟绑定接口和调试 clk 驱动的前提。

2.1 设备树文件结构与语法规则

设备树源文件(.dts)采用树形层级结构,每个硬件设备对应一个节点,节点内通过属性描述资源。基本语法如下:

/dts-v1/;

/ {
    model = "Embedded Clock Dev Board";
    compatible = "vendor,clk-dev-board";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a7";
            reg = <0x0>;
        };
    };

    soc {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        clkgen: clock-controller@ff760000 {
            compatible = "vendor,clk-gen";
            reg = <0xff760000 0x1000>;
            #clock-cells = <1>;
            clocks = <&osc24M>;
            clock-names = "osc";
        };
    };
};
关键语法要点:
  • 节点(Node): 以 node-name@unit-address 格式命名,@ 后跟寄存器基地址或单元地址。
  • 属性(Property): 键值对,值可以是 <u32>、[byte]、"string"、&phandle 等。
  • 标签(Label): 如 clkgen:,用于在其他节点中通过 &clkgen 引用。
  • 包含文件: #include "soc.dtsi" 引入公共外设定义。

2.2 节点与属性核心概念

概念 说明 示例
根节点 设备树的顶层容器,用 / 表示 / { ... };
子节点 挂载在父节点下的硬件单元 cpu@0、clock-controller@ff760000
compatible 驱动匹配字符串,格式为 "厂商,设备型号" "vendor,clk-gen"
reg 描述设备地址空间,由 address-cells 和 size-cells 决定长度 reg = <0xff760000 0x1000>;
interrupts 描述设备中断号、触发类型 interrupts = <0 31 4>;
phandle 节点引用句柄,通过 &label 或 <&label> 使用 clocks = <&osc24M>;
工程师提示: 在 clk 驱动中,compatible 是驱动与设备树绑定的唯一桥梁;reg 提供寄存器基地址;#clock-cells 决定时钟输出索引的编码方式(0 表示单输出,1 表示多输出)。

2.3 reg 属性详解:地址编码规则

reg 属性由 #address-cells 和 #size-cells 共同决定编码格式。这两个属性在父节点中定义,子节点的 reg 按此规则解析。

#address-cells #size-cells reg 格式 实际含义
1 1 <base size> 32 位基地址 + 32 位长度
2 1 <hi_base lo_base size> 64 位基地址 + 32 位长度
1 0 <base> 仅地址,无长度(如 CPU 核编号)
// 示例:64位地址空间
soc {
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;

    clkgen: clock-controller@ff760000 {
        reg = <0x0 0xff760000 0x0 0x1000>;
        // 高32位=0,低32位=0xff760000,长度高32位=0,长度低32位=0x1000
    };
};

2.4 interrupts 属性详解

中断属性描述设备的中断资源,通常与 interrupt-parent 配合使用。中断控制器节点需定义 #interrupt-cells 指定每个中断描述符包含的单元数。

中断控制器类型 #interrupt-cells interrupts 格式 示例
GIC(通用中断控制器) 3 <type irq flags> interrupts = <0 31 4>;
简单中断控制器 1 <irq_num> interrupts = <14>;
级联中断控制器 2 <parent_irq flags> interrupts = <3 4>;
调试陷阱: 中断号在设备树中通常从 0 开始编号,但 GIC 的 SPI(共享外设中断)类型号在 Linux 内核中会加上 32 的偏移。设备树中写 interrupts = <0 31 4>,内核实际使用的中断号是 31 + 32 = 63。时钟驱动中若使用 devm_request_irq,务必确认中断号映射是否正确。

2.5 设备树节点与硬件拓扑关系

以下 SVG 图展示了一个典型嵌入式 SoC 的设备树层级结构,突出时钟控制器节点及其与 CPU、外设、外部晶振的关系:

设备树节点层级与时钟拓扑关系 / (根节点) cpus soc chosen uart@ff690000 clkgen@ff760000 timer@ff710000 pll0 pll1 osc24M (外部晶振) 图例说明: • 蓝色:根节点,设备树顶层容器 • 绿色:总线/容器节点(cpus、soc、chosen) • 红色:时钟控制器节点(clkgen),是本章调试核心 • 橙色:PLL 子节点,时钟源;青色:外部晶振节点 • 虚线箭头:表示 clkgen 通过 clocks 属性引用外部晶振

2.6 设备树编译与反编译

设备树源文件(.dts)需编译为二进制设备树 Blob(.dtb),内核启动时由 bootloader 加载。常用工具如下:

命令 作用 示例
dtc -I dts -O dtb -o output.dtb input.dts 将 .dts 编译为 .dtb dtc -I dts -O dtb -o clk-board.dtb clk-board.dts
dtc -I dtb -O dts -o output.dts input.dtb 将 .dtb 反编译为 .dts(调试用) dtc -I dtb -O dts -o clk-board.dts clk-board.dtb
fdtdump input.dtb 以可读格式打印 .dtb 内容 fdtdump clk-board.dtb | head -50
调试技巧: 在开发板上通过 /sys/firmware/devicetree/base 可以查看内核实际使用的设备树内容。例如 cat /sys/firmware/devicetree/base/soc/clock-controller@ff760000/compatible 可验证 compatible 字符串是否与驱动匹配。

2.7 设备树在时钟驱动中的关键属性

时钟控制器节点除了 reg 和 interrupts 外,还包含以下时钟专用属性:

属性 含义 示例值
#clock-cells 时钟输出索引的编码单元数(0=单输出,1=多输出) <1>
clocks 引用该时钟控制器的输入时钟源(phandle 列表) <&osc24M &pll_ref>
clock-names 输入时钟的名称列表,与 clocks 一一对应 "osc", "pll_ref"
clock-frequency 固定频率时钟的默认频率值(单位 Hz) <24000000>
assigned-clocks 由 bootloader 或内核初始化的时钟列表 <&clkgen 0 &clkgen 1>
assigned-clock-rates 与 assigned-clocks 对应的目标频率 <500000000 200000000>
// 完整时钟控制器节点示例
clkgen: clock-controller@ff760000 {
    compatible = "vendor,clk-gen";
    reg = <0xff760000 0x1000>;
    interrupts = <0 31 4>;
    #clock-cells = <1>;
    clocks = <&osc24M>;
    clock-names = "osc";
    assigned-clocks = <&clkgen 0>, <&clkgen 1>;
    assigned-clock-rates = <500000000>, <200000000>;
};
常见错误: #clock-cells 的值必须与驱动中 clk_register 时的输出索引编码一致。若驱动注册了多个时钟输出但 #clock-cells = <0>,消费者节点将无法指定具体时钟索引,导致 clk_get 失败。反之,若只有单输出却设置 #clock-cells = <1>,消费者必须提供索引参数,增加不必要的复杂度。

2.8 设备树覆盖与中断映射流程

以下 SVG 图展示设备树中断映射的完整流程,从硬件中断源到 CPU 异常处理:

设备树中断映射流程 硬件中断源 设备树节点 interrupts = <0 31 4> 中断控制器 (GIC) #interrupt-cells = <3> Linux 中断子系统 irq_domain 映射 时钟驱动 devm_request_irq(irq, handler) 中断映射关键步骤: 1. 设备树声明 interrupts 属性(类型、中断号、触发标志) 2. 内核解析 interrupt-parent 找到中断控制器 3. irq_domain 将硬件中断号映射为 Linux IRQ 号 4. 驱动通过 platform_get_irq() 获取 IRQ 号并注册处理函数 5. 触发类型(上升沿/电平)由 interrupts 第3个单元控制

2.9 本章小结

  • 设备树核心三要素: 节点(Node)、属性(Property)、值(Value)。节点描述硬件单元,属性描述资源,值提供具体数据。
  • reg 属性: 由 #address-cells 和 #size-cells 决定编码,时钟驱动中用于获取寄存器基地址。
  • interrupts 属性: 与中断控制器配合,通过 #interrupt-cells 定义格式,时钟驱动中用于注册中断处理函数。
  • 时钟专用属性: #clock-cells、clocks、clock-names、assigned-clocks 是 clk 框架与设备树绑定的关键接口。
  • 调试入口: /sys/firmware/devicetree/base 提供运行时设备树快照,dtc 工具支持编译/反编译。