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 交互。
DRM 框架与 KMS 子系统关系 DRM 框架 (Direct Rendering Manager) KMS 子系统 模式设置 / 显示输出 CRTC / Encoder / Connector Plane / Framebuffer Atomic / Legacy 接口 /dev/dri/card0 GEM / TTM 子系统 显存分配 / 缓冲区管理 GPU 命令提交 / 渲染加速 DMA-BUF / 共享内存 /dev/dri/renderD128 协作 用户空间 (X11 / Wayland / Weston)

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 用于原子模式设置,保存完整的显示配置快照。
KMS 核心数据结构关系 drm_device 设备顶层结构,包含 driver 和 mode_config drm_driver 回调函数表 load / unload / irq_handler drm_mode_config KMS 配置根节点 链表 / ID 分配 / 尺寸限制 crtc_list CRTC 对象链表 encoder_list Encoder 对象链表 connector_list Connector 对象链表 plane_list Plane 对象链表 初始化流程 drm_dev_alloc() → drm_mode_config_init() → 驱动注册资源 → drm_dev_register()

2.4 初始化流程概述

KMS 组件的初始化遵循固定的步骤序列,驱动开发者需要按顺序调用框架提供的 API:

  1. 分配 drm_device:调用 drm_dev_alloc() 分配并初始化设备结构。
  2. 初始化 mode_config:调用 drm_mode_config_init() 初始化 KMS 配置根节点。
  3. 设置尺寸限制:填充 mode_config.min_width、max_width 等硬件能力字段。
  4. 注册显示资源:调用 drm_crtc_init()、drm_encoder_init()、drm_connector_init() 等 API 注册具体硬件对象。
  5. 注册设备:调用 drm_dev_register() 将设备暴露给用户空间。
最佳实践: 现代 DRM 驱动推荐使用 devm_drm_dev_alloc() 进行资源管理,它自动绑定设备生命周期,避免内存泄漏。初始化顺序必须严格遵循:先初始化 mode_config,再注册子资源,最后注册设备。