4. DRM/KMS核心数据结构
在 DRM/KMS 子系统中,显示通路的描述并不是靠一个“大而全”的结构体完成的,而是由一组职责清晰、彼此引用的核心对象共同构成。理解这些对象之间的拓扑关系,是驱动开发中“知道该在哪里填什么”的前提。可以把它们想象成一条从内存像素到屏幕像素的流水线:framebuffer 提供像素数据,plane 决定把哪块数据合成到扫描输出上,CRTC 负责生成时序并扫描输出,encoder 把 CRTC 的数字信号转换为某种物理接口信号,connector 则代表最终连接器与显示设备,而 drm_device 是承载这一切的顶层容器。
一、核心对象职责与关系
drm_device 是整个 DRM 设备的抽象,它持有 struct drm_mode_config,后者又通过链表管理本设备上的所有 CRTC、encoder、connector、plane 和 framebuffer。驱动在 probe 阶段调用 drm_dev_alloc() 分配 device,随后初始化 mode_config 并注册各类对象。
drm_framebuffer 描述一块可被扫描输出的像素缓冲区,它引用一个或多个 GEM 对象,并记录像素格式、宽高、pitch 等信息。它本身不参与时序生成,只是“数据源”。
drm_plane 是图层抽象,负责把 framebuffer 的某块区域合成到 CRTC 的扫描输出上。它分为 primary、overlay、cursor 三类,其中 primary plane 通常与 CRTC 一一对应。
drm_crtc 代表一个显示控制器通道,负责从 plane 取像素、按 mode 生成时序信号(HSYNC/VSYNC/DE),并输出给 encoder。它持有当前 mode、framebuffer 引用以及 plane 绑定关系。
drm_encoder 把 CRTC 输出的数字时序转换为特定物理接口所需的信号形式,例如 RGB 并行、LVDS、MIPI DSI、HDMI TMDS 等。一个 CRTC 可以连接多个 encoder,但同一时刻通常只有一个处于激活状态。
drm_connector 代表最终对外连接的物理接口或显示设备,负责读取 EDID、检测热插拔、维护连接状态,并向上层报告可用 mode 列表。encoder 与 connector 之间通过 drm_connector_attach_encoder() 建立关联。
二、初始化流程要点
典型驱动初始化顺序如下,顺序错误会导致对象无法注册或链路断裂:
1. drm_dev_alloc() // 分配 drm_device
2. drm_mode_config_init() // 初始化 mode_config 链表与限制
3. 创建 CRTC:drm_crtc_init_with_planes()
4. 创建 encoder:drm_encoder_init()
5. 创建 connector:drm_connector_init()
6. 关联 encoder 与 connector:
drm_connector_attach_encoder()
7. 创建 plane:drm_universal_plane_init()
8. 注册设备:drm_dev_register()
9. 启用轮询/热插拔:drm_kms_helper_poll_init()
三、典型场景:HDMI 输出链路
以一颗 SoC 的 HDMI 输出为例:显示控制器内部有两个 CRTC,分别对应 LCD 和 HDMI 通道。HDMI CRTC 绑定一个 primary plane 和一个 overlay plane;其输出连接到一个 HDMI encoder,encoder 再关联到 HDMI connector。当用户执行 modetest 或 Weston 启动时,KMS 会读取 connector 的 EDID,选择合适的 mode,通过 atomic commit 把 framebuffer 挂到 plane 上,最终由 CRTC 扫描输出,经 encoder 转为 TMDS 信号,从 connector 送出。
四、常见误区与复盘
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 先注册 device 再创建对象 | 对象未加入 mode_config,用户空间看不到 | 先完成所有对象初始化,最后 drm_dev_register() |
| connector 未 attach encoder | 链路断裂,atomic check 失败 | drm_connector_attach_encoder() 建立关联 |
| plane 未绑定 CRTC | 无法扫描输出,屏幕无显示 | 在 CRTC 初始化时传入 primary/cursor plane |
| framebuffer 未引用 GEM 对象 | 扫描时访问空指针或花屏 | drm_framebuffer_init() 时正确填充 GEM obj 数组 |
/sys/kernel/debug/dri/0/ 下的 state 文件,确认 connector 状态、CRTC 绑定关系和 plane 使用情况,比盲目改代码更高效。掌握这些结构体的职责边界与引用关系后,后续编写 atomic commit、mode set 以及自定义 plane 回调时,就能准确判断“该操作哪个对象、该改哪个字段”,避免在错误的层级上反复试错。