第二章:GEM 与 TTM 概述:2.1 DRM 设备模型速览

本节定位 :为 BO 主线提供最小必要的设备模型上下文。只覆盖后续章节反复依赖的三个核心概念(drm_devicedrm_file、设备节点),不涉及 PCI probe 流程和 KMS 初始化。

DRM 设备模型本质上遵循 Linux 标准的字符设备文件操作模型:内核通过 struct file_operations 向用户态暴露设备节点(/dev/dri/renderD128),用户态通过 open() / ioctl() / mmap() / close() 等标准系统调用与驱动交互。DRM 子系统在此基础上做了两层封装------drm_device 封装了硬件实例,drm_file 封装了每次 open 的客户端状态------但底层的 VFS → char dev → file_operations 调用链路与任何 Linux 字符设备完全一致。关于 Linux 设备驱动模型的基础知识(字符设备注册、file_operations、VFS 层交互等),请参阅 LDD(Linux Device Drivers)专栏,本节不再赘述。

1. 全景:从用户态 open 到内核对象

一句话关系drm_device 是"一块 GPU",drm_file 是"一个进程对这块 GPU 的一次打开"。后续章节中所有 "per-client" 资源(handle、syncobj、VM、entity)都挂在 drm_file 上。

2. drm_device --- GPU 硬件的内核抽象

源码include/drm/drm_device.h

drm_device 在整个驱动生命周期中只有 一个实例 (per PCI function),驱动初始化时由 devm_drm_dev_alloc() 分配。

2.1 与后续章节相关的关键字段

字段 类型 与 BO 主线的关联
driver const struct drm_driver * Ch3:gem_create_object 回调决定 GEM 对象如何分配
primary / render / accel struct drm_minor * 决定用户通过哪个设备节点访问
anon_inode struct inode * Ch3:GEM mmap 使用的匿名 inode(drm_vma_offset_manager 基于此)
filelist struct list_head 所有已打开的 drm_file 列表
driver_features u32 功能标志位,决定是否启用 GEM/RENDER/SYNCOBJ/GPUVA 等

2.2 driver_features 与 BO 主线的关联

c 复制代码
// include/drm/drm_drv.h
DRIVER_GEM          = BIT(0),   // 启用 GEM 子系统 → Ch3
DRIVER_RENDER       = BIT(3),   // 创建 render 节点(compute 必须)
DRIVER_SYNCOBJ      = BIT(5),   // 启用 syncobj → Ch6
DRIVER_GEM_GPUVA    = BIT(8),   // 启用 GPUVM → Ch8
DRIVER_COMPUTE_ACCEL = BIT(7),  // 计算加速专用节点(与 RENDER 互斥)

2.3 驱动嵌入模式

现代驱动不直接分配 drm_device,而是将其嵌入更大的驱动私有结构:

c 复制代码
// drivers/gpu/drm/amd/amdgpu/amdgpu.h
struct amdgpu_device {
    struct drm_device       ddev;       // 嵌入 drm_device
    // ... 数千个 amdgpu 私有字段
};

// 获取方式:
struct amdgpu_device *adev = drm_to_adev(ddev);  // container_of

Xe 驱动同理:struct xe_device 嵌入 struct drm_device

3. drm_file --- 客户端上下文(最关键)

源码include/drm/drm_file.h

每次用户态调用 open("/dev/dri/renderD128") 都会创建一个 drm_file 实例。这是 BO 主线中出现频率最高的关联对象

3.1 与 BO 主线直接相关的字段

字段 含义 后续章节使用场景
object_idr GEM handle → drm_gem_object * 的映射表 Ch3drm_gem_handle_create() 插入、GEM_CLOSE 删除
syncobj_idr syncobj handle → drm_syncobj * 的映射表 Ch6drm_syncobj_create() / SYNCOBJ_WAIT
prime PRIME 导入缓存(fd → handle) Ch5:避免同一个 dma-buf 重复导入
driver_priv 驱动私有指针 Ch7/Ch8 :amdgpu 在此存放 amdgpu_fpriv(含 VM、ctx_mgr)
pid / client_id 进程标识 调试:clients debugfs、fdinfo
minor 打开的设备节点 区分 primary/render/accel 权限
authenticated 是否已认证 render node 自动认证,primary 需要 master 授权

3.2 amdgpu 的 drm_file 扩展

c 复制代码
// drivers/gpu/drm/amd/amdgpu/amdgpu.h
struct amdgpu_fpriv {
    struct amdgpu_vm        vm;             // per-client GPU 虚拟地址空间 → Ch8
    struct amdgpu_bo_va     *prt_va;        // PRT(Partially Resident Textures)
    struct mutex            bo_list_lock;
    struct idr              bo_list_handles; // CS 提交时的 BO 列表
    struct amdgpu_ctx_mgr   ctx_mgr;        // 上下文管理 → Ch7 scheduler entity
    struct amdgpu_eviction_fence_mgr evf_mgr; // eviction fence
};

关键洞察 :在 amdgpu 中,每次 open() 都会创建独立的 amdgpu_vm(GPU 页表空间),这意味着不同进程有隔离的 GPU 地址空间。这是理解 Ch8 GPUVM 的前提。

3.3 drm_file 的生命周期

复制代码
open("/dev/dri/renderD128")
    → drm_open()
        → drm_file_alloc()          // 分配 drm_file,初始化 idr 表
        → drm_driver.open()         // 驱动回调:amdgpu_driver_open_kms()
            → 分配 amdgpu_fpriv
            → 创建 amdgpu_vm(GPU 页表)
            → 初始化 ctx_mgr

    ... 用户操作(create BO / submit job / ...)...

close(fd)
    → drm_release()
        → drm_file_free()
            → 遍历 object_idr,释放所有 GEM handle(减引用)
            → 遍历 syncobj_idr,释放所有 syncobj
            → drm_driver.postclose()  // 驱动回调
                → 销毁 amdgpu_vm
                → 销毁 ctx_mgr(flush 所有 entity)
                → kfree(amdgpu_fpriv)

对后续章节的启示

  • Ch3 GEM:handle 是 per-file 的------进程 A 的 handle 42 和进程 B 的 handle 42 指向不同对象
  • Ch5 共享:PRIME fd 是跨进程的,但导入后生成的 handle 仍是 per-file 的
  • Ch7 Scheduler:entity 通常在 amdgpu_ctx(属于 ctx_mgr)中创建,随 file close 一起销毁
  • Ch8 GPUVM:amdgpu_vm 是 per-file 的,file close 时所有 GPU 映射被拆除

4. 设备节点:render node vs primary node vs accel node

复制代码
/dev/dri/
├── card0        → drm_minor (DRM_MINOR_PRIMARY)   → 图形显示 + 渲染
├── renderD128   → drm_minor (DRM_MINOR_RENDER)    → 纯渲染/计算(无需 root/master)
└── accel/
    └── accel0   → drm_minor (DRM_MINOR_ACCEL)     → 纯计算加速
节点类型 权限要求 典型用户 与本专栏关系
card0 (primary) 需要 DRM master 或 authenticated X11/Wayland 合成器 本专栏不涉及
renderD128 (render) 任何用户可 open Mesa/Vulkan/ROCm 本专栏核心路径
accel0 (accel) 任何用户可 open 专用 AI 加速器 与 render 互斥,原理相同

对 BO 主线的影响 :本专栏所有 IOCTL 操作(GEM create、CS submit、VM_BIND 等)都走 render node 路径。render node 的用户无需 DRM_AUTH(自动 authenticated),也无法执行 modeset 操作------这确保了 compute 用户不会干扰显示。

5. IOCTL 分发机制(极简版)

后续每章都会涉及特定 IOCTL(GEM_CREATECSVM_BIND 等),这里简述调用路径以建立直觉:

复制代码
用户态: ioctl(fd, DRM_IOCTL_AMDGPU_GEM_CREATE, &args)
    │
    ▼  VFS → drm_ioctl()
    │
    ├─ 查表: drm_ioctls[] (DRM 核心) + amdgpu_ioctls[] (驱动)
    ├─ 权限检查: DRM_AUTH / DRM_MASTER / DRM_RENDER_ALLOW
    ├─ 参数拷贝: copy_from_user → 内核栈
    └─ 调用回调: amdgpu_gem_create_ioctl(dev, data, file_priv)
                                         ↑             ↑
                                     drm_device     drm_file

关键点 :几乎所有 IOCTL 回调都接收 (struct drm_device *dev, void *data, struct drm_file *file_priv) 三个参数。理解了 drm_devicedrm_file,就能看懂每个 IOCTL 的入口签名。

6. 小结:后续章节的阅读前提

读完本节,你需要记住的三件事:

  1. drm_device = 一块 GPU。驱动加载时创建,生命周期等同于硬件设备。
  2. drm_file = 一次 open。每个进程的每次 open 都是独立的客户端上下文,handle table、syncobj table、GPU VM 都是 per-file 隔离的。
  3. render node 是本专栏的入口 。所有 BO 操作通过 /dev/dri/renderD128 的 IOCTL 进入内核,回调签名统一为 (dev, data, file_priv)

有了这三个锚点,后续章节中遇到 file_priv->object_idr(Ch3)、file_priv->syncobj_idr(Ch6)、fpriv->vm(Ch8)时,就不会感到突兀。

7. 本专栏不涉及的内容:PCI Probe 流程

drm_device 的创建发生在 PCI probe 阶段------内核发现 PCI 设备后调用驱动的 probe() 回调(如 amdgpu_pci_probe()),在其中完成硬件初始化、devm_drm_dev_alloc() 分配 drm_devicedrm_dev_register() 注册设备节点等一系列操作。

本专栏聚焦于 drm_device 创建之后 的 BO 生命周期管理,不涉及以下内容:

  • PCI 设备枚举与资源分配(BAR 映射、中断配置)
  • 驱动的 probe() / remove() 回调实现
  • devm_drm_dev_alloc() / drm_dev_register() / drm_dev_unregister() 流程
  • 电源管理(suspend/resume)与热插拔

关于 PCI probe 的完整分析,请参阅 PCIe 专栏 。本专栏的起点是:GPU 硬件已经就绪,drm_device 已经注册,用户态可以 open() 设备节点------从这里开始讲 BO 的故事。