4. Linux DRM/KMS框架:DRM核心、CRTC、Encoder、Connector、Plane对象模型
在Linux系统中,显示子系统的驱动开发几乎绕不开DRM(Direct Rendering Manager)与KMS(Kernel Mode Setting)框架。DRM/KMS将显示硬件抽象为五个核心对象:DRM设备、CRTC、Encoder、Connector和Plane。理解这五个对象之间的协作关系,是移植LCD驱动从RTOS到Linux的关键门槛。
4.1 五对象模型总览
下表列出了DRM/KMS框架中五个核心对象的职责、硬件映射关系以及典型操作接口:
| 对象 | 职责 | 硬件映射 | 关键操作 |
|---|---|---|---|
| DRM Device | 管理整个显示设备,提供用户空间访问入口 | GPU/显示控制器整体 | open/close/ioctl |
| CRTC | 生成显示时序,控制扫描输出 | 显示控制器(Timing Controller) | mode_set/enable/disable |
| Encoder | 将CRTC输出的像素数据编码为特定接口信号 | LVDS/HDMI/DSI发送器 | mode_fixup/atomic_check |
| Connector | 物理连接器,检测热插拔与EDID信息 | HDMI座/LVDS座/eDP座 | detect/get_modes |
| Plane | 管理显示图层,支持叠加与合成 | 硬件图层(Overlay/Primary/Cursor) | update_plane/disable_plane |
mode_set中,接口电压配置放在Encoder的mode_fixup中。
4.2 对象关系与数据流
下图展示了DRM/KMS五对象之间的连接关系以及像素数据流路径:
atomic_commit调用,DRM Device接收请求后,依次调用Plane更新图层内容、CRTC重新配置时序、Encoder调整编码参数、Connector检测连接状态。整个流程在drm_atomic_state中统一提交,保证画面不撕裂。
4.3 各对象深度解析
4.3.1 DRM Device — 框架入口
DRM Device是整个框架的根对象,对应/dev/dri/card0设备节点。驱动开发者需要实现struct drm_driver结构体,注册文件操作、GEM内存管理、fence同步等基础设施。
// 典型的DRM驱动入口结构
static struct drm_driver my_lcd_driver = {
.driver_features = DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &my_lcd_fops,
.gem_create_object = my_gem_create_object,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.name = "my_lcd",
.desc = "My LCD Controller DRM Driver",
.date = "20250101",
.major = 1,
.minor = 0,
};
drm_gem_cma_helper简化实现;如果使用IOMMU/SMMU,则需要实现drm_gem_object_funcs中的vmap/vunmap回调。
4.3.2 CRTC — 时序大脑
CRTC(Cathode Ray Tube Controller,沿用历史名称)负责生成行场同步信号、像素时钟以及前后肩参数。每个CRTC对应一个独立的显示通道,可以输出一种分辨率和刷新率。
CRTC的核心回调函数如下:
| 回调函数 | 触发时机 | 典型实现内容 |
|---|---|---|
mode_set |
模式切换时 | 配置PLL分频、设置H/V总像素、前后肩、同步极性 |
enable |
CRTC被启用时 | 开启像素时钟、释放复位、启动DMA传输 |
disable |
CRTC被禁用时 | 停止DMA、关闭时钟、进入低功耗 |
atomic_flush |
Atomic提交刷新时 | 更新寄存器组、触发双缓冲切换 |
// CRTC回调实现示例
static void my_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct my_display_priv *priv = to_my_priv(crtc);
u32 htotal, vtotal;
htotal = mode->htotal;
vtotal = mode->vtotal;
// 配置硬件时序寄存器
writel(htotal, priv->mmio + TIMING_H_TOTAL);
writel(vtotal, priv->mmio + TIMING_V_TOTAL);
writel(mode->hdisplay, priv->mmio + TIMING_H_ACTIVE);
writel(mode->vdisplay, priv->mmio + TIMING_V_ACTIVE);
// ... 配置前后肩、同步宽度、极性
}
4.3.3 Encoder — 信号转换器
Encoder负责将CRTC输出的并行RGB/YCbCr数据转换为特定显示接口的串行信号。常见的Encoder类型包括:
- LVDS Encoder: 将并行RGB转换为低压差分信号,用于液晶屏
- HDMI Encoder: 添加TMDS编码、音频插入、HDCP加密
- DSI Encoder: 将像素数据打包为MIPI DSI协议包
- eDP Encoder: 嵌入DisplayPort辅助通道和训练序列
Encoder与CRTC之间通过drm_encoder.crtc指针建立绑定关系。在Atomic模式下,Encoder需要实现atomic_check来验证当前模式是否支持编码。
// Encoder atomic_check 示例:检查分辨率是否超出编码器带宽
static int my_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_display_mode *mode = &crtc_state->mode;
u64 pixel_clock = mode->clock * 1000; // Hz
u64 max_bandwidth = 150000000; // 150 MHz
if (pixel_clock > max_bandwidth) {
DRM_ERROR("Pixel clock %llu Hz exceeds encoder limit\n", pixel_clock);
return -EINVAL;
}
return 0;
}
4.3.4 Connector — 物理接口管家
Connector代表物理显示接口,负责检测显示器是否连接、读取EDID(Extended Display Identification Data)获取显示器支持的分辨率列表。Connector类型包括DRM_MODE_CONNECTOR_HDMIA、DRM_MODE_CONNECTOR_LVDS、DRM_MODE_CONNECTOR_DSI等。
Connector的两个关键回调:
detect:返回连接状态(connected/disconnected/unknown),通常通过读取HPD(Hot Plug Detect)引脚电平实现get_modes:读取EDID并解析为drm_display_mode列表,对于固定分辨率屏(如LVDS),直接添加预设模式
// 固定分辨率屏的get_modes实现
static int my_connector_get_modes(struct drm_connector *connector)
{
struct drm_display_mode *mode;
// 对于LVDS固定屏,直接添加预设模式
mode = drm_mode_duplicate(connector->dev, &my_lvds_mode);
if (!mode) {
DRM_ERROR("Failed to duplicate mode\n");
return 0;
}
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
return 1; // 返回添加的模式数量
}
4.3.5 Plane — 图层合成引擎
Plane是DRM/KMS中实现硬件图层叠加的核心对象。每个Plane对应一个硬件图层,支持不同的像素格式和缩放能力。Plane分为三种类型:
| Plane类型 | 用途 | 典型数量 | 硬件要求 |
|---|---|---|---|
| Primary Plane | 主显示层,必须存在 | 1个/CRTC | 支持帧缓冲扫描 |
| Overlay Plane | 叠加层,用于视频/UI | 0~N个 | 支持Alpha混合、缩放 |
| Cursor Plane | 硬件光标层 | 0~1个 | 支持小尺寸、高刷新 |
Plane的update_plane回调需要处理framebuffer地址更新、颜色空间转换、Alpha值设置等操作:
// Plane更新回调示例
static int my_plane_update(struct drm_plane *plane,
struct drm_plane_state *old_state)
{
struct my_display_priv *priv = to_my_priv(plane);
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
u32 addr;
// 获取framebuffer的物理地址(GEM对象)
addr = drm_fb_cma_get_gem_addr(fb, state, 0);
// 配置图层寄存器
writel(addr, priv->mmio + LAYER0_ADDR);
writel(state->crtc_x, priv->mmio + LAYER0_POS_X);
writel(state->crtc_y, priv->mmio + LAYER0_POS_Y);
writel(state->crtc_w, priv->mmio + LAYER0_WIDTH);
writel(state->crtc_h, priv->mmio + LAYER0_HEIGHT);
writel(state->alpha >> 8, priv->mmio + LAYER0_ALPHA); // 0~255
return 0;
}
4.4 对象绑定与拓扑结构
在DRM/KMS中,五个对象通过possible_crtcs、possible_clones等掩码建立绑定关系。下图展示了一个典型嵌入式设备的对象拓扑:
struct drm_encoder中设置possible_crtcs时,使用位掩码表示允许绑定的CRTC索引。例如possible_crtcs = BIT(0) | BIT(1)表示该Encoder可以绑定CRTC 0或CRTC 1。对于只有一个CRTC的系统,直接设为BIT(0)即可。
4.5 Atomic模式下的对象协作
DRM/KMS的Atomic模式将所有对象的状态变更打包为一个原子事务,确保整个提交过程要么全部成功,要么全部回滚。Atomic提交的核心流程如下:
- 状态复制: 调用
drm_atomic_state_alloc分配新状态对象,复制当前所有对象状态 - 状态修改: 用户空间通过
drm_atomic_set_crtc_for_connector、drm_atomic_set_fb_for_plane等接口修改状态 - 校验: 内核调用每个对象的
atomic_check回调,验证新状态是否合法 - 提交: 调用
drm_atomic_commit,依次执行每个对象的atomic_flush回调,更新硬件寄存器 - 完成: 等待VBlank中断,触发
atomic_commit_tail完成清理工作
// Atomic提交的驱动侧实现骨架
static void my_atomic_commit_tail(struct drm_atomic_state *state)
{
struct drm_device *dev = state->dev;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
int i;
// 1. 禁用所有需要关闭的CRTC
for_each_oldnew_crtc_in_state(state, crtc, crtc_state, i) {
if (crtc_state->active && !crtc->state->active)
my_crtc_disable(crtc);
}
// 2. 更新所有Plane的framebuffer地址
drm_atomic_helper_update_planes(dev, state);
// 3. 启用所有需要开启的CRTC
for_each_oldnew_crtc_in_state(state, crtc, crtc_state, i) {
if (!crtc_state->active && crtc->state->active)
my_crtc_enable(crtc);
}
// 4. 等待VBlank,完成提交
drm_atomic_helper_wait_for_vblanks(dev, state);
}
atomic_check阶段必须严格验证所有参数,因为一旦进入atomic_commit,硬件寄存器写入顺序错误可能导致画面撕裂或系统挂死。建议在atomic_check中模拟整个配置流程,确保所有资源(时钟、带宽、内存)都可用。
4.6 对象模型与RTOS驱动的对比
下表总结了DRM/KMS对象模型与典型RTOS显示驱动之间的关键差异,帮助开发者快速定位移植工作重点:
| 对比维度 | RTOS显示驱动 | Linux DRM/KMS | 移植工作量 |
|---|---|---|---|
| 代码组织 | 单一文件,所有功能耦合 | 按对象拆分,每个对象独立文件 | 中 |
| 时序配置 | 直接写寄存器 | CRTC的mode_set回调 | 低 |
| 接口编码 | 与时序混在一起 | Encoder独立处理 | 中 |
| 热插拔检测 | 轮询或中断直接处理 | Connector的detect回调 + 内核事件 | 中 |
| 图层管理 | 直接操作图层寄存器 | Plane对象 + framebuffer管理 | 高 |
| 状态同步 | 无事务概念 | Atomic事务 + 回滚机制 | 高 |
| 用户空间接口 | 自定义ioctl或直接mmap | 标准DRM ioctl + libdrm | 低(使用标准接口) |
modetest工具验证。