4. DRM驱动与Device Tree的绑定:如何通过Device Tree匹配DRM驱动、platform_driver与of_match_table的使用
在嵌入式Linux系统中,DRM/KMS显示驱动通常以platform_driver的形式存在,并通过Device Tree(设备树)完成硬件描述与驱动匹配。本章将深入剖析DRM驱动与Device Tree的绑定机制,重点讲解of_match_table在驱动匹配中的核心作用,以及如何编写兼容的设备树节点。
4.1 驱动匹配的核心机制:platform_driver与of_match_table
DRM显示驱动在Linux内核中注册为platform_driver,其匹配过程依赖于of_match_table。当内核启动时,Device Tree中的每个节点都会被解析为platform_device,而驱动则通过of_match_table中的compatible字符串与设备节点进行匹配。
of_match_table是一个指向of_device_id结构体数组的指针,每个条目包含一个compatible字符串和一个可选的data指针。内核通过比较设备树节点中的compatible属性与驱动表中的字符串,决定是否绑定驱动。
4.1.1 标准匹配流程
- 设备树解析: 内核启动时,DTB(Device Tree Blob)被解析,每个节点生成一个
platform_device。 - 驱动注册: DRM驱动调用
platform_driver_register(),将of_match_table注册到内核。 - 匹配检查: 内核遍历已注册的
platform_device,将设备节点的compatible属性与驱动的of_match_table逐一比对。 - 绑定与probe: 匹配成功后,内核调用驱动的
probe()函数,传入匹配的platform_device指针。
4.2 of_match_table 结构详解
of_match_table是驱动与设备树绑定的桥梁,其核心数据结构为struct of_device_id。以下表格展示了该结构体的关键字段及其用途:
| 字段 | 类型 | 说明 | 示例值 |
|---|---|---|---|
name |
const char * |
设备名称(较少使用,通常置NULL) | NULL |
type |
const char * |
设备类型(较少使用,通常置NULL) | NULL |
compatible |
const char * |
核心字段:与设备树节点的compatible属性匹配 |
"rockchip,rk3399-dp" |
data |
const void * |
指向私有数据的指针,用于传递平台特定配置 | &rk3399_dp_data |
4.2.1 典型驱动代码示例
以下是一个DRM显示驱动的of_match_table定义示例,展示了如何为不同硬件变体提供匹配条目:
static const struct of_device_id dp_driver_of_match[] = {
{
.compatible = "rockchip,rk3399-dp",
.data = &rk3399_dp_config,
},
{
.compatible = "rockchip,rk3568-dp",
.data = &rk3568_dp_config,
},
{
.compatible = "rockchip,rk3588-dp",
.data = &rk3588_dp_config,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dp_driver_of_match);
MODULE_DEVICE_TABLE(of, ...)宏导出匹配表,确保驱动在模块加载时能够被热插拔机制识别。同时,data指针应指向const结构体,避免运行时修改。
4.3 设备树节点编写规范
设备树节点必须与驱动的of_match_table严格对应。以下是一个典型的DRM显示控制器节点示例:
/* 设备树节点示例:RK3399 DisplayPort控制器 */
dp0: dp@fec00000 {
compatible = "rockchip,rk3399-dp";
reg = <0x0 0xfec00000 0x0 0x4000>;
interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_DP0>, <&cru SCLK_DP0>;
clock-names = "pclk", "sclk";
power-domains = <&power RK3399_PD_DP0>;
phys = <&dp0_phy>;
phy-names = "dp";
status = "disabled";
};
4.3.1 节点属性与驱动字段映射
| 设备树属性 | 驱动获取方式 | 说明 |
|---|---|---|
compatible |
自动匹配 | 驱动匹配的唯一标识 |
reg |
platform_get_resource(pdev, IORESOURCE_MEM, 0) |
寄存器基地址和范围 |
interrupts |
platform_get_irq(pdev, 0) |
中断号 |
clocks |
devm_clk_get(dev, "pclk") |
时钟句柄 |
phys |
devm_phy_get(dev, "dp") |
PHY(物理层)句柄 |
4.4 probe函数中的资源获取
当驱动匹配成功后,probe()函数被调用。开发者需要从platform_device中提取设备树描述的资源,并初始化DRM子组件。以下是一个典型的probe函数框架:
static int dp_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct of_device_id *match;
const struct dp_config *config;
struct drm_device *drm;
struct dp_device *dp;
int ret;
/* 1. 获取匹配的data指针 */
match = of_match_device(dp_driver_of_match, dev);
if (!match || !match->data)
return -EINVAL;
config = match->data;
/* 2. 分配私有数据结构 */
dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
/* 3. 获取寄存器资源 */
dp->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dp->regs))
return PTR_ERR(dp->regs);
/* 4. 获取中断 */
dp->irq = platform_get_irq(pdev, 0);
if (dp->irq < 0)
return dp->irq;
/* 5. 获取时钟 */
dp->pclk = devm_clk_get(dev, "pclk");
if (IS_ERR(dp->pclk))
return dev_err_probe(dev, PTR_ERR(dp->pclk), "failed to get pclk\n");
/* 6. 初始化DRM组件 */
drm = drm_dev_alloc(&dp_driver_fops, dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
/* 7. 注册DRM设备 */
ret = drm_dev_register(drm, 0);
if (ret) {
drm_dev_put(drm);
return ret;
}
platform_set_drvdata(pdev, dp);
return 0;
}
of_match_table的compatible字符串进行条件判断,而不是使用data指针。这会导致代码难以扩展,每次新增硬件变体都需要修改probe函数。正确的做法是将硬件差异封装在data指向的结构体中。
4.5 多平台适配策略
当同一驱动需要支持多个SoC平台时,of_match_table的data字段成为关键。以下是一个多平台适配的架构图:
4.6 匹配优先级与回退机制
内核在匹配of_match_table时,遵循以下优先级规则:
- 精确匹配: 设备树节点的
compatible属性与驱动表中的字符串完全一致。 - 通配符匹配: 驱动表支持使用
NULL作为通配符,但实际使用较少。 - 别名匹配: 设备树节点可以包含多个
compatible值,内核按顺序尝试匹配。
compatible = "rockchip,rk3399-dp", "rockchip,dp-common";,内核会先尝试匹配 "rockchip,rk3399-dp",失败后再尝试 "rockchip,dp-common"。这种机制允许通用驱动先处理,特定优化驱动后处理。
4.7 调试与验证方法
开发过程中,可以通过以下方法验证驱动与Device Tree的绑定是否正确:
| 方法 | 命令/操作 | 预期输出 |
|---|---|---|
| 查看设备树节点 | ls /proc/device-tree/ |
显示所有设备树节点 |
| 检查驱动绑定 | cat /sys/bus/platform/drivers/dp-driver/ |
显示已绑定的设备列表 |
| 查看内核日志 | dmesg | grep dp |
显示probe成功/失败信息 |
| 验证compatible匹配 | cat /sys/devices/platform/fec00000.dp/of_node/compatible |
输出设备树中的compatible字符串 |
dev_info(dev, "matched with %s\n", match->compatible);,可以快速确认驱动匹配到了哪个条目。同时,使用of_device_is_compatible()函数可以在运行时检查设备节点的兼容性。
4.8 本章小结
DRM驱动与Device Tree的绑定是嵌入式显示驱动开发的基础。通过of_match_table,驱动可以灵活适配多种硬件平台,而无需修改核心逻辑。关键要点包括:
of_match_table中的compatible字符串必须与设备树节点严格一致。- 使用
data指针传递平台特定配置,避免在probe函数中硬编码条件判断。 - 设备树节点中的资源(reg、interrupts、clocks等)通过
platform_get_resource系列函数获取。 - 多平台适配时,利用
of_match_table的优先级和回退机制,实现通用与专用驱动的共存。