2. KMS 组件概述:KMS 的作用、与 DRM 的关系、核心数据结构
KMS(Kernel Mode Setting)是 DRM 框架中负责显示模式设置与输出的核心子系统。它运行在内核空间,直接操作硬件寄存器,管理显示控制器、编码器、连接器和显示平面。理解 KMS 是掌握整个显示子系统初始化流程的关键。
2.1 KMS 的作用
KMS 将显示模式设置(Mode Setting)从用户空间(X Server、Wayland Compositor)迁移到内核空间,解决了传统 UMS(User Mode Setting)的竞态与性能问题。其核心职责包括:
- 模式管理:维护显示分辨率、刷新率、像素时钟等时序参数(drm_display_mode)。
- 资源枚举:枚举并管理物理显示设备(CRTC、Encoder、Connector、Plane)。
- 状态提交:通过原子(Atomic)或传统(Legacy)接口,将配置状态一次性提交到硬件。
- 帧缓冲管理:管理显存中的帧缓冲对象(drm_framebuffer),支持扫描输出。
- 电源管理:处理 DPMS(Display Power Management Signaling)状态切换。
关键优势: KMS 消除了用户空间进程切换带来的闪烁与撕裂,支持无缝模式切换(Seamless Mode Set),并提供了统一的硬件抽象层。
2.2 KMS 与 DRM 的关系
DRM(Direct Rendering Manager)是一个更大的框架,KMS 是 DRM 的显示输出子集。DRM 包含两大功能分支:
| 分支 | 功能 | 用户空间接口 |
|---|---|---|
| KMS | 模式设置、显示输出、硬件状态管理 | /dev/dri/card0(主设备) |
| GEM / TTM | 显存分配、GPU 命令提交、渲染加速 | /dev/dri/renderD128(渲染设备) |
KMS 通过 DRM 核心层提供的 ioctl 接口暴露给用户空间。一个典型的 DRM 驱动会同时实现 KMS 和 GEM 两部分,但 KMS 的初始化路径独立于渲染路径。
架构关系: DRM 是“容器”,KMS 是“内容”。驱动注册时通过 drm_mode_config_init() 初始化 KMS 组件,之后用户空间通过 DRM_IOCTL_MODE_* 系列命令与 KMS 交互。
2.3 核心数据结构
KMS 的初始化与运行依赖于三个核心数据结构:drm_device、drm_driver 和 drm_mode_config。它们构成了驱动与框架之间的契约。
2.3.1 drm_device
drm_device 是 DRM 框架中代表一个显示设备的顶层结构体。每个物理 GPU 或显示控制器对应一个 drm_device 实例。它包含设备级别的全局信息:
struct drm_device {
struct list_head devlist; // 全局设备链表
struct kref ref; // 引用计数
struct device *dev; // 内核通用设备结构
struct drm_driver *driver; // 指向驱动方法表
struct drm_mode_config mode_config; // KMS 配置核心
struct drm_file *primary; // 主文件节点
unsigned long irq; // 中断号
void *dev_private; // 驱动私有数据
// ... 其他字段
};
- devlist:全局链表,用于遍历所有已注册的 DRM 设备。
- driver:指向
drm_driver结构,驱动通过它注册回调函数。 - mode_config:KMS 子系统的配置根节点,管理所有显示资源。
- dev_private:驱动私有指针,通常指向芯片特定的硬件上下文。
2.3.2 drm_driver
drm_driver 是驱动开发者需要实现的结构体,它定义了驱动与 DRM 核心之间的回调接口。KMS 相关的回调包括:
| 回调函数 | 作用 | 调用时机 |
|---|---|---|
load() |
驱动加载时的初始化入口 | 设备探测时 |
unload() |
驱动卸载时的清理 | 设备移除时 |
irq_handler() |
处理显示相关中断(VBlank、错误) | 硬件中断触发时 |
get_vblank_counter() |
获取垂直消隐计数器 | 用户空间查询 VBlank |
enable_vblank() |
使能 VBlank 中断 | 开启 VBlank 事件时 |
disable_vblank() |
禁用 VBlank 中断 | 关闭 VBlank 事件时 |
注意: 在 DRM 框架演进中,
load() 和 unload() 已被标记为过时(deprecated),推荐使用 probe() 和 remove() 替代。新驱动应使用 drm_dev_alloc() + drm_dev_register() 流程。
2.3.3 drm_mode_config
drm_mode_config 是 KMS 子系统的“大脑”,它管理所有显示资源的链表和配置参数。驱动初始化 KMS 时,首先调用 drm_mode_config_init() 初始化此结构:
struct drm_mode_config {
struct mutex mutex; // 保护配置状态的互斥锁
struct idr crtc_idr; // CRTC 对象 ID 分配器
struct list_head crtc_list; // CRTC 链表
struct list_head connector_list; // Connector 链表
struct list_head encoder_list; // Encoder 链表
struct list_head plane_list; // Plane 链表
struct list_head fb_list; // Framebuffer 链表
struct list_head property_list; // 属性链表
int num_crtc; // CRTC 数量
int num_connector; // Connector 数量
int num_encoder; // Encoder 数量
int num_plane; // Plane 数量
uint32_t min_width, max_width; // 最小/最大宽度
uint32_t min_height, max_height; // 最小/最大高度
struct drm_atomic_state *atomic_state; // 原子状态对象
// ... 其他字段
};
- 链表管理:每个显示资源(CRTC、Encoder、Connector、Plane)都挂载在对应的链表中。
- ID 分配:
crtc_idr用于为 CRTC 分配全局唯一的用户空间 ID。 - 尺寸限制:
min_width/max_width等字段定义了硬件支持的最大显示分辨率。 - 原子状态:
atomic_state用于原子模式设置,保存完整的显示配置快照。
2.4 初始化流程概述
KMS 组件的初始化遵循固定的步骤序列,驱动开发者需要按顺序调用框架提供的 API:
- 分配 drm_device:调用
drm_dev_alloc()分配并初始化设备结构。 - 初始化 mode_config:调用
drm_mode_config_init()初始化 KMS 配置根节点。 - 设置尺寸限制:填充
mode_config.min_width、max_width等硬件能力字段。 - 注册显示资源:调用
drm_crtc_init()、drm_encoder_init()、drm_connector_init()等 API 注册具体硬件对象。 - 注册设备:调用
drm_dev_register()将设备暴露给用户空间。
最佳实践: 现代 DRM 驱动推荐使用
devm_drm_dev_alloc() 进行资源管理,它自动绑定设备生命周期,避免内存泄漏。初始化顺序必须严格遵循:先初始化 mode_config,再注册子资源,最后注册设备。