2. KMS基础回顾
在进入原子提交的实战之前,必须先把 DRM/KMS 这套显示子系统的“零件表”和“装配关系”重新梳理清楚。很多原子提交失败、画面撕裂、Plane 校验报错的根因,并不是原子 API 本身写错了,而是对 CRTC、Encoder、Connector、Plane、Framebuffer 之间的从属与数据流关系理解有偏差。本节把这条链路完整串起来,为后续多 Plane 状态校验与故障排查打地基。
一、核心原理:从 Framebuffer 到屏幕的完整链路
DRM(Direct Rendering Manager)是内核中负责显卡与显示管理的子系统,KMS(Kernel Mode Setting)是 DRM 中负责显示模式设置与画面扫描输出的部分。KMS 把显示硬件抽象成一组对象,每个对象承担明确职责:
- Framebuffer(FB):一块内存缓冲区的抽象,描述像素格式、宽高、pitch、modifier 以及底层 GEM 对象。它只回答“画什么”,不关心显示到哪里。
- Plane:图层,负责把某个 Framebuffer 按裁剪、缩放、位置、旋转等参数合成到 CRTC 的扫描输出上。一个 CRTC 通常挂多个 Plane(Primary、Overlay、Cursor)。
- CRTC:显示控制器,代表一路扫描输出引擎。它决定时序(mode)、刷新率、背景色,并把挂在其上的 Plane 合成后的像素按扫描线送出。
- Encoder:把 CRTC 产生的像素流转换成某种物理信号格式,例如 TMDS、DP、DSI、LVDS。它回答“用什么信号编码”。
- Connector:物理接口抽象,代表 HDMI、DP、eDP、DSI 等端口,负责连接状态检测(connected/disconnected)、EDID 读取、链路训练与模式枚举。
它们的关系可以概括为:Framebuffer → Plane → CRTC → Encoder → Connector → 屏幕。Plane 是“贴图”,CRTC 是“画布与扫描引擎”,Encoder 是“信号翻译”,Connector 是“插座”,Framebuffer 是“素材”。原子提交的本质,就是一次性把这条链路上所有对象的目标状态打包成一个 commit,由内核统一校验并生效。
二、方法流程:一次原子提交经历了什么
用户态通过 libdrm 或 DRM 的 atomic API 提交状态,典型流程如下:
- 调用
drmModeGetResources枚举 CRTC、Encoder、Connector、Plane 的 ID 与能力。 - 创建
drmModeAtomicReq,用drmModeAtomicAddProperty逐项设置目标对象的属性,例如CRTC_ID、FB_ID、SRC_X/SRC_Y/SRC_W/SRC_H、CRTC_X/CRTC_Y/CRTC_W/CRTC_H、mode_id、ACTIVE。 - 调用
drmModeAtomicCommit,可带DRM_MODE_ATOMIC_TEST_ONLY先做校验,或带DRM_MODE_PAGE_FLIP_EVENT等待 flip 完成事件。 - 内核执行
atomic_check:逐对象检查资源是否冲突、Plane 是否被重复占用、缩放是否超限、带宽是否足够。 - 校验通过后执行
atomic_commit,在合适的 vblank 时机整体切换,避免中间态被扫描输出。
drmModeAtomicReq *req = drmModeAtomicAlloc();
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id);
drmModeAtomicAddProperty(req, crtc_id, prop_mode_id, mode_blob);
drmModeAtomicAddProperty(req, conn_id, prop_crtc_id, crtc_id);
int ret = drmModeAtomicCommit(fd, req,
DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT, NULL);
drmModeAtomicFree(req);
三、案例/场景:双 Plane 叠加显示
假设要在 1080p 屏幕上叠加一个视频层和一个 UI 层。视频层用 Overlay Plane,UI 层用 Primary Plane,两者都指向各自的 Framebuffer,并绑定到同一个 CRTC。原子提交时,两个 Plane 的 FB_ID、CRTC_ID、坐标与尺寸属性必须在同一个 req 中一起下发。如果只提交了 Overlay 而漏掉 Primary,内核在 atomic_check 阶段可能因为 Primary 未激活而拒绝,或者出现黑屏。这正是“多 Plane 状态必须整体一致”的典型体现。
DRM_MODE_ATOMIC_TEST_ONLY,它只做校验不真正生效,能快速定位是哪个对象的哪个属性导致 -EINVAL。四、误区与复盘:常见理解偏差
| 误区 | 正确理解 |
|---|---|
| Connector 直接连 CRTC | 中间必须经过 Encoder,Connector 通过 possible_encoders 选择可用编码器 |
| Plane 属于 Connector | Plane 属于 CRTC,通过 CRTC_ID 绑定到某路扫描输出 |
| 改分辨率只需改 Connector | mode 设置在 CRTC 上,Connector 只负责链路与 EDID |
| 原子提交可以只改一个对象 | 涉及依赖关系的对象必须同批提交,否则校验失败或状态不一致 |
| Framebuffer 就是显存 | FB 是格式与布局描述,真正内存由 GEM 对象承载 |
-EINVAL 与 -ENOSPC 报错。把这条链路记牢:Framebuffer 提供素材,Plane 负责合成,CRTC 负责扫描,Encoder 负责编码,Connector 负责连接。原子提交则是把这五类对象的目标状态一次性、原子地切换。后续多 Plane 校验与故障排查,都会围绕这条链路上的对象属性与依赖关系展开。