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、外设、外部晶振的关系:
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 异常处理:
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工具支持编译/反编译。