Linux内核显示驱动开发实战 - 第4节
4.1 DRM/KMS 核心概念
DRM (Direct Rendering Manager) 是 Linux 内核中负责图形显示和渲染的子系统。KMS (Kernel Mode Setting) 是 DRM 的一部分,负责显示模式设置、分辨率切换、平面合成等显示控制功能。理解 DRM/KMS 架构是开发显示驱动的第一步。
核心认知: DRM 提供用户空间直接渲染的接口,KMS 则在内核空间管理显示硬件状态。两者共同构成了现代 Linux 图形栈的基石。
4.1.1 DRM 与 KMS 的关系
| 维度 | DRM | KMS |
|---|---|---|
| 全称 | Direct Rendering Manager | Kernel Mode Setting |
| 主要职责 | GPU 命令提交、内存管理、GEM/Dumb Buffer | 显示模式、分辨率、刷新率、平面合成 |
| 用户空间接口 | /dev/dri/card0 (render node) |
/dev/dri/card0 (primary node) |
| 核心对象 | GEM Object, File, VMA | CRTC, Encoder, Connector, Plane |
| 驱动类型 | GPU 驱动 (i915, amdgpu) | 显示控制器驱动 (drm_simple_kms_helper) |
4.2 DRM 子系统架构
DRM 子系统采用分层架构设计,从上到下依次为:用户空间接口层、DRM 核心框架、KMS 模式设置层、硬件驱动层。每一层都定义了清晰的接口和回调函数。
4.2.1 架构组件详解
- DRM Core (drm_core.c):提供设备注册、文件操作、ioctl 分发、内存管理基础框架。
- DRM GEM (Graphics Execution Manager):通用图形内存管理器,负责 buffer 分配、映射、共享。
- DRM KMS Helper (drm_kms_helper):简化 KMS 驱动开发的辅助层,提供 atomic commit 框架。
- DRM Driver:具体硬件驱动,实现
struct drm_driver中的回调函数。
4.3 DRM 核心对象详解
KMS 的核心在于四个对象:CRTC、Encoder、Connector、Plane。它们共同描述了从帧缓冲区到显示面板的完整数据通路。
4.3.1 核心对象职责
| 对象 | 内核结构体 | 职责描述 | 典型数量 |
|---|---|---|---|
| CRTC | struct drm_crtc |
显示控制器核心,负责扫描输出、时序生成、模式切换 | 1~2 (单/双屏) |
| Encoder | struct drm_encoder |
编码器,将 CRTC 输出的像素数据编码为特定接口信号 (HDMI/DP/LVDS) | 1~4 |
| Connector | struct drm_connector |
物理连接器,检测显示器热插拔、读取 EDID、报告支持的模式 | 1~4 |
| Plane | struct drm_plane |
硬件平面层,支持叠加、缩放、旋转,用于合成最终画面 | 1~8 |
4.3.2 数据通路与对象连接关系
典型的显示数据流为:Framebuffer → Plane → CRTC → Encoder → Connector → Display。每个对象通过 possible_crtcs、possible_clones 等掩码字段建立连接关系。
4.4 核心对象编程接口
4.4.1 CRTC 操作
CRTC 是显示控制器的核心,驱动需要实现 struct drm_crtc_helper_funcs 中的回调:
struct drm_crtc_helper_funcs {
void (*mode_valid)(struct drm_crtc *crtc,
const struct drm_display_mode *mode);
void (*mode_set)(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode);
void (*atomic_enable)(struct drm_crtc *crtc,
struct drm_crtc_state *old_state);
void (*atomic_disable)(struct drm_crtc *crtc,
struct drm_crtc_state *old_state);
};
4.4.2 Encoder 与 Connector 配对
Encoder 和 Connector 通过 drm_connector_attach_encoder() 建立绑定关系:
struct drm_encoder *encoder;
struct drm_connector *connector;
/* 初始化 encoder */
drm_encoder_init(drm_dev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
/* 初始化 connector */
drm_connector_init(drm_dev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
/* 建立连接 */
drm_connector_attach_encoder(connector, encoder);
4.4.3 Plane 配置
Plane 支持多种类型,驱动初始化时需指定:
struct drm_plane *primary_plane;
struct drm_plane *overlay_plane;
/* 主平面 — 必须存在 */
drm_universal_plane_init(drm_dev, primary_plane, 0,
&primary_plane_funcs,
primary_formats, ARRAY_SIZE(primary_formats),
NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
/* 叠加平面 — 可选 */
drm_universal_plane_init(drm_dev, overlay_plane, 0,
&overlay_plane_funcs,
overlay_formats, ARRAY_SIZE(overlay_formats),
NULL, DRM_PLANE_TYPE_OVERLAY, NULL);
开发提示: 对于简单的嵌入式显示控制器,可以使用
drm_simple_display_pipe_init() 一步完成 CRTC + Plane + Encoder + Connector 的初始化,大幅减少样板代码。
4.5 对象初始化流程
在驱动 probe 函数中,核心对象的初始化顺序至关重要:
- 初始化 DRM 设备:调用
drm_dev_alloc()或devm_drm_dev_alloc()。 - 初始化 CRTC:调用
drm_crtc_init_with_planes()绑定主平面和光标平面。 - 初始化 Encoder:调用
drm_encoder_init(),指定编码器类型。 - 初始化 Connector:调用
drm_connector_init(),实现.get_modes回调读取 EDID。 - 建立连接:调用
drm_connector_attach_encoder()绑定 Encoder。 - 注册设备:调用
drm_dev_register()暴露给用户空间。
4.6 常见问题与调试技巧
常见陷阱:
- Connector 的
.get_modes回调必须正确解析 EDID,否则用户空间无法获取有效显示模式。 - CRTC 的
possible_crtcs掩码必须与 Encoder 的possible_crtcs匹配,否则 atomic check 会失败。 - Plane 的格式列表必须包含硬件支持的格式,否则 framebuffer 创建会返回
-EINVAL。
4.6.1 调试命令
| 命令 | 用途 |
|---|---|
modetest -M <driver> |
列出所有 CRTC/Encoder/Connector/Plane 及其状态 |
cat /sys/kernel/debug/dri/0/state |
查看当前 atomic state 的完整快照 |
dmesg | grep drm |
查看 DRM 驱动加载和初始化日志 |
cat /sys/kernel/debug/dri/0/connectors |
查看 Connector 的 EDID 和连接状态 |
实战要点: 在开发初期,建议使用
drm_simple_display_pipe 快速验证硬件通路是否正常。待基本显示输出成功后,再逐步替换为完整的 CRTC + Plane + Encoder + Connector 分离实现,以获得更高的灵活性和性能。