4. CRTC 初始化:CRTC 的创建、配置与注册

CRTC(Cathode Ray Tube Controller)是 KMS 显示管线的核心调度引擎,负责从 Framebuffer 读取像素数据、叠加硬件图层、生成时序信号并驱动 Encoder 输出。在 Linux 内核显示子系统中,CRTC 的初始化流程包括 对象创建、模式配置 与 设备注册 三个阶段。本章将深入分析每个阶段的关键数据结构、回调函数实现以及内核框架的注册机制。

核心要点: CRTC 初始化必须完成三个关键任务——分配并初始化 drm_crtc 对象、绑定模式设置回调(mode_set/atomic_flush)、通过 drm_mode_crtc_register 将 CRTC 加入 KMS 设备列表。任何遗漏都会导致用户空间无法通过 DRM_IOCTL_MODE_GETCRTC 查询到该 CRTC。

4.1 CRTC 核心数据结构

CRTC 在内核中由 struct drm_crtc 表示,该结构体定义在 include/drm/drm_crtc.h 中。驱动开发者需要理解其关键字段,才能正确完成初始化。

字段 类型 说明
dev struct drm_device * 指向所属 DRM 设备,初始化时由框架自动赋值
head struct list_head 用于将 CRTC 挂入 drm_mode_config.crtc_list
base struct drm_mode_object KMS 对象基类,提供 ID 分配和查找能力
funcs const struct drm_crtc_funcs * CRTC 操作回调,包括 dpms、set_config、gamma_set 等
helper_private const struct drm_crtc_helper_funcs * 辅助回调,用于模式设置和 atomic 操作
state struct drm_crtc_state * 当前 CRTC 状态,包含 active、mode、planes 等
cursor struct drm_plane * 硬件光标平面,可选
primary struct drm_plane * 主平面,必须绑定
开发提示: 在 atomic 模式下,state 字段必须通过 drm_atomic_helper_crtc_reset 初始化,否则 CRTC 状态机无法正常工作。传统(legacy)模式则不需要。

4.2 CRTC 创建流程

CRTC 的创建通常发生在驱动 probe 函数的 bind 阶段,紧接在 drm_mode_config_init 之后。标准流程如下:

  1. 分配 CRTC 对象:调用 drm_crtc_init 或 drm_crtc_init_with_planes。
  2. 设置回调函数:填充 drm_crtc_funcs 和 drm_crtc_helper_funcs。
  3. 绑定主平面:将 primary plane 与 CRTC 关联。
  4. 初始化状态:调用 drm_atomic_helper_crtc_reset 创建初始 state。
  5. 注册 CRTC:通过 drm_mode_crtc_register 将 CRTC 暴露给用户空间。
// 典型 CRTC 创建代码片段
static int my_crtc_init(struct drm_device *drm, struct drm_crtc *crtc,
                         struct drm_plane *primary, struct drm_plane *cursor)
{
    int ret;

    // 1. 使用 drm_crtc_init_with_planes 创建 CRTC
    ret = drm_crtc_init_with_planes(drm, crtc, primary, cursor,
                                    &my_crtc_funcs, "my-crtc-%d", 0);
    if (ret)
        return ret;

    // 2. 设置辅助回调(atomic 模式必须)
    drm_crtc_helper_add(crtc, &my_crtc_helper_funcs);

    // 3. 初始化 atomic 状态
    drm_atomic_helper_crtc_reset(crtc);

    // 4. 注册 CRTC(旧内核需要,新内核由 drm_dev_register 自动完成)
    drm_mode_crtc_register(crtc);

    return 0;
}
兼容性注意: 在 Linux 5.0+ 内核中,drm_mode_crtc_register 已被弃用,CRTC 的注册由 drm_dev_register 统一完成。驱动开发者应检查内核版本,避免重复注册导致内核警告。

4.3 CRTC 回调函数配置

CRTC 的行为由两组回调函数控制:drm_crtc_funcs 提供基础操作,drm_crtc_helper_funcs 提供模式设置辅助逻辑。下表列出最关键的几个回调:

回调 所属结构体 触发时机 典型实现
set_config drm_crtc_funcs 用户调用 SETCRTC ioctl 调用 drm_atomic_helper_set_config
page_flip drm_crtc_funcs 用户调用 PAGE_FLIP ioctl 调用 drm_atomic_helper_page_flip
atomic_check drm_crtc_helper_funcs atomic 提交前校验 检查时钟、带宽、图层数量
atomic_flush drm_crtc_helper_funcs atomic 提交硬件前 编程硬件寄存器、触发 VBLANK
mode_set drm_crtc_helper_funcs 传统模式设置 配置 PLL、时钟、时序参数
// atomic 模式下的 CRTC 回调实现示例
static const struct drm_crtc_funcs my_crtc_funcs = {
    .set_config     = drm_atomic_helper_set_config,
    .page_flip      = drm_atomic_helper_page_flip,
    .reset          = drm_atomic_helper_crtc_reset,
    .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
    .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
};

static const struct drm_crtc_helper_funcs my_crtc_helper_funcs = {
    .mode_valid     = my_crtc_mode_valid,
    .atomic_check   = my_crtc_atomic_check,
    .atomic_flush   = my_crtc_atomic_flush,
    .atomic_enable  = my_crtc_atomic_enable,
    .atomic_disable = my_crtc_atomic_disable,
};

4.4 CRTC 初始化流程 SVG 架构图

下图展示了从驱动 probe 到 CRTC 注册完成的完整初始化流程,以及各阶段涉及的核心函数与数据结构:

CRTC 初始化流程架构图 CRTC 初始化流程架构图 阶段 1:驱动 Probe drm_mode_config_init() 分配 drm_device 资源 阶段 2:CRTC 创建 drm_crtc_init_with_planes() 绑定 primary/cursor plane 阶段 3:回调配置 drm_crtc_helper_add() 填充 funcs / helper_funcs 阶段 4:状态初始化 drm_atomic_helper_crtc_reset() 创建初始 drm_crtc_state 阶段 5:注册 CRTC drm_mode_crtc_register() 加入 crtc_list / 暴露 ioctl CRTC 对象就绪 struct drm_crtc 包含 state / funcs / planes 关键说明: 1. 阶段 1-3 在驱动 bind 函数中顺序执行,不可颠倒。 2. 阶段 4 仅在 atomic 模式下需要,legacy 模式可跳过。 3. 阶段 5 在新内核中由 drm_dev_register 自动完成,无需显式调用。

4.5 CRTC 与 Plane 的绑定关系

每个 CRTC 必须绑定至少一个 主平面(primary plane),这是 KMS 框架的硬性要求。主平面负责显示 CRTC 的帧缓冲数据,而光标平面(cursor plane)是可选的。绑定关系在 drm_crtc_init_with_planes 中建立:

int drm_crtc_init_with_planes(struct drm_device *dev,
                               struct drm_crtc *crtc,
                               struct drm_plane *primary,
                               struct drm_plane *cursor,
                               const struct drm_crtc_funcs *funcs,
                               const char *name, ...);

参数 primary 和 cursor 分别指向已初始化的 plane 对象。如果传入 NULL,框架会自动创建一个默认的主平面。但推荐驱动开发者显式创建并配置主平面,以便控制像素格式和缩放能力。

最佳实践: 在 CRTC 初始化前,先完成 primary plane 的创建。plane 的 possible_crtcs 掩码必须包含该 CRTC 的索引,否则绑定会失败。示例:plane->possible_crtcs = 1 << crtc_index;

4.6 CRTC 模式配置与状态管理

CRTC 的模式配置通过 drm_crtc_state 结构体管理,该结构体包含以下关键字段:

字段 类型 说明
crtc struct drm_crtc * 所属 CRTC
active bool CRTC 是否激活(输出信号)
mode struct drm_display_mode 当前显示模式(分辨率、刷新率)
enable bool CRTC 是否启用(传统模式使用)
plane_mask u32 已绑定的 plane 位掩码
connectors_changed bool 连接器状态是否变化
mode_changed bool 显示模式是否变化

在 atomic 模式下,状态更新流程如下:

  1. 复制状态:drm_atomic_helper_crtc_duplicate_state 创建新状态副本。
  2. 修改状态:驱动或用户空间修改 mode、active 等字段。
  3. 校验状态:atomic_check 回调检查新状态是否合法。
  4. 提交状态:atomic_flush 回调将状态写入硬件寄存器。
  5. 交换状态:框架自动将新状态设为当前状态。
// atomic_check 回调示例:检查时钟频率是否超限
static int my_crtc_atomic_check(struct drm_crtc *crtc,
                                 struct drm_atomic_state *state)
{
    struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
    struct drm_display_mode *mode = &crtc_state->mode;

    if (mode->clock > 300000) { // 300 MHz 上限
        DRM_ERROR("CRTC clock %d kHz exceeds limit\n", mode->clock);
        return -EINVAL;
    }

    return 0;
}

4.7 CRTC 注册与用户空间交互

CRTC 注册后,用户空间可以通过以下 ioctl 与 CRTC 交互:

ioctl 功能 对应回调
DRM_IOCTL_MODE_GETCRTC 获取 CRTC 当前模式、位置、绑定 FB 无(直接读取 state)
DRM_IOCTL_MODE_SETCRTC 设置 CRTC 模式、绑定 FB、连接器 set_config
DRM_IOCTL_MODE_PAGE_FLIP 请求 VBLANK 同步的 FB 切换 page_flip
DRM_IOCTL_MODE_GETPROPERTY 获取 CRTC 属性(如 CTM、gamma) 无(直接读取 state)
调试技巧: 使用 modetest -M <driver> -c 可以列出所有已注册的 CRTC 及其当前模式。如果 CRTC 未出现,检查 drm_crtc_init 返回值以及 crtc_list 是否包含该 CRTC。

4.8 常见初始化错误与排查

CRTC 初始化失败通常由以下原因导致:

  • 主平面未绑定:drm_crtc_init_with_planes 的 primary 参数为 NULL,且框架自动创建失败。
  • possible_crtcs 掩码错误:plane 的 possible_crtcs 未包含 CRTC 索引,导致绑定失败。
  • 状态未初始化:atomic 模式下未调用 drm_atomic_helper_crtc_reset,导致 state 为 NULL。
  • 回调函数缺失:set_config 或 atomic_flush 未实现,导致 ioctl 返回 -ENOSYS。
  • 重复注册:在新内核中显式调用 drm_mode_crtc_register,导致内核警告。
// 排查 CRTC 初始化问题的调试代码
static void my_crtc_debug(struct drm_crtc *crtc)
{
    DRM_INFO("CRTC %s: id=%d, primary=%s, cursor=%s\n",
             crtc->name, crtc->base.id,
             crtc->primary ? crtc->primary->name : "NULL",
             crtc->cursor ? crtc->cursor->name : "NULL");

    if (crtc->state) {
        DRM_INFO("  state: active=%d, mode=%dx%d@%d\n",
                 crtc->state->active,
                 crtc->state->mode.hdisplay,
                 crtc->state->mode.vdisplay,
                 crtc->state->mode.vrefresh);
    } else {
        DRM_WARN("  state is NULL!\n");
    }
}
内核版本差异: Linux 4.7 之前,CRTC 初始化需要手动调用 drm_crtc_init(无 plane 参数),然后通过 drm_crtc_helper_add 设置辅助回调。4.7+ 推荐使用 drm_crtc_init_with_planes,并显式绑定 plane。5.0+ 弃用了 drm_mode_crtc_register,注册由 drm_dev_register 统一管理。

4.9 本章小结

CRTC 初始化是 KMS 显示管线搭建的关键环节,涉及对象创建、回调配置、状态管理和设备注册四个核心步骤。驱动开发者需要:

  • 使用 drm_crtc_init_with_planes 创建 CRTC 并绑定主平面。
  • 实现 drm_crtc_funcs 和 drm_crtc_helper_funcs 中的关键回调。
  • 在 atomic 模式下正确初始化和管理 drm_crtc_state。
  • 根据内核版本选择合适的注册方式,避免兼容性问题。