2. KMS(Kernel Mode Setting)基础:KMS与UMS的区别、KMS初始化流程、modeset操作的基本步骤

2.1 KMS 与 UMS:两种显示模式设置机制

在 Linux DRM 子系统中,Mode Setting(模式设置)负责配置显示控制器的时序、分辨率、像素时钟以及帧缓冲区的格式。历史上存在两种实现路径:UMS(User Mode Setting) 和 KMS(Kernel Mode Setting)。理解两者的差异是掌握现代 DRM 驱动的基础。

对比维度 UMS(User Mode Setting) KMS(Kernel Mode Setting)
控制主体 用户空间的 X Server 或 Wayland compositor 内核空间的 DRM 驱动
模式切换路径 用户态通过 ioctl 直接操作硬件寄存器 用户态通过 DRM ioctl 请求内核执行模式设置
多进程支持 弱,通常需要独占显卡 强,内核统一管理资源,支持 VT 切换
电源管理 用户态自行处理,容易遗漏 内核统一管理,suspend/resume 自动恢复
安全性 用户态可直接访问硬件,存在风险 内核态校验参数,防止非法操作
现代嵌入式趋势 已淘汰,仅遗留代码中存在 标准方案,所有新驱动均采用
关键结论:KMS 将模式设置的控制权收归内核,使得显示状态切换、VT 切换、电源管理恢复等操作变得原子化且安全。嵌入式开发者只需关注 KMS 路径。
KMS vs UMS 架构对比 UMS(已淘汰) 用户空间 X Server / Compositor 直接 ioctl 操作硬件寄存器 硬件显示控制器 KMS(现代标准) 用户空间 DRM Client DRM ioctl(内核校验) 硬件显示控制器 KMS 在内核层增加校验与资源管理,用户态不再直接触碰硬件寄存器

2.2 KMS 初始化流程:从驱动加载到设备就绪

KMS 初始化是 DRM 驱动加载的核心阶段,涉及多个内核组件的协同。以下流程以嵌入式平台(如 i.MX、Rockchip、Allwinner)为背景,描述从 probe() 到 drm_dev_register() 的完整路径。

  1. 平台驱动 probe() 触发:Device Tree 匹配后,调用驱动的 probe() 函数。
  2. DRM 设备分配:调用 drm_dev_alloc() 分配 struct drm_device。
  3. DRM 驱动私有数据初始化:填充 struct drm_driver 中的 fops、gem 回调、prime 回调等。
  4. KMS 组件注册:调用 drm_mode_config_init() 初始化模式配置。
  5. CRTC/Encoder/Connector/Plane 创建:根据硬件能力创建 KMS 对象并注册。
  6. fbdev 兼容层(可选):调用 drm_fbdev_generic_setup() 提供早期控制台。
  7. DRM 设备注册:调用 drm_dev_register() 暴露设备节点 /dev/dri/card0。
KMS 初始化流程 ① 平台驱动 probe() 触发 ② drm_dev_alloc() 分配设备 ③ 填充 drm_driver 回调 ④ drm_mode_config_init() ⑤ 创建 CRTC / Encoder / Connector / Plane ⑥ drm_fbdev_generic_setup() ⑦ drm_dev_register() 完成
嵌入式要点:步骤⑤是驱动开发的核心工作量所在。CRTC 需要绑定硬件显示控制器,Encoder 对应 RGB/LVDS/HDMI 发送器,Connector 代表物理接口,Plane 负责图层叠加。Device Tree 中的 ports 节点正是用来描述这些对象之间的连接关系。

2.3 Modeset 操作的基本步骤:用户空间的视角

当 KMS 初始化完成后,用户空间应用程序(如 Weston、kmscube、modetest)通过标准 DRM ioctl 执行 modeset。一个完整的 modeset 操作包含以下原子步骤:

步骤 DRM ioctl / 操作 说明
1. 打开设备 open("/dev/dri/card0") 获取 DRM 文件描述符
2. 获取资源 drmModeGetResources() 获取 CRTC、Encoder、Connector、FB 的 ID 列表
3. 查找 Connector drmModeGetConnector() 遍历 Connector,找到已连接的接口
4. 获取 Mode connector->modes[] 从 Connector 的 modes 列表中选择合适的分辨率
5. 创建 Framebuffer drmModeAddFB() / drmModeAddFB2() 注册内存缓冲区为 DRM framebuffer
6. 设置 CRTC drmModeSetCrtc() 将 CRTC 与 Connector、Mode、FB 绑定,激活显示
7. Page Flip(可选) drmModePageFlip() 无撕裂切换帧缓冲区,用于动画/视频
用户空间 Modeset 操作步骤 ① 打开设备 open(card0) ② 获取资源 GetResources ③ 查找 Connector ④ 获取 Mode modes[] ⑤ 创建 FB AddFB2 ⑥ 设置 CRTC SetCrtc ⑦ Page Flip 关键数据结构关系 CRTC 是显示控制器核心,绑定 Encoder → Connector → 物理显示器 Framebuffer 是内存中的像素数据,通过 Plane 叠加到 CRTC 的扫描输出 代码示例:modeset 核心流程(伪代码) int fd = open("/dev/dri/card0", O_RDWR); drmModeRes *res = drmModeGetResources(fd); drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[0]); drmModeModeInfo *mode = &conn->modes[0]; uint32_t fb_id; drmModeAddFB2(fd, width, height, fmt, handles, pitches, offsets, &fb_id, 0); drmModeSetCrtc(fd, res->crtcs[0], fb_id, 0, 0, &conn->connector_id, 1, mode);
常见陷阱:步骤⑥ drmModeSetCrtc() 会立即生效,但不会等待 VBlank。如果需要在垂直消隐期切换帧缓冲区以避免撕裂,必须使用 drmModePageFlip() 并配合 DRM_EVENT 等待。在嵌入式平台上,Page Flip 的完成依赖于 CRTC 的 VBlank 中断,请确保中断正确注册。

2.4 KMS 对象模型:CRTC、Encoder、Connector、Plane

KMS 将显示硬件抽象为四个核心对象,它们通过 Modeset 对象图 相互连接。理解这个模型是编写 Device Tree 绑定的前提。

KMS 对象 硬件对应 主要职责 Device Tree 节点示例
CRTC 显示控制器(Display Controller) 生成时序信号、扫描帧缓冲区、产生 VBlank display-controller@...
Encoder 显示信号发送器(LVDS/HDMI/DSI 发送器) 将 CRTC 输出的并行数据编码为串行信号 lcdif@... / hdmi@...
Connector 物理接口(HDMI 座、LVDS 插座、eDP 接口) 检测显示器连接状态、获取 EDID、提供可用 modes hdmi-connector / lvds-connector
Plane 硬件图层(Overlay / Primary / Cursor) 管理帧缓冲区与 CRTC 的叠加关系 通常由驱动内部创建,不直接对应 DT 节点
KMS 对象模型与连接关系 Plane 帧缓冲区管理 CRTC 时序生成 + 扫描 Encoder 信号编码 Connector 物理接口 + EDID 嵌入式适配要点 • CRTC 通常对应 SoC 内部的 Display Controller 或 LCDIF • Encoder 可能是内部模块(如 MIPI DSI 主机)或外部芯片(如 LT8912) • Connector 在 Device Tree 中通过 endpoint 连接描述 • Plane 的数量由硬件图层数决定,嵌入式通常只有 1 个 Primary Plane
核心记忆点:KMS 初始化流程是“自底向上”的硬件发现过程,而 modeset 操作是“自顶向下”的配置过程。Device Tree 的作用正是在初始化阶段告诉驱动:CRTC 连接了哪个 Encoder,Encoder 又连接了哪个 Connector。这个连接关系通过 ports 和 endpoint 节点描述,我们将在后续章节详细展开。