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)
DRM/KMS 子系统层次关系 用户空间 (User Space) DRM 核心 (drm_core) — 通用框架 KMS 模式设置 (drm_mode_config) — CRTC/Encoder/Connector/Plane 显示控制器硬件 (Display Controller / GPU)

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 中的回调函数。
DRM 子系统软件架构 用户空间 (libdrm / Mesa / Weston / Xorg) 系统调用接口 (ioctl / mmap / poll) DRM 核心框架 drm_ioctl / drm_gem / drm_mode_config / drm_atomic KMS 模式设置 CRTC / Encoder / Connector / Plane GEM 内存管理 Buffer Object / DMA-BUF / Dumb Buffer DRM 驱动层 (Driver Callbacks) .load / .unload / .irq_handler / .gem_create_object / .mode_valid / .atomic_check 显示控制器硬件 (Display Controller / GPU / HDMI / DP / LVDS)

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 等掩码字段建立连接关系。

DRM/KMS 核心对象连接关系与数据流 Framebuffer 帧缓冲区 Plane 硬件平面层 CRTC 显示控制器 Encoder 编码器 Connector 连接器 Display 显示面板 连接关系说明 CRTC ↔ Encoder ↔ Connector 通过 possible_crtcs / possible_clones 掩码绑定 Plane 与 CRTC 关系 Plane 绑定到 CRTC 的 possible_planes 支持主平面 (Primary) + 叠加平面 (Overlay) + 光标平面 (Cursor)

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 函数中,核心对象的初始化顺序至关重要:

  1. 初始化 DRM 设备:调用 drm_dev_alloc() 或 devm_drm_dev_alloc()。
  2. 初始化 CRTC:调用 drm_crtc_init_with_planes() 绑定主平面和光标平面。
  3. 初始化 Encoder:调用 drm_encoder_init(),指定编码器类型。
  4. 初始化 Connector:调用 drm_connector_init(),实现 .get_modes 回调读取 EDID。
  5. 建立连接:调用 drm_connector_attach_encoder() 绑定 Encoder。
  6. 注册设备:调用 drm_dev_register() 暴露给用户空间。
DRM 核心对象初始化流程 ① drm_dev_alloc() — 分配并初始化 DRM 设备结构体 ② drm_crtc_init_with_planes() — 初始化 CRTC 并绑定 Plane ③ drm_encoder_init() — 初始化 Encoder 编码器 ④ drm_connector_init() — 初始化 Connector 连接器 ⑤ drm_dev_register() — 注册设备,暴露 /dev/dri/card0

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 分离实现,以获得更高的灵活性和性能。