4. CRTC 初始化:CRTC 的创建、配置与注册
CRTC(Cathode Ray Tube Controller)是 KMS 显示管线的核心调度引擎,负责从 Framebuffer 读取像素数据、叠加硬件图层、生成时序信号并驱动 Encoder 输出。在 Linux 内核显示子系统中,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 * |
主平面,必须绑定 |
state 字段必须通过 drm_atomic_helper_crtc_reset 初始化,否则 CRTC 状态机无法正常工作。传统(legacy)模式则不需要。
4.2 CRTC 创建流程
CRTC 的创建通常发生在驱动 probe 函数的 bind 阶段,紧接在 drm_mode_config_init 之后。标准流程如下:
- 分配 CRTC 对象:调用
drm_crtc_init或drm_crtc_init_with_planes。 - 设置回调函数:填充
drm_crtc_funcs和drm_crtc_helper_funcs。 - 绑定主平面:将 primary plane 与 CRTC 关联。
- 初始化状态:调用
drm_atomic_helper_crtc_reset创建初始 state。 - 注册 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;
}
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 注册完成的完整初始化流程,以及各阶段涉及的核心函数与数据结构:
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,框架会自动创建一个默认的主平面。但推荐驱动开发者显式创建并配置主平面,以便控制像素格式和缩放能力。
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 模式下,状态更新流程如下:
- 复制状态:
drm_atomic_helper_crtc_duplicate_state创建新状态副本。 - 修改状态:驱动或用户空间修改
mode、active等字段。 - 校验状态:
atomic_check回调检查新状态是否合法。 - 提交状态:
atomic_flush回调将状态写入硬件寄存器。 - 交换状态:框架自动将新状态设为当前状态。
// 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");
}
}
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。 - 根据内核版本选择合适的注册方式,避免兼容性问题。