3. DRM/KMS 框架入门:核心概念与组件

好,我们正式开始啃 DRM/KMS 这块硬骨头。说实话,我刚接触 Android 显示驱动时,也被这一堆缩写搞得头晕——CRTC、Encoder、Connector、Plane……每个词都认识,合在一起就不知道谁管谁了。

这一章,我会带你把这些概念一个一个拆开。你想想看,显示链路说白了就是一条流水线:图像数据从哪来,经过谁处理,最后送到哪去。DRM/KMS 就是这条流水线的标准化管理框架。

3.1 DRM 核心概念

DRM(Direct Rendering Manager)是 Linux 内核的显示子系统。它分两层:

  • DRM Core:提供用户空间与内核的通用接口,比如 open()、ioctl()。
  • KMS(Kernel Mode Setting):负责显示模式设置和画面合成,是我们驱动开发的重点。

我个人的习惯是,把 DRM 理解成「显示驱动的基础设施」,KMS 则是「具体的显示管线」。没有 KMS,你连屏幕分辨率都改不了。

关键认知:DRM 不等于 KMS。KMS 是 DRM 的一部分,但 DRM 还包含 GEM(图形内存管理)、FB(帧缓冲)等模块。做显示驱动,KMS 是主战场。

3.2 KMS 四大组件

KMS 的核心由四个对象组成:CRTC、Encoder、Connector、Plane。它们之间的关系,我画了张图,你先看个整体印象。

DRM/KMS 核心组件关系图 DRM/KMS 显示管线组件关系 Plane(图层) CRTC(显示控制器) Encoder(编码器) Connector(连接器) 显示设备(Panel/HDMI) 数据流向:Plane → CRTC → Encoder → Connector → 显示设备

嗯,这张图你得记住。每次调试显示问题,我都会在脑子里过一遍这个流程。下面我们逐个拆解。

3.2.1 CRTC(显示控制器)

CRTC 是整条管线的「大脑」。它负责:

  • 从内存中读取帧缓冲数据
  • 生成时序信号(行同步、场同步、像素时钟)
  • 控制画面刷新率、分辨率、色彩深度

我在项目中遇到过一个问题:换了块新屏幕,分辨率从 1080p 升到 4K,结果画面一直闪烁。查了两天才发现,是 CRTC 的像素时钟配置没跟上。说白了,CRTC 的带宽决定了你能跑多高的分辨率。

调试技巧:用 cat /sys/kernel/debug/dri/0/crtc-0/state 可以查看当前 CRTC 的状态,包括 active、mode、gamma 等信息。这是排查显示问题的第一站。

3.2.2 Encoder(编码器)

Encoder 负责把 CRTC 输出的并行数据转换成显示接口需要的串行信号。常见的编码方式有:

接口类型 编码方式 典型应用
LVDS 并行→差分对 老旧笔记本屏幕
eDP DisplayPort 编码 现代手机/平板
HDMI TMDS 编码 外接显示器
MIPI DSI D-PHY 串行 手机内置屏(最常见)

Encoder 和 CRTC 通常是 1:1 绑定的,但也可以 1:N。比如一个 CRTC 同时输出给 HDMI 和 eDP 两个 Encoder——不过这种情况在手机上很少见。

3.2.3 Connector(连接器)

Connector 是物理接口的抽象。它描述的是「屏幕插在哪」。每个 Connector 对应一个实际的物理端口,比如:

  • HDMI-A-1:HDMI 接口
  • eDP-1:内置 eDP 屏幕
  • DSI-1:MIPI DSI 接口

Connector 还负责读取屏幕的 EDID(Extended Display Identification Data)信息。EDID 里包含了屏幕支持的分辨率、刷新率、物理尺寸等。我曾经踩过一个坑:某款国产屏幕的 EDID 里写错了最大分辨率,导致系统只能输出 720p。最后我们不得不写死一个 override 模式。

注意:Connector 的 status 属性有四种状态:connected、disconnected、unknown、hotplug。热插拔事件就是通过 Connector 的 hotplug 状态变化来触发的。如果你的 HDMI 插上去没反应,先查 Connector 状态。

3.2.4 Plane(图层)

Plane 是 KMS 里最灵活、也最容易让人困惑的组件。它代表一个独立的图像层。每个 Plane 可以有自己的:

  • 帧缓冲(FB)
  • 位置(crtc_x, crtc_y)
  • 大小(crtc_w, crtc_h)
  • 透明度(alpha)
  • 旋转/缩放(rotation, scaling)

为什么需要 Plane?你想想看,手机屏幕上同时显示状态栏、桌面壁纸、悬浮窗——如果每次都要把所有图层合成成一个整图再送显,CPU/GPU 的负担会很重。Plane 允许硬件直接做图层叠加,这叫 硬件合成(Hardware Composition)。

Plane 分三种类型:

  • Primary Plane:主图层,每个 CRTC 至少有一个。通常用来显示桌面或主界面。
  • Overlay Plane:叠加层,可以有多个。用来显示视频、悬浮窗等。
  • Cursor Plane:光标层,专门给鼠标指针用的。硬件上通常有独立的 cursor buffer。

实战经验:我在调试某款平板时,发现视频播放卡顿。查了硬件文档才发现,它的 Overlay Plane 只支持 NV12 格式,而播放器默认输出的是 RGBA。改成 NV12 后,视频播放直接走硬件合成,CPU 占用从 40% 降到了 5%。这就是 Plane 的威力。

3.3 组件之间的绑定关系

这四个组件不是各自为政的。它们通过 atomic state 绑定在一起。一个典型的绑定关系是:

// 伪代码:KMS 组件绑定
struct drm_crtc *crtc;
struct drm_encoder *encoder;
struct drm_connector *connector;
struct drm_plane *plane;

// 绑定 CRTC 到 Encoder
encoder->crtc = crtc;

// 绑定 Encoder 到 Connector
connector->encoder = encoder;

// 绑定 Plane 到 CRTC
plane->crtc = crtc;

// 设置显示模式(分辨率、刷新率等)
drm_mode_set(crtc, connector, mode);

你可能会问:为什么不能直接 CRTC 连 Connector?非要中间插个 Encoder?

原因很简单:解耦。同一个 CRTC 可以接不同的 Encoder(比如 HDMI 或 DP),同一个 Encoder 也可以接不同的 Connector。这种分层设计让驱动更容易复用。我在做平台移植时,经常只需要换 Connector 的驱动,CRTC 和 Encoder 的代码基本不动。

3.4 模式设置流程

一个完整的 KMS 模式设置流程,大致分这几步:

  1. 枚举 Connector:遍历所有 Connector,检查哪些有屏幕插入。
  2. 读取 EDID:从 Connector 获取屏幕支持的显示模式列表。
  3. 选择 Mode:从列表里选一个合适的模式(比如 1920x1080@60Hz)。
  4. 绑定组件:把 Plane、CRTC、Encoder、Connector 串起来。
  5. 提交 Atomic Commit:一次性提交所有状态变更,保证画面不撕裂。

小技巧:用 modetest 工具可以手动测试模式设置。命令 modetest -M msm -s 32:1920x1080@60 就能把屏幕设成 1080p60。这是验证驱动是否正常的最快方法。

3.5 本章小结

DRM/KMS 的四个组件,说白了就是一条流水线上的四个工位:

  • Plane 提供原料(图层数据)
  • CRTC 负责加工(生成时序)
  • Encoder 负责包装(编码成接口信号)
  • Connector 负责发货(送到屏幕)

理解了这个流程,后面讲 atomic commit、vblank 同步、GPU 合成时,你就能快速定位问题出在哪个环节。嗯,这一章的内容就到这里。记住那张图,它会陪你走完整条显示驱动开发之路。