3、设备树(DT)语法入门:节点、属性、reg、interrupts、compatible等核心语法
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来向操作系统(尤其是Linux内核)传递板级硬件信息。在网关驱动加载流程中,内核通过解析设备树来发现CAN控制器、GPIO、时钟、中断控制器等外设,并完成驱动与设备的绑定。本节将系统讲解设备树的核心语法,包括节点定义、属性类型、地址编码、中断映射以及兼容性匹配规则。
3.1 设备树节点(Node)基础
设备树由一系列节点(Node)组成,每个节点代表一个硬件设备、总线或控制器。节点通过路径(Path)唯一标识,根节点为“/”。节点可以包含子节点和属性。
节点定义语法:
node-name@unit-address {
property-name = <value>;
property-name = "string";
child-node {
...
};
};
- node-name:节点名称,通常为设备类型或实例名,如
can0、i2c1。 - unit-address:可选,用于区分同名的节点,通常为寄存器基地址或设备地址。
- 属性(Property):键值对,描述设备的配置信息。
/ {
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" |
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。
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
};
};
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@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"。
3.6 其他常用属性
| 属性 | 用途 | CAN控制器场景 |
|---|---|---|
| clock-frequency | 指定设备工作时钟频率 | CAN控制器需要知道时钟频率以计算波特率 |
| pinctrl-names / pinctrl-0 | 引脚复用配置 | 将CAN TX/RX引脚配置为CAN功能 |
| status | 设备启用状态 | "okay" 启用CAN控制器,"disabled" 禁用 |
| label | 设备标签,用于用户空间识别 | "can0" 或 "can1" |
| max-speed | 最大通信速率 | 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
compatible 是否与驱动匹配,其次检查 status 是否为 "okay",最后确认 reg 和 interrupts 地址是否正确。使用 dtc 反编译内核使用的 .dtb 文件,验证实际加载的设备树内容。
3.8 本章小结
设备树语法是嵌入式Linux驱动开发的基础。通过本节学习,你应该掌握:
- 设备树节点的定义和层级结构。
- 核心属性
compatible、reg、interrupts的语法和用途。 - 地址编码规则(
#address-cells、#size-cells)和中断映射方法。 - 驱动如何通过
compatible属性与设备树节点匹配。 - 设备树的编译、反编译和调试工具。