2. 设备树基础:语法、关键属性与编译
设备树(Device Tree,DT)是描述硬件拓扑与资源分配的一种结构化数据格式,在 ARM、RISC-V 等非 x86 平台中取代了传统的 board-*.c 硬编码方式。KMS 显示驱动依赖设备树来获取显示控制器、HDMI/DP 编码器、I2C 通道、时钟与复位信号等硬件信息。本章从语法、节点/属性、关键属性及编译工具四个维度展开。
学习目标:掌握设备树源文件(.dts)的基本结构,能够独立编写或修改与显示驱动相关的设备树节点,并熟练使用 dtc 工具进行编译与反编译。
2.1 设备树语法基础
设备树源文件采用树形结构,以 /dts-v1/; 开头,根节点 / 包含所有子节点。每个节点代表一个硬件设备或总线,节点内包含若干属性(property)描述资源。
/dts-v1/;
/ {
model = "Example ARM Board";
compatible = "vendor,board-name";
#address-cells = <1>;
#size-cells = <1>;
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
display_controller: display@1e00000 {
compatible = "vendor,display-controller";
reg = <0x01e00000 0x1000>;
interrupts = <0 42 4>;
clocks = <&clk 5>;
status = "okay";
};
};
};
关键语法规则:
- 节点名称:格式为
node-name@unit-address,其中unit-address通常为寄存器基址。 - 标签(label):如
display_controller:,用于在其它节点中引用。 - 属性值:支持
<u32>数组、"字符串"、[byte 序列]、&phandle引用。 - 包含文件:使用
#include "soc.dtsi"引入 .dtsi 头文件,实现 SoC 级描述复用。
显示驱动常见节点层级:根 → soc → display-controller → hdmi-encoder → i2c-bus(用于 EDID 读取)→ backlight → panel。
2.2 节点与属性
设备树中每个节点由一对花括号 { } 界定,内部包含属性与子节点。属性是键值对,值类型决定硬件资源的描述方式。
| 属性名 | 值类型 | 说明 | 显示驱动示例 |
|---|---|---|---|
compatible |
字符串列表 | 设备驱动匹配标识,格式 "厂商,设备型号" |
"rockchip,rk3399-vop" |
reg |
<u32> 数组 |
寄存器地址与长度,按 (address, length) 对排列 |
<0x1e00000 0x1000> |
interrupts |
<u32> 数组 |
中断号、触发类型(依赖 interrupt-parent) |
<0 42 4>(SPI 42, 高电平触发) |
clocks |
phandle + 索引 | 时钟源引用,可带 clock-names 辅助识别 |
<&clk 5> |
status |
字符串 | 设备启用状态:"okay" / "disabled" |
"okay" |
pinctrl-* |
phandle 列表 | 引脚复用配置,通常配合 pinctrl-names |
<&pinctrl_lcd> |
常见错误:忘记设置
#address-cells 和 #size-cells 会导致 reg 解析错误;interrupt-parent 缺失会导致中断无法路由到 GIC。
2.3 关键属性详解
2.3.1 compatible —— 驱动匹配的钥匙
内核通过 of_match_table 将设备树节点的 compatible 值与驱动结构体匹配。显示驱动中通常使用两级匹配:
static const struct of_device_id vop_dt_match[] = {
{ .compatible = "rockchip,rk3399-vop" },
{ .compatible = "rockchip,rk3288-vop" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, vop_dt_match);
2.3.2 reg —— 地址空间描述
reg 属性由 #address-cells 和 #size-cells 决定每个地址/长度对的宽度。对于 32 位 SoC,通常为 <1 1>;对于 64 位 SoC,可能为 <2 2>。
display@1e00000 {
reg = <0x01e00000 0x1000>; /* 基址 0x01e00000,长度 0x1000 */
};
2.3.3 interrupts —— 中断资源
中断描述依赖 interrupt-parent 指向中断控制器节点。ARM GIC 中断类型:
- SPI(Shared Peripheral Interrupt):类型值 0,用于外设,中断号范围 32~1019。
- PPI(Private Peripheral Interrupt):类型值 1,用于 CPU 本地中断。
- 触发类型:1=上升沿,2=下降沿,4=高电平,8=低电平。
interrupt-parent = <&gic>;
interrupts = <0 42 4>; /* SPI 42, 高电平触发 */
2.3.4 clocks / resets —— 时钟与复位
显示驱动通常需要多个时钟源(像素时钟、总线时钟、AHB 时钟)和复位信号。使用 clock-names 和 reset-names 提高可读性:
clocks = <&cru PCLK_HDMI>, <&cru SCLK_HDMI>;
clock-names = "pclk", "sclk";
resets = <&cru SRST_HDMI>;
reset-names = "hdmi";
2.4 设备树编译与反编译
设备树编译器(dtc)将 .dts 源文件编译为 .dtb 二进制文件,也可将 .dtb 反编译为 .dts 用于调试。
| 命令 | 说明 | 示例 |
|---|---|---|
dtc -I dts -O dtb -o output.dtb input.dts |
编译 .dts → .dtb | dtc -I dts -O dtb -o rk3399.dtb rk3399.dts |
dtc -I dtb -O dts -o output.dts input.dtb |
反编译 .dtb → .dts | dtc -I dtb -O dts -o rk3399.dts rk3399.dtb |
dtc -I dtb -O dts -o output.dts input.dtb -@ |
反编译时保留插件符号 | 用于调试 overlay 场景 |
fdtdump input.dtb |
快速打印 .dtb 结构 | fdtdump rk3399.dtb | head -50 |
实战技巧:在开发板上使用
dtc -I fs -O dts /sys/firmware/devicetree/base 可以从运行中的内核导出当前设备树,用于对比实际硬件配置与 .dts 源文件是否一致。
2.5 设备树包含与覆盖(Overlay)
实际项目中,SoC 厂商提供 .dtsi 头文件描述芯片内部外设,板级 .dts 文件通过 #include 引入并覆盖或添加节点。显示驱动常需要根据具体面板型号调整时序参数。
// rk3399.dtsi (SoC 级)
#include "rk3399-clocks.dtsi"
&display_controller {
compatible = "rockchip,rk3399-vop";
reg = <0x1e00000 0x1000>;
interrupts = <0 42 4>;
status = "disabled"; // 默认关闭
};
// my-board.dts (板级)
#include "rk3399.dtsi"
&display_controller {
status = "okay"; // 板级启用
rockchip,panel = <&panel>;
};
&i2c4 {
panel: panel@36 {
compatible = "boe,nv140fhm-n62";
reg = <0x36>;
enable-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
};
};
Overlay 机制:Linux 4.4+ 支持设备树覆盖(.dtbo),允许在运行时动态加载/卸载设备节点,常用于 FPGA 动态重配置或可插拔显示模块。