Linux platform驱动开发与设备树资源适配 - 第3节
3.1 设备树的起源与设计动机
在早期的 Linux 内核中,板级硬件描述信息(如内存基址、中断号、GPIO 映射)被直接硬编码在 arch/arm/mach-xxx 下的 C 代码中。这种“代码即配置”的方式导致每次更换硬件平台都需要重新编译内核,且不同厂商的代码高度耦合,难以维护。
核心痛点: 内核源码中充斥着大量板级
platform_device 静态定义,每增加一款开发板就要修改内核源码,违背了“内核与硬件描述分离”的设计原则。
设备树(Device Tree)正是为解决此问题而生。它由 PowerPC 架构率先采用,后被 ARM、RISC-V 等架构广泛接纳,成为描述硬件拓扑结构的标准机制。
| 阶段 | 描述方式 | 缺点 |
|---|---|---|
| 早期(Linux 2.6 ~ 3.x) | C 结构体硬编码 | 代码冗余、平台耦合、升级困难 |
| 过渡期 | 部分板级文件 + 设备树 | 兼容性差,维护成本高 |
| 现代(Linux 4.x+) | 纯设备树 + 通用驱动 | 一次编译,多板运行 |
3.2 DTS / DTC / DTB 文件格式
设备树体系由三种文件形态构成,分别对应人类可读的源文件、编译器、以及内核可解析的二进制文件。
| 文件后缀 | 全称 | 格式 | 作用 |
|---|---|---|---|
.dts |
Device Tree Source | 文本(类 C 语法) | 开发者编写硬件描述 |
.dtsi |
Device Tree Source Include | 文本 | 公共头文件,被 .dts 引用 |
.dtb |
Device Tree Blob | 二进制 | 内核 Bootloader 加载的最终产物 |
.dtbo |
Device Tree Blob Overlay | 二进制 | 运行时叠加片段(热插拔场景) |
编译流程:
.dts + .dtsi → DTC(Device Tree Compiler) → .dtb。DTC 工具位于内核源码 scripts/dtc/ 目录下,也可通过 dtc 命令单独安装。
3.3 设备树语法详解
3.3.1 节点(Node)
设备树的基本组成单元是节点,每个节点代表一个硬件设备或总线。节点采用树形层级结构,根节点为 /。
/dts-v1/;
/ {
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";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
节点命名规则:
节点名@单元地址。其中“单元地址”通常对应设备在总线上的基址,用于区分同一名称下的多个实例(如 cpu@0、cpu@1)。
3.3.2 属性(Property)
属性是节点的键值对,用于描述设备的配置参数。常见属性类型如下:
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "ti,am3352-uart"; |
驱动匹配标识,格式为 厂商,型号 |
| 32 位整数 | reg = <0x44e09000 0x2000>; |
地址 + 长度,可包含多个 <> |
| 64 位整数 | reg = <0x00000000 0x80000000 0x00000000 0x20000000>; |
高 32 位 + 低 32 位组合 |
| 布尔值 | status = "okay"; 或 status = "disabled"; |
字符串形式表示开关 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
十六进制字节序列 |
| 字符串列表 | compatible = "ti,am3352-uart", "ti,omap3-uart"; |
逗号分隔,优先级从高到低 |
3.3.3 标签(Label)与引用
标签为节点提供一个全局别名,方便在其他位置引用。标签定义在节点名之前,使用 : 分隔。
// 定义标签
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
// 引用标签(追加属性)
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
};
标签的典型用途: 在
.dtsi 头文件中定义基础节点,在 .dts 文件中通过 &标签 引用并追加板级特有属性(如引脚复用、时钟频率)。这种机制实现了 SoC 公共描述与板级定制描述的分离。
3.4 关键属性详解
3.4.1 compatible —— 驱动匹配的核心
compatible 是设备树中最重要的属性,它建立了硬件节点与内核驱动之间的桥梁。内核通过 of_match_table 匹配该属性。
// 设备树节点
i2c0: i2c@44e0b000 {
compatible = "ti,am3352-i2c", "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
// 内核驱动匹配表
static const struct of_device_id i2c_omap_of_match[] = {
{ .compatible = "ti,omap4-i2c", },
{ .compatible = "ti,am3352-i2c", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_omap_of_match);
匹配优先级: 内核遍历
compatible 字符串列表时,从左到右依次匹配。通常将最具体的型号放在最左侧,通用型号放在右侧作为 fallback。
3.4.2 reg —— 地址与内存映射
reg 属性描述设备的地址空间,由 #address-cells 和 #size-cells 决定每个地址和长度的单元数。
| 父节点属性 | 含义 | reg 示例 | 解析结果 |
|---|---|---|---|
#address-cells = <1>; #size-cells = <1>; |
32 位地址 + 32 位长度 | reg = <0x44e09000 0x2000>; |
基址 0x44e09000,长度 0x2000 |
#address-cells = <2>; #size-cells = <1>; |
64 位地址 + 32 位长度 | reg = <0x00000000 0x80000000 0x20000000>; |
基址 0x80000000,长度 0x20000000 |
#address-cells = <1>; #size-cells = <0>; |
仅地址,无长度 | reg = <0x0>; |
CPU 核心编号 0 |
3.4.3 interrupts —— 中断描述
中断属性需要配合 interrupt-parent 和 #interrupt-cells 使用。典型的中断控制器(如 GIC)使用 3 个 cell:中断类型、中断号、触发方式。
// 中断控制器定义
gic: interrupt-controller@48241000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x48241000 0x1000>;
};
// 外设节点引用中断
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <0 72 4>; // GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH
interrupt-parent = <&gic>;
};
中断 cell 含义(ARM GIC 标准): 第一个 cell 为 0(SPI 共享外设中断)或 1(PPI 私有外设中断);第二个 cell 为中断号;第三个 cell 为触发类型(1=上升沿,2=下降沿,4=高电平,8=低电平)。
3.5 设备树实战要点
- 复用 .dtsi: SoC 厂商提供
.dtsi文件定义公共 IP 核,板级.dts通过#include引入并覆盖或追加属性。 - status 属性: 默认设为
"disabled",板级文件中通过&节点 { status = "okay"; };启用。 - pinctrl 绑定: 引脚复用通过
pinctrl-0和pinctrl-names属性关联,引用 pin controller 节点中的配置。 - 编译验证: 使用
dtc -I dts -O dtb -o test.dtb test.dts编译,用dtc -I dtb -O dts test.dtb反编译检查。
学习建议: 从内核源码
arch/arm/boot/dts/ 目录下找一个实际 .dts 文件(如 am335x-boneblack.dts),对照本节的语法规则逐行分析,是掌握设备树最有效的方法。