基于 Linux Kernel 源码深度分析 路径:
drivers/gpu/drm/|include/drm/
- DRM 概述
- DRM 核心数据结构
- GEM 内存管理
- KMS 内核模式设置
- 原子模式设置
- DRM GPU 调度器
- 渲染管线:用户态视角
- 具体驱动示例
- Display Engine vs Compute Engine
- drivers/gpu/drm/ 主要文件一览
- 调试工具
- DRM 设备节点:renderD 与 cardN 的区别
- DRM MM 内存范围管理器
- dma-fence 同步机制深度分析
- KMS 对象模型:Encoder、Bridge 与 Connector 热插拔
- 原子提交流程源码级详解
- DRM fbdev 兼容层
- DRM 驱动注册与设备节点创建
- GEM 对象生命周期源码追踪
- DRM 调度器:源码级深度分析
- 参考源码路径索引
DRM(Direct Rendering Manager)是 Linux 内核中负责管理 GPU 及显示输出的子系统。它诞生于 1999 年(XFree86 项目),最初目的是解决多进程争用 GPU 硬件的问题。现代 DRM 承载两大职责:
- KMS(Kernel Mode Setting):控制显示输出管线(分辨率、刷新率、显示器检测)
- GEM(Graphics Execution Manager):管理 GPU 显存(分配、共享、同步)
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
屏幕上的像素
+------------------------------------------------------------------+
| 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 |
+------------------------------------------------------------------+
文件: 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 绑定 |
文件: 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() -- 标记为已拔出,阻止新访问
文件: 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 内有效。
GEM(Graphics Execution Manager)由 Intel 工程师 Eric Anholt 于 2008 年为 i915 驱动设计,后成为 DRM 核心组件。GEM 用整数 handle 代替 fd 来引用 GPU 缓冲区,规避了进程 fd 数量上限问题(详见 drivers/gpu/drm/drm_gem.c 第 60-86 行注释)。
文件: 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;
};
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 等
文件: 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 操作
};
用户态 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()
--> 驱动为具体页分配物理内存并建立映射
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
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[] (物理接口)
[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] 物理屏幕
文件: 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
};
文件: 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;
};
文件: 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;
};
文件: 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(同步)
显示模式描述完整的水平/垂直时序(兼容 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
};
Legacy KMS Atomic KMS
----------------------------------------------------------------------
每次改一个属性立即生效 事务性:所有改动打包为一个 state
无原子性保证 全部成功或全部回滚
page_flip / set_config ioctl DRM_IOCTL_MODE_ATOMIC ioctl
驱动直接操作硬件 先 check,通过后再 commit
难以实现无撕裂多平面更新 保证无撕裂(在 vblank 边界切换)
原子状态是所有 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)
};
用户态 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 |
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 开始扫描
DRM GPU 调度器(drivers/gpu/drm/scheduler/)是通用的 GPU 命令队列管理框架,由 AMD 贡献,被 amdgpu、radeon、lima、panfrost、v3d、virtio-gpu 等驱动使用。
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
};文件: 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; // 超时检测工作队列
};
文件: 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);
};
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() -- 释放资源
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
物理显示器
现代 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
目录: 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/)
目录: 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
+---------------------------+ +---------------------------+
| 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 内存) |
+-----------------------------------+
include/drm/drm_drv.h 第 103-110 行:
DRIVER_COMPUTE_ACCEL = BIT(7),
// 纯计算加速设备(如 NPU、ML 加速器)
// 与 DRIVER_RENDER 和 DRIVER_MODESET 互斥
// 如果设备同时支持图形和计算,应通过 auxiliary bus 连接两个驱动典型用例:
- Intel NPU(
drivers/accel/ivpu/):纯计算,暴露/dev/accel/accel0 - AMD KFD(ROCm 计算):通过 amdkfd 子模块与 amdgpu 共享设备
- AMDGPU display + compute:单
amdgpu驱动同时支持 KMS 和渲染/计算
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 扫描输出
文件 作用
--------------------------------------------------------------------
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 接口
scheduler/
sched_main.c -- drm_gpu_scheduler 主逻辑(init/fini/push/run)
sched_entity.c -- drm_sched_entity 管理
sched_fence.c -- drm_sched_fence 实现
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(纯软件,用于测试)
枚举所有 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)来自 libdrm-tests,可进行 KMS 测试:
# 列出所有 KMS 对象
modetest -M i915
# 在指定 CRTC/connector 上设置模式并显示测试图案
modetest -M i915 -s <connector_id>:<mode>
# 测试所有 connector 的默认模式
modetest -a实时监控 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%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# 跟踪 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| 工具 | 用途 |
|---|---|
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 对应关系 |
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)
+--------------------+--------------------+--------------------+
| 特性 | 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 权限
drivers/gpu/drm/drm_drv.c:142 中 drm_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/
drm_mm 是 DRM 内部用于管理 GPU 地址空间的通用范围分配器,定义在
drivers/gpu/drm/drm_mm.c 和 include/drm/drm_mm.h(第 1-100 行注释)。
与 Linux 通用内存分配器(buddy allocator/slab)不同,drm_mm 专门用于管理:
- GPU 虚拟地址空间(GTT/PPGTT)
- VRAM 物理地址范围
- 命令缓冲区的地址空间窗口
它不分配物理内存,只负责地址范围的划分与跟踪。
文件: 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; // 是否正在扫描(驱逐)
};
文件: 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,选择刚刚被驱逐出的空洞
文件: 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);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 模式 -- 重用最近释放的地址段
当地址空间不足时,需要驱逐已有节点腾出空间。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);文件: 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:硬件信号已启用
文件: 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 是否有依赖关系。
文件: 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 持有期间调用
}
}文件: 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 同步路径
文件: 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
}文件: 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(),因此完成路径不能反向获取该锁
所有 KMS 对象都继承自 drm_mode_object(include/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 属性)
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;
};
文件: 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
文件: drivers/gpu/drm/drm_connector.c(第 46-74 行)
Connector 是 KMS 对象中唯一在运行时可热插拔的对象。
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 事件发往用户态
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)
文件: 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
这是原子提交的核心——状态切换必须对 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 锁
} 时序图
-------
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 可读)
当 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 指针,可用于将回调与具体帧关联。
fbdev(framebuffer device)是 Linux 的老式显示接口(/dev/fb0),许多工具(Plymouth、内核崩溃转储、虚拟终端)仍依赖它。现代 DRM 驱动通过 drm_fb_helper 模拟 fbdev,避免每个驱动单独实现。
文件: drivers/gpu/drm/drm_fb_helper.c(第 47-113 行)
/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 (工作队列) -- 延迟刷新脏区域
根据 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、虚拟驱动等 |
文件: 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);
}文件: 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);
...
}现代 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%)文件: 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;
}文件: 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);
}/* 进入设备受保护区域(使用 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);
} +--------------------+
| 未创建状态 |
+--------------------+
|
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)
|
+--------------------+
| 已销毁 |
+--------------------+
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)
每个 drm_gem_object 都关联一个 dma_resv(include/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)
文件: include/drm/drm_gem.h(第 283 行,lru_node 和 lru 字段)
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 链表移除
文件: 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
适合需要严格公平的场景
文件: 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)
任务超时处理流程(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
文件: 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);
}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 分析生成