4. DRM/KMS核心数据结构

在 DRM/KMS 子系统中,显示通路的描述并不是靠一个“大而全”的结构体完成的,而是由一组职责清晰、彼此引用的核心对象共同构成。理解这些对象之间的拓扑关系,是驱动开发中“知道该在哪里填什么”的前提。可以把它们想象成一条从内存像素到屏幕像素的流水线:framebuffer 提供像素数据,plane 决定把哪块数据合成到扫描输出上,CRTC 负责生成时序并扫描输出,encoder 把 CRTC 的数字信号转换为某种物理接口信号,connector 则代表最终连接器与显示设备,而 drm_device 是承载这一切的顶层容器。

DRM/KMS 核心对象拓扑关系 drm_device 顶层容器 / mode_config 持有者 drm_framebuffer 像素数据 / GEM 对象引用 drm_plane 图层合成 / 扫描来源 drm_crtc 时序生成 / 扫描输出 drm_encoder 信号编码 / 接口转换 drm_connector 物理接口 / EDID / 热插拔 drm_mode_config 对象链表 / 限制参数 引用 绑定 连接 关联 管理 数据流:framebuffer → plane → crtc → encoder → connector → 显示设备

一、核心对象职责与关系

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()
初始化阶段必须保证每个 connector 至少关联一个 encoder,每个 CRTC 至少绑定一个 primary plane,否则后续 atomic commit 会因链路不完整而失败。

三、典型场景: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 回调时,就能准确判断“该操作哪个对象、该改哪个字段”,避免在错误的层级上反复试错。