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,由内核统一校验并生效。

KMS 对象链路与原子提交状态打包 Framebuffer 像素格式 / pitch GEM 对象 / modifier Plane Primary / Overlay src / dst / 缩放 CRTC mode / 刷新率 扫描输出引擎 Encoder TMDS / DP / DSI 信号编码 Connector HDMI/DP EDID/热插拔 屏幕 drm_atomic_commit:一次性打包所有对象的目标状态 FB 状态 Plane 状态 CRTC 状态 Encoder Connector atomic_check 校验 → atomic_commit 生效 → 一次 vblank 内整体切换

二、方法流程:一次原子提交经历了什么

用户态通过 libdrm 或 DRM 的 atomic API 提交状态,典型流程如下:

  1. 调用 drmModeGetResources 枚举 CRTC、Encoder、Connector、Plane 的 ID 与能力。
  2. 创建 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。
  3. 调用 drmModeAtomicCommit,可带 DRM_MODE_ATOMIC_TEST_ONLY 先做校验,或带 DRM_MODE_PAGE_FLIP_EVENT 等待 flip 完成事件。
  4. 内核执行 atomic_check:逐对象检查资源是否冲突、Plane 是否被重复占用、缩放是否超限、带宽是否足够。
  5. 校验通过后执行 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 属于 ConnectorPlane 属于 CRTC,通过 CRTC_ID 绑定到某路扫描输出
改分辨率只需改 Connectormode 设置在 CRTC 上,Connector 只负责链路与 EDID
原子提交可以只改一个对象涉及依赖关系的对象必须同批提交,否则校验失败或状态不一致
Framebuffer 就是显存FB 是格式与布局描述,真正内存由 GEM 对象承载
排查原子提交失败时,先确认对象 ID 是否来自同一张 card、属性是否属于该对象、以及是否遗漏了依赖对象(如只设 Plane 未设 CRTC 的 mode)。这三类问题覆盖了绝大多数 -EINVAL 与 -ENOSPC 报错。

把这条链路记牢:Framebuffer 提供素材,Plane 负责合成,CRTC 负责扫描,Encoder 负责编码,Connector 负责连接。原子提交则是把这五类对象的目标状态一次性、原子地切换。后续多 Plane 校验与故障排查,都会围绕这条链路上的对象属性与依赖关系展开。