2. 设备树节点与属性:详解节点(node)、属性(property)、标准属性(compatible、reg、interrupts等)的编写规范
设备树(Device Tree,DT)是描述硬件拓扑结构和配置信息的树形数据结构。在嵌入式Linux系统中,设备树是驱动与硬件解耦的核心桥梁。本章将深入解析设备树的节点与属性编写规范,帮助开发者掌握从硬件原理图到设备树源文件(.dts)的转换方法。
2.1 节点(Node)的层次结构与编写规范
设备树由一系列节点组成,每个节点代表一个硬件设备、总线或控制器。节点从根节点“/”开始,形成树状层次结构。
2.1.1 节点命名规则
节点名称由两部分组成:名称@地址。其中“地址”通常是设备在总线上的片选号、寄存器基地址或端口号,用于区分同一名称下的多个实例。
| 组件 | 规则 | 示例 |
|---|---|---|
| 节点名称 | 通用名称,小写字母、数字、连字符 | uart, i2c, ethernet |
| 地址部分 | 十六进制地址,无0x前缀 | uart@1c28000, i2c@1000 |
| 完整节点名 | 名称@地址 | serial@44e09000 |
mmc、spi),而非厂商特定名称(如mycompany-mmc)。地址部分必须与reg属性中的第一个地址一致。
2.1.2 节点层次结构示例
以下是一个典型的设备树层次结构,展示了根节点、总线节点和设备节点的组织方式:
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
chosen {
stdout-path = &uart0;
};
ocp {
compatible = "simple-bus";
reg = <0x44e00000 0x200000>;
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ns16550a";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
clock-frequency = <48000000>;
status = "okay";
};
i2c0: i2c@44e0b000 {
compatible = "ti,am3352-i2c", "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c32";
reg = <0x50>;
pagesize = <32>;
};
};
};
};
&符号用于引用标签(label),如&uart0。标签必须在节点定义时声明(如uart0: serial@44e09000),然后在其他位置通过&uart0引用。
2.2 属性(Property)的类型与编写规范
属性是节点中存储的键值对,用于描述设备的特性。属性值可以是空、整数、字符串、字节数组或这些类型的组合。
2.2.1 属性值类型
| 类型 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 空属性 | 属性名; |
interrupt-controller; |
仅表示存在,无值 |
| 字符串 | 属性名 = "字符串"; |
compatible = "ti,am3352-uart"; |
单个字符串 |
| 字符串列表 | 属性名 = "str1", "str2"; |
compatible = "ti,am3352-uart", "ns16550a"; |
多个字符串,逗号分隔 |
| 32位整数 | 属性名 = <值>; |
reg = <0x44e09000 0x2000>; |
尖括号内为32位整数 |
| 64位整数 | 属性名 = <高32位 低32位>; |
reg = <0x0 0x44e09000 0x0 0x2000>; |
两个32位整数组合 |
| 字节数组 | 属性名 = [十六进制字节]; |
local-mac-address = [00 11 22 33 44 55]; |
方括号内为十六进制字节 |
| 混合类型 | 属性名 = <整数> "字符串"; |
gpios = <&gpio1 15 0>; |
整数和字符串组合 |
2.3 标准属性详解
设备树规范定义了一组标准属性,这些属性在几乎所有设备节点中都会出现。掌握它们的编写规范是驱动调试的基础。
2.3.1 compatible 属性:驱动匹配的关键
compatible属性是设备树与驱动之间建立关联的桥梁。它指定了设备与哪些驱动兼容,内核通过该属性查找匹配的驱动。
编写规范:
- 格式:
"厂商,型号",厂商前缀小写,型号精确 - 优先级:最精确的兼容字符串放在最前面,通用兼容字符串放在后面
- 匹配机制:内核遍历兼容字符串列表,找到第一个匹配的驱动即停止
// 正确示例:精确到具体型号,再到通用型号
compatible = "ti,am3352-uart", "ti,omap3-uart", "ns16550a";
// 错误示例:缺少厂商前缀
compatible = "uart"; // 不推荐,可能导致命名冲突
// 错误示例:顺序颠倒
compatible = "ns16550a", "ti,am3352-uart"; // 可能匹配到不精确的驱动
compatible字符串是否与驱动中的of_match_table完全匹配。注意大小写和逗号后的空格。
2.3.2 reg 属性:描述地址空间
reg属性描述设备在总线上的地址范围,包括起始地址和长度。其解析依赖于父节点的#address-cells和#size-cells属性。
地址单元与大小单元:
| 父节点属性 | 含义 | 常见值 |
|---|---|---|
#address-cells |
地址占用的32位单元数 | 1(32位地址)、2(64位地址) |
#size-cells |
大小占用的32位单元数 | 1(32位大小)、0(无大小) |
// 32位地址空间示例
parent {
#address-cells = <1>; // 地址用1个32位单元
#size-cells = <1>; // 大小用1个32位单元
device@44e09000 {
reg = <0x44e09000 0x2000>; // 起始地址0x44e09000,大小0x2000
};
};
// 64位地址空间示例
parent {
#address-cells = <2>; // 地址用2个32位单元(64位)
#size-cells = <2>; // 大小用2个32位单元(64位)
device@100000000 {
reg = <0x1 0x00000000 0x0 0x100000>; // 地址0x100000000,大小0x100000
};
};
2.3.3 interrupts 属性:中断连接描述
interrupts属性描述设备使用的中断资源。它需要与interrupt-parent属性配合使用,后者指定中断控制器节点。
中断描述格式:
- 中断号:在中断控制器中的中断索引
- 中断触发类型:高电平、低电平、上升沿、下降沿等
- 中断类型标志:如
IRQ_TYPE_EDGE_RISING、IRQ_TYPE_LEVEL_HIGH
// 中断控制器定义
intc: interrupt-controller@48200000 {
compatible = "ti,am33xx-intc";
reg = <0x48200000 0x1000>;
interrupt-controller; // 声明本节点为中断控制器
#interrupt-cells = <1>; // 每个中断描述占用1个32位单元
};
// 设备使用中断
device@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupt-parent = <&intc>; // 引用中断控制器
interrupts = <72>; // 中断号72,默认触发类型
};
// 带触发类型的中断描述(#interrupt-cells = 2)
interrupts = <72 0x4>; // 中断号72,触发类型0x4(高电平有效)
#interrupt-cells定义。
2.3.4 其他标准属性
| 属性名 | 用途 | 示例 |
|---|---|---|
status |
设备状态控制 | "okay"(启用)、"disabled"(禁用) |
model |
设备型号描述 | "TI AM335x BeagleBone Black" |
clock-frequency |
时钟频率(Hz) | <48000000> |
gpios |
GPIO引脚描述 | <&gpio1 15 0>(GPIO1_15,无标志) |
pinctrl-names |
引脚复用状态名称 | "default", "sleep" |
pinctrl-0 |
引脚复用配置引用 | &pinctrl_uart0_default |
dmas |
DMA通道描述 | &edma 0 2 |
dma-names |
DMA通道名称 | "rx", "tx" |
2.4 节点与属性的高级编写技巧
2.4.1 标签与引用
标签(label)是设备树中实现节点引用的关键机制。通过标签,可以在多个位置引用同一个节点,避免重复定义。
// 定义标签
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
};
// 在其他位置引用
&uart0 {
pinctrl-names = "default";
pinctrl-0 = &pinctrl_uart0_default;
status = "okay";
};
&标签的方式引用SoC级设备树(.dtsi)中定义的节点,而不是重新定义整个节点。这样可以保持代码的模块化和可维护性。
2.4.2 包含文件与设备树继承
设备树使用#include指令实现文件包含,通常用于将SoC公共定义(.dtsi)与板级特定配置(.dts)分离。
// am335x-boneblack.dts
#include "am33xx.dtsi" // 包含SoC级定义
#include "am335x-bone-common.dtsi" // 包含板级公共定义
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
// 板级特定配置
leds {
pinctrl-names = "default";
pinctrl-0 = &user_leds_s0;
led2 {
label = "beaglebone:green:heartbeat";
gpios = &gpio1 21 GPIO_ACTIVE_HIGH;
linux,default-trigger = "heartbeat";
};
};
};
// 覆盖SoC级节点配置
&uart0 {
status = "okay";
};
2.4.3 属性覆盖与追加
在设备树中,子节点可以覆盖父节点中定义的属性,也可以追加新的属性。这是实现板级定制的核心机制。
| 操作 | 语法 | 效果 |
|---|---|---|
| 覆盖属性 | &节点 { 属性 = 新值; }; |
完全替换原属性值 |
| 追加属性 | &节点 { 新属性 = 值; }; |
添加原节点不存在的属性 |
| 追加子节点 | &节点 { 子节点 { }; }; |
在已有节点下添加子节点 |
| 删除属性 | /delete-property/ 属性名; |
删除继承的属性 |
| 删除节点 | /delete-node/ &标签; |
删除继承的节点 |
// 示例:覆盖与追加
// am33xx.dtsi 中定义
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled"; // SoC级默认禁用
};
// am335x-boneblack.dts 中覆盖
&uart0 {
status = "okay"; // 板级启用
pinctrl-names = "default"; // 追加属性
pinctrl-0 = &pinctrl_uart0_default;
};
=赋值,会完全替换原属性。如果原属性是字符串列表,新值必须包含所有需要的字符串,否则会导致兼容字符串丢失。
2.5 设备树编译与调试
编写完设备树源文件(.dts)后,需要编译为设备树二进制文件(.dtb)供内核使用。掌握编译和反编译工具是调试设备树问题的基本技能。
2.5.1 编译与反编译命令
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o output.dtb input.dts
# 反编译 .dtb 为 .dts
dtc -I dtb -O dts -o output.dts input.dtb
# 检查设备树语法
dtc -I dts -O dtb -o /dev/null input.dts # 只检查语法,不生成文件
# 生成设备树符号表
dtc -I dts -O dtb -@ -o output.dtb input.dts # -@ 生成符号表,支持插件
2.5.2 常见编译错误与解决方法
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
ERROR: Input tree has errors |
语法错误或属性值格式错误 | 检查括号、分号、引号是否匹配 |
WARNING: unit name should not have a unit-address |
节点名称包含地址但无reg属性 | 添加reg属性或移除地址部分 |
ERROR: phandle 0x... not found |
引用的标签不存在 | 检查标签定义和引用是否一致 |
ERROR: Missing property '#address-cells' |
总线节点缺少地址单元定义 | ← 上一章 📖 返回目录 下一章 → |