2. 设备树基础语法:节点、属性、标签、包含文件与 dts/dtsi 文件结构
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来描述系统中所有可寻址设备。在 Linux 内核中,设备树是 Platform 驱动与硬件解耦的核心媒介。掌握设备树的基础语法,是进行驱动移植的第一步。
2.1 节点(Node)与根节点
设备树由一系列节点组成,每个节点代表一个硬件设备或总线。节点从根节点 / 开始,形成树状层级结构。每个节点可以包含子节点和属性。
节点命名规则: 节点名称格式为
node-name@unit-address,其中 unit-address 通常为设备在总线上的起始地址或寄存器基址,用于区分同名节点。
/ {
#address-cells = <1>;
#size-cells = <1>;
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
device_type = "cpu";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
};
上述示例中:
/是根节点,包含整个系统的描述。cpus节点描述 CPU 拓扑,其子节点cpu@0描述具体 CPU 核心。memory@80000000节点描述物理内存区域。
2.2 属性(Property)
属性是节点的键值对,用于描述设备的特性。属性值可以是字符串、整数数组、字节数组或布尔值。以下是驱动开发中最常用的属性:
| 属性名 | 值类型 | 说明 | 示例 |
|---|---|---|---|
compatible |
字符串列表 | 设备兼容性标识,驱动匹配的关键字段 | "ti,am335x-uart", "ns16550" |
reg |
整数数组 | 设备地址空间(基址 + 长度) | <0x48020000 0x1000> |
interrupts |
整数数组 | 中断号与触发类型 | <0x1c 0x4> |
clocks |
phandle 数组 | 时钟源引用 | <&clk_uart1> |
status |
字符串 | 设备状态:"okay" 或 "disabled" |
"okay" |
#address-cells |
整数 | 子节点 reg 地址字段的 32 位字数量 | <1> |
#size-cells |
整数 | 子节点 reg 长度字段的 32 位字数量 | <1> |
关键提示:
compatible 属性是驱动与设备匹配的“身份证”。内核通过 of_match_table 中的 compatible 字符串与设备树节点的 compatible 属性进行比对,匹配成功则调用驱动的 probe 函数。
2.3 标签(Label)与引用
标签(Label)是节点的别名,用于在设备树的其他位置引用该节点。标签以冒号结尾,放在节点名称之前。通过 &label 语法可以引用标签,实现节点内容的追加或覆盖。
/* 定义标签 */
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ns16550";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled";
};
/* 在其他 dtsi 文件中通过标签引用并修改 */
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
标签机制的优势:
- 解耦: SoC 级 dtsi 定义节点骨架,板级 dts 通过标签覆盖具体配置。
- 复用: 同一 SoC 的不同板卡可以共享 dtsi,仅通过标签差异化配置。
- 可读性: 避免重复编写完整节点路径,减少错误。
2.4 包含文件(Include)与 dts/dtsi 文件结构
设备树源文件分为两种扩展名:
- .dts(Device Tree Source):板级设备树文件,通常包含具体板卡的硬件描述。
- .dtsi(Device Tree Source Include):通用设备树包含文件,通常描述 SoC 内部外设、CPU 架构等公共部分。
通过 #include 预处理指令,可以将 dtsi 文件包含到 dts 中,形成层次化的文件结构。
/* am335x-boneblack.dts */
#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
&mmc1 {
vmmc-supply = <&vmmc_reg>;
status = "okay";
};
&am33xx_pinmux {
uart0_pins: pinmux_uart0_pins {
pinctrl-single,pins = <
0x170 (PIN_INPUT | MUX_MODE0) /* uart0_rxd */
0x174 (PIN_OUTPUT | MUX_MODE0) /* uart0_txd */
>;
};
};
典型的设备树文件层级结构如下:
2.5 节点地址编码:reg、address-cells 与 size-cells
设备树使用 reg 属性描述设备的地址空间。其格式由父节点的 #address-cells 和 #size-cells 决定:
#address-cells:表示reg中地址字段占用的 32 位整数个数。#size-cells:表示reg中长度字段占用的 32 位整数个数。
/* 32 位地址空间,1 个地址 + 1 个长度 */
bus@10000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x10000000 0x10000>; /* 基址 0x10000000,长度 0x10000 */
device@1000 {
reg = <0x1000 0x200>; /* 偏移 0x1000,长度 0x200 */
};
};
/* 64 位地址空间,2 个地址 + 1 个长度 */
bus@0 {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <1>;
reg = <0x0 0x40000000 0x10000>; /* 高32位 0x0,低32位 0x40000000,长度 0x10000 */
device@0,40001000 {
reg = <0x0 0x40001000 0x1000>;
};
};
常见错误: 忘记设置
#address-cells 和 #size-cells 会导致内核解析 reg 属性时出现地址错位。对于 simple-bus 节点,这两个属性必须显式声明。
2.6 中断属性与中断控制器
设备树通过 interrupt-parent、interrupts 和 interrupt-controller 描述中断拓扑:
/ {
interrupt-parent = <&gic>; /* 全局默认中断控制器 */
gic: interrupt-controller@48241000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x48241000 0x1000>,
<0x48242000 0x2000>;
};
uart1: serial@44e09000 {
compatible = "ti,am3352-uart", "ns16550";
reg = <0x44e09000 0x2000>;
interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&gic>;
};
};
中断属性关键点:
interrupt-controller;声明该节点为中断控制器。#interrupt-cells指定interrupts属性中每个中断描述占用的单元数(通常为 3:中断类型、中断号、触发标志)。interrupt-parent指定设备连接的中断控制器,若未指定则继承父节点或根节点的设置。
2.7 设备树编译与反编译
设备树源文件需要通过编译器生成二进制设备树 Blob(.dtb),内核在启动时解析 dtb 并构建 device_node 树。
| 命令 | 说明 | 示例 |
|---|---|---|
dtc -I dts -O dtb -o output.dtb input.dts |
将 .dts 编译为 .dtb | dtc -I dts -O dtb -o am335x-boneblack.dtb am335x-boneblack.dts |
dtc -I dtb -O dts -o output.dts input.dtb |
将 .dtb 反编译为 .dts | dtc -I dtb -O dts -o am335x-boneblack.dts am335x-boneblack.dtb |
dtc -I dts -O dtb -@ -o output.dtb input.dts |
编译时保留符号(支持插件覆盖) | dtc -I dts -O dtb -@ -o output.dtb input.dts |
调试技巧: 使用
dtc -I fs -O dts /proc/device-tree 可以实时查看内核当前使用的设备树内容,用于验证驱动解析是否正确。
2.8 设备树语法速查表
2.9 实战案例:解析一个 UART 节点
以下是一个完整的 UART 设备树节点及其对应的驱动解析流程:
/* 设备树节点 */
uart2: serial@48022000 {
compatible = "ti,am3352-uart", "ns16550";
reg = <0x48022000 0x2000>;
interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&gic>;
clocks = <&uart2_clk>;
clock-names = "fck";
status = "okay";
};
驱动中对应的属性解析代码:
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
static int my_uart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource res;
int irq;
/* 1. 解析 reg 属性:获取 IO 内存资源 */
if (of_address_to_resource(np, 0, &res)) {
dev_err(dev, "failed to get reg resource\n");
return -EINVAL;
}
/* 2. 解析 interrupts 属性:获取中断号 */
irq = of_irq_get(np, 0);
if (irq < 0) {
dev_err(dev, "failed to get irq\n");
return irq;
}
/* 3. 解析 clocks 属性:获取时钟句柄 */
struct clk *clk = of_clk_get(np, 0);
if (IS_ERR(clk)) {
dev_err(dev, "failed to get clock\n");
return PTR_ERR(clk);
}
/* 4. 解析 status 属性:检查设备是否启用 */
if (!of_device_is_available(np)) {
dev_info(dev, "device is disabled\n");
return -ENODEV;
}
dev_info(dev, "UART probe success: reg=0x%llx, irq=%d\n",
res.start, irq);
return 0;
}
核心要点: 设备树语法是驱动移植的“硬件描述语言”。节点定义硬件拓扑,属性描述设备参数,标签实现配置复用,dtsi 文件实现 SoC 级抽象。驱动开发者需要熟练掌握
compatible、reg、interrupts、clocks 等核心属性的解析方法,这是后续章节中 Platform 驱动与设备树交互的基础。