Linux 内核 DRM/KMS 显示管理架构深度实战:从 CRTC/Encoder/Connector 到原子模式设置

在 Linux 内核的众多子系统中,DRM(Direct Rendering Manager)是最复杂、历史包袱最重、却又对现代桌面和移动端用户体验至关重要的图形栈核心。从古老的 XFree86 到今天的 Wayland 合成器,从嵌入式 MCU 屏幕到多 GPU 异构渲染,DRM/KMS 提供了一套统一的显示与渲染管理抽象。本文将从硬件拓扑建模出发,层层深入 CRTC/Encoder/Connector 核心对象模型、GEM/TTM 显存管理、KMS 原子模式设置、Vblank 事件同步、以及 DRM 驱动开发实战,为你揭开 Linux 图形栈内核侧的神秘面纱。

本文所有分析基于 Linux 6.6+ 内核源码(drivers/gpu/drm/),代码示例参考 i915/amdgpu/virtio-gpu 等主流 DRM 驱动实现。

一、DRM 子系统全景:从混乱到统一的历史演进

1.1 早期 Linux 图形栈的黑暗时代

在 DRM 出现之前,Linux 的图形管理处于完全的"丛林状态":用户态 X Server 直接通过 /dev/mem 或 ioperm() 操作显卡 MMIO 寄存器,没有内核级的资源隔离与冲突调解。这意味着:

  • 两个图形应用同时写入同一组显卡寄存器会直接导致系统死机
  • 没有统一的显存分配机制,X Server 自行管理全部 VRAM
  • 没有 VSync 同步,画面撕裂(tearing)成为常态
  • 没有模式设置(modesetting)抽象,切换分辨率需要直接操作显卡硬件

2003 年,Keith Packard 和 Eric Anholt 主导了 DRM 子系统的首次重构,引入了核心对象模型:drm_device 作为全局管理器,TTM(Translation Table Maps)作为统一的显存管理器。从 2.6.28 内核开始,DRM 正式成为 Linux 图形栈的内核基石。

1.2 从 DRI1 到 DRI3/Present:渲染与显示的解耦

Linux 图形栈的完整演进路径为:

  • DRI1(2000):全局锁机制,所有应用共享锁,串行化渲染——性能灾难
  • DRI2(2008):引入 GEM(Graphics Execution Manager),每个应用有独立渲染缓冲,SwapBuffer 仍通过 X Server 中转
  • DRI3 + Present(2013):DRI3 让应用直接获取渲染缓冲的 GEM handle,Present 扩展让用户态直接将缓冲"present" 给显示控制器,绕过 X Server
  • Wayland(2010+):合成器直接从应用接收 dma-buf,直接通过 KMS 上屏——DRM/KMS 成为 Wayland 合成的唯一后端

1.3 DRM 的两个侧面:Rendering 与 KMS

DRM 子系统实际上包含两个相对独立的功能域:

  • Rendering(渲染侧):通过 GEM/TTM 管理显存,通过 GPU 调度器提交渲染命令(command submission),支持 fence 同步、dma-buf 跨设备共享
  • KMS(Kernel Mode Setting,显示侧):管理显示管线(display pipeline),控制分辨率、刷新率、显示时序

关键洞察是:KMS 是显示硬件无关的抽象层,它用一套统一的对象模型描述所有类型的显示硬件——无论是嵌入式 LCD 控制器、手机 MIPI DSI 接口,还是桌面 DisplayPort 输出。

二、KMS 核心对象模型:CRTC、Encoder、Connector 三件套

2.1 显示管线(Display Pipeline)拓扑

KMS 将显示硬件抽象为三个核心对象和一组辅助对象:

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│  Frame   │────▶│  Plane   │────▶│  CRTC    │────▶│ Encoder  │
│  Buffer  │     │ (图层)   │     │ (扫描器)  │     │ (编码器) │
└──────────┘     └──────────┘     └──────────┘     └──────────┘
                                                      │
                                                      ▼
                              ┌──────────┐     ┌──────────┐
                              │  Panel   │◀────│Connector │◀─── HDMI/eDP/DP
                              │ (显示器) │     │ (连接器) │
                              └──────────┘     └──────────┘

2.2 CRTC(Cathode Ray Tube Controller)

CRTC 是显示管线的"心脏",负责从帧缓冲读取像素数据并按照精确的时序向外发送(即"扫描输出")。每个 CRTC 对应一个独立的时序发生器(timing generator),可以通过 drmModeSetCrtc() 配置其输出模式。

关键数据结构:

struct drm_crtc {
    struct drm_device *dev;
    struct drm_mode_config *config;
    
    // 显示模式
    struct drm_display_mode mode;    // 当前分辨率+时序
    struct drm_display_mode hw_mode;  // 硬件调整后的时序
    
    // 像素扫描
    struct drm_framebuffer *primary; // 主图层(primary plane)
    uint32_t x, y;                   // 扫描起始位置
    bool enabled;                    // CRTC 是否开启
    
    // Gamma 校正与色彩管理
    struct drm_color_lut *gamma_lut;
    size_t gamma_size;
    
    // 模式设置函数
    struct drm_crtc_funcs *funcs;
    void *helper_private;
};

一个 GPU 通常有 1~6 个 CRTC(Intel 集成显卡典型为 3 个),支持多路独立显示输出。

2.3 Encoder(编码器)

Encoder 负责将 CRTC 输出的像素数据编码为适合传输的物理信号:

  • DSI Encoder:将像素转为 MIPI DSI 协议,用于手机/嵌入式 LCD 面板
  • TMDS Encoder:用于 HDMI/DVI(i915 中的 intel_hdmi_encoder)
  • DP Encoder:用于 DisplayPort(支持 MST 多流传输)
  • TV Encoder:将像素转为模拟信号(复合视频),已渐淘汰
  • Virtual Encoder:virtio-gpu 等虚拟化场景使用
struct drm_encoder {
    struct drm_device *dev;
    uint32_t possible_crtcs; // 位掩码:该编码器可连接的 CRTC
    uint32_t possible_clones;// 位掩码:可克隆的编码器
    
    // 编码函数
    const struct drm_encoder_funcs *funcs;
    const struct drm_encoder_helper_funcs *helper_funcs;
};

编码器与 CRTC 并非一一对应——有些硬件可以通过"复用器"(mux)让同一编码器向不同 CRTC 输出。possible_crtcs 位掩码表示了这种连接可能性。

2.4 Connector(连接器)与状态检测

Connector 代表一个物理显示输出接口(HDMI 端口、eDP 端口、DP 端口等)。它管理与显示器的连接状态和 EDID(Extended Display Identification Data)读取:

struct drm_connector {
    struct drm_device *dev;
    
    // 连接器类型
    enum drm_connector_type connector_type;
    // DRM_MODE_CONNECTOR_HDMIA / DRM_MODE_CONNECTOR_eDP / 
    // DRM_MODE_CONNECTOR_DisplayPort / DRM_MODE_CONNECTOR_DSI...
    
    // 状态
    enum drm_connector_status status;
    // connector_status_connected / disconnected / unknown
    
    // EDID
    struct drm_edid *edid;
    struct edid *full_edid;  // 完整的 EDID 1.4 数据
    
    // 可用模式列表
    struct list_head modes;  // drm_display_mode 链表
    
    // 对应编码器
    uint32_t encoder_id;
    uint32_t possible_encoders;
    
    // 热插拔处理
    struct drm_hpd hpd;
    bool polled; // 是否通过轮询检测连接状态
};

热插拔检测(HPD)是连接器的关键功能。当 HDMI 插入时,显卡上的 HPD 引脚触发中断,DRM 子系统执行以下流程:

  1. HPD 中断触发 → 调用 drm_helper_hpd_irq_event()
  2. 读取 DDC 通道上的 EDID 数据块
  3. 解析 EDID → 构建可用模式列表(drm_add_edid_modes())
  4. 通过 udev 通知用户态(X11/Wayland)显示器状态变化
  5. 用户态请求模式设置 → KMS 原子提交

三、Plane 系统:从单一主图层到硬件叠加

3.1 Plane 类型与层次

现代 DRM/KMS 支持多种 Plane 类型,每个 Plane 对应显示管线中的一个独立硬件层(Hardware Overlay):

  • Primary Plane(主图层):必须存在,对应 CRTC 的底层背景。Cursor 除外不可禁用
  • Overlay Plane(叠加图层):硬件覆盖层,支持任意位置放置较小的帧缓冲
  • Cursor Plane(光标图层):通常为 64x64 的小尺寸层,支持硬件鼠标光标

Plane 的核心价值在于硬件合成(Hardware Composition)——视频层在独立的 overlay plane 上显示、UI 层在 primary plane 上显示,两者由显示控制器硬件级合成,无需 GPU 参与,零带宽消耗。

3.2 Plane 属性系统

Linux 4.0 引入的 Atomic KMS 为 Plane 添加了一套通用属性(Property)机制:

struct drm_plane {
    // 标准属性
    struct drm_property *type_prop;        // PRIMARY/OVERLAY/CURSOR
    struct drm_property *alpha_prop;       // 全局透明度 0-255
    struct drm_property *blend_mode_prop;  // 像素混合模式
    struct drm_property *rotation_prop;    // 旋转角度
    struct drm_property *zpos_prop;        // 层级顺序
    struct drm_property *color_encoding_prop; // BT.601/BT.709/BT.2020
    struct drm_property *color_range_prop;    // Limited/Full Range
    
    // 缩放与裁剪
    bool has_scaling;
    uint32_t max_downscale;
    uint32_t max_upscale;
    
    // 支持的像素格式
    const uint32_t *format_modifiers;
    uint32_t format_count;
};

四、GEM 显存管理与 dma-buf 跨设备共享

4.1 GEM:图形执行管理

GEM(Graphics Execution Manager)是 DRM 子系统的显存管理核心,由 Intel Tungsten Graphics 团队在 DRI2 时代引入。其设计目标是统一不同 GPU 厂商的显存分配 API。

GEM 的核心创新在于"命名对象"(Named Object)机制——通过 32-bit 全局唯一 handle 在进程间传递显存对象引用:

// 创建 GEM 缓冲
struct drm_gem_create create = { .size = 4096 * 1024 }; // 4MB
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
uint32_t gem_handle = create.handle;

// 获取 CPU 可访问的映射地址
struct drm_gem_map_off map = { .handle = gem_handle };
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
void *vaddr = mmap(NULL, size, PROT_READ|PROCT_WRITE, MAP_SHARED, fd, map.offset);

// 将 GEM handle 传递给另一个进程(通过 Unix 域套接字)
struct drm_gem_open open = { .name = gem_name };
ioctl(other_fd, DRM_IOCTL_GEM_OPEN, &open);
uint32_t local_handle = open.handle;

4.2 TTM 与 GEM 的关系

许多新手混淆 GEM 与 TTM:实际上 GEM 是 API 层(用户态接口规范),TTM 是后端内存管理算法。TTM 负责:

  • 在 VRAM、系统内存(GTT)、以及换出磁盘间动态迁移显存页
  • 处理 GPU CPU 访问冲突
  • 管理 placement 策略(TTM_PL_VRAM / TTM_PL_TT)
// TTM placement 结构
struct ttm_place {
    uint32_t mem_type;      // TTM_PL_SYSTEM / TTM_PL_VRAM
    uint32_t flags;         // TTM_PL_FLAG_CMA / TTM_PL_FLAG_NO_EVICT
};

// Buffer 对象迁移
struct ttm_buffer_object {
    struct ttm_mem_reg mem;           // 当前位置
    struct ttm_bo_type type;          // kern/user/dma
    struct ttm_resource *resource;     // 当前资源
    bool evictable;                    // 是否可被驱逐
    kref_t kref;
};

4.3 dma-buf 跨设备共享机制

dma-buf 是 DRM 子系统导出为独立内核模块的 DMA 缓冲共享框架,解决了 GPU 间零拷贝数据传递问题:

// 导出 GEM 对象为 dma-buf
struct drm_prime_handle prime = {
    .handle = gem_handle,
    .flags = DRM_CLOEXEC | DRM_RDWR,
};
ioctl(fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &prime);
int dma_buf_fd = prime.fd;

// 导入 dma-buf 为本地 GEM 对象
struct drm_prime_handle import = { .fd = dma_buf_fd };
ioctl(other_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import);
uint32_t imported_handle = import.handle;

典型的多 GPU 使用场景:NVIDIA dGPU 渲染完成后,将 dma-bfd 传递给 Intel iGPU 的 KMS 显示,实现 PRIME 渲染输出——笔记本 Optimus 技术的内核基础。

五、KMS 原子模式设置(Atomic Modesetting)深度剖析

5.1 从 Legacy 到 Atomic:为什么需要新 API

旧的 KMS 接口(Legacy Modesetting)的核心问题是"部分失败"(partial failure):

// Legacy KMS 的问题示例
drmModeSetCrtc(crtc_id, fb_id, 0, 0, &connector_id, 1, &mode);
// 如果 Connector 不支持该模式 → 已设置的 Plane 和 CRTC 进入了损坏状态
// 如果 CRTC 不支持该分辨率 → 画面直接黑屏,无法回滚

Legacy API 的每次调用都是独立提交——drmModeSetCrtc()、drmModeSetPlane()、drmModeSetCursor() 各自生效,中间状态可能导致画面撕裂、硬件挂起。

Atomic KMS 的解决之道:将所有显示状态打包为原子事务(Atomic Transaction),硬件要么全部应用、要么全部不应用。

5.2 原子提交的三大对象与属性

Atomic KMS 引入的新对象是 drm_modeset_lock + 属性(Property)系统,所有状态变更通过属性值设置:

// 创建 Atomic 请求
drmModeAtomicReq *req = drmModeAtomicAlloc();

// 1. 设置 CRTC 属性
drmModeAtomicAddProperty(req, crtc_id, 
    crtc_prop_mode_id, blob_id);       // 目标分辨率模式
drmModeAtomicAddProperty(req, crtc_id,
    crtc_prop_active, 1);              // CRTC 启用

// 2. 设置 Connector 属性
drmModeAtomicAddProperty(req, connector_id,
    connector_prop_crtc_id, crtc_id);  // 绑定 CRTC

// 3. 设置 Plane 属性
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_fb_id, fb_id);          // 帧缓冲
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_crtc_id, crtc_id);      // 目标 CRTC
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_src_x, 0);              // 源矩形 X
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_src_y, 0);              // 源矩形 Y
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_src_w, 1920 << 16);     // 源矩形宽(Q16.16 定点)
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_src_h, 1080 << 16);     // 源矩形高
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_crtc_x, 0);             // 屏幕位置 X
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_crtc_y, 0);             // 屏幕位置 Y
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_crtc_w, 1920);          // 屏幕宽
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_crtc_h, 1080);          // 屏幕高

// 4. 提交(带标志控制同步行为)
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
drmModeAtomicCommit(fd, req, flags, NULL);

// 5. 清理
drmModeAtomicFree(req);

5.3 原子标志位详解

标志含义
DRM_MODE_ATOMIC_NONBLOCK非阻塞提交,通过 event 通知完成;Wayland 合成器常用
DRM_MODE_ATOMIC_TEST_ONLY仅测试不变更,验证配置是否可行
DRM_MODE_ATOMIC_ALLOW_MODESET允许真正的模式设置(硬件时序变更),闪屏
DRM_MODE_PAGE_FLIP_EVENT请求 VBlank 事件通知,用于帧精确同步

5.4 内核侧原子提交流程

当用户态调用 DRM_IOCTL_MODE_ATOMIC 时,内核侧的关键流程如下:

// drivers/gpu/drm/drm_atomic.c
int drm_mode_atomic_commit(struct drm_device *dev,
                           struct drm_atomic_state *state,
                           uint32_t flags)
{
    // 1. 复制当前状态(备份用于回滚)
    drm_atomic_state_init(dev, state);
    
    // 2. 遍历所有 Plane → 验证裁剪/缩放参数
    for_each_plane_in_state_safe(state, plane, old_plane)
        funcs->atomic_check(plane, state);
    
    // 3. 遍历所有 CRTC → 验证显示模式
    for_each_crtc_in_state_safe(state, crtc, old_crtc)
        funcs->atomic_check(crtc, state);
    
    // 4. 遍历所有 Connector → 验证链路
    for_each_connector_in_state_safe(state, connector)
        funcs->atomic_check(connector, state);
    
    // 5. TEST_ONLY 标志 → 到此结束
    if (flags & DRM_MODE_ATOMIC_TEST_ONLY)
        return 0;
    
    // 6. 非阻塞提交 → 将 commit 入队到 workqueue
    if (flags & DRM_MODE_ATOMIC_NONBLOCK)
        return commit->commit_entry.work.func(&worker);
    
    // 7. 同步提交 → 执行完整 commit 流程
    drm_atomic_helper_swap_state(dev, state); // 保存旧状态用于回滚
    
    // 8. 调用各对象的 atomic_commit
    for_each_plane_in_state(state, plane)
        funcs->atomic_commit(plane, state);
    
    for_each_crtc_in_state(state, crtc)
        funcs->atomic_commit(crtc, state);
    
    // 9. 等待 VBlank 后提交
    drm_crtc_vblank_get(crtc);
    funcs->atomic_flush(crtc, state);
}

六、Vblank 与垂直同步(VSync):告别画面撕裂

6.1 Vblank 中断机制

CRT 电子束从上到下扫描一行后需要回到左上角继续,这段时间称为 Vertical Blanking Interval(Vblank)。在此期间显示控制器不读取帧缓冲,是更新画面的完美窗口——这就是 VSync 同步的理论基础。

DRM 子系统将 Vblank 抽象为可计数的参考点:

// 获取当前 Vblank 计数
struct drm_vblank_crtc vblank;
ioctl(fd, DRM_IOCTL_VBLANK, &vblank);

// 请求在下一个 Vblank 时刻触发事件
struct drm_wait_vblank vbl_wait = {
    .request = {
        .type = _DRM_VBLANK_RELATIVE,
        .sequence = 1, // 相对当前 +1
    }
};
ioctl(fd, DRM_IOCTL_WAIT_VBLANK, &vbl_wait);

// Flip 事件:Page Flip 完成通知
struct drm_event_vblank vblank_event = {
    .type = DRM_MODE_EVENT_VBLANK,
    .sequence = current_vblank_count,
    .tv_sec = kernel_time.tv_sec,
    .tv_usec = kernel_time.tv_usec,
};

6.2 Page Flip:无撕裂画面更新

Page Flip 是 DRM VSync 同步的核心操作——在 Vblank 期间从一帧缓冲切换到另一帧缓冲,实现无撕裂画面更新:

// 用户态请求 Page Flip
drmModePageFlip(fd, crtc_id, new_fb_id,
    DRM_MODE_PAGE_FLIP_EVENT, user_data);

// 内核侧处理流程
int drm_mode_page_flip_ioctl(struct drm_device *dev, void *data)
{
    // 1. 验证新帧缓冲与 CRTC 兼容
    fb = drm_framebuffer_lookup(dev, data->fb_id);
    
    // 2. 获取 Vblank 引用
    drm_crtc_vblank_get(crtc);
    
    // 3. 提交 Page Flip(入队等待 VBlank)
    funcs->page_flip(crtc, fb, event, data->flags);
    
    // 4. 硬件扫描当前帧缓冲,在下一个 Vblank 开始时切换到新帧
    
    // 5. 发送 DRM_EVENT_VBLANK + DRM_EVENT_FLIP_COMPLETE
}

6.3 Adaptive Sync、FreeSync 与 G-SYNC

现代可变刷新率(VRR)技术让显示器不再固定在 60Hz 或 144Hz:

  • FreeSync / Adaptive-Sync:DisplayPort 1.2a 标准,DRM 中通过 DRM_MODE_FLAG_SUPPORTS_VRR 标记
  • G-SYNC Compatible:NVIDIA 认证的 FreeSync 显示器,amdgpu 驱动自动支持
  • DRM 实现:通过 drm_connector 的
    vrr_capable 属性 + CRTC 的
    VRR_ENABLED 属性控制

七、色彩管理与 HDR 元数据

7.1 Linux 色彩管理管线

DRM 的色彩管理管线(Color Management Pipeline,CMP)支持硬件级的色调映射:

帧缓冲 → De-Gamma → CSC(Color Space Conversion) → 
Gamma → [HDR Output] → Physical Display

标准 DRM Color Properties:

  • CTM(Color Transformation Matrix):3x3 矩阵色彩变换,16.16 定点
  • DEGAMMA_LUT:De-Gamma 查找表,将线性光转为非线性空间
  • GAMMA_LUT:最终 Gamma 校正,适配显示器光电特性
  • OUTPUT_COLORSPACE:BT.601 / BT.709 / BT.2020(DP 2.0+)

7.2 HDR 元数据传递

Linux 5.1+ 引入 HDR_OUTPUT_METADATA 属性,通过以下机制实现:

struct drm_hdr_output_metadata {
    enum drm_colorspace colorspace;
    
    // SMPTE 2086 静态 HDR
    struct drm_hdr_metadata_type1 {
        uint16_t display_primary[3][2];  // R/G/B 色域坐标
        uint16_t white_point[2];          // 白点坐标
        uint16_t max_cll;                 // 最大内容亮度 (nits)
        uint16_t max_fall;                // 最大帧平均亮度
    } hdmi_metadata_type1;
    
    // Dolby Vision / HDR10+ 动态 HDR
    void *dynamic_metadata;
    uint32_t dynamic_metadata_size;
};

八、同步与 Fence:跨设备渲染管线同步

8.1 DMA Fence 机制

DMA Fence 是 DRM 子系统的同步原语,用于 GPU 间、GPU-Display 间的操作序列化:

struct dma_fence {
    struct kref refcount;
    spinlock_t *lock;
    
    // Fence 状态
    uint64_t context;              // Fence 上下文 ID
    uint32_t seqno;                // 序列号
    
    // 回调注册
    struct list_head cb_list;
    signed long expires;
    
    // 状态查询
    bool signaled;                 // 是否已信号
    
    // 调试支持
    const char *ops_name;
    char driver_name[32];
};

8.2 同步链(Sync Chain)工作流

// ① GPU 渲染完成后发 Fence
struct sync_file *render_fence = drm_sched_push_job(sched, job);

// ② 显示控制器等待 Fence 信号后再显示
drmModeAtomicAddProperty(req, plane_id,
    plane_prop_in_fence_fd, render_fence_fd);

// ③ 合成器链(Wayland)
// app renders → export sync_fd
// compositor waits on sync_fd before import to KMS plane
// KMS ensures the buffer is ready before scanning out

8.3 Explicit Sync:从 Implicit 到 Explicit 的革命

传统 DRM 使用"隐式同步"(Implicit Sync)——每个 GPU 命令流隐式关联一个 fence,dma-buf 的读写者通过 resv(reservation object)协调:

// 隐式同步问题:不确定性
// 用户态不知道"何时"GPU 完成渲染 → 依赖内核判断 → 额外的同步开销
buf->resv->writers = sched->fence;
buf->resv->readers = (struct dma_fence **)(reader_count++);

Linux 6.8+ 主导的 Explicit Sync 改革:用户态直接传递 fence,内核不再需要猜测同步时机:

// 显式同步流程
struct sync_file *out_fence;

// App → Compositor(渲染完成 fenced buffer)
drm_syncobj_to_sync_file(sync_obj, &out_fence);
unix_fd_send(out_fence);  // 通过 Unix Domain Socket 传递

// Compositor → KMS(显示前先确保渲染完成)
drmModeAtomicAddProperty(req, plane_id,
    plane_sync_fd_prop, fence_fd);
// KMS 内部:dma_fence_wait_timeout(fence, 2s)

九、DRM 驱动开发实战:最小 DRM 驱动骨架

9.1 DRM 驱动注册过程

编写最小 DRM 驱动只需实现一组回调并注册到 DRM core:

#include <drm/drm_drv.h>
#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>

static const struct drm_driver my_drm_driver = {
    .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
    .fops = &my_drm_fops,
    .name = "my_drm",
    .desc = "My Virtual DRM Driver",
    .date = "20260101",
    .major = 1,
    .minor = 0,
    
    // GEM 对象操作
    .gem_create_object = my_gem_create,
    ._prime_handle_to_fd = drm_gem_prime_handle_to_fd,
    .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
    
    // Dumb Buffer 接口(用于简易场景)
    .dumb_create = my_dumb_create,
    .dumb_map_offset = my_dumb_map_offset,
};

static int __init my_drm_init(void)
{
    struct drm_device *ddev;
    struct platform_device *pdev;
    
    // 1. 分配设备
    ddev = drm_dev_alloc(&my_drm_driver, &pdev->dev);
    
    // 2. 注册 DRM 设备
    drm_dev_register(ddev, 0);
    
    // 3. 初始化显示管线(CRTC/Encoder/Connector/Plane)
    my_drm_modeset_init(ddev);
}

module_init(my_drm_init);

9.2 实现 Dummy CRTC 与 Connector

// 定义 CRTC 操作
static const struct drm_crtc_funcs my_crtc_funcs = {
    .reset = drm_atomic_helper_crtc_reset,
    .destroy = drm_crtc_cleanup,
    .set_config = drm_atomic_helper_set_config,
    .page_flip = drm_atomic_helper_page_flip,
    .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
    .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
};

static const struct drm_crtc_helper_funcs my_crtc_helper_funcs = {
    .mode_valid = my_crtc_mode_valid,
    .atomic_check = my_crtc_atomic_check,
    .atomic_begin = NULL,
    .atomic_flush = my_crtc_atomic_flush,
    .atomic_enable = my_crtc_atomic_enable,
    .atomic_disable = my_crtc_atomic_disable,
};

// 定义 Encoder 操作
static const struct drm_encoder_funcs my_encoder_funcs = {
    .destroy = drm_encoder_cleanup,
};

// 定义 Connector(虚拟显示器,用于测试)
static const struct drm_connector_funcs my_connector_funcs = {
    .fill_modes = drm_helper_probe_single_connector_modes,
    .destroy = drm_connector_cleanup,
    .reset = drm_atomic_helper_connector_reset,
    .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
    .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};

static const struct drm_connector_helper_funcs my_connector_helper_funcs = {
    .get_modes = my_connector_get_modes,
    .best_encoder = my_connector_best_encoder,
    .atomic_check = my_connector_atomic_check,
};

// 初始化显示管线
static int my_drm_modeset_init(struct drm_device *ddev)
{
    struct drm_crtc *crtc;
    struct drm_encoder *encoder;
    struct drm_connector *connector;
    struct drm_plane *primary;
    
    // 创建 Primary Plane
    primary = drm_universal_plane_init(ddev, 0,
        &my_plane_funcs,
        my_formats, my_formats_count,
        my_format_modifiers,
        DRM_PLANE_TYPE_PRIMARY,
        "primary");
    
    // 创建 CRTC
    drm_crtc_init_with_planes(ddev, crtc,
        primary, NULL,
        &my_crtc_funcs, "crtc");
    crtc->helper_private = &my_crtc_helper_funcs;
    
    // 创建 Encoder
    drm_encoder_init(ddev, encoder,
        &my_encoder_funcs,
        DRM_MODE_ENCODER_VIRTUAL,
        "encoder");
    
    // 创建 Connector(虚拟固定连接器)
    drm_connector_init(ddev, connector,
        &my_connector_funcs,
        DRM_MODE_CONNECTOR_VIRTUAL);
    drm_connector_helper_add(connector,
        &my_connector_helper_funcs);
    
    // 绑定 Encoder ↔ Connector
    drm_connector_attach_encoder(connector, encoder);
}

9.3 帧缓冲与 Dumb Buffer 创建

// VBlank 回调
static int my_crtc_enable_vblank(struct drm_crtc *crtc)
{
    // 启用 Vblank 中断
    writel(INT_ENABLE_VBLANK, base + INT_ENABLE_REG);
    return 0;
}

static void my_crtc_disable_vblank(struct drm_crtc *crtc)
{
    writel(0, base + INT_ENABLE_REG);
}

// Dumb Buffer 模式(最常见的简易帧缓冲)
static int my_dumb_create(struct drm_file *file,
                          struct drm_device *dev,
                          struct drm_mode_create_dumb *args)
{
    // args->width, args->height, args->bpp → args->pitch, args->size, args->handle
    args->pitch = ALIGN(args->width * args->bpp / 8, 64);
    args->size = args->pitch * args->height;
    
    // 分配内存,通过 drm_gem_object 注册
    return drm_gem_handle_create(file,
        gem_object, &args->handle);
}

十、GPU 调度器:从 DRM Sched 到 EXECLUSIVE 优先级

10.1 DRM Scheduler 的演进

在 Linux 6.0 之前,各 GPU 厂商自行实现命令提交调度(i915 用 intel_execlists,amdgpu 用 drm_sched,lima/panfrost 各自实现)。6.0 起 DRM core 抽象出统一调度框架:

// DRM 调度器核心结构
struct drm_gpu_scheduler {
    // Job 队列
    struct list_head work_list[kernel_pipe_priority_count]; // HIGH/NORMAL/LOW
    struct drm_sched_rq run_list;
    
    // 工作线程
    struct task_struct *thread;
    
    // 超时处理
    struct delayed_work work_tdr;  // Timeout Detection Recovery
    
    // Fence 机制
    struct dma_fence *last_scheduled;
    
    // 操作函数
    const struct drm_sched_backend_ops {
        .prepare_job = ...,
        .run_job = ...,
        .timedout_job = ...,
        .free_job = ...,
    } *ops;
};

// Job 提交流程
static void drm_sched_main(struct work_struct *w)
{
    while (!kthread_should_stop()) {
        // 1. 从优先级队列取 Job
        job = drm_sched_select_job(sched);
        
        // 2. 提交给 GPU 执行
        sched->ops->run_job(job);
        
        // 3. 注册 Fence 回调
        dma_fence_add_callback(&job->hw_fence,
            drm_sched_cb, job);
        
        // 4. 处理 TDR(Timeout Detection Recovery)
        mod_delayed_work(&sched->work_tdr,
            msecs_to_jiffies(sched->timeout));
    }
}

10.2 AMDGPU 的艺术:GFX 与 Compute 双引擎

AMDGPU 驱动是 DRM 子系统中代码最复杂(50万+ 行)的驱动之一,其核心创新在于多硬件队列调度:

// AMDGPU 的多引擎体系
struct amdgpu_device {
    struct amdgpu_ring rings[AMDGPU_MAX_RINGS];
    
    // GFX 引擎:图形渲染(OpenGL/Vulkan 绘图命令)
    struct amdgpu_ring gfx_ring[AMDGPU_MAX_GFX_RINGS];
    
    // SDMA 引擎:System DMA,用于 Copy Buffer
    struct amdgpu_ring sdma_ring[AMDGPU_MAX_SDMA_RINGS];
    
    // Compute 引擎:计算着色器(OpenCL/CUDA 替代)
    struct amdgpu_ring compute_ring[AMDGPU_MAX_COMPUTE_RINGS];
    
    // VCN 引擎:视频编解码
    // UVD 引擎:旧版视频解码
};

Mesa/OpenGL 驱动调用链:glDrawArrays() → Mesa Gallium3D → RADV/AMD RadeonSI → libdrm_amdgpu → amdgpu_cs_submit() → drm_sched_entity_push_job() → 硬件 Ring Buffer → GPU 执行

十一、常见陷阱与调试技巧

11.1 原子提交的常见错误

❌ 错误 1:忘记设置 CRTC 的 plane_id
drmModeAtomicAddProperty(req, plane_id, prop_fb_id, fb_id);
drmModeAtomicAddProperty(req, plane_id, prop_crtc_id, crtc_id); // 必须绑定 CRTC!

❌ 错误 2:分辨率超出 Connector 的 max_bpc × max_pixel_clock
// 使用 modetest 查询限制:
// modetest -M i915 -c | grep "modes"

❌ 错误 3:在 TEST_ONLY 检查通过后认为原子提交一定成功
// TEST_ONLY 只验证静态参数,运行时可能因显存不足或 TDR 失败

❌ 错误 4:非阻塞提交后过早释放帧缓冲
// NONBLOCK 提交后,用户态应等 FRAME_COMPLETE 事件再释放
// 否则正在扫描输出的 Frame Buffer 被释放 → GPU hang / 屏幕闪白

11.2 DRM 调试工具链

工具用途
modetest -M <driver> -c查看 Connector/CRTC/Plane 拓扑与可用属性
modetest -M <driver> -s <crtc_id>:<resolution>测试模式设置(快速验证硬件支持)
cat /sys/kernel/debug/dri/0/state读取 DRM 设备完整状态(debugfs)
intel_gpu_topIntel GPU 使用情况实时监控
amdgpu_topAMD GPU 使用情况实时监控
radeontopRadeon GPU 使用率显示(OpenGL/Vulkan)
kernel tracing: /sys/kernel/debug/tracing/events/drm/DRM 子系统 ftrace 事件追踪

11.3 调试显示管线死锁

DRM 的一个棘手问题是 mode_config 大锁死锁。在 Atomic 路径中,drm_device.mode_config.lock 是一个全局 mutex,所有状态操作竞争此锁。典型死锁场景:

// 场景:App 持有 mode_config.lock(原子提交中)
//       HPD 中断触发 → 也想获取 lock → 死锁

// 解决方案:HPD 事件使用 workqueue 异步处理
static void hpdr_work_fn(struct work_struct *work)
{
    mutex_lock(&dev->mode_config.lock);  // 安全获取,不发生死锁
    drm_helper_hpd_irq_event(dev);
    mutex_unlock(&dev->mode_config.lock);
}

十二、DRM 生态系统与前沿趋势

12.1 最新技术动向

  • Intel Xe 驱动(2023+):全新 Intel 独立 GPU 驱动,取代传统的 i915 驱动
  • NVIDIA Open GPU Kernel Module(2022+):NVIDIA 终于开源了内核模块(MIT/GPL 双协议),采用 GSP(GPU System Processor)架构,DRM 兼容层与 amdgpu 类似
  • Asahi Linux(2022+):将 Apple Silicon GPU 的驱动逆向并引入主线内核,Apple AGX GPU 在 DRM 框架下运行 Mesa 驱动
  • Panfrost/PanVK:ARM Mali GPU 开源驱动栈,完全基于 DRM/KMS
  • VirtIO-GPU(virtio-gpu 1.2+):云桌面虚拟化显示支持,原生 Atomic KMS
  • Qualcomm Adreno "Freedreno":移动 GPU 的开源 DRM 驱动

12.2 Explicit Sync 的完整实现

2024-2026 年 DRM 子系统最重要的演变是 Explicit Sync 的全面落地。这涉及 DRM Core、Wayland(wp_linux_drm_syncobj_v1协议)、Android(Sync Framework)的协调转换。核心变化:

// 旧:隐式同步
// 用户态看不到 Fence,一切由内核协调

// 新:显式同步
struct wp_linux_drm_syncobj_timeline_v1 {
    // 用户态直接管理 timeline 值
    uint64_t current_point;
    uint64_t target_point;
};

// Wayland 合成器显式管理
zwp_linux_dmabuf_feedback_v1 → 
  main_device + tranche_flags +
  target_device + sync_obj_timeline

// KMS 直接使用显式 fence
drmModeAtomicAddProperty(req, plane_id,
    "IN_FENCE_FD", imported_fence_fd);

12.3 对 Rust 驱动支持的展望

随着 Rust-for-Linux 的成熟,DRM 子系统已出现首个 Rust 驱动实验——Apple Silicon GPU 驱动的部分组件(如 DMA-BUF 子系统封装)已在用 Rust 编写。AMDGPU 驱动中也有计划引入 Rust 实现的 GEM 管理和电源管理子系统。未来的趋势是 C/Rust 混合实现——性能关键路径用 C,复杂逻辑和状态管理用 Rust。

十二、总结:DRM 子系统哲学

DRM 子系统的设计哲学可以归纳为三个核心原则:

  1. 硬件无关的抽象:CRTC/Encoder/Connector/Plane 四原件模型统一了所有显示硬件的描述,用户态代码无需关心底层差异
  2. 状态打包与原子提交:从 Legacy 到 Atomic 的演进,本质是将"部分失败"变为"原子成功"——分布式一致性理论在内核的缩影
  3. 显式优于隐式:从 Implicit Sync 到 Explicit Sync、从 Legacy 到 Atomic、从 ioctl 到 Property,DRM 的长期趋势是将控制权还给用户态,内核只做最小化的仲裁

理解 DRM 不仅是理解 Linux 图形栈的必经之路,也是学习大规模内核子系统设计的绝佳案例——如何在硬件多样性中建立统一抽象,如何在高性能与安全性间取得平衡,如何让子系统渐进式演进而不破坏兼容性。从嵌入式 MCU 的多点触控屏幕,到服务器 BMC 的 BMC 图形输出,到 AI 训练机多卡渲染输出,DRM/KMS 正以其惊人的灵活性默默承载着这一切。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部