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的关键门槛。

核心认知: DRM/KMS不是简单的“画图框架”,而是一个面向硬件的对象化模型。每个对象对应硬件中的独立功能单元,驱动开发者需要将硬件寄存器操作映射到这些对象的回调函数中。

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
移植要点: RTOS中通常直接操作寄存器设置时序和图层,而DRM/KMS要求将这些操作分散到不同对象的回调中。例如,时序参数放在CRTC的mode_set中,接口电压配置放在Encoder的mode_fixup中。

4.2 对象关系与数据流

下图展示了DRM/KMS五对象之间的连接关系以及像素数据流路径:

DRM/KMS五对象模型与数据流 DRM/KMS 五对象模型与像素数据流 DRM Device (设备驱动上下文) Plane 图层管理 / 合成 CRTC 时序生成 / 扫描输出 Encoder 信号编码 / 接口转换 Connector 物理连接 / 热插拔 像素数据 时序+像素 编码信号 连接关系说明 • Plane → CRTC:图层合成 • CRTC → Encoder:时序传递 • Encoder → Connector:信号输出 用户空间 libdrm / ioctl 调用 atomic_commit / setcrtc
数据流解读: 用户空间通过libdrm发起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,
};
RTOS移植陷阱: RTOS中显示驱动通常直接暴露framebuffer地址给应用层,而DRM Device要求通过GEM/PRIME机制管理显存。如果硬件支持连续物理内存,可以使用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等掩码建立绑定关系。下图展示了一个典型嵌入式设备的对象拓扑:

DRM/KMS对象绑定拓扑示例 典型嵌入式设备 DRM/KMS 对象拓扑 CRTC 0 CRTC 1 Encoder 0 LVDS Encoder 1 HDMI Encoder 2 DSI Connector 0 LVDS屏 Connector 1 HDMI座 Connector 2 DSI屏 绑定规则说明 • CRTC 0 可绑定 Encoder 0 (LVDS) 和 Encoder 1 (HDMI),通过 possible_crtcs 掩码控制 • CRTC 1 只能绑定 Encoder 2 (DSI),用于副屏显示
驱动实现技巧: 在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提交的核心流程如下:

  1. 状态复制: 调用drm_atomic_state_alloc分配新状态对象,复制当前所有对象状态
  2. 状态修改: 用户空间通过drm_atomic_set_crtc_for_connector、drm_atomic_set_fb_for_plane等接口修改状态
  3. 校验: 内核调用每个对象的atomic_check回调,验证新状态是否合法
  4. 提交: 调用drm_atomic_commit,依次执行每个对象的atomic_flush回调,更新硬件寄存器
  5. 完成: 等待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);
}
RTOS迁移关键差异: RTOS中显示驱动通常直接操作寄存器,没有事务回滚机制。在DRM/KMS Atomic模式下,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 低(使用标准接口)
移植策略建议: 从RTOS到DRM/KMS的移植,建议按照“Connector → Encoder → CRTC → Plane → Atomic”的顺序逐步实现。先让Connector能检测到屏幕,再让Encoder输出信号,然后让CRTC产生时序,接着让Plane显示图像,最后接入Atomic事务管理。每完成一步都可以通过modetest工具验证。