引言

在 Linux 内核庞大而复杂的子系统中,DRM(Direct Rendering Manager,直接渲染管理器)和 KMS(Kernel Mode Setting,内核模式设置)共同构成了现代图形栈的基石。它们不仅驱动着从嵌入式手机屏幕到 8K 专业显示器的所有显示设备,还为 GPU 硬件加速渲染、合成器输出以及多屏协同提供了底层基础设施。尽管 DRM/KMS 在桌面和移动端无处不在,但其内部架构——CRTC/Encoder/Connector 的硬件抽象模型、原子模式设置(Atomic Modesetting)的引入动机、Plane 的硬件合成机制、以及与现代 GPU 驱动(amdgpu / i915 / nouveau)的关系——对于大多数应用开发者仍然是一个"黑箱"。本文将深入 DRM/KMS 内核子系统,从硬件拓扑抽象、用户态交互、原子提交流水线、以及与现代 Wayland/Xorg 合成器的协作机制四个维度,完整揭示 Linux 图形显示管理的全貌。

1. DRM/KMS 架构总览

1.1 历史演进:从 XFree86 到 Atomic KMS

Linux 图形显示子系统的演化可以分为四个阶段:

  • 1990s — XFree86 时代:用户态 X Server 通过直接映射显卡 MMIO 寄存器来控制显示模式(Mode Setting)。内核仅提供基本的帧缓冲(fbdev)抽象。这种用户态模式设置导致系统崩溃、恢复困难、安全性极差。
  • 2004 — Kernel Mode Setting 起步:VESA 和早期 Intel i915 驱动在内核中接管显示模式设置,但架构混乱,缺乏统一抽象。
  • 2008 — DRM 子系统正式化:Dave Airlie 创建统一的 DRM 子系统,引入 CRTC/Encoder/Connector/Plane 的硬件抽象模型。
  • 2014 — Atomic KMS 引入:传统 modeset 存在中间状态(partial state),Atomic API 将所有显示变更打包为一个事务,要么全部成功要么全部回滚,彻底消除闪屏和中间错误状态。
  • 2018+ — Universal Planes + HDR + Color Management:Universal Planes 将 Overlay/Cursor/Primary Plane 统一抽象,HDR 元数据和色彩管理加入 KMS 属性系统。

1.2 核心硬件抽象模型

DRM/KMS 将复杂的 GPU 显示管线抽象为四个核心对象类型和一组辅助属性:

┌────────────────── Pipeline Model ──────────────────┐
│                                                      │
│  ┌─────────┐    ┌──────────┐    ┌───────────────┐   │
│  │ Plane   │───▶│ CRTC     │───▶│ Encoder       │   │
│  │ (像素源) │    │ (扫描器)  │    │ (信号编码)     │──▶│──▶ Connector ──▶ Monitor
│  └─────────┘    └──────────┘    └───────────────┘   │
│       │              │               │               │
│  ┌────┴────┐    ┌────┴────┐    ┌────┴────┐         │
│  │ Primary │    │ pixclk  │    │ DP/HDMI │         │
│  │ Overlay │    │ h/v sync│    │ eDP/DSI │         │
│  │ Cursor  │    │ margins │    │ VGA/CVBS│         │
│  └─────────┘    └────────┘    └─────────┘         │
│                                                      │
└──────────────────────────────────────────────────────┘

1.3 四大核心对象类型

Plane(像素平面)

Plane 是像素数据的来源。GPU 通常包含多种类型的 Plane:

  • Primary Plane:主显示平面,每个 CRTC 必须有且仅有一个 Primary Plane。它作为 "framebuffer 基底",全屏切换发生时主要由它驱动。
  • Overlay Plane(硬件叠加层):硬件光标/视频叠加专用,支持 YUV 格式直接扫描输入,零拷贝视频回放的关键。现代 GPU 通常有 2-4 个 Overlay/Cursor Planes。
  • Cursor Plane:硬件光标专用,通常支持固定小尺寸(64x64或256x256),在扫描过程中由显示控制器硬件叠加。

Plane 的关键属性包括:

struct drm_plane_properties {
    FB_ID       — 绑定的 framebuffer 对象 ID
    CRTC_ID     — 绑定的 CRTC(可切换)
    SRC_X/Y/W/H — 源图像裁剪矩形(定点 16.16)
    CRTC_X/Y/W/H — 在 CRTC 上的显示矩形
    rotation    — 旋转/翻转标志 (DRM_ROTATE_* / DRM_REFLECT_*)
    alpha       — 全局透明度 (0-65535)
    pixel_blend_mode — 像素混合方式 (None/Premultiplied/Coverage)
    zpos        — Plane 深度优先级 (Atomic 新增)
    IN_FORMATS  — 支持的像素格式 (DRM Format Modifier)
    COLOR_ENCODING — BT.601/BT.709/BT.2020
    COLOR_RANGE — Limited/Full Range
};

CRTC(阴极射线管扫描控制器)

CRTC 是扫描输出引擎,负责从 Plane 读取像素数据、按照时序参数(pixclock、hdisplay、hsync、vdisplay、vsync、margins)生成扫描信号。每个 CRTC 独立驱动一个显示管线。

struct drm_crtc_properties {
    ACTIVE      — 是否激活模式输出
    MODE_ID     — 当前模式 (drm_mode_modeinfo)
    OUT_FENCE_PTR — 帧完成 fence(用于同步)
    VRR_ENABLED — Variable Refresh Rate (FreeSync/G-Sync)
    GAMMA_LUT   — Gamma 校正查找表
    DEGAMC_LUT  — sRGB 解码 Degamma 表
    CTM         — Color Transformation Matrix
    OUTPUT_FORMAT — YCbCr/RGB 输出格式
};

/* 模式结构 (简化) */
struct drm_mode_modeinfo {
    uint32_t clock;        /* kHz */
    uint16_t hdisplay, hsync_start, hsync_end, htotal;
    uint16_t vdisplay, vsync_start, vsync_end, vtotal;
    uint32_t flags;        /* HSKEW | CSYNC | DOUBLESCAN | ... */
    uint32_t type;         /* DRM_MODE_TYPE_PREFERRED | ... */
    char name[32];         /* e.g. "1920x1080@60" */
};

Encoder(信号编码器)

Encoder 将 CRTC 输出的像素流转换为物理信号协议:

  • DAC Encoder:模拟 VGA 输出
  • TMDS Encoder:DVI / HDMI 数字信号
  • LVDS/eDP Encoder:笔记本内置屏
  • DP/eDP:DisplayPort(主链路)
  • DSI Encoder:MIPI DSI(嵌入式设备)
  • Virtual Encoder:无物理连接的虚拟显示(虚拟 Weston、headless)

Encoder 的关键属性:

struct drm_encoder_properties {
    possible_crtcs  — 可路由的 CRTC 位掩码
    possible_clones — 可克隆的 Encoder 位掩码
    encoder_type    — DRM_MODE_ENCODER_* 类型
    DPMS            — Power state: On/Suspend/Off
    OUTPUT_FORMAT   — DisplayPort DSC, HDMI FRL ...
    HDMI Output Infoframe — AVI/DVI/SPD/HDMI/DRM Infoframe
};

Connector(连接器/显示器端口)

Connector 是 GPU 的物理输出接口连接点。它负责检测热插拔事件(HPD)、读取显示器 EDID 数据、管理显示电源状态。

struct drm_connector_properties {
    EDID            — 显示器 EDID (Binary blob)
    DPMS            — ON / SUSPEND / OFF
    CONNECTOR_STATUS — connected / disconnected / unknown
    WIDTH_MM/HEIGHT_MM — 显示器物理尺寸
    subconnector    — VGA-Composite / S-Video / ...
    TILE             — Mosaic/Tiled Display 信息
    panel_orientation — Normal/Inverted (平板旋转)
    non-desktop      — 非桌面显示器 (VR/AR)
    DITHERING        — 色彩抖动配置
    HDR_OUTPUT_METADATA — HDR 静态/动态元数据
    CONTENT_PROTECTION — HDCP 状态
};

2. DRM 内存管理与 Buffer 共享

2.1 GEM(Graphics Execution Manager)

GEM 是 DRM 子系统中管理 GPU 显存的核心框架。它提供了 buffer 对象(BO)的生命周期管理和跨设备 buffer 共享。

/* GEM buffer 对象 */
struct drm_gem_object {
    struct kref refcount;
    uint32_t handle;          /* 全局唯一的 32-bit handle */
    const struct drm_gem_object_funcs *funcs;
    size_t size;
    struct dma_buf *imported; /* import 的外部 buffer */
    struct dma_buf *dmabuf;   /* export 给其他设备的 dma_buf */
    struct file *filp;        /* shmem backing 文件 */
};

/* GEM 操作 API */
int drm_gem_handle_create(struct drm_file *file_priv,
                          struct drm_gem_object *obj,
                          uint32_t *handle);   /* 创建用户态 visible handle */

struct drm_gem_object *drm_gem_object_lookup(struct drm_file *file_priv,
                                              uint32_t handle);    /* 查找 BO */

int drm_gem_prime_fd_to_handle(struct drm_device *dev,
                               struct drm_file *file_priv,
                               int fd, uint32_t *handle);   /* import dma_buf */

int drm_gem_prime_handle_to_fd(struct drm_device *dev,
                               struct drm_file *file_priv,
                               uint32_t handle, uint32_t flags,
                               int *prime_fd);              /* export dma_buf */

2.2 DMA-BUF 与零拷贝共享

DMA-BUF 是 Linux kernel 的 buffer 共享机制,允许 GPU 内核驱动(DRM)、V4L2 摄像头、视频编解码器等设备通过文件描述符(fd)共享内存 buffer,内核负责维护引用计数和缓存一致性。

/* 典型同步流程:生产者/消费者 fence */
struct dma_buf_sync {
    unsigned long flags;
};

#define DMA_BUF_SYNC_READ    (1 << 0)  /* CPU/device 即将读取 */
#define DMA_BUF_SYNC_WRITE   (1 << 1)  /* CPU/device 即将写入 */
#define DMA_BUF_SYNC_RW      (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
#define DMA_BUF_SYNC_START   (0 << 2)  /* 同步开始标记 */
#define DMA_BUF_SYNC_END     (1 << 2)  /* 同步结束标记 */

2.3 DMA-BUF Heap 与 Allocator

Linux 5.6+ 引入了 dma-buf heaps 框架,为不同类型的 buffer 分配提供统一的接口:

  • system heap:分配普通匿名内存(CACHED/UNCACHED 可选)
  • cma heap:分配 CMA (Contiguous Memory Allocator) 连续物理内存,适用于 IOMMU 不存在或不覆盖的设备
  • vendor heaps:特定供应商的受保护内存(安全摄像头、TEE 等)

2.4 DMA-BUF ioctl 扩展:隐式同步同步点

传统的 DMA-BUF 同步依赖 ioctl 显式调用(DMA_BUF_IOCTL_SYNC)。Linux 6.6+ 引入了 SYNC_FILE 和 DRM 时序 fence 的整合机制,让生产者通过 timeline fence 通知消费者,消费者通过 poll/epoll 等待 producer fence signal:

/* DMA buf 导入时携带 in-fences */
struct drm_prime_attachment {
    struct dma_buf *dmabuf;
    struct device *dev;
    struct list_head obj_node;
    enum dma_data_direction dir;
    // 关键:import 时使用 in-fence 数组
};

/* 渲染完成后 export 的 out-fence 传递给显示控制器 */
struct drm_mode_fb_cmd2_with_fence {
    struct drm_mode_fb_cmd2 mode_cmd;
    __s32 fence_fd;  /* 渲染完成 fence,NULL 表示同步完成 */
};

3. KMS 用户态接口:IOCTL 与属性系统

3.1 DRM IOCTL 家族

KMS 通过 /dev/dri/cardX 或 /dev/dri/renderD128 设备节点暴露给用户态。核心 IOCTL 包括:

/* 模式获取与配置 */
DRM_IOCTL_SET_MASTER          — 申请 master 权限(用于 modesetting)
DRM_IOCTL_DROP_MASTER         — 释放 master 权限
DRM_IOCTL_MODE_GETRESOURCES   — 获取所有 Connector/Encoder/CRTC/Plane ID
DRM_IOCTL_MODE_GETCONNECTOR   — 获取 connector 详情 + 模式列表
DRM_IOCTL_MODE_GETENCODER     — 获取 encoder 详情
DRM_IOCTL_MODE_GETCRTC        — 获取当前 CRTC 配置
DRM_IOCTL_MODE_GETPLANE       — 获取 plane 详情(支持的格式等)

/* Page Flip 与 VBlank */
DRM_IOCTL_MODE_PAGE_FLIP      — 异步换帧(VBlank 同步)
DRM_IOCTL_WAIT_VBLANK         — 等待 VBlank 信号

/* Atomic Commit */
DRM_IOCTL_MODE_ATOMIC         — 提交原子属性事务
DRM_IOCTL_MODE_OBJ_SETPROPERTY — 设置对象属性
DRM_IOCTL_MODE_CREATEPROPBLOB — 创建属性 blob (EDID/Gamma/Mode)
DRM_IOCTL_MODE_DESTROYPROPBLOB — 销毁属性 blob

/* 显存管理 */
DRM_IOCTL_GEM_CLOSE           — 关闭 gem handle
DRM_IOCTL_GEM_FLINK           — 获取全局名称(跨进程共享)
DRM_IOCTL_GEM_OPEN            — 通过全局名称打开
DRM_IOCTL_PRIME_FD_TO_HANDLE  — dma_buf fd → gem handle
DRM_IOCTL_PRIME_HANDLE_TO_FD  — gem handle → dma_buf fd

/* Buffer 管理 */
DRM_IOCTL_MODE_ADDFB2         — 注册 framebuffer 对象
DRM_IOCTL_MODE_RMFB           — 注销 framebuffer
DRM_IOCTL_MODE_MAP_DUMB       — 映射 dumb buffer(CPU 访问)
DRM_IOCTL_MODE_CREATE_DUMB    — 创建 dumb buffer(线性、非 GPU 友好)
DRM_IOCTL_MODE_DESTROY_DUMB   — 销毁 dumb buffer

3.2 属性系统(Property System)

KMS 使用动态属性系统来管理对象的可配置参数。每个属性都有一个类型和值:

/* 属性类型 */
DRM_MODE_PROP_RANGE       — 范围 [min, max]
DRMR_MODE_PROP_ENUM       — 枚举值列表
DRM_MODE_PROP_BLOB        — 二进制 blob(EDID/Gamma/Mode info)
DRM_MODE_PROP_BITMASK     — 位掩码枚举
DRM_MODE_PROP_OBJECT      — 指向另一个 KMS 对象的 ID
DRM_MODE_PROP_SIGNED_RANGE — 有符号范围

/* 属性 flags */
DRM_MODE_PROP_PENDING     — 属性变更待审议
DRM_MODE_PROP_IMMUTABLE   — 不可变属性
DRM_MODE_PROP_ATOMIC      — 非 Atomic commit 下不可修改(Atomic only)

3.3 Dumb Buffer vs. GPU-Optimized Buffer

DRM 提供两种 buffer 分配方式:

  • Dumb Buffer:线性(Linear)内存分配,适用于简单的 CPU 访问和软件渲染(fbdev 兼容)。不支持 GPU tile/compression 布局。
  • GPU-Optimized Buffer:通过特定驱动的 allocator(AMDGPU TMZ/VRAM、i915 GGTT/LMEM/VRAM),支持 GPU 内部 tile mod compressed 布局(如 AMD DGPU 的 Swizzle Mode、Intel 的 TileY/X)。这种 buffer 对 CPU mmap 不友好,但 GPU 访问零开销。通常需要配合 GPU 命令流(CS)使用。

4. 原子模式设置(Atomic Modesetting)

4.1 为什么需要 Atomic

传统的 modeset 是逐步进行的:先设置 CRTC mode,再绑定 framebuffer,再设置 Plane 坐标,最后 enable CRTC。问题在于每一步都是独立的 ioctl,如果中间步骤失败,系统处于"半配置"状态,可能导致:

  • 显示器信号中断(黑屏/闪屏)
  • 设置了一半的属性(遗漏或无意义的中间态)
  • 硬件处于不一致状态(需手动 rollback)

Atomic Modesetting 将所有变更打包成一个事务:所有属性先写入 staging state,只有所有验证通过后一次性提交到硬件。

4.2 Atom 提交流程

/* 1. 创建原子请求 */
drmModeAtomicReq *req = drmModeAtomicAlloc();

/* 2. 收集所有属性变更 */
drmModeAtomicAddProperty(req, crtc_id, 
   .prop = CRTC_MODE_ID,
    value = mode_blob_id);    /* blob 中的 mode */

drmModeAtomicAddProperty(req, crtc_id,
    prop = CRTC_ACTIVE, 
    value = 1);

drmModeAtomicAddProperty(req, plane_id,
    prop = PLANE_FB_ID,
    value = fb_id);

drmModeAtomicAddProperty(req, plane_id,
    prop = PLANE_CRTC_X, value = 0);
drmModeAtomicAddProperty(req, plane_id,
    prop = PLANE_CRTC_Y, value = 0);
drmModeAtomicAddProperty(req, plane_id,
    prop = PLANE_CRTC_W, value = 1920);
drmModeAtomicAddProperty(req, plane_id, 
    prop = PLANE_CRTC_H, value = 1080);

drmModeAtomicAddProperty(req, connector_id,
    prop = CONNECTOR_CRTC_ID, 
    value = crtc_id);

/* 3. 提交事务 */
uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET  |  /* 允许模式更改 */
                 DRM_MODE_PAGE_FLIP_EVENT;         /* 请求页面翻转事件 */

ret = drmModeAtomicCommit(fd, req, flags, user_data);

/* 4. 释放请求对象 */
drmModeAtomicFree(req);

4.3 Atomic Flags

/* 提交 flags */
DRM_MODE_ATOMIC_ALLOW_MODESET   — 允许全模式设置(如 CRTC mode 变更)
DRM_MODE_ATOMIC_NONBLOCK        — 异步提交(不等待 VBlank)
DRM_MODE_ATOMIC_TEST_ONLY       — 仅验证,不实际应用(用于提前检测错误)
DRM_MODE_PAGE_FLIP_EVENT        — 监听 Page Flip 完成事件
DRM_MODE_ATOMIC_NO_UPDATE_BO    — 不等待 buffer 释放事件

/* 典型组合: 
 * 仅更新 Plane: DRM_MODE_PAGE_FLIP_EVENT
 * 完整 modeset: DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT
 */

4.4 TEST_ONLY 验证流程

Atomic 提交前,DRM 驱动会在 staging state 上执行验证链:

Validation Pipeline:
├── plane_atomic_check()
│   ├── check pixel format supported
│   ├── check source/crtc rect within bounds  
│   ├── check rotation against plane caps
│   ├── check zpos/ordering consistency
│   └── check modifier/fb layout supported
├── crtc_atomic_check()
│   ├── check mode timings (hw clock range)
│   ├── check bandwidth requirements
│   ├── validate blob mode structure
│   └── check gamma/CTM/color pipeline consistency
├── encoder_atomic_check()
│   ├── validate signal format support
│   └── check bandwidth vs clock limits
├── connector_atomic_check()
│   ├── validate EDID compatibility
│   └── check HDCP/content protection state
└── TEST_ONLY 成功 → apply
    TEST_ONLY 失败 → return -EINVAL / -ERANGE

5. VBlank、Page Flip 与帧同步

5.1 VBlank 中断与事件

显示器每帧回扫(Vertical Blanking Interval,VBlank)时,GPU 硬件产生中断。DRM 子系统利用此中断实现精确的帧时序同步:

/* 等待 VBlank */
struct drm_wait_vblank_request req = {
    .request = {
        .type = _DRM_VBLANK_RELATIVE,
        .sequence = 1,  /* 等下一个 vblank */
    }
};
drmWaitVBlank(fd, &req);

/* 注册 VBlank 回调 */
typedef void (*drmVBlankHandler)(int fd, unsigned int sequence,
                                  unsigned int tv_sec, unsigned int tv_usec,
                                  void *user_data);

/* Page Flip 事件 */
drmEventContext ev = {
    .version = DRM_EVENT_CONTEXT_VERSION,
    .page_flip_handler = page_flip_handler,
    .vblank_handler = vblank_handler,
};
drmHandleEvent(fd, &ev);

5.2 双重缓冲与 Page Flip

现代显示使用 Page Flip 实现无撕裂的帧切换。drmModePageFlip 在下一个 VBlank 时刻将 CRTC 的当前 framebuffer 切换到新 buffer,不会在扫描中途修改(避免 tearing):

/* Page Flip 流程 */
1. 渲染到 back buffer (fb_back)
2. drmModePageFlip(fd, crtc_id, fb_back->fb_id, flags, user_data)
3. 内核标记待处理 flip
4. 下一 VBlank 中断触发
5. CRTC 硬件从 fb_front 切换到 fb_back
6. 翻转事件回调触发(带 user_data)
7. fb_front 变为可用 back buffer,循环继续

6. 与现代显示栈的协作

6.1 Wayland 合成器

Wayland 合成器(Weston、Mutter、Sway、KWin)直接使用 KMS Atomic API 进行输出管理。weston 的典型显示管线:

┌─────────────────────────────────────────────┐
│              Compositor (Userspace)           │
│  ┌─────────┐  ┌──────────┐  ┌──────────┐    │
│  │ Surface │  │ Surface  │  │ Surface  │    │
│  │ Buffer  │  │ Buffer   │  │ Buffer   │    │
│  │ wl_shm/ │  │ (DMA-BUF)│  │(DMA-BUF) │    │
│  │ zwp_linux │ └─────┬────┘  └─────┬────┘    │
│  │ _dmabuf) │       │            │         │
│  └─────────┘       ▼            ▼         │
│              ┌──────────────────────────┐   │
│              │      GL/Vulkan 渲染      │   │
│              │   (EGLStreams/GBM)       │   │
│              └──────────┬───────────────┘   │
│                         │                    │
│              ┌──────────▼───────────────┐   │
│              │    Atomic Modeset Commit   │   │
│              │    ┌─────┬──────┬──────┐ │   │
│              │    │Cur. │Prim. │Ovly. │ │   │
│              │    │Plane │Plane │Plane │ │   │
│              │    └──┬──┴──┬───┴──┬───┘ │   │
│              │       ▼      ▼       ▼    │   │
│              │       │ CRTC 引擎 │        │   │
│              │       └────┬────┘         │   │
│              └────────────┼──────────────┘   │
└──────────────────────────┼────────────────┘
                           ▼
                      ┌──────────┐
                      │ Monitor  │
                      │ (DP/HDMI)│
                      └──────────┘

6.2 EGL 平台:GBM vs EGLStreams

两大 EGL Wayland platform 核心差异:

  • gbm(Generic Buffer Management):Mesa 的跨平台方案。基于 DRM dumb buffer + GEM,通过 zwp_linux_dmabuf_v1 Wayland 协议直接向合成器提交 DMA-BUF。i915 / amdgpu / nouveau 均支持。
  • EGLStreams:NVIDIA 的私有方案。在 NVIDIA PRO 驱动上通过 EGLStreamProducer/Consumer 将渲染 frame 传递给合成器。NVIDIA 现已转向 GBM 路线。

6.3 Xorg 适配层:xf86-video-modesetting

Xorg 合成器通过 xf86-video-modesetting 驱动适配 KMS。模式下设置借用了 libdrm 的 API,通过 DDX(Device Dependent X)层和 X-Server 的 Present Extension 实现 modern VSync 和 page flip。

7. DRM 驱动核心结构

7.1 驱动注册框架

/* 核心描述符 */
struct drm_driver {
    const struct drm_driver *driver_features;
    
    /* GEM hooks */
    struct drm_gem_object *(*gem_create_object)(struct drm_device *dev, size_t size);
    int (*prime_pin)(struct drm_gem_object *obj);
    void (*prime_unpin)(struct drm_gem_object *obj);
    struct sg_table *(*prime_get_sg_table)(struct drm_gem_object *obj);
    struct drm_gem_object *(*prime_import_sg_table)(struct drm_device *dev,
        struct dma_buf_attachment *attach, struct sg_table *sgt);
    
    /* Display hooks */
    int (*drm_mode_config_funcs)(struct drm_device *dev);
    
    /* IOCTL hooks */
    int (*ioctls[DRM_IOCTL_DEF_NR])(struct drm_device *dev, void *data,
                                    struct drm_file *file_priv);
    int ioctl_count;
    
    /* Sysfs */
    int (*debugfs_init)(struct drm_minor *minor, int minor_id);
};

/* 驱动注册流程 */
int drm_dev_init(struct drm_device *dev, struct drm_driver *driver,
                 struct device *parent);
int drm_dev_register(struct drm_device *dev, unsigned long driver_features);
void drm_dev_unregister(struct drm_device *dev);
void drm_dev_exit(struct drm_device *dev, unsigned long driver_features);

7.2 三大主流 DRM 驱动

驱动GPU 厂商关键特性内存管理架构
amdgpuAMDGFX11/RDNA3、APU、GPU Reset、TMZ、FreeSync、SR-IOVVRAM(System/Local/Visible) + GTT + TT Mov
i915IntelGen12/Xe/Arc、TileX/Y/LMEM/LMEM+Context、HDR、VRRGGTT + LMEM(dGPU) + LLC(iGPU)
nouveauNVIDIARe-clocking(受限)、GSP firmware(Turing+)VRAM + GART + IOMMU

7.3 AMDGPU 特有:Ring Buffer 与 GPU 调度

AMDGPU 驱动中,用户态提交 GPU 命令通过 6 个 Ring Buffer(gfx、compute、sdma、vce、uvd、vcn)实现:

/* GPU 命令提交路径 */
1. 用户态填充 IB (Indirect Buffer) 命令包
   - PACKET3 (PM4) 格式
   - 包含 DRAW_INDEX_2、CONTEXT_CONTROL、FRAME_CONTROL 等操作码

2. 通过 amdgpu_cs_ioctl 提交 job
   -> amdgpu_cs_ioctl()
   -> amdgpu_cs_parser_init()  [CS 解析]
   -> amdgpu_cs_sync [同步处理]
   -> amdgpu_job_submit [加入调度队列]

3. 调度器 (drm_sched) 决定执行时机
   - 按 VM id / priority 排队
   - 高级调度:抢占(VM 切换)和恢复

4. 硬件 Ring Buffer 执行
   - 写入 doorbell 寄存器通知硬件
   - 硬件 DMA 读取 IB → 执行 → 写回状态
   - 完成后发出 IRQ → 信号 fence

5. fence signal → 触发用户态同步回调
   - libdrm: amdgpu_cs_syncobj_wait
   - Wayland: zwp_linux_dmabuf_feedback_v1

8. GPU 重置与错误恢复(GPU Reset)

现代 DRM 驱动支持 "GPU Reset" —— 当 GPU 因命令提交挂起时,驱动可尝试重置单个进程或整个设备而非崩溃。机制包括:

  • Job Hanging 检测:调度器超时(默认 50ms)后标记 job 为 hung
  • TTM Memory Eviction:重置前尝试将 GPU 内存驱逐到系统 RAM
  • HARD RESET:重置 GPU 硬件,恢复驱动状态,可能丢失上下文
  • SOFT RESET(部分支持):仅终止出错进程,保留其他任务运行

8.1 amdgpu 的 GPU Reset 流程

GPU Hung Detection
    ↓
amdgpu_device_gpu_recover()
    ├── Step 1: 识别受影响进程(收集 guilty/non-guildly 标记)
    ├── Step 2: 通知用户态 DMA-BUF 不可访问
    ├── Step 3: TTM evict VRAM to system RAM
    ├── Step 4: 硬件 reset (PCIe FLR 或 SOC reset)
    ├── Step 5: 驱动状态恢复(重新加载 firmware/ring/IRQ)
    └── Step 6: 通知等待 fence(signal error=-EIO)

用户态感知:
- 渲染 client 获取 GPU reset status (CONTEXT_RESET 事件)
- Wayland 合成器可重新初始化 EGLSurface/GBM Surface
- Xorg DDX 重启 GLAMOR 加速

9. DRM 调试工具与性能分析

9.1 kernel debugfs 节点

DRM 驱动导出丰富的 debugfs 信息:

/sys/kernel/debug/dri/0/
├── amdgpu_gtt_mm       — GTT 内存分配详细状态
├── amdgpu_vram_mm      — VRAM 内存分配列表
├── amdgpu_ring_gfx     — GFX Ring Buffer 状态和近期命令
├── amdgpu_gpu_recov    — GPU Reset 记录
├── amdgpu_fences       — 当前活跃 fence 列表
├── i915_display_info   — i915 display pipeline 详细状态
├── i915_gem_objects    — 所有 GEM 对象和内存区域
├── i915_contexts       — 活跃 GPU 上下文
├── i915_dmc            — Display Micro Controller 状态
├── i915_dpst           — 节能状态统计
└── force_reset         — 写入触发模拟 reset

9.2 modetest:KMS 调试瑞士军刀

libdrm 自带的 modetest 工具可用于快速测试和调试:

# 列出所有 KMS 对象
$ modetest -M amdgpu
Encoders:
  id=51   crtc=42   type=TV
Connectors:
  id=52   encoder=51   status=connected   modes:[3840x2160@60, 1920x1080@60, ...]
CRTCs:
  id=42   3840x2160@60 mode   connector=52  encoder=51  fb=57
Planes:
  id=34   type=Primary     formats:XRGB8888 ARGB8888
  id=38   type=Overlay     formats:XRGB8888 NV12 YUYV
  id=42   type=Cursor      formats:ARGB8888

# 测试 atomic modeset
$ modetest -M amdgpu -a -s 52:3840x2160

# 测试 page flip
$ modetest -M amdgmu -P 34@42:1920x1080 -v

9.3 GPU 调试工具链

  • radeontop / intel_gpu_top / nvtop:实时 GPU 利用率展示
  • perf top / perf stat:通过 perf_event_open 监控 GPU 硬件计数器
  • AMD ROCprofiler / Intel GPA:从属厂商的性能分析工具
  • Mesa tracers (e.g. MESA_DEBUG=1、apitrace):更上层 API 追踪
  • DRM MC(MultiChannel):内核 framebuffer console

10. 用户态 DRM 编程实战

10.1 最小 KMS 应用程序:libdrm 示例

#include <xf86drm.h>
#include <xf86drmMode.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

/* 假设设备节点 /dev/dri/card0 */
int main() {
    int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
    
    /* 设置 Universal Planes 和 Atomic */
    uint64_t cap;
    drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
    drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
    
    /* 获取资源 */
    drmModeRes *res = drmModeGetResources(fd);
    drmModePlaneRes *planes = drmModeGetPlaneResources(fd);
    drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[0]);
    
    /* 找到合适的 mode */
    drmModeModeInfo *mode = &conn->modes[0]; /* 首选 */
    
    /* 获取 CRTC */
    drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoder_id);
    uint32_t crtc_id = enc->crtc_id;
    
    /* 创建 dumb buffer */
    struct drm_mode_create_dumb create_req = {
        .width = mode->hdisplay,
        .height = mode->vdisplay,
        .bpp = 32,
    };
    drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
    
    /* 注册 framebuffer */
    uint32_t fb_id;
    drmModeAddFB(fd, mode->hdisplay, mode->vdisplay, 24, 32,
                 create_req.pitch, create_req.handle, &fb_id);
    
    /* 映射 dumb buffer */  
    struct drm_mode_map_dumb map_req = { .handle = create_req.handle };
    drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
    
    void *map = mmap(0, create_req.size, PROT_READ|PROT_WRITE, MAP_SHARED,
                     fd, map_req.offset);
    uint32_t color = 0xFF3366FF;
    for (int i = 0; i < mode->hdisplay * mode->vdisplay; i++) {
        ((uint32_t*)map)[i] = color;
    }
    
    /* 首次 modeset */
    drmModeSetCrtc(fd, crtc_id, fb_id, 0, 0,
                   &conn->connector_id, 1, mode);
    
    sleep(3); /* 显示 3 秒 */
    
    /* 清理 */
    munmap(map, create_req.size);
    struct drm_mode_destroy_dumb destroy_req = { .handle = create_req.handle };
    drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
    drmModeRmFB(fd, fb_id);
    drmModeFreeConnector(conn);
    drmModeFreeEncoder(enc);
    drmModeFreePlaneResources(planes);
    drmModeFreeResources(res);
    close(fd);
    return 0;
}

/* 编译: gcc -o kms_test kms_test.c -ldrm */

10.2 高级模式:Atomic + Page Flip 双缓冲

/* 简化的 atomic page flip 双缓冲渲染循环 */
struct flip_context {
    int fd;
    uint32_t crtc_id, plane_id, conn_id;
    uint32_t fb_front, fb_back;
    drmModeAtomicReq *req;
    drmModeModeInfo *mode;
};

void atomic_flip(struct flip_context *ctx) {
    drmModeAtomicReq *req = ctx->req;
    
    /* 渲染到 back buffer(先省略具体渲染代码) */
    // render_to_back_buffer(ctx->fb_back_map);
    
    /* 设置 plane 属性 */
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "FB_ID"), ctx->fb_back);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "CRTC_ID"), ctx->crtc_id);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "SRC_X"), 0);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "SRC_Y"), 0);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "SRC_W"), (uint64_t)ctx->mode->hdisplay << 16);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "SRC_H"), (uint64_t)ctx->mode->vdisplay << 16);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "CRTC_X"), 0);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "CRTC_Y"), 0);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "CRTC_W"), ctx->mode->hdisplay);
    drmModeAtomicAddProperty(req, ctx->plane_id,
        plane_property(req, "CRTC_H"), ctx->mode->vdisplay);
    
    /* 提交:等待 page flip 事件 */
    uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
    drmModeAtomicCommit(ctx->fd, req, flags, ctx); /* user_data = ctx */
    
    /* 交换前后 buffer */
    uint32_t tmp = ctx->fb_front;
    ctx->fb_front = ctx->fb_back;
    ctx->fb_back = tmp;
    
    /* 重置 atomic request */
    drmModeAtomicFree(ctx->req);
    ctx->req = drmModeAtomicAlloc();
}

/* Page Flip 完成回调 */
void page_flip_handler(int fd, unsigned int frame, unsigned int sec,
                       unsigned int usec, void *data) {
    struct flip_context *ctx = data;
    /* 后置处理:记录帧时间、触发下一帧 */
}

11. 安全与权限模型

11.1 DRM Master 权限

传统上,modesetting 需要 DRM Master 权限(通过 DRM_IOCTL_SET_MASTER 获取)。自 Linux 5.10 起,现代驱动支持非 Master 模式的 modesetting(如翻页),但部分关键操作仍需要 Master:

  • DRM Master:持有 modesetting 最高权限,同一时间仅一个 Master
  • 非 Master 渲染:通过 DRM_IOCTL_AUTH_MAGIC 认证的 fd 可提交 GPU 渲染命令
  • renderD128:render-only 设备节点,无需 Master 即可提交渲染和计算

11.2 Render 节点隔离

DRM 子系统自 Linux 3.12 引入 render 节点,将渲染计算与 modesetting 权限分离:

/dev/dri/
├── card0          — Master + Render (传统接口,权限大)
├── renderD128     — Render-only (无 modesetting)
└── controlD64     — Control-only (极少使用)

安全应用:容器内只需 bind mount /dev/dri/renderD128 即可获得 GPU 渲染/计算能力,无需 modesetting 权限。

11.3 KMS 安全帽

DRM 子系统内置 Landlock + SECCOMP 集成:

  • KMS fd 可被 seccomp 过滤危险 ioctl
  • Wayland 合成器使用 PRCTL_SECCOMP_FILTER 防止 compositor 自身作恶
  • capability(CAP_SYS_ADMIN) 检查 opens a new master 限制

12. 未来方向

12.1 显示虚拟化:SR-IOV + GPU-PV

AMDGPU 和 i915 已支持 SR-IOV(Single Root I/O Virtualization),可将物理 GPU 显示管线划分为多个虚拟 GPU,多虚拟机共享显示引擎输出。

12.2 Dynamic Display Multiplexer (DYNDMUX)

Intel Xe 引入 Dynamic Display Mux Switching,支持在 boot 时无缝切换 GPU(避免传统 dreaded "GPU Switch" 需要的重启 Xorg/Compositor)。

12.3 色彩管理标准化:Colorspace KMS API 完善

Sysfs ABI 和 KMS property 的规范化和文档化,让能为用户态和合成器提供完整、标准化的色彩管线控制。

12.4 DMA-BUF 与 FPGA/加速器的整合

随着异构计算兴起(Xilinx、Intel QAT、AMD/Xilinx Versal),DMA-BUF 正在成为 GPU-FPGA 零拷贝协作的中间件基石。Linux 6.10+ 引入了 dma_buf_import_device 来支持设备间 buffer import。

结语

Linux DRM/KMS 子系统是我们日常与计算机视觉交互的底层基石。从 CRTC/Encoder/Connector/Plane 的精妙硬件抽象,到 Atomic Modesetting 的事务性保证,再到与 Wayland/合成器的精密协作,DRM/KMS 以超过千万行代码的规模支撑着世界上最复杂的图形显示栈。理解 DRM/KMS 不仅对驱动开发者和嵌入式工程师必不可少,对于任何需要深入理解计算机系统全栈——从像素到显示器——的开发者而言,这都是一段值得投入的知识旅程。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部