2. DRM 核心数据结构:drm_device、drm_driver、drm_file 的结构与初始化流程
DRM 子系统通过三个核心数据结构来抽象显示设备、驱动实例与用户态会话。理解 drm_device、drm_driver 和 drm_file 的职责与初始化顺序,是编写稳定显示驱动的第一步。本章从结构体定义出发,逐步拆解其初始化流程与关联关系。
2.1 核心数据结构概览
下表对比了三个结构体的核心定位与生命周期:
| 结构体 | 内核对象 | 生命周期 | 主要职责 |
|---|---|---|---|
drm_driver |
静态全局 | 模块加载 → 卸载 | 定义驱动能力、回调函数集、IOCTL 表 |
drm_device |
设备实例 | 设备绑定 → 解绑 | 管理硬件资源、模式对象、内存管理器 |
drm_file |
文件句柄 | open → close | 跟踪用户态会话、权限、私有对象句柄 |
设计原则: DRM 将驱动描述(drm_driver)、设备实例(drm_device)和用户会话(drm_file)严格分离,使得一个驱动可以同时管理多个 GPU 设备,每个设备又能服务多个独立用户进程。
2.2 drm_driver:驱动能力描述符
drm_driver 是一个静态结构体,通常在驱动模块的入口处定义。它向 DRM 核心声明该驱动支持哪些特性、使用哪些回调函数。
2.2.1 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
driver_features |
u32 | 位掩码,声明支持的特性(如 DRIVER_MODESET、DRIVER_ATOMIC) |
name |
const char * | 驱动名称,用于 /sys 和调试 |
desc |
const char * | 驱动描述 |
date |
const char * | 驱动发布日期 |
major / minor / patchlevel |
int | 驱动版本号 |
fops |
struct file_operations * | 字符设备文件操作(open、release、ioctl 等) |
gem_create_object |
回调 | 创建 GEM 对象,用于内存管理 |
prime_handle_to_fd / prime_fd_to_handle |
回调 | PRIME 缓冲区共享 |
2.2.2 典型初始化示例
static const struct drm_driver my_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
.name = "mygpu",
.desc = "My GPU DRM Driver",
.date = "20250101",
.major = 1,
.minor = 0,
.patchlevel = 0,
.fops = &my_drm_fops,
.gem_create_object = my_gem_create_object,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
};
关键点:
driver_features 必须在驱动加载时正确设置。例如,若未设置 DRIVER_ATOMIC,则内核不会注册 atomic IOCTL,用户态将无法使用原子模式设置。
2.3 drm_device:设备实例核心
drm_device 是 DRM 驱动中最重要的运行时结构体,代表一个物理或虚拟显示设备。它包含模式配置、内存管理器、中断处理等所有子系统的入口。
2.3.1 结构体核心字段
| 字段 | 说明 |
|---|---|
dev |
指向 struct device(Linux 设备模型) |
driver |
指向所属的 drm_driver |
mode_config |
模式配置管理结构体,包含所有 encoder、crtc、connector、plane |
primary |
主文件节点(通常为 card0) |
render |
渲染节点(renderD128) |
filelist_mutex / filelist |
保护已打开文件句柄列表 |
vblank |
VBlank 相关数据结构(后续章节重点展开) |
irq_enabled |
中断是否已启用 |
2.3.2 初始化流程
- 分配 drm_device: 使用
drm_dev_alloc()或devm_drm_dev_alloc()分配并初始化基础字段。 - 设置驱动私有数据: 通过
container_of或内嵌结构体访问驱动自定义数据。 - 注册设备: 调用
drm_dev_register()创建设备节点并注册到文件系统。 - 初始化模式配置: 调用
drm_mode_config_init(),然后驱动添加自己的 CRTC、Encoder、Connector、Plane。 - 安装中断: 注册 VBlank 中断处理函数。
struct drm_device *ddev;
int ret;
// 步骤1:分配设备
ddev = devm_drm_dev_alloc(&pdev->dev, &my_drm_driver,
struct my_drm_private, drm);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
// 步骤2:初始化模式配置
drm_mode_config_init(ddev);
ddev->mode_config.min_width = 640;
ddev->mode_config.max_width = 4096;
ddev->mode_config.min_height = 480;
ddev->mode_config.max_height = 2160;
// 步骤3:添加 CRTC、Encoder、Connector(驱动实现)
my_drm_crtc_init(ddev);
my_drm_encoder_init(ddev);
my_drm_connector_init(ddev);
// 步骤4:注册设备
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_unregister;
常见错误: 在调用
drm_dev_register() 之前,必须确保所有模式对象(CRTC、Encoder、Connector)已初始化完毕。否则用户态在打开设备节点后可能立即访问到未就绪的对象。
2.4 drm_file:用户态会话跟踪
每次用户态进程打开 DRM 设备节点(如 /dev/dri/card0)时,内核会创建一个 drm_file 实例。它用于隔离不同进程的权限、对象句柄空间和状态。
2.4.1 核心字段
| 字段 | 说明 |
|---|---|
authenticated |
是否通过权限认证(Master 权限) |
is_master |
是否为当前 DRM Master |
table_lock / object_idr |
管理该会话创建的 GEM 对象句柄 |
event_list |
待发送给用户态的事件链表(如 VBlank 事件) |
minor |
指向所属的 drm_minor(主节点或渲染节点) |
2.4.2 创建流程
当用户态调用 open() 时,DRM 核心的 drm_open() 函数被调用:
static int drm_open(struct inode *inode, struct file *filp)
{
struct drm_device *ddev = drm_minor_get_device(inode);
struct drm_file *priv;
int ret;
// 分配 drm_file
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
// 初始化
priv->minor = drm_minor_acquire(drm_minor_from_inode(inode));
priv->authenticated = 0;
priv->is_master = 0;
idr_init(&priv->object_idr);
mutex_init(&priv->table_lock);
INIT_LIST_HEAD(&priv->event_list);
// 调用驱动回调
if (ddev->driver->open)
ret = ddev->driver->open(ddev, priv);
if (ret)
goto out_free;
filp->private_data = priv;
return 0;
out_free:
kfree(priv);
return ret;
}
2.5 三者的关联关系
下面的 SVG 图展示了 drm_driver、drm_device 和 drm_file 之间的引用与生命周期关系:
2.6 初始化流程总结
完整的初始化时序如下:
- 模块加载: 驱动注册 PCI/platform 驱动,
drm_driver静态结构体已就绪。 - 设备探测: 内核调用驱动的
probe()函数,在其中分配drm_device,初始化模式配置,注册设备。 - 设备注册:
drm_dev_register()创建设备节点/dev/dri/card0,用户态可开始访问。 - 用户态打开: 每次
open()调用触发drm_open(),分配并初始化drm_file,调用驱动的open回调。 - 用户态操作: 通过 IOCTL 与
drm_device交互,drm_file跟踪会话状态。 - 设备移除:
drm_dev_unregister()销毁设备节点,释放drm_device。
调试建议: 在
probe() 和 open() 回调中添加 DRM_DEBUG_DRIVER() 日志,可以清晰追踪每个数据结构的初始化状态。使用 cat /sys/kernel/debug/dri/0/name 可验证设备是否注册成功。
2.7 本章小结
drm_driver是静态描述符,定义驱动能力和回调;drm_device是运行时设备实例,管理所有模式对象;drm_file是用户态会话句柄,实现进程隔离。- 初始化顺序严格遵循:驱动定义 → 设备分配 → 模式对象初始化 → 设备注册 → 用户态打开。
- 三个结构体通过指针和链表相互引用,形成完整的 DRM 驱动骨架。