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 四者之间的引用关系
下图展示了四个核心结构体之间的指针引用与层级关系:
2.3 初始化流程详解
DRM 驱动的初始化遵循严格的顺序,从静态定义到动态注册,再到用户态交互。以下是标准流程:
- 定义全局 drm_driver:在驱动源码中声明一个静态的
struct drm_driver实例,填充driver_features、fops、gem_create_object等回调。 - 分配 drm_device:在
probe()函数中调用drm_dev_alloc(),传入drm_driver指针和父设备struct device *。 - 初始化驱动私有数据:通过
devm_drm_dev_init()或手动设置dev_private,完成 GPU 硬件特定的初始化(如 MMIO 映射、中断注册)。 - 注册设备:调用
drm_dev_register(),该函数内部会创建drm_minor实例(primary、render、control),并在 sysfs 中创建设备节点。 - 用户打开设备:当用户态调用
open()时,VFS 层调用drm_stub_open(),分配drm_file并调用drm_driver->open回调。
关键 API 调用链:
drm_dev_alloc(driver, parent) → drm_dev_register(dev, flags) → drm_minor_alloc() × 3open() → 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 的生命周期与文件描述符绑定。关键字段 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 可以明确知道用户打开的是哪个设备节点,从而区分访问权限和功能范围。