2. DRM 核心数据结构:drm_device、drm_driver、drm_file、drm_minor 的关系与初始化流程

DRM 子系统通过四个核心数据结构来管理显示设备、驱动实例、用户态会话以及设备节点访问。理解 drm_device、drm_driver、drm_file 和 drm_minor 之间的关系,是编写 GPU 驱动的基础。它们共同构成了从硬件抽象到用户空间接口的完整链路。

核心要点: 这四个结构体分别对应“硬件设备”、“驱动行为”、“用户会话”和“设备节点”。初始化顺序为:先注册 drm_driver,再创建 drm_device,然后通过 drm_dev_register 生成 drm_minor,最后在用户打开设备时分配 drm_file。

2.1 结构体定义与职责

结构体 生命周期 核心职责 关键字段
drm_driver 全局静态 定义驱动回调函数集(如 open、ioctl、gem_create) driver_features、fops、gem_create_object
drm_device 设备绑定期间 代表一个物理或虚拟显示设备,管理所有子对象 driver、dev_private、mode_config、filelist
drm_minor 设备注册后 表示一个设备节点(primary、render、control) type、dev、device、index
drm_file 每次 open() 调用 代表用户态与内核之间的一个打开会话 minor、filp、driver_priv、authenticated
设计意图: drm_driver 是静态的,通常定义为全局变量;drm_device 是动态分配的,每个硬件实例对应一个;drm_file 是每个进程打开设备时临时创建,关闭时销毁。

2.2 四者之间的引用关系

下图展示了四个核心结构体之间的指针引用与层级关系:

DRM 核心数据结构关系图 drm_driver 全局静态实例 fops, ioctl 表 drm_device 每个硬件实例一个 dev_private, mode_config drm_minor 设备节点抽象 primary/render/control drm_file 每次 open() 创建 driver_priv, authenticated driver 指针 minors[] 数组 filelist 链表 minor 指针 说明:drm_driver 通过 driver 指针被 drm_device 引用;drm_device 维护 minors[] 数组指向 drm_minor; drm_file 通过 minor 指针反向引用 drm_minor,并通过 filelist 链表挂接到 drm_device。

2.3 初始化流程详解

DRM 驱动的初始化遵循严格的顺序,从静态定义到动态注册,再到用户态交互。以下是标准流程:

  1. 定义全局 drm_driver:在驱动源码中声明一个静态的 struct drm_driver 实例,填充 driver_features、fops、gem_create_object 等回调。
  2. 分配 drm_device:在 probe() 函数中调用 drm_dev_alloc(),传入 drm_driver 指针和父设备 struct device *。
  3. 初始化驱动私有数据:通过 devm_drm_dev_init() 或手动设置 dev_private,完成 GPU 硬件特定的初始化(如 MMIO 映射、中断注册)。
  4. 注册设备:调用 drm_dev_register(),该函数内部会创建 drm_minor 实例(primary、render、control),并在 sysfs 中创建设备节点。
  5. 用户打开设备:当用户态调用 open() 时,VFS 层调用 drm_stub_open(),分配 drm_file 并调用 drm_driver->open 回调。
关键 API 调用链:
drm_dev_alloc(driver, parent) → drm_dev_register(dev, flags) → drm_minor_alloc() × 3
open() → drm_stub_open() → drm_file_alloc(minor) → driver->open()

2.4 drm_minor 的三种类型

每个 drm_device 最多可以关联三个 drm_minor,分别对应不同的设备节点:

类型 设备节点路径 用途 访问权限
DRM_MINOR_PRIMARY /dev/dri/card0 显示控制、Mode Setting、Page Flip 需要 DRM Master 权限
DRM_MINOR_RENDER /dev/dri/renderD128 纯 GPU 计算、渲染(无需显示) 无需认证,任何进程可用
DRM_MINOR_CONTROL /dev/dri/controlD64 显示控制(仅限授权进程) 需要 CAP_SYS_ADMIN
常见误区: 初学者常误以为 drm_minor 是“次要设备号”的简单封装。实际上,它包含了完整的设备类结构、sysfs 入口和文件操作函数表。每个 drm_minor 都有独立的 struct device 嵌入其中。

2.5 初始化流程的代码示例

以下是一个简化的 GPU 驱动 probe 函数,展示核心数据结构的初始化顺序:

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

static const struct drm_driver mygpu_driver = {
    .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_RENDER,
    .fops = &mygpu_fops,
    .gem_create_object = mygpu_gem_create_object,
    .open = mygpu_open,
    .postclose = mygpu_postclose,
    .name = "mygpu",
    .desc = "My GPU DRM Driver",
    .date = "20250101",
    .major = 1,
    .minor = 0,
};

static int mygpu_probe(struct platform_device *pdev)
{
    struct drm_device *drm;
    struct mygpu_private *priv;
    int ret;

    /* 步骤1: 分配 drm_device,传入静态 drm_driver */
    drm = drm_dev_alloc(&mygpu_driver, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    /* 步骤2: 分配并初始化驱动私有数据 */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) {
        ret = -ENOMEM;
        goto err_free;
    }
    drm->dev_private = priv;

    /* 步骤3: 硬件初始化(MMIO、IRQ、Fence 等) */
    ret = mygpu_hw_init(priv, pdev);
    if (ret)
        goto err_free;

    /* 步骤4: 注册 DRM 设备,内部创建 drm_minor */
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_hw_fini;

    platform_set_drvdata(pdev, drm);
    return 0;

err_hw_fini:
    mygpu_hw_fini(priv);
err_free:
    drm_dev_put(drm);
    return ret;
}

2.6 drm_file 的创建与销毁

当用户态进程打开 DRM 设备节点时,内核调用 drm_stub_open(),其内部流程如下:

drm_file 创建流程 用户 open() 调用 drm_stub_open() drm_file_alloc() driver->open() 返回 fd 给用户

drm_file 的生命周期与文件描述符绑定。关键字段 driver_priv 允许驱动存储每个会话的私有数据(如命令队列、同步对象)。当用户调用 close() 时,内核调用 drm_lastclose() 或 driver->postclose() 进行清理。

性能提示: drm_file 的分配使用 kzalloc,且通过 idr 机制管理。驱动在 open 回调中应避免执行耗时操作,因为 open() 调用可能来自高频率的客户端连接。

2.7 关系总结

关系对 方向 实现方式 典型访问宏/函数
drm_device → drm_driver 1:1 引用 drm_device.driver 指针 drm->driver
drm_device → drm_minor 1:N (最多3) drm_device.minors[] 数组 drm_minor_acquire()
drm_device → drm_file 1:N drm_device.filelist 链表 list_for_each_entry()
drm_file → drm_minor N:1 drm_file.minor 指针 file->minor
drm_file → drm_device N:1 通过 minor->dev 间接访问 file->minor->dev
面试高频题: 为什么 DRM 不直接把 drm_device 指针放在 drm_file 中,而是通过 drm_minor 间接访问?
答案:因为一个 drm_device 可能有多个 drm_minor(primary/render/control),通过 drm_file.minor 可以明确知道用户打开的是哪个设备节点,从而区分访问权限和功能范围。