4. drm_driver 结构体详解:关键回调函数的作用与配置

struct drm_driver 是 DRM 框架中驱动侧的核心结构体,它定义了驱动程序的能力、文件操作接口、内存管理方式以及私有数据生命周期。每个 DRM 驱动都必须实例化并填充该结构体,然后通过 drm_dev_alloc() 或 drm_dev_register() 注册到内核。本节将深入解析 driver_features、fops、gem_ops 和 prime_ops 四个关键成员。

核心要点: drm_driver 是驱动与 DRM 核心之间的契约,它决定了用户空间能调用哪些 ioctl、驱动使用哪种内存模型(GEM 还是 TTM)、以及如何导出/导入缓冲区。

4.1 driver_features:驱动能力位图

driver_features 是一个 u32 类型的位掩码,用于向 DRM 核心声明驱动支持的特性。DRM 核心在初始化设备时会根据该字段启用或禁用相应的内部逻辑。

特性标志 值 说明
DRIVER_GEM 0x1 支持 GEM(Graphics Execution Manager)内存管理。现代 DRM 驱动必须设置此标志。
DRIVER_MODESET 0x2 支持 KMS(Kernel Mode Setting)。显示驱动必须设置此标志。
DRIVER_RENDER 0x4 支持渲染节点(render node),允许非特权用户空间直接访问 GPU 计算能力。
DRIVER_ATOMIC 0x8 支持原子模式设置(Atomic Modeset),提供事务性、无撕裂的显示更新。
DRIVER_SYNCOBJ 0x10 支持同步对象(syncobj),用于用户空间同步 GPU 和显示操作。
配置建议: 对于显示子系统驱动,至少应设置 DRIVER_GEM | DRIVER_MODESET。如果硬件支持原子提交,强烈建议同时设置 DRIVER_ATOMIC。
driver_features 位图与驱动能力映射 driver_features (u32 位掩码) BIT(0): GEM BIT(1): MODESET BIT(2): RENDER BIT(3): ATOMIC BIT(4): ... GEM 内存管理 (必须) KMS 模式设置 (显示驱动必须) 渲染节点 (可选) 原子模式设置 (推荐) 其他特性 (扩展)

4.2 fops:文件操作回调表

fops 是一个指向 struct file_operations 的指针,它定义了用户空间通过 open()、ioctl()、mmap() 等系统调用与 DRM 设备交互时的行为。DRM 核心提供了通用的 drm_open()、drm_ioctl()、drm_mmap() 等函数,驱动通常只需填充 owner 字段,其余直接使用 DRM 核心实现。

fops 成员 推荐值 说明
owner THIS_MODULE 内核模块所有者,防止模块在使用中被卸载。
open drm_open 打开设备文件,分配并初始化 drm_file 结构体。
release drm_release 关闭设备文件,清理文件私有数据。
ioctl drm_ioctl 分发 ioctl 请求到对应的 DRM 核心或驱动回调。
mmap drm_mmap 将 GEM 对象映射到用户空间地址。
poll drm_poll 支持用户空间通过 poll/select 等待事件(如 VBlank)。
常见错误: 驱动开发者有时会错误地直接使用 drm_ioctl 作为 unlocked_ioctl,而忽略了 compat_ioctl 的配置。对于 32 位兼容层,应同时设置 .compat_ioctl = drm_compat_ioctl。
fops 回调与用户空间系统调用映射 用户空间系统调用 open() ioctl() mmap() poll() close() struct file_operations (fops) drm_open drm_ioctl drm_mmap drm_poll drm_release

4.3 gem_ops:GEM 对象生命周期管理

gem_ops 是一个指向 struct drm_gem_object_funcs 的指针,它定义了 GEM 对象的创建、销毁、内存映射、导出等操作。从 Linux 4.19 内核开始,DRM 核心推荐驱动使用 gem_ops 替代旧的 drm_driver 级别的 GEM 回调。

回调函数 作用 是否必须
free 释放 GEM 对象及其关联的物理内存。 是
open 当 GEM 对象被新的文件描述符引用时调用,用于增加引用计数。 否
close 当文件描述符关闭对 GEM 对象的引用时调用。 否
print_info 用于 debugfs 输出 GEM 对象信息。 否
export 将 GEM 对象导出为 dma-buf,用于跨设备共享。 否(但推荐)
pin 将 GEM 对象固定到物理内存,防止被换出。 否
unpin 解除 GEM 对象的固定状态。 否
vmap 将 GEM 对象映射到内核虚拟地址空间。 否
vunmap 解除内核虚拟地址映射。 否
// 典型的 gem_ops 配置示例
static const struct drm_gem_object_funcs my_gem_funcs = {
    .free = my_gem_free_object,
    .open = my_gem_open,
    .close = my_gem_close,
    .print_info = my_gem_print_info,
    .export = my_gem_export,
    .pin = my_gem_pin,
    .unpin = my_gem_unpin,
    .vmap = my_gem_vmap,
    .vunmap = my_gem_vunmap,
};

// 在 drm_driver 中赋值
static struct drm_driver my_driver = {
    .driver_features = DRIVER_GEM | DRIVER_MODESET,
    .gem_ops = &my_gem_funcs,
    // ... 其他成员
};
最佳实践: 即使驱动不打算实现所有回调,也建议将 gem_ops 初始化为一个非空指针。DRM 核心会检查 gem_ops 是否为 NULL,如果为 NULL 则回退到旧的 drm_driver 级别回调,这可能导致行为不一致。

4.4 prime_ops:dma-buf 导入/导出回调

prime_ops 是一个指向 struct drm_prime_ops 的指针,它定义了驱动如何处理 dma-buf 的导入和导出操作。PRIME 是 DRM 框架中实现跨设备缓冲区共享的机制,基于内核的 dma-buf 子系统。

回调函数 作用 典型实现
init_object 初始化一个空的 GEM 对象,准备从 dma-buf 导入数据。 分配 GEM 对象结构体,但不分配物理内存
begin_cpu_access 在 CPU 访问 dma-buf 前调用,确保缓存一致性。 执行 dma_sync_single_for_cpu()
end_cpu_access 在 CPU 访问 dma-buf 结束后调用。 执行 dma_sync_single_for_device()
gem_prime_export 将 GEM 对象导出为 dma-buf 文件描述符。 调用 drm_gem_prime_export()
gem_prime_import 从 dma-buf 文件描述符导入并创建 GEM 对象。 调用 drm_gem_prime_import()
PRIME 导入/导出流程与 prime_ops 回调 导出路径 (Export) 导入路径 (Import) 1. gem_prime_export() 2. 创建 dma-buf 并关联 GEM 3. 返回 dma-buf fd 给用户空间 1. gem_prime_import() 2. init_object() 初始化 GEM 3. 关联 dma-buf 到 GEM 对象 关键说明 begin_cpu_access / end_cpu_access 在 CPU 访问 dma-buf 时由核心自动调用 驱动只需实现缓存同步操作,无需手动管理 dma-buf 生命周期

4.5 关键回调函数配置总结

下表汇总了 drm_driver 中四个关键成员的配置要点和常见陷阱:

成员 类型 必须配置 常见陷阱
driver_features u32 是 忘记设置 DRIVER_GEM 导致 GEM 功能不可用;未设置 DRIVER_ATOMIC 导致原子提交失败
fops struct file_operations * 是 未设置 .compat_ioctl 导致 32 位应用无法使用;直接使用自定义 ioctl 绕过 DRM 核心分发
gem_ops struct drm_gem_object_funcs * 推荐 设为 NULL 导致回退到旧回调;未实现 free 导致内存泄漏
prime_ops struct drm_prime_ops * 需要 PRIME 时 未实现 begin_cpu_access 导致缓存一致性问题;导入时未正确初始化 GEM 对象
架构决策: 在编写显示驱动时,建议优先使用 gem_ops 而非旧的 drm_driver 级别 GEM 回调。这不仅能获得更好的代码组织,还能利用 DRM 核心提供的默认实现,减少重复代码。对于 PRIME 支持,如果驱动不需要跨设备共享缓冲区,可以将 prime_ops 设为 NULL,DRM 核心会提供基本的 dma-buf 支持。