Linux DRM KMS显示控制器驱动开发与适配 - 第2节
2.1 DRM 架构概览
DRM(Direct Rendering Manager)是 Linux 内核中负责图形显示与渲染管理的核心框架。它向上提供统一的用户空间接口(通过 /dev/dri/card0 等设备节点),向下抽象各类显示控制器硬件。KMS(Kernel Mode Setting)是 DRM 框架中专门负责显示模式设置与输出管理的子系统。
2.2 五大核心组件详解
KMS 将显示输出链路抽象为五个核心组件,它们共同构成一条完整的显示管线。每个组件在 drm_mode_config 结构中注册,并通过 ID 相互关联。
| 组件 | 内核结构体 | 硬件对应 | 核心职责 |
|---|---|---|---|
| Framebuffer | drm_framebuffer |
显存中的帧缓冲区 | 管理像素数据的内存对象,定义格式、宽高、步幅 |
| Plane | drm_plane |
硬件图层(Overlay / Primary / Cursor) | 将 Framebuffer 的内容叠加到 CRTC 输出上,支持缩放与裁剪 |
| CRTC | drm_crtc |
显示控制器核心(Timing Generator) | 生成像素时钟与同步信号,驱动扫描输出 |
| Encoder | drm_encoder |
显示信号编码器(如 HDMI TX、DP 编码器) | 将 CRTC 输出的并行像素数据编码为串行链路信号 |
| Connector | drm_connector |
物理连接器(HDMI、DP、eDP、DSI 接口) | 检测显示器热插拔,读取 EDID,提供可用模式列表 |
2.2.1 Framebuffer(帧缓冲区)
Framebuffer 是 DRM 中最基础的内存对象,它封装了一块用于存储像素数据的 DMA 缓冲区。用户空间通过 drm_mode_addfb2 创建 Framebuffer,内核驱动负责将其绑定到具体的显存地址。
struct drm_framebuffer {
struct drm_device *dev;
struct drm_mode_object base;
const struct drm_framebuffer_funcs *funcs;
unsigned int width; // 像素宽度
unsigned int height; // 像素高度
unsigned int pitches[4]; // 每行字节数(支持多平面)
unsigned int offsets[4]; // 平面偏移
uint32_t format; // 像素格式(如 DRM_FORMAT_XRGB8888)
uint64_t modifier; // 内存布局修饰符(如 AFBC、线性)
struct drm_gem_object *obj[4];
};
drm_framebuffer_funcs 中的 create_handle 和 destroy 回调。对于支持 AFBC 压缩格式的硬件,务必正确设置 modifier 字段。
2.2.2 Plane(硬件图层)
Plane 是 KMS 中负责图层合成的组件。每个 Plane 对应一个硬件图层,支持独立的 Framebuffer 绑定、位置调整、缩放和透明度混合。现代 SoC 显示控制器通常包含 3 类 Plane:
- Primary Plane: 主图层,必须存在,通常用于显示桌面背景或主应用界面。
- Overlay Plane: 叠加图层,用于视频播放、UI 弹窗等,支持硬件 Alpha 混合。
- Cursor Plane: 光标图层,硬件光标,支持快速移动而不重绘整个画面。
struct drm_plane {
struct drm_device *dev;
struct drm_crtc *crtc; // 当前绑定的 CRTC
struct drm_crtc *possible_crtcs; // 可绑定的 CRTC 位掩码
uint32_t type; // DRM_PLANE_TYPE_PRIMARY / OVERLAY / CURSOR
unsigned int format_count;
const uint32_t *format_list; // 支持的像素格式列表
struct drm_plane_state *state; // 当前状态
};
2.2.3 CRTC(显示控制器核心)
CRTC(Cathode Ray Tube Controller,历史名称沿用)是显示管线的核心调度器。它负责从绑定的 Plane 读取像素数据,生成符合显示器要求的时序信号(Hsync、Vsync、像素时钟),并驱动 Encoder 输出。
| CRTC 回调 | 功能说明 | 适配注意事项 |
|---|---|---|
mode_valid |
验证显示模式是否被硬件支持 | 检查时钟频率、分辨率是否在硬件限制内 |
mode_set |
配置 CRTC 的时序寄存器 | 设置 Htotal、Vtotal、时钟分频等参数 |
enable |
启动 CRTC 输出 | 使能像素时钟、释放复位、启动扫描 |
disable |
停止 CRTC 输出 | 关闭时钟、进入低功耗模式 |
atomic_flush |
原子提交所有 Plane 状态变更 | 在 VBlank 期间一次性更新寄存器 |
2.2.4 Encoder(信号编码器)
Encoder 将 CRTC 输出的并行 RGB/YCbCr 数据转换为特定显示接口所需的串行信号。常见的 Encoder 类型包括:
- TMDS Encoder: 用于 HDMI/DVI,将像素数据编码为 TMDS 差分信号。
- DisplayPort Encoder: 将数据打包为 DP 微包,支持多流传输(MST)。
- DSI Encoder: 用于 MIPI DSI 接口,输出串行命令/像素流。
- LVDS Encoder: 用于传统 LCD 屏,输出低压差分信号。
struct drm_encoder {
struct drm_device *dev;
struct drm_crtc *crtc; // 当前绑定的 CRTC
unsigned int possible_crtcs; // 可绑定的 CRTC 位掩码
unsigned int possible_clones; // 可克隆的 Encoder 位掩码
enum drm_encoder_type encoder_type; // DRM_MODE_ENCODER_TMDS / DSI / LVDS / DP
};
2.2.5 Connector(物理连接器)
Connector 是显示输出链路的最末端,直接对应物理接口。它负责检测显示器连接状态、读取 EDID 数据、提供可用显示模式列表,并管理电源控制(如 DPMS)。
get_modes 回调必须正确解析 EDID。如果硬件不支持 EDID(如 DSI 固定分辨率屏),驱动应通过 drm_add_modes_noedid 添加固定模式,并调用 drm_set_preferred_mode 设置首选分辨率。
struct drm_connector {
struct drm_device *dev;
struct drm_encoder *encoder; // 当前绑定的 Encoder
enum drm_connector_status status; // connected / disconnected / unknown
struct edid *edid_blob_ptr; // 缓存的 EDID 数据
struct drm_display_mode *modes; // 可用模式链表
int (*get_modes)(struct drm_connector *connector);
int (*detect)(struct drm_connector *connector, bool force);
};
2.3 组件连接关系与显示管线
五个组件按照 Framebuffer → Plane → CRTC → Encoder → Connector 的顺序串联,形成完整的显示管线。用户空间通过 Atomic Commit 或 Legacy SetConfig 接口配置这条管线。
2.4 组件注册与初始化流程
在显示控制器驱动的 bind 或 probe 函数中,需要按以下顺序注册 KMS 组件:
- 初始化
drm_mode_config: 调用drm_mode_config_init,设置min_width/height、max_width/height等硬件限制。 - 注册 CRTC: 调用
drm_crtc_init_with_planes,绑定 Primary 和 Cursor Plane。 - 注册 Plane: 调用
drm_universal_plane_init,指定支持的像素格式和类型。 - 注册 Encoder: 调用
drm_encoder_init,设置possible_crtcs位掩码。 - 注册 Connector: 调用
drm_connector_init,指定 Connector 类型和drm_connector_funcs。 - 建立连接: 调用
drm_connector_attach_encoder将 Connector 绑定到 Encoder。 - 注册模式配置: 调用
drm_mode_config_reset初始化默认状态。
static int my_display_bind(struct drm_device *drm)
{
// 1. 初始化 mode_config
drm_mode_config_init(drm);
drm->mode_config.min_width = 320;
drm->mode_config.max_width = 4096;
drm->mode_config.min_height = 240;
drm->mode_config.max_height = 2160;
// 2. 注册 Plane
drm_universal_plane_init(drm, &primary_plane, 0xFF,
&primary_plane_funcs, formats, num_formats,
DRM_PLANE_TYPE_PRIMARY, NULL);
// 3. 注册 CRTC
drm_crtc_init_with_planes(drm, &crtc, &primary_plane, &cursor_plane,
&crtc_funcs, NULL);
// 4. 注册 Encoder
drm_encoder_init(drm, &encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
// 5. 注册 Connector
drm_connector_init(drm, &connector, &connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
// 6. 建立连接
drm_connector_attach_encoder(&connector, &encoder);
// 7. 重置配置
drm_mode_config_reset(drm);
return 0;
}
2.5 组件间的状态依赖关系
KMS 组件之间存在严格的状态依赖链。在进行 Atomic Check 时,DRM 核心会验证以下约束:
| 依赖关系 | 约束说明 | 验证函数 |
|---|---|---|
| Plane → CRTC | Plane 只能绑定到 possible_crtcs 位掩码允许的 CRTC |
drm_atomic_plane_check |
| CRTC → Encoder | CRTC 的输出必须连接到兼容的 Encoder | drm_atomic_encoder_check |
| Encoder → Connector | Encoder 必须与 Connector 建立绑定关系 | drm_atomic_connector_check |
| Framebuffer → Plane | Framebuffer 的格式必须被 Plane 支持 | drm_plane_check_pixel_format |
| Connector → 显示模式 | Connector 必须提供至少一个有效显示模式 | drm_connector_mode_valid |
atomic_check 回调时,必须遍历所有组件状态,确保硬件约束不被违反。例如,某些 SoC 的 Overlay Plane 不支持与 Primary Plane 使用不同的像素时钟,此时需要在 check 阶段拒绝此类配置。