4. DRM架构与核心对象
在 Linux DRM/KMS 子系统中,显示管线并不是由某一个“大而全”的结构体独立完成,而是由一组职责清晰、彼此关联的核心对象协同工作。理解这些对象的定位与连接关系,是阅读驱动源码、调试显示问题、编写自定义显示驱动的基础。本节围绕 drm_device、drm_driver、drm_crtc、drm_encoder、drm_connector、drm_plane 六个核心对象展开,先建立整体架构认知,再逐层拆解其职责与数据流。
一、核心原理:从设备到管线的对象分层
DRM 的核心设计思想是“设备抽象 + 管线抽象”。drm_device 代表一个 DRM 显示设备实例,是内核中所有 DRM 资源的容器;drm_driver 则描述“这个设备如何被驱动”,包含初始化、卸载、IOCTL 扩展等回调。二者关系类似“对象实例”与“操作函数集”:drm_device 持有 drm_driver 指针,驱动通过 drm_driver 注册进 DRM 框架。
真正描述“画面如何从内存走到屏幕”的,是 KMS 的四个管线对象:drm_plane 负责提供图像数据源,drm_crtc 负责扫描输出与时序生成,drm_encoder 负责将 CRTC 的像素流转换为特定接口信号,drm_connector 负责描述物理连接器与显示能力。它们通过 possible_crtcs、possible_clones、encoder 指针等字段建立拓扑关系,最终形成一条完整的显示链路。
一句话记忆:plane 是“画什么”,crtc 是“怎么扫”,encoder 是“怎么转”,connector 是“接到哪”,device 是“整个设备”,driver 是“驱动怎么管”。
二、对象职责与关键字段
下面用表格梳理六个核心对象的职责、关键字段与典型操作,便于在源码中快速定位。
| 对象 | 核心职责 | 关键字段/接口 | 典型操作 |
|---|---|---|---|
drm_device | DRM 设备实例,资源容器 | dev_private、driver、mode_config | drm_dev_alloc、drm_dev_register |
drm_driver | 驱动回调与能力描述 | load、unload、dumb_create、gem_create_object | 注册、初始化、内存管理 |
drm_plane | 图像数据源与合成层 | possible_crtcs、format_types、funcs | drm_universal_plane_init |
drm_crtc | 扫描输出与时序控制 | mode、enabled、funcs、state | drm_crtc_init_with_planes |
drm_encoder | 像素流到接口信号的转换 | possible_crtcs、possible_clones、funcs | drm_encoder_init |
drm_connector | 物理连接器与显示能力 | connector_type、modes、status、funcs | drm_connector_init |
需要特别注意的是,drm_crtc 与 drm_plane 之间通过 possible_crtcs 位掩码建立“可用关系”,而不是硬绑定;drm_encoder 与 drm_connector 之间则通过 drm_connector_attach_encoder 建立连接。这种“能力声明 + 运行时选择”的机制,使同一套驱动可以适配多种硬件拓扑。
三、方法流程:一条显示链路的建立过程
以典型的显示驱动初始化为例,核心对象的创建顺序通常如下:
- 分配并初始化
drm_device,绑定drm_driver。 - 创建
drm_plane,声明其支持的像素格式与可挂载的 CRTC。 - 创建
drm_crtc,并关联主 plane 与 cursor plane。 - 创建
drm_encoder,声明其可连接的 CRTC 与可克隆的 encoder。 - 创建
drm_connector,读取 EDID、填充modes,并 attach 到 encoder。 - 注册设备,向用户空间暴露
/dev/dri/cardX与/dev/dri/renderDX。
下面是一段简化的内核驱动初始化代码片段,展示对象创建与连接的关键调用:
static int my_display_probe(struct platform_device *pdev)
{
struct drm_device *drm;
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_plane *primary, *cursor;
drm = drm_dev_alloc(&my_drm_driver, &pdev->dev);
if (IS_ERR(drm))
return PTR_ERR(drm);
/* 创建 plane */
primary = my_plane_create(drm, DRM_PLANE_TYPE_PRIMARY);
cursor = my_plane_create(drm, DRM_PLANE_TYPE_CURSOR);
/* 创建 CRTC,并绑定主 plane 与 cursor plane */
crtc = my_crtc_create(drm, primary, cursor);
/* 创建 encoder,声明可用的 CRTC */
encoder = my_encoder_create(drm, BIT(drm_crtc_index(crtc)));
/* 创建 connector,并 attach 到 encoder */
connector = my_connector_create(drm);
drm_connector_attach_encoder(connector, encoder);
drm_dev_register(drm, 0);
return 0;
}
这段代码体现了 DRM 的“先建对象、再连关系、最后注册”的典型流程。实际驱动中,plane、crtc、encoder、connector 的创建往往封装在各自模块中,但连接关系必须显式建立,否则用户空间无法枚举出完整显示链路。
四、案例与场景:HDMI 输出链路的对象映射
假设一块 SoC 内置显示控制器,支持一个 HDMI 输出。其对象映射关系可以描述为:
drm_plane:主图层,支持 ARGB8888、XRGB8888 等格式。drm_crtc:显示控制器,负责生成 1080p60 时序。drm_encoder:HDMI TX 编码器,将并行像素流转换为 TMDS 信号。drm_connector:HDMI-A 连接器,读取 EDID,报告热插拔状态。
当用户空间执行 modetest 或 weston 时,会通过 GET_CONNECTOR、GET_ENCODER、GET_CRTC 等 IOCTL 枚举这些对象,并根据 possible_crtcs 与 attach 关系选择一条可用链路。若 connector 的 status 为 disconnected,则不会出现在可用模式列表中。
调试提示:使用 modetest -M <driver> -c 可以列出所有 connector、encoder、crtc 及其连接关系;cat /sys/kernel/debug/dri/0/state 可查看当前原子状态,是排查链路问题的第一手资料。
五、误区与复盘
初学者常犯的错误是把 drm_encoder 与 drm_connector 混为一谈。encoder 是“信号转换逻辑”,connector 是“物理接口与能力描述”。一个 encoder 可以连接多个 connector(通过 clone),一个 connector 也可以在不同场景下切换到不同 encoder。另一个常见误区是忽略 possible_crtcs 的位掩码含义,导致 plane 或 encoder 无法被正确挂载到目标 CRTC。
复盘时建议按以下清单检查:
- plane 的
possible_crtcs是否包含目标 CRTC 的索引位? - encoder 的
possible_crtcs是否与 CRTC 匹配? - connector 是否已通过
drm_connector_attach_encoder绑定? - connector 的
status与modes是否已正确填充? drm_device是否已注册并暴露 card 节点?
只有这些对象各司其职、关系正确,KMS 才能顺利完成模式设置与页面翻转。理解它们,就等于掌握了 DRM 显示驱动的“骨架”。