2. 设备树基础:语法、节点与属性详解
设备树(Device Tree,DT)是嵌入式 Linux 系统中描述硬件拓扑结构的核心数据格式。DRM 显示驱动通过设备树获取显示控制器、接口、面板等硬件的地址、中断、时钟、复位等资源信息。本节从语法、节点、属性、匹配机制和关键属性五个维度展开,为后续显示链路调试打下基础。
学习目标:掌握设备树源文件(.dts)的语法规则;理解节点与属性的层级关系;熟悉 compatible 匹配机制;能够正确解析 reg 和 interrupts 属性在显示驱动中的应用。
2.1 设备树语法基础
设备树源文件(.dts)采用树形结构描述硬件,每个硬件设备对应一个节点,节点包含若干属性。语法规则如下:
- 节点定义:
node-name@unit-address,其中 unit-address 通常为寄存器基地址。 - 属性格式:
property-name = <value>;或property-name = "string"; - 标签(label):用于引用节点,如
display: display-controller@13800000。 - 包含文件:
#include "soc.dtsi"引入 SoC 级设备树包含文件。
// 一个典型的显示控制器节点示例
display: display-controller@13800000 {
compatible = "vendor,display-controller", "simple-bus";
reg = <0x0 0x13800000 0x0 0x1000>;
interrupts = <0 42 4>;
clocks = <&clk DISPLAY_CLK>;
status = "okay";
port {
#address-cells = <1>;
#size-cells = <0>;
display_out: endpoint@0 {
reg = <0>;
remote-endpoint = <&hdmi_in>;
};
};
};
提示:设备树中的
#address-cells 和 #size-cells 决定了 reg 属性中地址和长度的字长(32 位或 64 位)。对于 64 位地址空间,通常设置为 <2> 和 <2>。
2.2 节点与属性详解
设备树节点是硬件设备的抽象表示,属性则描述设备的资源配置。下表列出显示驱动中常见的节点类型和关键属性:
| 节点类型 | 典型节点名 | 关键属性 | 说明 |
|---|---|---|---|
| 显示控制器 | display-controller@13800000 | compatible, reg, interrupts, clocks, ports | 核心显示硬件单元,负责图像合成与输出 |
| 显示接口 | hdmi@13810000 | compatible, reg, interrupts, ddc-i2c-bus | HDMI/DP/LVDS/MIPI DSI 等物理接口 |
| 显示面板 | panel@0 | compatible, enable-gpios, backlight, display-timings | LCD/OLED 面板,定义时序和电源控制 |
| 时钟控制器 | clock-controller@10000000 | compatible, reg, #clock-cells | 提供显示相关时钟源 |
| 端口/端点 | port, endpoint@0 | reg, remote-endpoint | 描述显示链路的数据流连接关系 |
注意:节点名中的 unit-address(如
@13800000)必须与 reg 属性中的首地址一致,否则设备树编译器(dtc)会报地址不匹配警告。
2.3 compatible 匹配机制
compatible 属性是设备树与内核驱动绑定的关键。内核通过设备驱动模型中 of_match_table 进行匹配,匹配顺序遵循“最具体优先”原则。
匹配流程如下:
- 内核解析设备树,提取每个节点的
compatible字符串列表。 - 遍历已注册的平台驱动,比较驱动的
of_match_table与节点的compatible。 - 按字符串列表顺序匹配,第一个匹配成功的驱动获得绑定。
- 匹配成功后调用驱动的
probe函数,传入struct platform_device。
// 设备树节点中的 compatible
compatible = "vendor,display-controller-v2", "vendor,display-controller", "simple-bus";
// 内核驱动中的 of_match_table
static const struct of_device_id display_of_match[] = {
{ .compatible = "vendor,display-controller-v2", .data = &v2_data },
{ .compatible = "vendor,display-controller", .data = &v1_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, display_of_match);
static struct platform_driver display_driver = {
.probe = display_probe,
.remove = display_remove,
.driver = {
.name = "vendor-display",
.of_match_table = display_of_match,
},
};
最佳实践:compatible 字符串应遵循“厂商,型号”格式,版本号放在最后。内核优先匹配列表中的第一个字符串,因此将最精确的版本放在最前面。
2.4 reg 属性详解
reg 属性描述设备占用的地址空间,格式为 <address length> 的序列。地址和长度的字长由父节点的 #address-cells 和 #size-cells 决定。
| #address-cells | #size-cells | reg 格式示例 | 地址空间 |
|---|---|---|---|
| 1 | 1 | reg = <0x13800000 0x1000>; |
32 位地址,32 位长度 |
| 2 | 2 | reg = <0x0 0x13800000 0x0 0x1000>; |
64 位地址,64 位长度 |
| 1 | 0 | reg = <0x42>; |
仅地址,无长度(如 I2C 地址) |
在 DRM 驱动中,reg 属性通常映射为 struct resource,通过 platform_get_resource() 获取:
static int display_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
// 获取第 0 个 reg 段
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
// 将物理地址映射到内核虚拟地址空间
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 后续通过 base 访问显示控制器寄存器
dev_info(&pdev->dev, "display controller at 0x%llx mapped\n",
(unsigned long long)res->start);
return 0;
}
关键点:显示控制器通常需要多个 reg 段(如控制寄存器、帧缓冲器、gamma 表等),通过索引 0、1、2 分别获取。务必核对芯片手册中的地址范围与设备树中的 reg 值一致。
2.5 interrupts 属性详解
interrupts 属性描述设备的中断资源,格式依赖于中断控制器的 #interrupt-cells。ARM GIC(Generic Interrupt Controller)是最常见的中断控制器,其 #interrupt-cells = <3>。
| 中断域 | #interrupt-cells | interrupts 格式 | 说明 |
|---|---|---|---|
| ARM GIC | 3 | <0 42 4> |
0=SPI, 42=中断号, 4=触发类型 |
| RISC-V PLIC | 1 | <42> |
仅中断号 |
| 简单中断控制器 | 2 | <42 1> |
中断号 + 触发类型 |
ARM GIC 中断域的三个字段含义:
- 第 1 字段:中断类型,0=SPI(共享外设中断),1=PPI(私有外设中断)。
- 第 2 字段:中断号,SPI 范围为 32~1019,PPI 范围为 16~31。
- 第 3 字段:触发类型,1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发。
// 设备树中的中断定义
display-controller@13800000 {
compatible = "vendor,display-controller";
reg = <0x0 0x13800000 0x0 0x1000>;
interrupts = <0 42 4>; // SPI, 中断号42, 高电平触发
interrupt-parent = <&gic>;
};
// 内核驱动中获取中断号
static int display_probe(struct platform_device *pdev)
{
int irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, display_irq_handler,
IRQF_SHARED, "display", dev);
if (ret)
return ret;
dev_info(&pdev->dev, "registered irq %d\n", irq);
return 0;
}
常见错误:中断号在设备树中通常使用硬件中断号,而非 Linux IRQ 号。GIC 的 SPI 中断号从 32 开始计数,设备树中的 42 对应 GIC 的 SPI 42,在 Linux 中映射为 IRQ 74(42+32)。驱动中应始终使用
platform_get_irq() 获取已映射的 IRQ 号。
2.6 显示链路中的设备树实践
在 DRM 显示驱动中,设备树不仅描述单个设备,还通过 ports 和 endpoint 描述显示链路的数据流连接。以下是一个完整的显示链路设备树片段:
// 显示控制器 -> HDMI 接口 -> HDMI 连接器
display-controller@13800000 {
compatible = "vendor,display-controller";
reg = <0x0 0x13800000 0x0 0x1000>;
interrupts = <0 42 4>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
dc_out_hdmi: endpoint {
remote-endpoint = <&hdmi_in_dc>;
};
};
};
};
hdmi@13810000 {
compatible = "vendor,hdmi-tx";
reg = <0x0 0x13810000 0x0 0x1000>;
interrupts = <0 43 4>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
hdmi_in_dc: endpoint {
remote-endpoint = <&dc_out_hdmi>;
};
};
port@1 {
reg = <1>;
hdmi_out_con: endpoint {
remote-endpoint = <&con_in_hdmi>;
};
};
};
};
hdmi-connector {
compatible = "hdmi-connector";
type = "a";
port {
con_in_hdmi: endpoint {
remote-endpoint = <&hdmi_out_con>;
};
};
};
调试技巧:使用
dtc -I dtb -O dts 反编译设备树二进制文件,检查 remote-endpoint 引用是否正确。使用 ls /sys/firmware/devicetree/base/ 查看内核实际解析的设备树结构。