Skip to content

Latest commit

 

History

History
2579 lines (2059 loc) · 86.3 KB

File metadata and controls

2579 lines (2059 loc) · 86.3 KB

Linux DRM 与 GPU 驱动框架深度解析

基于 Linux Kernel 源码深度分析 路径:drivers/gpu/drm/ | include/drm/


目录

  1. DRM 概述
  2. DRM 核心数据结构
  3. GEM 内存管理
  4. KMS 内核模式设置
  5. 原子模式设置
  6. DRM GPU 调度器
  7. 渲染管线:用户态视角
  8. 具体驱动示例
  9. Display Engine vs Compute Engine
  10. drivers/gpu/drm/ 主要文件一览
  11. 调试工具
  12. DRM 设备节点:renderD 与 cardN 的区别
  13. DRM MM 内存范围管理器
  14. dma-fence 同步机制深度分析
  15. KMS 对象模型:Encoder、Bridge 与 Connector 热插拔
  16. 原子提交流程源码级详解
  17. DRM fbdev 兼容层
  18. DRM 驱动注册与设备节点创建
  19. GEM 对象生命周期源码追踪
  20. DRM 调度器:源码级深度分析
  21. 参考源码路径索引

1. DRM 概述

1.1 什么是 DRM

DRM(Direct Rendering Manager)是 Linux 内核中负责管理 GPU 及显示输出的子系统。它诞生于 1999 年(XFree86 项目),最初目的是解决多进程争用 GPU 硬件的问题。现代 DRM 承载两大职责:

  • KMS(Kernel Mode Setting):控制显示输出管线(分辨率、刷新率、显示器检测)
  • GEM(Graphics Execution Manager):管理 GPU 显存(分配、共享、同步)

1.2 从 /dev/dri/card0 到屏幕像素

DRM 向用户态暴露三类设备节点:

/dev/dri/
  card0       <-- 主节点(primary node),KMS + 渲染,需要 DRM master 权限
  renderD128  <-- 渲染节点(render node),仅 GPU 渲染,无需 master 权限
  accel0      <-- 计算加速节点(accel node),DRIVER_COMPUTE_ACCEL 专用

像素从应用到屏幕的完整路径:

Application (OpenGL/Vulkan)
    |
    v
Mesa / libdrm (用户态驱动)
    |  DRM_IOCTL_*
    v
/dev/dri/renderD128 or /dev/dri/card0
    |
    v
drm_ioctl() --> 驱动私有 ioctl 处理
    |
    +-- GEM: 分配 Buffer Object (BO)
    |         GPU 渲染写入 BO
    |
    v
drm_atomic_commit()  (KMS 路径)
    |
    v
Plane --> CRTC --> Encoder --> Connector --> 显示器
                                              |
                                              v
                                         屏幕上的像素

1.3 整体架构图

+------------------------------------------------------------------+
|                       User Space                                  |
|  Wayland/X11 Compositor   OpenGL App    Vulkan App               |
|       |                      |              |                     |
|   libdrm                   Mesa          Mesa/ANV                |
+-----|------------------------|--------------|---------------------+
      |  ioctl                 |              |
+-----|------------------------|--------------|---------------------+
|     v          Linux Kernel DRM Subsystem                        |
|  +------------------+   +-----------+   +-----------+           |
|  |   drm_file       |   |    GEM    |   | Scheduler |           |
|  |  (per-fd state)  |   | (memory)  |   | (GPU jobs)|           |
|  +------------------+   +-----------+   +-----------+           |
|           |                   |               |                  |
|  +------------------------------------------------------------------+
|  |              drm_device  (核心设备抽象)                       |  |
|  |    drm_driver (ops) | drm_mode_config (KMS state)            |  |
|  +------------------------------------------------------------------+
|           |                                                      |
|  +------------------+                                           |
|  |       KMS        |                                           |
|  |  plane -> crtc   |                                           |
|  |  -> encoder      |                                           |
|  |  -> connector    |                                           |
|  +------------------+                                           |
|           |                                                      |
+-----------|------------------------------------------------------+
            |  硬件操作
+-----------|------------------------------------------------------+
|  GPU Driver (i915 / amdgpu / nouveau / ...)                      |
|      PCI / platform bus                                          |
|      Physical GPU Hardware                                       |
+------------------------------------------------------------------+

2. DRM 核心数据结构

2.1 struct drm_driver

文件: include/drm/drm_drv.h(第 181 行)

drm_driver 是驱动程序向 DRM 核心注册的操作表(vtable),一个驱动对应一个 drm_driver 实例,一个驱动可对应多个设备实例(drm_device)。

struct drm_driver {
    /* 生命周期回调 */
    int  (*load)(struct drm_device *, unsigned long flags);   // 已废弃
    int  (*open)(struct drm_device *, struct drm_file *);     // fd 打开
    void (*postclose)(struct drm_device *, struct drm_file *); // fd 关闭
    void (*unload)(struct drm_device *);                       // 已废弃
    void (*release)(struct drm_device *);                      // 设备销毁

    /* GEM 相关 */
    struct drm_gem_object *(*gem_create_object)(...);
    struct drm_gem_object *(*gem_prime_import)(...);           // dma-buf 导入
    struct drm_gem_object *(*gem_prime_import_sg_table)(...);

    /* KMS 显示缓冲 */
    int (*dumb_create)(...);       // 创建 dumb buffer
    int (*dumb_map_offset)(...);   // 获取 mmap offset

    /* 能力标志 */
    u32 driver_features;   // DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC ...

    /* ioctl 扩展 */
    const struct drm_ioctl_desc *ioctls;
    int num_ioctls;

    /* 文件操作 */
    const struct file_operations *fops;
};

关键 feature 标志(include/drm/drm_drv.h,第 57 行):

标志 bit 含义
DRIVER_GEM 0 使用 GEM 内存管理
DRIVER_MODESET 1 支持 KMS 模式设置
DRIVER_RENDER 3 支持独立 render node
DRIVER_ATOMIC 4 支持完整原子 KMS userspace API
DRIVER_SYNCOBJ 5 支持 drm_syncobj 显式同步
DRIVER_COMPUTE_ACCEL 7 计算加速设备(与 RENDER/MODESET 互斥)
DRIVER_GEM_GPUVA 8 支持用户自定义 GPU VA 绑定

2.2 struct drm_device

文件: include/drm/drm_device.h(第 75 行)

drm_device 代表一块物理 GPU 卡,生命周期与 PCI/platform 设备绑定。

struct drm_device {
    struct kref           ref;           // 引用计数
    struct device        *dev;           // 总线设备
    struct device        *dma_dev;       // DMA 控制器(USB GPU 等场景)
    const struct drm_driver *driver;

    /* 设备节点 */
    struct drm_minor *primary;   // /dev/dri/card0
    struct drm_minor *render;    // /dev/dri/renderD128
    struct drm_minor *accel;     // /dev/dri/accel0

    /* KMS 状态 */
    struct drm_mode_config mode_config;
    unsigned int num_crtcs;

    /* GEM 对象管理 */
    struct mutex object_name_lock;
    struct idr   object_name_idr;        // handle -> gem_object 映射
    struct drm_vma_offset_manager *vma_offset_manager;

    /* 打开的文件列表 */
    struct mutex    filelist_mutex;
    struct list_head filelist;           // 所有打开的 drm_file

    /* Vblank 跟踪 */
    struct drm_vblank_crtc *vblank;
    u32 max_vblank_count;
    spinlock_t vbl_lock;

    /* 调试 */
    struct dentry *debugfs_root;
};

设备注册流程:

devm_drm_dev_alloc()   -- 分配并初始化 drm_device(devres 管理)
        |
        v
drm_dev_register()     -- 向内核注册,创建字符设备节点
        |
        v
drm_dev_unregister()   -- 注销(hotplug 场景)
drm_dev_unplug()       -- 标记为已拔出,阻止新访问

2.3 struct drm_file

文件: include/drm/drm_file.h,实现在 drivers/gpu/drm/drm_file.c

每个 open("/dev/dri/card0") 产生一个 drm_file,包含:

struct drm_file {
    struct drm_device *minor->dev;  // 所属设备

    /* 身份与权限 */
    bool is_master;               // 是否为 DRM master(控制 KMS)
    struct drm_master *master;

    /* GEM handle 表:每个 fd 私有,handle 是 u32 整数 */
    struct idr   object_idr;      // handle -> drm_gem_object
    spinlock_t   table_lock;

    /* dma-buf 导入的对象 */
    struct list_head blobs;

    /* 事件队列(vblank/page-flip 完成通知) */
    struct list_head  pending_event_list;
    struct list_head  event_list;
    wait_queue_head_t event_wait;

    /* syncobj */
    struct idr syncobj_idr;
};

drm_file 的关键职责:在同一设备上,不同进程的 GEM handle 空间完全隔离,handle 只在创建它的 drm_file 内有效。


3. GEM 内存管理

3.1 GEM 概述

GEM(Graphics Execution Manager)由 Intel 工程师 Eric Anholt 于 2008 年为 i915 驱动设计,后成为 DRM 核心组件。GEM 用整数 handle 代替 fd 来引用 GPU 缓冲区,规避了进程 fd 数量上限问题(详见 drivers/gpu/drm/drm_gem.c 第 60-86 行注释)。

3.2 struct drm_gem_object(Buffer Object)

文件: include/drm/drm_gem.h(第 283 行)

struct drm_gem_object {
    struct kref    refcount;       // 引用计数(内核内部)
    unsigned       handle_count;   // 用户态 handle 引用数
    struct drm_device *dev;

    struct file   *filp;           // shmem 文件(可换出的 BO)
                                   // NULL = 驱动私有后备存储(连续 DMA 等)

    struct drm_vma_offset_node vma_node;  // mmap offset 节点
    size_t size;                           // BO 大小(字节,不可变)
    int    name;                           // GEM global name(flink 命名空间)

    struct dma_buf            *dma_buf;       // 关联的 dma-buf(PRIME 导出)
    struct dma_buf_attachment *import_attach;  // 外部导入时的 attach 点

    struct dma_resv  *resv;   // 预留锁(fence 同步)
    struct dma_resv  _resv;   // 本地预留对象

    /* GPU VA 映射列表(DRIVER_GEM_GPUVA) */
    struct { struct list_head list; struct mutex lock; } gpuva;

    const struct drm_gem_object_funcs *funcs;

    struct list_head lru_node;  // LRU 链表节点(内存压力回收)
    struct drm_gem_lru  *lru;
};

3.3 BO 生命周期

drm_gem_object_init()         -- 初始化,关联 shmem 文件
    |
    v
drm_gem_handle_create()       -- 在 drm_file 的 idr 中分配 handle
    |
    v  (用户态通过 handle 引用 BO)
drm_gem_object_lookup()       -- handle --> drm_gem_object*
    |
    v
drm_gem_handle_delete()       -- handle_count--
    |                              当 handle_count == 0 时,清除 global name
    v
drm_gem_object_put()          -- refcount--
    |                              当 refcount == 0 时调用 funcs->free()
    v
驱动的 free() 回调             -- 释放 VRAM/GART 映射、backing store 等

3.4 GEM 对象操作表

文件: include/drm/drm_gem.h(第 75 行)

struct drm_gem_object_funcs {
    void (*free)(obj);                  // 析构(必须实现)
    int  (*open)(obj, file);            // handle 创建时
    void (*close)(obj, file);           // handle 释放时
    struct dma_buf *(*export)(obj, flags); // 导出为 dma-buf
    int  (*pin)(obj);                   // 固定到内存(供 dma-buf 访问)
    void (*unpin)(obj);
    struct sg_table *(*get_sg_table)(obj); // 获取 scatter-gather 表
    int  (*vmap)(obj, map);             // CPU 虚拟地址映射
    void (*vunmap)(obj, map);
    int  (*mmap)(obj, vma);             // 用户态 mmap 处理
    int  (*evict)(obj);                 // 内存压力下的驱逐
    enum drm_gem_object_status (*status)(obj); // 对象当前状态
    size_t (*rss)(obj);                 // 实际占用物理内存大小
    const struct vm_operations_struct *vm_ops; // mmap vm 操作
};

3.5 mmap 流程

用户态 mmap 一个 GEM BO 的完整流程:

1. 用户态调用 DRM_IOCTL_MODE_MAP_DUMB 或驱动私有 ioctl
   --> 内核调用 drm_gem_create_mmap_offset()
       在 vma_offset_manager 中分配一个假地址偏移

2. 用户态 mmap(fd, offset, size, ...)
   --> drm_gem_mmap()  (include/drm/drm_gem.h 第 539 行)
       --> 根据 offset 在 vma_offset_manager 查找对应 BO
       --> 调用 obj->funcs->mmap() 或使用 vm_ops

3. 缺页处理 (page fault)
   --> vm_ops->fault()
       --> 驱动为具体页分配物理内存并建立映射

3.6 PRIME 跨设备共享(dma-buf)

PRIME 是 GEM 对象跨驱动/设备共享的机制,基于内核的 dma-buf 框架:

GPU-A (exporter)                    GPU-B (importer)
     |                                   |
drm_gem_prime_export()             drm_gem_prime_import()
     |                                   |
     +--> dma_buf_export()               +-- dma_buf_attach()
           |                                  |
           v                                  v
        dma_buf fd (可通过 SCM_RIGHTS 传递)

drm_gem_object 中两个关键字段(include/drm/drm_gem.h 第 363、383 行):

  • dma_buf:该 BO 作为 exporter 时对应的 dma_buf 指针
  • import_attach:该 BO 作为 importer 时的 dma_buf_attachment

4. KMS 内核模式设置

4.1 KMS 概述

KMS(Kernel Mode Setting)在 2009 年前后引入,将显示模式设置从用户态 X Server 移入内核,解决了模式切换时的闪烁、多显示器竞争等问题。

KMS 的对象层次结构,全部保存在 drm_mode_config 中:

drm_device
    |
    +-- drm_mode_config
          |
          +-- plane_list      --> drm_plane[]      (显示层)
          |
          +-- crtc_list       --> drm_crtc[]       (扫描输出控制器)
          |
          +-- encoder_list    --> drm_encoder[]    (信号编码器)
          |
          +-- connector_list  --> drm_connector[]  (物理接口)

4.2 显示管线

[Framebuffer]                     (存放像素数据的 GEM BO)
     |
     v
[drm_plane]   PRIMARY / OVERLAY / CURSOR
     |         src: 从 FB 取哪块区域(16.16 定点数)
     |         dst: 显示在 CRTC 的哪个位置
     v
[drm_crtc]    Cathode Ray Tube Controller(历史名称,实为扫描控制器)
     |         输出时序:htotal/vtotal/hsync/vsync
     |         颜色管理:degamma LUT -> CTM -> gamma LUT
     v
[drm_encoder] 信号格式转换(TMDS/LVDS/DP/eDP 等物理层)
     |         一个 encoder 可连接多个 connector(如 HDMI + DVI 同源)
     v
[drm_connector] 物理连接器(HDMI-A-1 / DP-1 / eDP-1 等)
     |           读取 EDID,枚举支持的 drm_display_mode 列表
     v
[Display]     物理屏幕

4.3 struct drm_mode_config

文件: include/drm/drm_mode_config.h(第 360 行)

struct drm_mode_config {
    struct mutex mutex;                    // 全局 KMS 大锁
    struct drm_modeset_lock connection_mutex; // connector/encoder/crtc 路由锁

    /* 对象 IDR */
    struct mutex idr_mutex;
    struct idr   object_idr;              // 所有 KMS 对象的 ID 空间

    /* 各类对象链表 */
    struct list_head crtc_list;
    struct list_head encoder_list;
    struct list_head connector_list;
    struct list_head plane_list;
    struct list_head fb_list;

    int num_crtc, num_encoder, num_connector, num_total_plane;

    /* 硬件尺寸限制 */
    unsigned int min_width, min_height;
    unsigned int max_width, max_height;
    uint32_t cursor_width, cursor_height;

    /* 原子相关 */
    struct drm_atomic_state *suspend_state;

    /* 标准 KMS 属性 */
    struct drm_property *edid_property;
    struct drm_property *dpms_property;
    struct drm_property *prop_active;     // CRTC active 状态
    struct drm_property *prop_mode_id;    // CRTC 当前 mode blob
    struct drm_property *prop_vrr_enabled; // 可变刷新率

    /* 颜色管理 */
    struct drm_property *degamma_lut_property;
    struct drm_property *ctm_property;
    struct drm_property *gamma_lut_property;

    const struct drm_mode_config_funcs *funcs; // atomic_check / atomic_commit
};

4.4 struct drm_crtc

文件: include/drm/drm_crtc.h(第 943 行)

CRTC 是显示管线的核心,负责从 plane 读取像素数据,按照指定时序扫描输出。

struct drm_crtc {
    struct drm_device *dev;
    struct drm_plane  *primary;     // 主 plane(legacy IOCTL 使用)
    struct drm_plane  *cursor;      // 光标 plane(legacy IOCTL 使用)
    unsigned index;                 // 在 mode_config.crtc_list 中的位置

    /* 当前状态(legacy 驱动) */
    struct drm_display_mode mode;   // 当前模式
    struct drm_display_mode hwmode; // 硬件实际编程的模式
    bool   enabled;

    /* 原子状态 */
    struct drm_crtc_state *state;

    const struct drm_crtc_funcs *funcs;

    /* Vblank */
    unsigned int fence_context;
    spinlock_t   fence_lock;
    unsigned long fence_seqno;

    /* 调试 */
    struct dentry *debugfs_entry;
    struct drm_crtc_crc crc;
};

4.5 struct drm_crtc_state

文件: include/drm/drm_crtc.h(第 81 行)

原子 KMS 的状态对象,每次 commit 都克隆旧状态后修改:

struct drm_crtc_state {
    struct drm_crtc *crtc;
    bool enable;          // 是否分配资源
    bool active;          // 是否实际输出(DPMS)

    /* 变化标志位 */
    bool planes_changed   : 1;
    bool mode_changed     : 1;
    bool active_changed   : 1;
    bool connectors_changed : 1;
    bool color_mgmt_changed : 1;

    struct drm_display_mode adjusted_mode;  // 硬件编程用的时序
    struct drm_display_mode mode;           // 用户态请求的时序

    /* 颜色管理 LUT */
    struct drm_property_blob *degamma_lut;
    struct drm_property_blob *ctm;
    struct drm_property_blob *gamma_lut;

    bool vrr_enabled;     // 可变刷新率(FreeSync/G-Sync)
    bool async_flip;      // 异步翻页

    struct drm_pending_vblank_event *event; // commit 完成时通知用户态
    struct drm_crtc_commit *commit;          // 跟踪 commit 进度
    struct drm_atomic_state *state;
};

4.6 Plane(显示层)

文件: include/drm/drm_plane.h

enum drm_plane_type {
    DRM_PLANE_TYPE_OVERLAY,   // 叠加层(sprite)
    DRM_PLANE_TYPE_PRIMARY,   // 主平面(每个 CRTC 至少一个)
    DRM_PLANE_TYPE_CURSOR,    // 鼠标光标(硬件加速移动)
};

Plane 状态(drm_plane_state,第 54 行)的关键坐标:

Framebuffer 像素坐标(16.16 定点数)       CRTC 屏幕坐标(整数像素)
+---------------------------+              +---------------------------+
|                           |              |                           |
|  (src_x, src_y)           |              | (crtc_x, crtc_y)         |
|  +--------+               |   scale/     |  +--------+              |
|  | src_w  |               |  --------->  |  | crtc_w |              |
|  | src_h  |               |              |  | crtc_h |              |
|  +--------+               |              |  +--------+              |
+---------------------------+              +---------------------------+

其他 plane 属性:

  • alpha:平面整体透明度(0x0000~0xffff)
  • pixel_blend_mode:像素混合模式(Pre-multiplied / Coverage / None)
  • rotation:旋转(0/90/180/270 度及翻转)
  • zpos:Z 轴堆叠顺序(值越大越靠前)
  • color_encoding / color_range:YUV 格式的颜色空间参数
  • fence:等待渲染完成的显式 fence(同步)

4.7 drm_display_mode

显示模式描述完整的水平/垂直时序(兼容 VESA/CEA 标准):

struct drm_display_mode {
    char name[DRM_DISPLAY_MODE_LEN]; // 如 "1920x1080"

    /* 像素时钟(kHz) */
    int clock;

    /* 水平时序(像素单位) */
    int hdisplay;    // 可见宽度
    int hsync_start, hsync_end, htotal;

    /* 垂直时序(行单位) */
    int vdisplay;    // 可见高度
    int vsync_start, vsync_end, vtotal;

    int vrefresh;    // 刷新率(Hz)= clock*1000 / (htotal * vtotal)
    u32 flags;       // DRM_MODE_FLAG_PHSYNC / NVSYNC / INTERLACE 等
    u32 type;        // DRM_MODE_TYPE_PREFERRED / DRIVER / USERDEF
};

5. 原子模式设置(Atomic KMS)

5.1 Legacy vs Atomic 对比

Legacy KMS                          Atomic KMS
----------------------------------------------------------------------
每次改一个属性立即生效               事务性:所有改动打包为一个 state
无原子性保证                         全部成功或全部回滚
page_flip / set_config ioctl        DRM_IOCTL_MODE_ATOMIC ioctl
驱动直接操作硬件                     先 check,通过后再 commit
难以实现无撕裂多平面更新             保证无撕裂(在 vblank 边界切换)

5.2 drm_atomic_state

原子状态是所有 KMS 对象变化的快照集合:

struct drm_atomic_state {
    struct drm_device *dev;

    /* 各对象的新旧状态数组 */
    struct __drm_crtcs_state    *crtcs;
    struct __drm_planes_state   *planes;
    struct __drm_connectors_state *connectors;
    struct __drm_private_objs_state *private_objs;

    int num_connector, num_private_objs;
    bool allow_modeset;   // 是否允许需要 modeset 的变化
    bool legacy_cursor_update; // 遗留光标更新路径
    bool async_update;    // 异步更新(不等 vblank)
};

5.3 Atomic Commit 流程

用户态                    DRM 核心                   驱动
  |                          |                         |
  | DRM_IOCTL_MODE_ATOMIC    |                         |
  |------------------------->|                         |
  |                          | 1. 解析属性变化,构建   |
  |                          |    drm_atomic_state     |
  |                          |                         |
  |                          | 2. drm_atomic_check()   |
  |                          |    -->                  |
  |                          |    mode_config_funcs    |
  |                          |    .atomic_check()      |
  |                          |          |              |
  |                          |          +------------>  crtc_helper.atomic_check()
  |                          |          +------------>  plane_helper.atomic_check()
  |                          |          +------------>  connector_helper.atomic_check()
  |                          |                         |
  |                          | 3. (DRM_MODE_ATOMIC_TEST_ONLY: 只检查不提交)
  |                          |                         |
  |                          | 4. drm_atomic_commit()  |
  |                          |    -->                  |
  |                          |    mode_config_funcs    |
  |                          |    .atomic_commit()     |
  |                          |          |              |
  |                          |          +------------>  prepare_fb() [pin BO]
  |                          |          +------------>  crtc.disable()
  |                          |          +------------>  plane.update()
  |                          |          +------------>  crtc.enable()
  |                          |          +------------>  cleanup_fb()
  |                          |                         |
  |  vblank event / fence    |  <-- hw_done            |
  |<-------------------------|  <-- flip_done          |

5.4 drm_crtc_commit 追踪提交进度

drivers/gpu/drm/drm_atomic.c 第 69 行,commit 等待机制:

int drm_crtc_commit_wait(struct drm_crtc_commit *commit)
{
    // 等待硬件编程完成(hw_done)
    ret = wait_for_completion_timeout(&commit->hw_done, 10 * HZ);
    // 等待 flip 切换完成(flip_done = vblank 触发)
    ret = wait_for_completion_timeout(&commit->flip_done, 10 * HZ);
}

两个 completion 的含义:

  • hw_done:硬件寄存器已被写入(但扫描仍使用旧 FB)
  • flip_done:下一个 vblank 到来,新 FB 开始扫描

6. DRM GPU 调度器

6.1 概述

DRM GPU 调度器(drivers/gpu/drm/scheduler/)是通用的 GPU 命令队列管理框架,由 AMD 贡献,被 amdgpu、radeon、lima、panfrost、v3d、virtio-gpu 等驱动使用。

6.2 核心数据结构层次

drm_gpu_scheduler          (调度器实例,对应一条硬件 ring/queue)
    |
    +-- sched_rq[PRIORITY_COUNT]   (按优先级分组的运行队列)
          |
          +-- drm_sched_rq         (单个优先级的运行队列)
                |
                +-- entities       (链表:排队等待的 entity)
                |
                +-- rb_tree_root   (红黑树:FIFO 时间顺序)

drm_sched_entity           (代表一个命令流,通常对应一个 GPU context)
    |
    +-- job_queue            (SPSC 队列:该 entity 的待提交任务)
    +-- sched_list[]         (该 entity 可使用的调度器列表,负载均衡)
    +-- priority             (KERNEL/HIGH/NORMAL/LOW)
    +-- last_scheduled       (上一个已调度任务的 finished fence)
    +-- dependency           (当前队首任务的依赖 fence)

drm_sched_job              (单个 GPU 命令提交)
    |
    +-- s_fence              (调度 fence:scheduled + finished)
    +-- dependencies[]       (等待的前置 fence,xarray)
    +-- credits              (消耗的调度器 credit)
    +-- karma                (hang 计数,超限则 guilty)

include/drm/gpu_scheduler.h 第 65 行,优先级定义:

enum drm_sched_priority {
    DRM_SCHED_PRIORITY_KERNEL,  // 0, 最高
    DRM_SCHED_PRIORITY_HIGH,
    DRM_SCHED_PRIORITY_NORMAL,
    DRM_SCHED_PRIORITY_LOW,
    DRM_SCHED_PRIORITY_COUNT
};

6.3 drm_gpu_scheduler

文件: include/drm/gpu_scheduler.h(第 539 行)

struct drm_gpu_scheduler {
    const struct drm_sched_backend_ops *ops;  // 驱动实现的后端操作
    u32 credit_limit;    // 最大 credit(限制在途任务数)
    atomic_t credit_count;
    long timeout;        // 任务超时时间(jiffies)
    const char *name;

    unsigned int num_rqs;
    struct drm_sched_rq **sched_rq;  // 按优先级的运行队列数组

    struct workqueue_struct *submit_wq;   // 提交任务的工作队列
    struct workqueue_struct *timeout_wq;  // 超时检测工作队列
};

6.4 调度器后端操作

文件: include/drm/gpu_scheduler.h(第 412 行)

struct drm_sched_backend_ops {
    // 检查任务是否还有额外依赖(可选)
    struct dma_fence *(*prepare_job)(job, entity);

    // 将任务提交给硬件,返回 hw fence
    struct dma_fence *(*run_job)(job);

    // 任务超时,触发 GPU reset 恢复
    enum drm_gpu_sched_stat (*timedout_job)(job);

    // 任务 finished fence 信号后释放资源
    void (*free_job)(job);

    // drm_sched_fini() 时取消未执行的任务
    void (*cancel_job)(job);
};

6.5 任务生命周期

drm_sched_job_init()           -- 初始化 job,分配 s_fence
    |
    v
drm_sched_job_add_dependency() -- 添加前置依赖 fence
    |
    v
drm_sched_job_arm()            -- 设置 sched,准备提交
    |
    v
drm_sched_entity_push_job()    -- 推入 entity 的 job_queue
    |
    v (调度器工作队列取出)
drm_sched_backend_ops.prepare_job() -- 检查额外依赖
    |
    v (所有依赖满足)
drm_sched_backend_ops.run_job()     -- 提交到硬件,返回 hw_fence
    |
    v (hw_fence 信号)
drm_sched_fence.finished 信号       -- 通知等待方
    |
    v
drm_sched_backend_ops.free_job()    -- 释放资源

7. 渲染管线:用户态视角

7.1 完整调用链

OpenGL/Vulkan Application
    |
    |  glDrawArrays() / vkQueueSubmit()
    v
Mesa3D (用户态 3D 驱动)
    |  -- 编译 shader(GLSL -> NIR -> 驱动 ISA)
    |  -- 构建命令缓冲区(command buffer)
    v
libdrm (https://gitlab.freedesktop.org/mesa/drm)
    |  -- 包装 ioctl 调用(drm_ioctl_*)
    |  -- GEM handle 管理
    v
DRM ioctl interface
    |  -- DRM_IOCTL_GEM_CREATE      创建 BO
    |  -- DRM_IOCTL_PRIME_HANDLE_TO_FD  导出 dma-buf
    |  -- 驱动私有 ioctl(如 I915_GEM_EXECBUFFER2)
    v
内核 DRM 核心 + 具体驱动
    |  -- GEM 分配、绑定 GPU VA
    |  -- 构建 drm_sched_job
    |  -- 推入 GPU 调度器
    v
GPU 硬件执行
    |  -- 顶点处理、光栅化、片段着色
    |  -- 结果写入 framebuffer BO
    v
KMS (drm_atomic_commit)
    |  -- 将渲染结果 BO 绑定到 plane
    |  -- 在下一个 vblank 切换扫描
    v
物理显示器

7.2 同步机制

现代 DRM 使用多种同步机制:

隐式同步(Implicit Fencing)
  drm_gem_object.resv (dma_resv)
  -- 每个 BO 有一个预留锁,记录"写者"和"读者" fence
  -- GEM 操作自动等待前置 fence

显式同步(Explicit Fencing,需要 DRIVER_SYNCOBJ)
  drm_syncobj
  -- 用户态可控的 fence 容器
  -- 支持 timeline(类似 Vulkan Timeline Semaphore)
  -- 用于精确控制渲染与显示之间的同步

DMA fence
  -- 内核通用异步操作完成通知
  -- drm_sched_fence 包含 scheduled + finished 两个 dma_fence

8. 具体驱动示例

8.1 i915(Intel)架构

目录: drivers/gpu/drm/i915/

i915 是最成熟的 DRM 驱动之一,架构高度模块化:

i915/
  i915_drv.h           -- drm_i915_private 主结构(含所有硬件状态)
  i915_driver.c        -- 驱动注册、PCI probe/remove
  i915_gem.c           -- GEM 对象管理
  i915_gem_gtt.c       -- GTT(Graphics Translation Table)管理
  gt/                  -- GPU Tile(渲染引擎)
    intel_engine_*.c   -- 引擎(render/blit/compute/video)
    intel_lrc.c        -- Logical Ring Context(硬件上下文)
    intel_guc*.c       -- GuC(微控制器,固件调度)
  display/             -- 显示子系统
    intel_display.c    -- KMS 主入口
    intel_ddi.c        -- DDI(Digital Display Interface)
    intel_dp.c         -- DisplayPort 实现
    intel_hdmi.c       -- HDMI 实现
    intel_atomic.c     -- 原子提交
  gem/                 -- GEM 辅助

i915 特有的设计要点:

1. GuC 调度:
   用户态 context --> GUC_SUBMIT ioctl --> GuC 固件 --> 硬件 ring
   GuC 负责负载均衡、上下文抢占、功耗管理

2. GTT(GGTT + PPGTT):
   GGTT: 全局 GTT,CPU 与 GPU 共享,aperture 映射
   PPGTT: 每进程页表,隔离不同进程的 GPU VA 空间

3. Execbuffer:
   DRM_IOCTL_I915_GEM_EXECBUFFER2
   -- 批处理命令缓冲(batch buffer)提交
   -- 关联 BO 列表(relocation 或 softpin 绑定 GPU VA)

4. Tile 架构(Xe2/Ponte Vecchio 等):
   每个 Tile 有独立的 GT(Graphics Tile)
   i915 -> xe 驱动迁移(drivers/gpu/drm/xe/)

8.2 amdgpu 架构

目录: drivers/gpu/drm/amd/amdgpu/

amdgpu/
  amdgpu_drv.c         -- 驱动注册
  amdgpu_device.c      -- 设备初始化、IP 发现
  amdgpu_gem.c         -- GEM 对象(基于 TTM)
  amdgpu_ring.c        -- 硬件 ring buffer 管理
  amdgpu_cs.c          -- Command Submission(CS)入口
  amdgpu_job.c         -- drm_sched_job 封装
  amdgpu_vm.c          -- GPU 虚拟内存管理(GPUVM)
  amdgpu_display.c     -- KMS 显示
  amdgpu_ttm.c         -- TTM 内存管理器集成

IP 模块(每代硬件一套):
  gfx_v9_0.c / gfx_v11_0.c    -- 渲染/计算引擎
  sdma_v4_0.c                  -- SDMA(copy engine)
  vcn_v3_0.c                   -- 视频编解码引擎
  dcn10/dcn21/dcn32/           -- Display Core Next(显示引擎)

amdgpu 与 i915 的主要区别:

  • amdgpu 使用 TTM(Translation Table Manager)管理显存,支持 BO 在 VRAM/GTT/system 内存之间迁移
  • 使用 DRM GPU scheduler 框架(amdgpu 是主要贡献者)
  • ROCm/HSA 计算栈集成(amdkfd 子模块)
  • DCN(Display Core Next)完全独立的显示 IP

9. Display Engine vs Compute Engine

9.1 两者分工

+---------------------------+     +---------------------------+
|    Display Engine (KMS)   |     |   Compute/Render Engine   |
+---------------------------+     +---------------------------+
| 职责:                    |     | 职责:                    |
| - 从 FB 读取像素          |     | - 执行着色器              |
| - 扫描时序控制(CRTC)    |     | - 渲染到 BO               |
| - 颜色空间转换            |     | - GPGPU 计算              |
| - 信号编码(HDMI/DP)     |     | - 视频解码                |
|                           |     |                           |
| 访问内存:只读 FB          |     | 访问内存:读写 BO         |
| 同步:vblank 中断         |     | 同步:dma_fence / syncobj |
| 代码:drivers/gpu/drm/   |     | 代码:drivers/gpu/drm/    |
|       display/ 或 dcn/   |     |       gt/ 或 amdkfd/     |
+---------------------------+     +---------------------------+
        |                                   |
        +------- drm_device ----------------+
        |         (共享 GEM 内存)            |
        +-----------------------------------+

9.2 DRIVER_COMPUTE_ACCEL 标志

include/drm/drm_drv.h 第 103-110 行:

DRIVER_COMPUTE_ACCEL = BIT(7),
// 纯计算加速设备(如 NPU、ML 加速器)
// 与 DRIVER_RENDER 和 DRIVER_MODESET 互斥
// 如果设备同时支持图形和计算,应通过 auxiliary bus 连接两个驱动

典型用例:

  • Intel NPUdrivers/accel/ivpu/):纯计算,暴露 /dev/accel/accel0
  • AMD KFD(ROCm 计算):通过 amdkfd 子模块与 amdgpu 共享设备
  • AMDGPU display + compute:单 amdgpu 驱动同时支持 KMS 和渲染/计算

9.3 内存共享路径

Display Engine 显示 Compute Engine 渲染结果的标准路径:

Compute Engine               Display Engine
    |                             |
    | 渲染到 GEM BO               |
    |                             |
    | 信号 render_fence           |
    |                             |
    +-------- GEM BO ------------>|
              (dma-buf 或同设备   |
               直接 handle)       |
                                  | atomic_commit()
                                  | plane.fence = render_fence
                                  | (等待 fence 后切换扫描)
                                  |
                                  v
                             vblank 中断 --> 新 FB 扫描输出

10. drivers/gpu/drm/ 主要文件一览

10.1 核心框架

文件                            作用
--------------------------------------------------------------------
drm_drv.c                      drm_dev_alloc/register/unregister
drm_file.c                     drm_open/release/ioctl/read/poll
drm_gem.c                      GEM 核心:handle 管理、mmap、LRU
drm_gem_shmem_helper.c         shmem 后备存储的 GEM helper
drm_gem_dma_helper.c           连续 DMA 内存的 GEM helper
drm_atomic.c                   原子状态管理、commit 等待
drm_atomic_helper.c            原子 KMS helper(check/commit 实现)
drm_atomic_uapi.c              用户态 ioctl 解析
drm_crtc.c                     CRTC 管理
drm_plane.c                    Plane 管理
drm_connector.c                Connector 管理(热插拔、EDID)
drm_encoder.c                  Encoder 管理
drm_framebuffer.c              Framebuffer 管理(GEM BO -> FB)
drm_modes.c                    drm_display_mode 工具函数
drm_edid.c                     EDID 解析(DDC/I2C 读取)
drm_bridge.c                   Bridge 抽象(MIPI DSI -> HDMI 等转换器)
drm_vblank.c                   Vblank 计数和时间戳管理
drm_prime.c                    PRIME dma-buf 导入/导出
drm_syncobj.c                  drm_syncobj 显式同步对象
drm_exec.c                     GEM 对象批量锁定(执行前准备)
drm_buddy.c                    Buddy 分配器(VRAM 管理)
drm_mm.c                       内存范围管理器(MM allocator)
drm_fb_helper.c                fbdev 模拟 helper
drm_debugfs.c                  debugfs 接口

10.2 调度器

scheduler/
  sched_main.c       -- drm_gpu_scheduler 主逻辑(init/fini/push/run)
  sched_entity.c     -- drm_sched_entity 管理
  sched_fence.c      -- drm_sched_fence 实现

10.3 主要驱动目录

i915/        Intel GPU(Gen4-Xe2)
xe/          Intel Xe GPU(新一代,替代 i915)
amd/amdgpu/  AMD GPU(GCN/RDNA/CDNA)
nouveau/     NVIDIA(逆向工程,功能受限)
radeon/      AMD 老款 GPU(已遗留)
msm/         Qualcomm Adreno GPU
panfrost/    ARM Mali(Midgard/Bifrost)
panthor/     ARM Mali(Valhall/5th Gen)
lima/        ARM Mali(Utgard,OpenGL ES 2.0 级别)
v3d/         Broadcom VideoCore VI(树莓派 4)
vc4/         Broadcom VideoCore IV(树莓派 3)
etnaviv/     Vivante GPU(NXP i.MX 系列)
rockchip/    Rockchip SoC 显示子系统
mediatek/    MediaTek SoC 显示
tegra/       NVIDIA Tegra SoC
virtio/      虚拟机 virtio GPU
vkms/        Virtual KMS(纯软件,用于测试)

11. 调试工具

11.1 drm_info

枚举所有 DRM 设备、能力、KMS 对象、属性:

# 查看所有 DRM 设备及其 KMS 拓扑
drm_info

# 典型输出
Node: /dev/dri/card0
  Driver: i915 (Intel) version 1.6.0
  Unavailable nodes: primary
  Available nodes: primary, render
  CRTCs: 3
  Encoders: 3
  Connectors: 3
    Connector 0: eDP-1 (connected)
      Status: connected
      Subpixel: Unknown
      Modes:
        2560x1600@60.00 (preferred)

11.2 modetest

来自 libdrm-tests,可进行 KMS 测试:

# 列出所有 KMS 对象
modetest -M i915

# 在指定 CRTC/connector 上设置模式并显示测试图案
modetest -M i915 -s <connector_id>:<mode>

# 测试所有 connector 的默认模式
modetest -a

11.3 intel_gpu_top(Intel 专用)

实时监控 Intel GPU 使用率:

intel_gpu_top

# 典型输出
intel-gpu-top: Intel Tigerlake (Gen12) @ /dev/dri/card0
  Freq:  650/ 650/ 1300 MHz   RC6: 45.3%
  IRQ:    247/s  Err:     0/s

  ENGINE      BUSY                     MI_SEMA MI_WAIT
  Render/3D   ████████░░░░░░░░ 47.8%   0%      0%
  Blitter     ░░░░░░░░░░░░░░░░  0.0%   0%      0%
  Video       ████░░░░░░░░░░░░ 24.1%   0%      0%
  VideoEnhance░░░░░░░░░░░░░░░░  0.0%   0%      0%

11.4 debugfs 接口

DRM 在 /sys/kernel/debug/dri/ 下暴露丰富的调试信息:

# 查看所有 DRM 设备的 debugfs 入口
ls /sys/kernel/debug/dri/

# i915 示例:查看 GEM 对象
cat /sys/kernel/debug/dri/0/i915_gem_objects

# 查看当前 KMS 状态
cat /sys/kernel/debug/dri/0/state

# 查看 CRTC vblank 统计
cat /sys/kernel/debug/dri/0/i915_vblank_info

# amdgpu:查看 GPU 调度器状态
cat /sys/kernel/debug/dri/1/amdgpu_sched

11.5 ftrace / perf

# 跟踪 DRM ioctl
echo 'drm:*' > /sys/kernel/debug/tracing/set_event
cat /sys/kernel/debug/tracing/trace_pipe

# 跟踪 atomic commit
echo 'drm:drm_atomic_commit' > /sys/kernel/debug/tracing/set_event

# GPU 性能事件(需要 PMU 支持)
perf stat -e '{i915/,}' -- glmark2

11.6 其他工具

工具 用途
udevadm info /dev/dri/card0 查看 DRM 设备的 udev 属性
cat /proc/dri/0/clients 查看当前打开 DRM fd 的客户端
drmstat (mesa-utils) 显示 DRM 设备统计信息
radeontop AMD GPU 实时监控(类似 intel_gpu_top)
nvtop 多厂商 GPU top(支持 i915/amdgpu/nouveau)
glxinfo | grep renderer 确认用户态驱动与 GPU 对应关系

12. DRM 设备节点:renderD 与 cardN 的区别

12.1 节点类型与编号规则

DRM 向用户态暴露两类主要字符设备节点,其编号规则由 drivers/gpu/drm/drm_drv.c 第 136-140 行中的宏决定:

/* drivers/gpu/drm/drm_drv.c:136 */
#define DRM_MINOR_LIMIT(t) ({ \
    typeof(t) _t = (t); \
    _t == DRM_MINOR_ACCEL ? XA_LIMIT(0, ACCEL_MAX_MINORS) : \
    XA_LIMIT(64 * _t, 64 * _t + 63); \
})
#define DRM_EXTENDED_MINOR_LIMIT XA_LIMIT(192, (1 << MINORBITS) - 1)

编号映射关系:

minor 类型              编号范围          设备节点示例
------------------------------------------------------------
DRM_MINOR_PRIMARY  (0)  [0,   63]        /dev/dri/card0 ~ card63
DRM_MINOR_RENDER   (2)  [128, 191]       /dev/dri/renderD128 ~ renderD191
DRM_MINOR_ACCEL    (N/A) [0, MAX]        /dev/accel/accel0   (独立 major)

超过 64 个设备时,从 192 开始动态分配(DRM_EXTENDED_MINOR_LIMIT)

12.2 权限与用途对比

+--------------------+--------------------+--------------------+
|   特性              |   card0 (primary)  |  renderD128        |
+--------------------+--------------------+--------------------+
| 需要 DRM_MASTER     | 是(KMS 操作)      | 否                 |
| 文件权限(默认)     | crw-rw---- video   | crw-rw---- render  |
| KMS ioctl          | 支持                | 不支持             |
| GEM/渲染 ioctl     | 支持                | 支持               |
| 热插拔显示器检测     | 通过此节点          | 不适用             |
| Wayland/X11 使用   | KMS 显示输出        | GPU 渲染           |
| 多进程并发          | 需要 master 协商   | 完全支持           |
+--------------------+--------------------+--------------------+

render node 的引入(Linux 3.10,2013 年)解决了以下问题:

  • 非特权进程可直接访问 GPU 渲染,无需 root 或 DRM master
  • 将显示控制(需要信任)与渲染(可沙箱化)分离
  • 容器/沙箱中的 GPU 计算无需 display 权限

12.3 设备节点创建流程

drivers/gpu/drm/drm_drv.c:142drm_minor_alloc() 函数完成设备节点的分配:

/* drivers/gpu/drm/drm_drv.c:142 */
static int drm_minor_alloc(struct drm_device *dev, enum drm_minor_type type)
{
    struct drm_minor *minor;
    int r;

    minor = drmm_kzalloc(dev, sizeof(*minor), GFP_KERNEL);
    // 在 XArray 中分配编号(原子操作,保证唯一性)
    r = xa_alloc(drm_minor_get_xa(type), &minor->index,
                 NULL, DRM_MINOR_LIMIT(type), GFP_KERNEL);
    // 若 0-63 满了,从 192 开始扩展分配
    if (r == -EBUSY && (type == DRM_MINOR_PRIMARY || type == DRM_MINOR_RENDER))
        r = xa_alloc(&drm_minors_xa, &minor->index,
                     NULL, DRM_EXTENDED_MINOR_LIMIT, GFP_KERNEL);
    ...
    // 创建 sysfs 节点(/dev/dri/cardN 或 /dev/dri/renderDN)
    minor->kdev = drm_sysfs_minor_alloc(minor);
}

注册时序:

drm_dev_register(dev)
    |
    +-- drm_minor_alloc(DRM_MINOR_PRIMARY)   card0 节点
    |
    +-- drm_minor_alloc(DRM_MINOR_RENDER)    renderD128 节点
    |
    +-- drm_minor_register()                 device_add() 激活 udev 事件
    |   --> udevd 创建 /dev/dri/cardN
    |   --> udevd 创建 /dev/dri/renderDN
    |
    +-- drm_debugfs_register()               /sys/kernel/debug/dri/N/

13. DRM MM 内存范围管理器

13.1 概述

drm_mm 是 DRM 内部用于管理 GPU 地址空间的通用范围分配器,定义在 drivers/gpu/drm/drm_mm.cinclude/drm/drm_mm.h(第 1-100 行注释)。

与 Linux 通用内存分配器(buddy allocator/slab)不同,drm_mm 专门用于管理:

  • GPU 虚拟地址空间(GTT/PPGTT)
  • VRAM 物理地址范围
  • 命令缓冲区的地址空间窗口

它不分配物理内存,只负责地址范围的划分与跟踪。

13.2 核心数据结构

文件: include/drm/drm_mm.h(第 157-243 行)

struct drm_mm_node {
    unsigned long color;   // 驱动私有标签(用于颜色调整回调)
    u64 start;             // 分配块起始地址
    u64 size;              // 分配块大小

    /* 内部字段(不透明) */
    struct drm_mm    *mm;
    struct list_head  node_list;      // 所有节点的有序链表
    struct list_head  hole_stack;     // 空洞栈(LRU 逐出优化)
    struct rb_node    rb;             // 区间树节点(按地址快速查找)
    struct rb_node    rb_hole_size;   // 按空洞大小排序的红黑树
    struct rb_node    rb_hole_addr;   // 按空洞地址排序的红黑树
    u64 __subtree_last;
    u64 hole_size;         // 该节点后方空洞大小(0 表示无空洞)
    u64 subtree_max_hole;  // 子树中最大空洞(辅助快速查找)
};

struct drm_mm {
    // 可选的颜色调整回调(如 i915 用于插入保护页)
    void (*color_adjust)(const struct drm_mm_node *node,
                         unsigned long color,
                         u64 *start, u64 *end);

    /* 内部字段(不透明) */
    struct list_head  hole_stack;        // 最近释放的空洞栈
    struct drm_mm_node head_node;        // 哨兵头节点
    struct rb_root_cached interval_tree; // 按地址的区间树
    struct rb_root_cached holes_size;    // 按大小排序的空洞树
    struct rb_root    holes_addr;        // 按地址排序的空洞树
    unsigned long     scan_active;       // 是否正在扫描(驱逐)
};

13.3 分配模式(drm_mm_insert_mode)

文件: include/drm/drm_mm.h(第 70-146 行)

enum drm_mm_insert_mode {
    DRM_MM_INSERT_BEST    = 0,  // 最小适合(减少碎片)
    DRM_MM_INSERT_LOW,           // 最低地址(从低址向上分配)
    DRM_MM_INSERT_HIGH,          // 最高地址(从高址向下分配)
    DRM_MM_INSERT_EVICT,         // 最近逐出的空洞(与扫描器配合)
    DRM_MM_INSERT_ONCE  = BIT(31), // 只检查第一个空洞,失败即报错

    // 快捷组合:
    DRM_MM_INSERT_HIGHEST = DRM_MM_INSERT_HIGH | DRM_MM_INSERT_ONCE,
    DRM_MM_INSERT_LOWEST  = DRM_MM_INSERT_LOW  | DRM_MM_INSERT_ONCE,
};

分配策略选择示意:

地址空间示意图(低地址在左,高地址在右):

[已分配] [  空洞A  ] [已分配] [   空洞B   ] [已分配]
   0                                          MAX

INSERT_BEST:   选择最小能容纳请求大小的空洞(减少碎片)
INSERT_LOW:    选择地址最低的合适空洞(紧凑向低址排列)
INSERT_HIGH:   选择地址最高的合适空洞(从高址向低址填充)
INSERT_EVICT:  配合 drm_mm_scan,选择刚刚被驱逐出的空洞

13.4 API 使用方式

文件: include/drm/drm_mm.h(第 406-467 行)

/* 初始化管理器,管理 [start, start+size) 地址范围 */
void drm_mm_init(struct drm_mm *mm, u64 start, u64 size);
/* drivers/gpu/drm/drm_mm.c 中实现,head_node 作为哨兵 */

/* 释放管理器(必须确保所有节点已被 remove) */
void drm_mm_takedown(struct drm_mm *mm);

/* 分配:搜索并插入节点(最简版本,无范围限制) */
static inline int drm_mm_insert_node(struct drm_mm *mm,
                                     struct drm_mm_node *node,
                                     u64 size);
/* 等价于调用 drm_mm_insert_node_generic(mm, node, size, 0, 0, 0) */

/* 分配:带对齐、颜色标签和搜索模式 */
static inline int drm_mm_insert_node_generic(struct drm_mm *mm,
                                             struct drm_mm_node *node,
                                             u64 size, u64 alignment,
                                             unsigned long color,
                                             enum drm_mm_insert_mode mode);
/* 等价于调用 drm_mm_insert_node_in_range(mm, node, size, alignment,
                                          color, 0, U64_MAX, mode) */

/* 分配:带地址范围约束(最完整版本) */
int drm_mm_insert_node_in_range(struct drm_mm *mm,
                                struct drm_mm_node *node,
                                u64 size, u64 alignment,
                                unsigned long color,
                                u64 start, u64 end,
                                enum drm_mm_insert_mode mode);

/* 预留:将已知地址范围标记为已占用(固件预留等场景) */
int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node);
/* node->start 和 node->size 必须预先设好 */

/* 释放节点(O(1) 操作) */
void drm_mm_remove_node(struct drm_mm_node *node);

/* 检查是否干净(无已分配节点) */
static inline bool drm_mm_clean(const struct drm_mm *mm);

13.5 内部实现:多树结构

drm_mm 同时维护三个搜索树以支持不同查询模式(drm_mm.c:150-244):

drm_mm 内部数据结构布局

  node_list(有序链表):
  [head] <-> [node@0x1000] <-> [node@0x3000] <-> [node@0x6000]
              hole:0x1000         hole:0x2000
              size:0x2000         size:0x1000

  interval_tree(区间树,按 start 排序):
  用于 drm_mm_interval_first() -- 快速查找覆盖某地址的节点

  holes_size(红黑树,按空洞大小排序):
  用于 INSERT_BEST 模式 -- O(log n) 找到最小合适空洞

  holes_addr(红黑树,按空洞地址排序,增广树存储子树最大空洞):
  用于 INSERT_LOW/HIGH 模式 -- O(log n) 按地址方向搜索

  hole_stack(链表,LIFO 顺序):
  用于 INSERT_EVICT 模式 -- 重用最近释放的地址段

13.6 驱逐(Eviction)扫描

当地址空间不足时,需要驱逐已有节点腾出空间。drm_mm_scan 提供协助:

/* 典型驱逐流程 */
struct drm_mm_scan scan;

/* 1. 初始化扫描器,指定需要的空间大小 */
drm_mm_scan_init(&scan, mm, size, alignment, color, DRM_MM_INSERT_LOW);

/* 2. 遍历候选节点,标记可驱逐的 */
list_for_each_entry(node, &lru_list, lru_node) {
    if (drm_mm_scan_add_block(&scan, node))
        break;  // 找到足够空间
}

/* 3. 驱逐标记的节点(迁移到系统内存或丢弃) */
list_for_each_entry_safe(node, tmp, &evict_list, evict_node) {
    evict_bo(node);
    drm_mm_remove_node(node);
}

/* 4. 用 INSERT_EVICT 模式分配(重用刚释放的地址段) */
drm_mm_insert_node_generic(mm, new_node, size, alignment, 0,
                            DRM_MM_INSERT_EVICT);

/* 5. 将非驱逐节点归还 */
drm_mm_scan_remove_block(&scan, node);

14. dma-fence 同步机制深度分析

14.1 dma_fence 基础

文件: drivers/dma-buf/dma-fence.c(第 1-66 行)

dma_fence 是 Linux 内核中跨驱动、跨设备异步操作同步的核心原语,由 Rob Clark(MSM/freedreno)和 Maarten Lankhorst(Canonical)于 2012 年引入。

struct dma_fence {
    spinlock_t            *lock;       // 保护 flags 和 cb_list 的自旋锁
    const struct dma_fence_ops *ops;   // 操作表
    struct rcu_head        rcu;        // RCU 安全释放
    struct list_head       cb_list;    // 信号时触发的回调列表
    u64                    context;    // fence 上下文 ID(由 dma_fence_context_alloc() 分配)
    u64                    seqno;      // 在该 context 中的序列号(单调递增)
    unsigned long          flags;      // DMA_FENCE_FLAG_* 位
    ktime_t                timestamp;  // 信号时间戳
    int                    error;      // 错误码(0 = 成功)
};

关键 flags 位:

  • DMA_FENCE_FLAG_SIGNALED_BIT:fence 已完成(不可逆)
  • DMA_FENCE_FLAG_TIMESTAMP_BIT:时间戳有效
  • DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT:硬件信号已启用

14.2 fence 上下文分配

文件: drivers/dma-buf/dma-fence.c(第 185-190 行)

/* 全局原子计数器,保证 context 全局唯一 */
static atomic64_t dma_fence_context_counter = ATOMIC64_INIT(1);

u64 dma_fence_context_alloc(unsigned num)
{
    WARN_ON(!num);
    return atomic64_fetch_add(num, &dma_fence_context_counter);
}

上下文规则:同一 context 内的 fence 按 seqno 完全有序;不同 context 间没有顺序保证。这允许内核快速判断两个 fence 是否有依赖关系。

14.3 fence 信号机制

文件: drivers/dma-buf/dma-fence.c(第 362-385 行)

void dma_fence_signal_timestamp_locked(struct dma_fence *fence,
                                       ktime_t timestamp)
{
    struct dma_fence_cb *cur, *tmp;
    struct list_head cb_list;

    lockdep_assert_held(fence->lock);

    /* 原子设置 SIGNALED 位(CAS 操作,确保只信号一次) */
    if (unlikely(test_and_set_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
                                  &fence->flags)))
        return;

    /* 将 cb_list 替换为时间戳(节省内存,cb_list 信号后不再需要) */
    list_replace(&fence->cb_list, &cb_list);
    fence->timestamp = timestamp;
    set_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags);
    trace_dma_fence_signaled(fence);

    /* 触发所有注册的回调 */
    list_for_each_entry_safe(cur, tmp, &cb_list, node) {
        INIT_LIST_HEAD(&cur->node);
        cur->func(fence, cur);  // 在 fence->lock 持有期间调用
    }
}

14.4 三种 fence 使用模式

文件: drivers/dma-buf/dma-fence.c(第 39-66 行文档注释)

模式 1: sync_file(显式 fence,跨进程)
  sync_file_create(fence)
      |
      v
  fd(可通过 SCM_RIGHTS 传给其他进程/设备)
      |
      v
  接收方: sync_file_get_fence(fd)
          dma_fence_wait(fence, ...)
  应用场景: Android SurfaceFlinger, Vulkan explicit sync

模式 2: drm_syncobj(可更新的 fence 容器)
  DRM_IOCTL_SYNCOBJ_CREATE
      |
      v
  drm_syncobj(用户态通过 handle 引用)
      |
      +-- 可以绑定/替换内部 fence(timeline 支持链式 fence)
      +-- 用于 Vulkan fence / semaphore 实现
  应用场景: Vulkan, OpenGL ES fence sync

模式 3: dma_resv(隐式 fence,随 BO 流动)
  drm_gem_object.resv (struct dma_resv)
      |
      +-- excl fence: 当前写者(唯一)
      +-- shared fences[]: 当前读者列表
  读取/修改 BO 前自动等待前置 fence
  应用场景: 默认 GEM/PRIME 同步路径

14.5 drm_sched_fence:调度器 fence

文件: drivers/gpu/drm/scheduler/sched_fence.c

每个 drm_sched_job 包含一个 drm_sched_fence,其内部有两个 dma_fence

drm_sched_fence
    |
    +-- scheduled (dma_fence)  -- job 被提交到硬件时信号
    |                              父 fence 指向 hw_fence(硬件完成)
    |
    +-- finished (dma_fence)   -- job 完全完成(包括错误处理)
    |
    +-- parent (dma_fence *)   -- 关联的硬件 fence(由 run_job() 返回)

信号流程(sched_fence.c:65-85):

/* 任务提交到硬件时 */
void drm_sched_fence_scheduled(struct drm_sched_fence *fence,
                                struct dma_fence *parent)
{
    if (!IS_ERR_OR_NULL(parent))
        drm_sched_fence_set_parent(fence, parent);  // 关联 hw_fence
    dma_fence_signal(&fence->scheduled);            // 通知等待方
}

/* 任务完成时(hw_fence 信号后,调度器 worker 调用) */
void drm_sched_fence_finished(struct drm_sched_fence *fence, int result)
{
    if (result)
        dma_fence_set_error(&fence->finished, result);
    dma_fence_signal(&fence->finished);  // 触发所有 cb
}

14.6 lockdep 注解

文件: drivers/dma-buf/dma-fence.c(第 192-344 行)

为了防止 dma_fence_wait()dma_fence_signal() 之间的死锁,内核提供了专门的 lockdep 注解机制:

/* 任何可能调用 dma_fence_signal() 的代码路径必须用此注解包围 */
bool cookie = dma_fence_begin_signalling();
    /* ... 此区间内不能 acquire 已在 fence_wait 持有的锁 ... */
    dma_fence_signal(fence);
dma_fence_end_signalling(cookie);

关键约束(dma-fence.c:72-112):

  • fence 完成路径不能在 GFP_KERNEL 分配内存(可能触发 shrinker)
  • fence 完成路径不能在 GFP_NOFS/GFP_NOIO 上下文分配(mmu_notifier 约束)
  • fence 等待期间可以持有 dma_resv_lock(),因此完成路径不能反向获取该锁

15. KMS 对象模型:Encoder、Bridge 与 Connector 热插拔

15.1 KMS 对象继承体系

所有 KMS 对象都继承自 drm_mode_objectinclude/drm/drm_mode_object.h),通过统一的 ID 命名空间管理:

drm_mode_object (基类)
    |-- id:       对象 ID(用户态通过 ioctl 使用)
    |-- type:     DRM_MODE_OBJECT_CRTC/ENCODER/CONNECTOR/PLANE/...
    |-- properties: 附加属性数组(key-value blob)
    |-- refcount: 引用计数(connector 是唯一的热插拔对象)
    |
    +-- drm_crtc       (CRTC)
    +-- drm_encoder    (Encoder)
    +-- drm_connector  (Connector)
    +-- drm_plane      (Plane)
    +-- drm_framebuffer (Framebuffer)
    +-- drm_property_blob (Blob 属性)

15.2 Encoder 深度解析

drm_encoder 代表将 CRTC 的像素时序数据转换为特定物理信号格式(TMDS/LVDS/eDP 等)的硬件模块。

struct drm_encoder {
    struct drm_device  *dev;
    struct list_head    head;      // 加入 mode_config.encoder_list
    struct drm_mode_object base;   // 继承基类

    const char *name;              // 如 "TMDS-0"

    /* 能力掩码:哪些 CRTC 可以驱动此 encoder */
    uint32_t possible_crtcs;      // bitmask,bit N 对应 crtc index N
    /* 哪些 encoder 可以与此 encoder 同时使用(克隆) */
    uint32_t possible_clones;     // bitmask

    struct drm_crtc *crtc;        // 当前连接的 CRTC(legacy 状态)

    /* Bridge 链表头(encoder 输出接 bridge) */
    struct drm_bridge *bridge;

    const struct drm_encoder_funcs        *funcs;
    const struct drm_encoder_helper_funcs *helper_private;
};

15.3 Bridge 链详解

文件: drivers/gpu/drm/drm_bridge.c(第 43-100 行)

Bridge 是 DRM 对"信号转换器"硬件的抽象,解决了 encoder 不足以表达整个信号链的问题。典型场景:

SoC (encoder) --> MIPI DSI bridge IC --> HDMI transmitter --> HDMI connector

代码层面的链:

  drm_encoder
      |
      v (encoder->bridge)
  drm_bridge (MIPI-DSI-to-HDMI converter)
      |
      v (bridge->next)
  drm_bridge (HDMI transmitter)
      |
      v (bridge->connector)
  drm_connector (HDMI-A-1)

Bridge 操作表(include/drm/drm_bridge.h):

struct drm_bridge_funcs {
    /* 初始化:attach 到 encoder 时调用 */
    int  (*attach)(bridge, encoder, flags);
    void (*detach)(bridge);

    /* 模式验证 */
    enum drm_mode_status (*mode_valid)(bridge, info, mode);

    /* 原子 KMS 操作(现代驱动推荐使用) */
    void (*atomic_pre_enable)(bridge, old_state);    // 上电前配置
    void (*atomic_enable)(bridge, old_state);        // 使能信号输出
    void (*atomic_disable)(bridge, old_state);       // 关闭信号输出
    void (*atomic_post_disable)(bridge, old_state);  // 下电后清理

    /* EDID/modes(可选,bridge 充当 sink) */
    const struct drm_edid *(*edid_read)(bridge, connector);
    int  (*get_modes)(bridge, connector);

    /* 热插拔检测 */
    enum drm_connector_status (*detect)(bridge);
    void (*hpd_enable)(bridge);
    void (*hpd_disable)(bridge);
    void (*hpd_notify)(bridge, status);
};

Bridge 链的遍历顺序(drm_bridge.c:83-96):

enable 顺序(从 encoder 到 display):
  encoder --> bridge_A.pre_enable --> bridge_B.pre_enable
  encoder --> bridge_A.enable    --> bridge_B.enable

disable 顺序(从 display 到 encoder):
  bridge_B.disable     --> bridge_A.disable
  bridge_B.post_disable --> bridge_A.post_disable

15.4 Connector 热插拔处理

文件: drivers/gpu/drm/drm_connector.c(第 46-74 行)

Connector 是 KMS 对象中唯一在运行时可热插拔的对象。

连接器类型枚举(drm_connector.c:93-115

static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
    { DRM_MODE_CONNECTOR_Unknown,      "Unknown"   },
    { DRM_MODE_CONNECTOR_VGA,          "VGA"       },
    { DRM_MODE_CONNECTOR_DVII,         "DVI-I"     },
    { DRM_MODE_CONNECTOR_DVID,         "DVI-D"     },
    { DRM_MODE_CONNECTOR_DisplayPort,  "DP"        },
    { DRM_MODE_CONNECTOR_HDMIA,        "HDMI-A"    },
    { DRM_MODE_CONNECTOR_eDP,          "eDP"       },
    { DRM_MODE_CONNECTOR_DSI,          "DSI"       },
    { DRM_MODE_CONNECTOR_WRITEBACK,    "Writeback" },
    ...
};

连接状态机

drm_connector_status 三种状态:

  connector_status_connected    = 1  // 检测到设备
  connector_status_disconnected = 2  // 未连接
  connector_status_unknown      = 3  // 无法确定(如 DVI 无热插拔针)

  +------------------+   hpd_irq    +-------------------+
  |   disconnected   |------------>|    connected       |
  +------------------+             +-------------------+
           ^                               |
           |         unplug               |
           +-------------------------------+
           |     detect() = disconnected  |
           |                              |
           |   detect() 调用时序:         |
           |   drm_kms_helper_poll_work() -- 轮询(无硬件 HPD 中断时)
           |   drm_helper_hpd_irq_event() -- 中断驱动(HPD 引脚)

热插拔中断处理流程

硬件 HPD 中断
    |
    v
驱动 ISR(如 intel_dp_hpd_pulse())
    |
    v
drm_helper_hpd_irq_event(dev)     -- drivers/gpu/drm/drm_probe_helper.c
    |
    v
对每个 connector 调用 .detect()
    |
    v
若状态变化:
    drm_kms_helper_hotplug_event(dev)
        |
        v
    drm_client_hotplug(client)     -- 通知 fbdev/Wayland 合成器
        |
        +-- uevent: "HOTPLUG=1"    -- udev 事件发往用户态

EDID 读取与模式枚举(drm_probe_helper.c:100-143

drm_helper_probe_single_connector_modes(connector, maxX, maxY)
    |
    +-- 1. connector_funcs.get_modes()   -- 驱动从硬件读取 EDID
    |          |
    |          v
    |       drm_edid_read()              -- I2C DDC 读取 128/256 字节
    |       drm_edid_connector_update()  -- 解析 EDID,更新 display_info
    |       drm_edid_connector_add_modes() -- 从 EDID 构建 drm_display_mode 列表
    |
    +-- 2. drm_helper_probe_add_cmdline_mode() -- 处理 video= 内核参数
    |
    +-- 3. drm_mode_validate_pipeline()  -- 验证每个 mode 对于 encoder/crtc 是否可行
    |          |
    |          +-- connector_mode_valid()
    |          +-- encoder_mode_valid()
    |          +-- bridge_chain_mode_valid()
    |          +-- crtc_mode_valid()
    |
    +-- 4. 更新 connector.modes 列表(保留有效 mode,删除无效 mode)

16. 原子提交流程源码级详解

16.1 drm_atomic_commit() 总览

文件: drivers/gpu/drm/drm_atomic_helper.c

原子提交的核心是"先验证,后执行"。整个流程分为两大阶段:

Phase 1: CHECK(状态验证)
    drm_atomic_helper_check(dev, state)
        |
        +-- drm_atomic_helper_check_modeset()    // 管线合法性检查
        |       |
        |       +-- 遍历每个 connector/encoder/crtc 的状态变化
        |       +-- 检查 encoder/crtc 是否兼容
        |       +-- bridge_chain 验证
        |
        +-- drm_atomic_helper_check_planes()     // plane 状态验证
                |
                +-- 每个 plane 调用 plane_helper.atomic_check()
                +-- 检查格式、旋转、缩放比例、对齐限制

Phase 2: COMMIT(状态应用)
    drm_atomic_helper_commit(dev, state, nonblock)
        |
        +-- drm_atomic_helper_prepare_planes()   // pin BO(防止被换出)
        |
        +-- drm_atomic_helper_swap_state()       // 原子切换新旧状态指针
        |
        +-- schedule_work(commit_work)           // 异步提交到工作队列
                |
                v
            commit_tail (工作队列线程)
                |
                +-- drm_atomic_helper_commit_modeset_disables()
                |   -- disable 需要关闭的 encoder/bridge/crtc
                |
                +-- drm_atomic_helper_commit_planes()
                |   -- update_plane() 写入 plane 寄存器
                |
                +-- drm_atomic_helper_commit_modeset_enables()
                |   -- enable 新激活的 crtc/bridge/encoder
                |
                +-- drm_atomic_helper_fake_vblank()     // 仿真 vblank(如 vkms)
                |
                +-- drm_atomic_helper_commit_hw_done()
                |   -- 标记 crtc_commit.hw_done = complete
                |
                +-- 等待 vblank
                |   -- drm_crtc_wait_one_vblank()
                |
                +-- drm_atomic_helper_cleanup_planes()
                    -- unpin 旧 BO,完成 crtc_commit.flip_done

16.2 drm_atomic_helper_swap_state() 的原子性保证

这是原子提交的核心——状态切换必须对 vblank 中断处理程序原子可见:

/* drivers/gpu/drm/drm_atomic_helper.c */
int drm_atomic_helper_swap_state(struct drm_atomic_state *state, bool stall)
{
    // stall=true:等待所有 pending commit 完成后再切换
    if (stall) {
        for_each_old_crtc_in_state(state, crtc, old_crtc_state, i)
            drm_crtc_commit_wait(old_crtc_state->commit);
    }

    // 将 state->crtcs[].new_state 与 crtc->state 互换
    // 将 state->planes[].new_state 与 plane->state 互换
    // 将 state->connectors[].new_state 与 connector->state 互换
    // 这些交换是通过指针赋值完成的,非原子但持有 modeset 锁
}

16.3 vblank 与 commit 同步

                时序图
                -------

CPU(提交线程)              GPU/显示硬件              vblank 中断

atomic_commit()
    |
    v
写硬件寄存器(plane addr)
    |
    v                           显示硬件扫描帧 N-1
hw_done.complete()
    |
    v                           ... 扫描到帧末 ...
                                                    vblank 中断!
                                                         |
                                                    drm_handle_vblank()
                                                         |
                                                    page_flip_complete()
                                                         |
                                                    flip_done.complete()
                                                         |
                                显示硬件扫描帧 N(新 FB)

等待 flip_done
    |
    v
cleanup_planes()
(unpin 旧 BO)
    |
    v
drm_send_event()  --> 用户态 vblank event(fd 可读)

16.4 非阻塞提交(nonblocking commit)

DRM_MODE_ATOMIC_NONBLOCK 标志传入时,drm_atomic_helper_commit() 立即返回,提交工作在内核工作队列中异步完成:

用户态                     内核
  |                          |
  | DRM_IOCTL_MODE_ATOMIC    |
  | (NONBLOCK)               |
  |------------------------->|
  |                          | commit_work = alloc_work(commit_tail)
  |<-------------------------|
  |  (立即返回,fd 可读时有   | queue_work(dev->mode_config.wq)
  |   vblank event)          |
  |                          |        ...异步执行 commit_tail...
  |  select/poll(card0 fd)  |
  |<------------------------ - - - - vblank event 就绪
  |  read() vblank event    |

drm_pending_vblank_event 结构包含用户态设置的 user_data 指针,可用于将回调与具体帧关联。


17. DRM fbdev 兼容层

17.1 为什么需要 fbdev 兼容

fbdev(framebuffer device)是 Linux 的老式显示接口(/dev/fb0),许多工具(Plymouth、内核崩溃转储、虚拟终端)仍依赖它。现代 DRM 驱动通过 drm_fb_helper 模拟 fbdev,避免每个驱动单独实现。

文件: drivers/gpu/drm/drm_fb_helper.c(第 47-113 行)

17.2 fbdev 模拟架构

/dev/fb0 (fbdev 接口)
    |
    v
struct fb_ops  (drm_fbdev_shmem_fb_ops 等)
    |
    v
struct drm_fb_helper
    |
    +-- struct drm_client (KMS 客户端接口)
    |       |
    |       v
    |   drm_client_modeset_commit()  -- 通过 atomic commit 设置模式
    |
    +-- struct drm_framebuffer *fb   -- 当前 fbdev 使用的 FB
    |
    +-- struct drm_client_buffer *buffer -- GEM BO 后备存储
    |
    +-- dirty_work (工作队列)       -- 延迟刷新脏区域

17.3 三种 fbdev 后端

根据 GEM 后备存储类型,DRM 提供三种 fbdev 实现:

文件 后备存储 适用驱动
drm_fbdev_shmem.c shmem(可换出的系统内存) panfrost、lima、vkms 等
drm_fbdev_dma.c 连续 DMA 内存(物理连续) vc4、etnaviv、simple 等
drm_fbdev_ttm.c TTM 管理的内存 amdgpu、nouveau、虚拟驱动等

17.4 drm_fbdev_shmem 实现分析

文件: drivers/gpu/drm/drm_fbdev_shmem.c

/* 核心 fb_ops 定义 */
static const struct fb_ops drm_fbdev_shmem_fb_ops = {
    .owner     = THIS_MODULE,
    .fb_open   = drm_fbdev_shmem_fb_open,
    .fb_release = drm_fbdev_shmem_fb_release,
    /* 使用延迟 IO 模拟(写入触发 dirty_work) */
    __FB_DEFAULT_DEFERRED_OPS_RDWR(drm_fbdev_shmem),
    /* DRM 标准 ops(setcmap, cursor, pan_display 等) */
    DRM_FB_HELPER_DEFAULT_OPS,
    __FB_DEFAULT_DEFERRED_OPS_DRAW(drm_fbdev_shmem),
    .fb_mmap   = drm_fbdev_shmem_fb_mmap,
    .fb_destroy = drm_fbdev_shmem_fb_destroy,
};

mmap 实现细节:

static int drm_fbdev_shmem_fb_mmap(struct fb_info *info,
                                    struct vm_area_struct *vma)
{
    struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);

    /* Write-Combining 映射提升 CPU 写性能(避免 cache 失效)*/
    if (shmem->map_wc)
        vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

    return fb_deferred_io_mmap(info, vma);
}

17.5 fbdev 热插拔响应

文件: drivers/gpu/drm/drm_fb_helper.c(第 115-170 行)

/* 热插拔时恢复 fbdev 显示模式 */
static int __drm_fb_helper_restore_fbdev_mode_unlocked(
    struct drm_fb_helper *fb_helper, bool force)
{
    if (!drm_fbdev_emulation || !fb_helper)
        return -ENODEV;

    mutex_lock(&fb_helper->lock);
    if (force)
        /* 强制恢复:不等待 DRM master 释放,用于 VT 切换 */
        ret = drm_client_modeset_commit_locked(&fb_helper->client);
    else
        ret = drm_client_modeset_commit(&fb_helper->client);

    /* 若热插拔期间有延迟事件,在此补发 */
    if (fb_helper->delayed_hotplug)
        drm_fb_helper_hotplug_event(fb_helper);
    ...
}

17.6 fbdev 与 Wayland/X11 的共存

现代 Linux 系统中,fbdev 兼容层的角色已退化:

引导阶段:
  Plymouth(开机动画)使用 /dev/fb0 或直接 DRM 渲染

VT(虚拟终端)阶段:
  fbcon 内核控制台通过 /dev/fb0 输出文字

图形会话阶段:
  Wayland compositor(如 Sway/GNOME)直接使用 DRM card0
  此时 fbdev 进入 DPMS off 状态

KMS master 切换机制:
  VT 切换时 drm_fb_helper_restore_fbdev_mode_unlocked() 被调用
  Wayland compositor 感知 DRM_EVENT_FLIP_COMPLETE 和 vblank event

模块参数控制 fbdev 行为(drm_fb_helper.c:47-56):

# 完全禁用 fbdev 模拟(适合纯 Wayland 系统)
modprobe drm_kms_helper fbdev_emulation=0

# 过分配 fbdev 缓冲(保留额外内存用于双缓冲)
# CONFIG_DRM_FBDEV_OVERALLOC=100 (默认 100%)

18. DRM 驱动注册与设备节点创建

18.1 驱动注册完整流程

文件: drivers/gpu/drm/drm_drv.c(第 264-397 行)

内核文档注释中给出了标准 DRM 显示驱动的 probe/remove 模式:

/* 典型 platform 驱动 probe 函数 */
static int driver_probe(struct platform_device *pdev)
{
    struct driver_device *priv;
    struct drm_device *drm;

    /* 1. 分配 drm_device(嵌入到驱动私有结构中)
          devm_ 表示随 platform_device 自动释放 */
    priv = devm_drm_dev_alloc(&pdev->dev, &driver_drm_driver,
                              struct driver_device, drm);
    drm = &priv->drm;

    /* 2. 初始化 mode_config(KMS 状态容器)*/
    drmm_mode_config_init(drm);

    /* 3. 初始化 KMS 对象(planes/crtcs/encoders/connectors)*/
    driver_modeset_init(drm);

    /* 4. 重置所有 KMS 状态到初始值 */
    drm_mode_config_reset(drm);

    /* 5. 注册设备(创建 /dev/dri/cardN 等节点)*/
    drm_dev_register(drm, 0);

    /* 6. 设置 fbdev 模拟(如需要)*/
    drm_fbdev_shmem_setup(drm, 32);

    return 0;
}

static int driver_remove(struct platform_device *pdev)
{
    struct drm_device *drm = platform_get_drvdata(pdev);

    /* 注销设备(停止新访问)*/
    drm_dev_unregister(drm);

    /* 原子关闭所有显示输出 */
    drm_atomic_helper_shutdown(drm);

    return 0;
}

18.2 minor 设备节点的 XArray 管理

文件: drivers/gpu/drm/drm_drv.c(第 64-99 行)

/* 全局 XArray:minor_index -> drm_minor* 的映射 */
DEFINE_XARRAY_ALLOC(drm_minors_xa);

/* 根据 minor 类型获取对应的 XArray */
static struct xarray *drm_minor_get_xa(enum drm_minor_type type)
{
    if (type == DRM_MINOR_PRIMARY || type == DRM_MINOR_RENDER)
        return &drm_minors_xa;
#if IS_ENABLED(CONFIG_DRM_ACCEL)
    else if (type == DRM_MINOR_ACCEL)
        return &accel_minors_xa;  // accel 使用独立的 XArray
#endif
    else
        return ERR_PTR(-EOPNOTSUPP);
}

注册与注销的原子性(drm_drv.c:174-228):

static int drm_minor_register(struct drm_device *dev, enum drm_minor_type type)
{
    /* 调用 device_add() 触发 udev 创建设备节点 */
    ret = device_add(minor->kdev);

    /* XArray store:NULL -> minor*(此后用户态 open() 可成功)*/
    entry = xa_store(drm_minor_get_xa(type), minor->index, minor, GFP_KERNEL);
}

static void drm_minor_unregister(struct drm_device *dev, enum drm_minor_type type)
{
    /* 先将 XArray 条目置 NULL(新的 open() 将失败)*/
    xa_store(drm_minor_get_xa(type), minor->index, NULL, GFP_KERNEL);

    /* 再调用 device_del() 触发 udev 删除事件 */
    device_del(minor->kdev);
}

18.3 drm_dev_enter/exit:防止访问已拔出设备

/* 进入设备受保护区域(使用 SRCU 读锁)*/
int drm_dev_enter(struct drm_device *dev, int *idx)
{
    *idx = srcu_read_lock(&drm_unplug_srcu);
    if (drm_dev_is_unplugged(dev)) {
        srcu_read_unlock(&drm_unplug_srcu, *idx);
        return false;
    }
    return true;
}

void drm_dev_exit(int idx)
{
    srcu_read_unlock(&drm_unplug_srcu, idx);
}

使用模式:

if (drm_dev_enter(dev, &idx)) {
    /* 访问硬件寄存器(安全,设备不会在此期间消失)*/
    writel(value, mmio_base + REGISTER);
    drm_dev_exit(idx);
}

19. GEM 对象生命周期源码追踪

19.1 完整生命周期状态图

                    +--------------------+
                    |   未创建状态        |
                    +--------------------+
                             |
                  drm_gem_object_init() / drm_gem_object_init_with_mnt()
                             |
                    +--------------------+
                    |   已初始化          |
                    | refcount = 1       |
                    | handle_count = 0  |
                    +--------------------+
                             |
               drm_gem_handle_create(file, obj, &handle)
                             |
                    +--------------------+
                    |   已绑定 handle     |
                    | refcount = 2       |
                    | handle_count = 1  |
                    +--------------------+
                     /               \
      flink (global name)          PRIME export
    drm_gem_flink()               drm_gem_prime_export()
       name != 0                    obj->dma_buf != NULL
                     \               /
                    +--------------------+
                    |   共享中            |
                    | (可被其他进程引用)   |
                    +--------------------+
                             |
            drm_gem_handle_delete() / close(fd)
                             |
                    +--------------------+
                    |   handle 已释放     |
                    | handle_count = 0  |
                    | refcount = 1       |
                    +--------------------+
                             |
                   drm_gem_object_put()
                             |
                    +--------------------+
                    |   refcount == 0    |
                    +--------------------+
                             |
                    funcs->free(obj)
                             |
                    +--------------------+
                    |   已销毁            |
                    +--------------------+

19.2 handle_count 与 refcount 的区别

refcount (kref):
  - 内核内部引用(任何持有指针的地方)
  - GEM handle 创建时 +1(通过 drm_gem_object_get())
  - PRIME attach 时 +1
  - dma_resv fence 等待时 +1
  - refcount=0 时调用 funcs->free()

handle_count:
  - 仅计算用户态 handle(每个 drm_file 的 object_idr 条目)
  - handle_count=0 时清除 global name(flink)
  - handle_count 是 refcount 的子集(handle 存在意味着至少一个 ref)

19.3 dma_resv 预留锁

每个 drm_gem_object 都关联一个 dma_resvinclude/linux/dma-resv.h),用于跟踪该 BO 上的 fence:

dma_resv(预留对象):

  excl_fence:     当前对 BO 具有独占写权限的 fence(唯一)
                  GPU 渲染命令的 finished fence 设置于此

  shared_fences[]: 当前对 BO 具有共享读权限的 fence 数组
                   显示扫描(KMS plane)的 fence 添加于此

操作接口:
  dma_resv_lock(resv, ctx)     -- 排它锁(修改 excl_fence)
  dma_resv_lock_shared(resv, ctx) -- 共享锁(添加 shared fence)
  dma_resv_wait_timeout()      -- 等待所有关联 fence 完成
  dma_resv_copy_fences()       -- PRIME 跨设备复制 fence

PRIME 时的 fence 传播:
  GPU-A 渲染完成 --> excl_fence 设置到 BO.resv
  BO 通过 dma-buf 传给 GPU-B
  GPU-B 在 dma_buf_map_attachment() 时等待 excl_fence
  -- 这就是隐式同步(implicit fencing)

19.4 LRU 内存压力回收

文件: include/drm/drm_gem.h(第 283 行,lru_nodelru 字段)

DRM 通过 drm_gem_lru 实现内存压力下的 BO 驱逐:

drm_gem_lru(每个 GPU 内存区域一个):
  lru_lock      -- 保护链表
  mem_avail     -- 可用内存字节数(由驱动维护)
  list          -- LRU 链表(tail=最近使用,head=最久未使用)

内存压力时的驱逐流程:

  shrinker_count() -- 报告可回收的对象数量
      |
      v
  shrinker_scan() -- 从 LRU 头部选取对象
      |
      v
  funcs->evict(obj) -- 驱动将 BO 从 VRAM 迁移到系统内存
      |
      v
  drm_gem_lru_remove(obj) -- 从 LRU 链表移除

20. DRM 调度器:源码级深度分析

20.1 调度策略

文件: drivers/gpu/drm/scheduler/sched_main.c(第 87-94 行)

/* 运行时可通过内核参数调整 */
int drm_sched_policy = DRM_SCHED_POLICY_FIFO;
MODULE_PARM_DESC(sched_policy,
    "Specify the scheduling policy for entities on a run-queue, "
    "RR = Round Robin, FIFO = FIFO (default).");
module_param_named(sched_policy, drm_sched_policy, int, 0444);

两种策略对比:

FIFO(先进先出):
  每个 entity 的任务按提交顺序执行
  同优先级 entity 之间按时间顺序轮换
  公平性好,适合混合工作负载

Round Robin(轮询):
  强制在同优先级 entity 之间轮换,不论任务数量
  防止一个 entity 的大量任务饿死其他 entity
  适合需要严格公平的场景

20.2 credit 流控机制

文件: drivers/gpu/drm/scheduler/sched_main.c(第 51-67 行注释)

credit 机制原理:

  scheduler.credit_limit  -- 最大在途 credit 总量(硬件容量上限)
  scheduler.credit_count  -- 当前已占用 credit

  job.credits             -- 该任务消耗的 credit 数(驱动设置)

  提交规则:
    if (credit_count + job.credits > credit_limit)
        等待直到 credit 空闲

  完成时:
    job 的 finished fence 信号 --> credit 归还

  典型用法:
    job.credits = 1             -- 每个 job 消耗 1 credit
    credit_limit = ring_depth   -- 限制 ring buffer 深度

  drm_sched_available_credits(sched):
    return sched->credit_limit - atomic_read(&sched->credit_count)

20.3 超时处理与 GPU reset

任务超时处理流程(sched_main.c):

  watchdog_timer 触发
      |
      v
  drm_sched_job_timedout()
      |
      +-- 将 job 标记为 guilty(karma++)
      |
      +-- 调用 ops->timedout_job(job)
      |       |
      |       v
      |   驱动执行 GPU reset(如 amdgpu_device_gpu_recover())
      |       |
      |       v
      |   强制完成所有 in-flight fence(避免永久阻塞)
      |
      +-- drm_sched_resubmit_jobs()
          -- 将 innocent jobs 重新放入队列(reset 后重试)
          -- guilty entity 的 jobs 全部丢弃并信号 error fence

20.4 drm_sched_fence 内存管理

文件: drivers/gpu/drm/scheduler/sched_fence.c(第 34-50 行)

/* slab 分配器优化(避免频繁的 kmalloc/kfree)*/
static struct kmem_cache *sched_fence_slab;

static int __init drm_sched_fence_slab_init(void)
{
    /* SLAB_HWCACHE_ALIGN:按 CPU cache line 对齐,减少 false sharing */
    sched_fence_slab = KMEM_CACHE(drm_sched_fence, SLAB_HWCACHE_ALIGN);
    if (!sched_fence_slab)
        return -ENOMEM;
    return 0;
}

fence 的 RCU 安全释放(sched_fence.c:98-104):

static void drm_sched_fence_free_rcu(struct rcu_head *rcu)
{
    struct dma_fence *f = container_of(rcu, struct dma_fence, rcu);
    struct drm_sched_fence *fence = to_drm_sched_fence(f);

    /* 延迟到 RCU 宽限期后释放,安全地在 lockless read 场景使用 */
    kmem_cache_free(sched_fence_slab, fence);
}

20.5 多调度器负载均衡

drm_sched_entity 支持关联多个调度器(sched_list[]),当多个同类 ring 可用时(如 amdgpu 有多个 compute ring),DRM 调度器会自动选择负载最低的:

entity.sched_list = [sched0, sched1, sched2]  // 三条 compute ring

drm_sched_entity_get_free_sched(entity):
    选择 credit_count 最少的调度器
    (即 pending job 最少的 ring)

应用场景:
  amdgpu 多 CU 并行计算
  ROCm dispatch
  视频编解码多引擎

参考源码路径索引

主题 文件路径
drm_driver 结构定义 include/drm/drm_drv.h:181
drm_driver_feature 枚举 include/drm/drm_drv.h:57
drm_device 结构定义 include/drm/drm_device.h:75
drm_gem_object 结构定义 include/drm/drm_gem.h:283
drm_gem_object_funcs include/drm/drm_gem.h:75
GEM 设计注释 drivers/gpu/drm/drm_gem.c:60
drm_mode_config 结构定义 include/drm/drm_mode_config.h:360
drm_mode_config_funcs include/drm/drm_mode_config.h:47
drm_crtc 结构定义 include/drm/drm_crtc.h:943
drm_crtc_state include/drm/drm_crtc.h:81
drm_crtc_funcs include/drm/drm_crtc.h:413
drm_plane_type 枚举 include/drm/drm_plane.h:601
drm_plane_state include/drm/drm_plane.h:54
drm_crtc_commit_wait drivers/gpu/drm/drm_atomic.c:69
drm_sched_entity include/drm/gpu_scheduler.h:82
drm_sched_rq include/drm/gpu_scheduler.h:251
drm_sched_fence include/drm/gpu_scheduler.h:264
drm_sched_job include/drm/gpu_scheduler.h:340
drm_sched_backend_ops include/drm/gpu_scheduler.h:412
drm_gpu_scheduler include/drm/gpu_scheduler.h:539
DRM_GEM_FOPS 宏 include/drm/drm_gem.h:467
drm_sched_priority 枚举 include/drm/gpu_scheduler.h:65
drm minor 编号宏 drivers/gpu/drm/drm_drv.c:136
drm_minor_alloc drivers/gpu/drm/drm_drv.c:142
drm_minor_register drivers/gpu/drm/drm_drv.c:174
XARRAY drm_minors_xa drivers/gpu/drm/drm_drv.c:64
drm_minor_get_xa drivers/gpu/drm/drm_drv.c:90
drm_mm_node 结构体 include/drm/drm_mm.h:157
drm_mm 结构体 include/drm/drm_mm.h:190
drm_mm_insert_mode 枚举 include/drm/drm_mm.h:70
drm_mm_init include/drm/drm_mm.h:466
drm_mm_insert_node include/drm/drm_mm.h:458
drm_mm_insert_node_generic include/drm/drm_mm.h:433
drm_mm_insert_node_in_range include/drm/drm_mm.h:407
drm_mm_remove_node include/drm/drm_mm.h:465
drm_mm_takedown include/drm/drm_mm.h:467
drm_mm_scan 结构体 include/drm/drm_mm.h:227
drm_mm 内部实现(区间树) drivers/gpu/drm/drm_mm.c:153
dma_fence 结构体 include/linux/dma-fence.h
dma_fence 概述文档 drivers/dma-buf/dma-fence.c:39
dma_fence 跨驱动契约 drivers/dma-buf/dma-fence.c:68
dma_fence_context_alloc drivers/dma-buf/dma-fence.c:185
dma_fence_signal_timestamp_locked drivers/dma-buf/dma-fence.c:362
dma_fence lockdep 注解 drivers/dma-buf/dma-fence.c:281
dma_fence_begin_signalling drivers/dma-buf/dma-fence.c:300
dma_fence_end_signalling drivers/dma-buf/dma-fence.c:323
drm_sched_fence_scheduled drivers/gpu/drm/scheduler/sched_fence.c:65
drm_sched_fence_finished drivers/gpu/drm/scheduler/sched_fence.c:80
sched_fence slab 初始化 drivers/gpu/drm/scheduler/sched_fence.c:36
调度策略参数 drivers/gpu/drm/scheduler/sched_main.c:87
drm_connector 类型列表 drivers/gpu/drm/drm_connector.c:93
drm_connector 概述 drivers/gpu/drm/drm_connector.c:46
drm_helper_probe_single_connector_modes drivers/gpu/drm/drm_probe_helper.c:52
drm_mode_validate_pipeline drivers/gpu/drm/drm_probe_helper.c:89
drm_bridge 概述 drivers/gpu/drm/drm_bridge.c:43
drm_bridge 链 drivers/gpu/drm/drm_bridge.c:51
drm_fb_helper 概述文档 drivers/gpu/drm/drm_fb_helper.c:78
drm_fbdev_emulation 参数 drivers/gpu/drm/drm_fb_helper.c:47
drm_fbdev_shmem_fb_ops drivers/gpu/drm/drm_fbdev_shmem.c:71
drm_fbdev_shmem mmap drivers/gpu/drm/drm_fbdev_shmem.c:43
drm_atomic_helper 概述 drivers/gpu/drm/drm_atomic_helper.c:52
drm_atomic_helper_plane_changed drivers/gpu/drm/drm_atomic_helper.c:79
驱动 probe 示例 drivers/gpu/drm/drm_drv.c:300

由 Claude Code 分析生成