3. DRM 基础数据结构:drm_device、drm_driver、drm_file、drm_minor 详解

DRM 子系统的核心由四个基础数据结构支撑:drm_device 代表一个显示设备实例,drm_driver 描述驱动行为,drm_file 管理用户态文件句柄上下文,drm_minor 提供设备节点访问入口。理解这四个结构体及其关系,是掌握 DRM 框架的基石。

DRM 核心数据结构关系图 drm_device 显示设备实例(核心枢纽) drm_driver 驱动行为与回调函数 drm_minor 设备节点(主/次编号) drm_file 用户态文件句柄上下文 驱动回调 load/unload open/close/postclose 设备节点类型 DRM_MINOR_PRIMARY DRM_MINOR_CONTROL 文件上下文 universal_planes event_list/lock

3.1 drm_device:显示设备的核心抽象

drm_device 是 DRM 框架中最高层级的设备抽象,每个物理或虚拟显示设备对应一个实例。它聚合了所有子组件,是驱动初始化时第一个被创建的对象。

成员类型说明
devstruct device *内核通用设备指针,关联到硬件平台设备
driverstruct drm_driver *指向驱动描述结构体,定义回调函数集
primarystruct drm_minor *主设备节点(/dev/dri/cardX)
renderstruct drm_minor *渲染节点(/dev/dri/renderD128)
mode_configstruct drm_mode_config显示模式配置管理,包含 CRTC/Encoder/Connector 链表
fileliststruct list_head已打开文件句柄链表,遍历所有 drm_file
event_lockspinlock_t保护事件队列的自旋锁

关键要点: drm_device 的生命周期从 drm_dev_alloc() 开始,经过 drm_dev_register() 暴露给用户态,最后通过 drm_dev_unregister() 和 drm_dev_put() 销毁。驱动通常将其嵌入到自己的私有结构体中,使用 container_of 宏访问。

3.2 drm_driver:驱动行为描述符

drm_driver 是一个静态常量结构体,定义了驱动的名称、版本、支持的 IOCTL 以及一系列回调函数。它不存储运行时状态,而是作为驱动与 DRM 核心之间的契约。

回调函数触发时机典型实现
load设备注册时调用(已废弃,推荐使用 devm_drm_dev_init)初始化硬件、分配资源
open用户态打开设备节点时创建私有数据、初始化文件上下文
postclose用户态关闭文件描述符时清理 GEM 句柄、释放资源
lastclose最后一个文件句柄关闭时恢复显示模式、关闭显示输出
irq_handler硬件中断触发时处理 VBlank 等显示中断
gem_create_object创建 GEM 对象时分配驱动私有 GEM 结构体

最佳实践: 现代 DRM 驱动推荐使用 drmm_ 系列 API(Managed DRM)替代传统的 load/unload 回调,利用设备资源管理自动处理生命周期。

3.3 drm_file:用户态文件句柄上下文

每次用户态进程通过 open() 系统调用打开 DRM 设备节点时,内核都会创建一个 drm_file 实例。它保存了该文件描述符的私有状态,包括权限、对象句柄映射、事件订阅等。

成员说明
authenticated是否通过 Magic 认证,控制主节点访问权限
universal_planes是否启用通用平面(Universal Planes)模式
event_list待发送给用户态的事件链表(如 VBlank 事件)
table_lock保护句柄表的读写锁
object_idrIDR 映射表,将用户态句柄映射到内核对象指针
minor指向所属的 drm_minor,标识设备节点类型

安全注意: drm_file->authenticated 标志控制着对主节点(primary)的访问权限。只有经过 Magic 认证的进程才能执行模式设置(Mode Setting)操作。渲染节点(render)默认不需要认证,但无法进行显示控制。

3.4 drm_minor:设备节点与访问入口

drm_minor 是 DRM 设备节点在内核中的表示,每个 drm_minor 对应一个 /dev/dri/ 下的设备文件。它负责将用户态的文件操作(open/close/ioctl)路由到正确的 drm_device 和 drm_driver 回调。

类型设备节点路径次设备号范围功能
DRM_MINOR_PRIMARY/dev/dri/card00–63主节点,支持模式设置和渲染
DRM_MINOR_CONTROL/dev/dri/controlD6464–127控制节点,仅用于模式设置(已废弃)
DRM_MINOR_RENDER/dev/dri/renderD128128–191渲染节点,仅支持 GPU 渲染操作
drm_minor 设备节点类型与访问路径 用户空间(User Space) card0 主节点 controlD64 控制节点(已废弃) renderD128 渲染节点 内核空间(Kernel Space)

3.5 四个结构体的协作流程

当用户态程序调用 open("/dev/dri/card0") 时,内核中的协作流程如下:

  1. VFS 层 根据设备号找到对应的 drm_minor 实例。
  2. drm_minor 通过 minor->dev 指针找到所属的 drm_device。
  3. drm_device 调用 dev->driver->open() 回调,创建 drm_file 实例。
  4. drm_file 被添加到 dev->filelist 链表中,并初始化句柄映射表。
  5. 用户态通过 ioctl() 发送命令时,DRM 核心根据 drm_file->minor->type 判断权限,然后路由到对应的 IOCTL 处理函数。

核心关系总结:

  • drm_device 是“容器”,包含所有子对象。
  • drm_driver 是“行为”,定义驱动如何响应事件。
  • drm_minor 是“入口”,提供用户态访问路径。
  • drm_file 是“会话”,管理每个打开文件的状态。

3.6 代码示例:驱动初始化中的结构体使用

// 定义驱动回调
static const struct drm_driver my_driver = {
    .driver_features = DRIVER_MODESET | DRIVER_GEM,
    .open = my_driver_open,
    .postclose = my_driver_postclose,
    .lastclose = my_driver_lastclose,
    .gem_create_object = my_gem_create_object,
    .name = "my_drm_driver",
    .desc = "My DRM Driver",
    .date = "20250101",
    .major = 1,
    .minor = 0,
};

// 在 probe 函数中创建设备
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    struct drm_device *dev;
    int ret;

    // 1. 分配 drm_device
    dev = drm_dev_alloc(&my_driver, &pdev->dev);
    if (IS_ERR(dev))
        return PTR_ERR(dev);

    // 2. 初始化硬件(私有数据通过 dev->dev_private 访问)
    pci_set_drvdata(pdev, dev);

    // 3. 注册设备,创建 drm_minor 节点
    ret = drm_dev_register(dev, ent->driver_data);
    if (ret) {
        drm_dev_put(dev);
        return ret;
    }

    return 0;
}

// open 回调:创建 drm_file 私有数据
static int my_driver_open(struct drm_device *dev, struct drm_file *file)
{
    struct my_file_private *priv;

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    file->driver_priv = priv;
    return 0;
}

调试技巧: 可以通过 /sys/kernel/debug/dri/ 下的 debugfs 文件查看当前系统中所有 drm_device、drm_minor 和 drm_file 的状态。例如 cat /sys/kernel/debug/dri/0/name 可以查看显卡驱动名称。