4. DRM 驱动模型:DRM 驱动注册流程与 struct drm_driver 结构体详解

DRM 驱动模型是 Linux 显示子系统从 Framebuffer 演进到现代图形栈的核心。与传统的 fbdev 驱动不同,DRM 驱动需要遵循一套标准化的注册流程,并通过 struct drm_driver 结构体向内核框架暴露驱动能力。本节将深入剖析 DRM 驱动的初始化路径与关键数据结构。

4.1 DRM 驱动注册全流程

一个典型的 DRM 设备驱动从硬件探测到用户空间可见,需要经历以下关键阶段:

  1. 平台设备匹配:驱动通过 platform_driver 或 pci_driver 与硬件设备树/ACPI 表匹配,触发 probe() 回调。
  2. 核心数据结构分配:调用 drm_dev_alloc() 分配 struct drm_device,并初始化 struct drm_driver 回调表。
  3. 硬件资源初始化:映射 I/O 内存、注册中断、初始化 CRTC/Encoder/Connector/Plane 等显示管道组件。
  4. DRM 设备注册:调用 drm_dev_register() 将设备注册到 DRM 核心,创建设备节点 /dev/dri/cardX。
  5. fbdev 兼容层(可选):通过 drm_fbdev_generic_setup() 或自定义 fbdev 模拟层,为遗留应用提供 /dev/fbX 接口。
移植经验: 从 fbdev 迁移到 DRM 时,probe() 函数是重写工作量最大的部分。原 fbdev 驱动中的 xxxfb_probe() 需要拆解为 DRM 的 bind() 回调,并将显存管理从 fb_info 迁移到 drm_gem 对象。
DRM 驱动注册流程 DRM 驱动注册流程 平台设备匹配 probe() drm_dev_alloc() 硬件资源初始化 drm_dev_register() fbdev 兼容层 关键说明 • probe() 返回 0 表示成功 • drm_dev_alloc 分配 struct drm_device • 硬件初始化包括: - 寄存器映射 - 中断注册 - CRTC/Encoder/ Connector 创建 • 注册后生成 /dev/dri/cardX • fbdev 兼容层可选 通过 drm_fbdev_generic_setup

4.2 struct drm_driver 结构体详解

struct drm_driver 是 DRM 驱动的“身份证”与“功能清单”,它定义了驱动名称、支持的 IOCTL、内存管理方式、GEM 操作、fops 文件操作等核心回调。该结构体在 include/drm/drm_drv.h 中定义,是每个 DRM 驱动必须填充的入口。

4.2.1 核心成员分类

分类 成员名称 说明 移植注意事项
基本信息 driver_feature DRM 功能标志位掩码,如 DRIVER_GEM、DRIVER_MODESET 必须包含 DRIVER_GEM 和 DRIVER_MODESET
基本信息 name / desc 驱动名称与描述字符串 与设备树 compatible 属性保持一致
文件操作 fops 指向 struct file_operations,定义 open/release/ioctl/mmap 通常使用 drm_driver_fops 模板
GEM 操作 gem_create_object 创建 GEM 对象回调,用于分配显存 fbdev 驱动需新增 GEM 分配逻辑
GEM 操作 gem_prime_import_sg_table DMA-BUF 导入回调,用于多设备共享显存 嵌入式平台需实现 dma_alloc_attrs
显示管道 irq_handler 中断处理函数,处理 VBLANK 等事件 原 fbdev 中断逻辑可直接复用
显示管道 get_vblank_timestamp 获取 VBLANK 时间戳,用于页面翻转同步 硬件需支持精确时间戳寄存器
调试支持 debugfs_init 初始化 debugfs 调试接口 可选,但强烈建议实现

4.2.2 典型驱动实例代码

以下是一个嵌入式 DRM 驱动的 struct drm_driver 填充示例,展示了从 fbdev 移植时的关键适配点:

static const struct drm_driver xxx_drm_driver = {
    .driver_features    = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
    .name               = "xxx_display",
    .desc               = "XXX SoC Display Controller DRM Driver",
    .date               = "20250101",
    .major              = 1,
    .minor              = 0,

    /* GEM 相关回调 */
    .gem_create_object  = xxx_gem_create_object,
    .gem_prime_import_sg_table = xxx_gem_prime_import_sg_table,
    .dumb_create        = xxx_dumb_create,
    .dumb_destroy       = xxx_dumb_destroy,
    .dumb_map_offset    = xxx_dumb_map_offset,

    /* 文件操作 */
    .fops               = &xxx_drm_fops,

    /* 中断与 VBLANK */
    .irq_handler        = xxx_irq_handler,
    .get_vblank_timestamp = xxx_get_vblank_timestamp,

    /* 调试 */
    .debugfs_init       = xxx_debugfs_init,
};
移植提示: 从 fbdev 迁移时,dumb_create 回调对应原 fbdev 中的 fb_ops.fb_alloc_cmap 和显存分配逻辑。DRM 的 dumb buffer 是用户空间通过 IOCTL 分配的简单帧缓冲,必须保证物理连续(对于不支持 MMU 的嵌入式设备尤为重要)。

4.3 DRM 设备注册核心 API

驱动在 probe() 函数中需要按顺序调用以下 API 完成注册:

  • drm_dev_alloc(&xxx_drm_driver, &pdev->dev):分配并初始化 struct drm_device,绑定驱动结构体。
  • dmm_xxx_init(drm):自定义函数,初始化显示控制器硬件、创建 CRTC/Encoder/Connector/Plane。
  • drm_dev_register(drm, 0):将设备注册到 DRM 子系统,生成 /dev/dri/card0 节点。
  • drm_fbdev_generic_setup(drm, 32):创建 fbdev 兼容层,32 表示 bpp。
struct drm_driver 结构体关系 struct drm_driver 核心成员关系 struct drm_driver 基本信息 name / desc driver_features 文件操作 fops open/release/ioctl GEM 操作 gem_create_object dumb_create / prime_import 显示管道 irq_handler get_vblank_timestamp 调试支持 debugfs_init debugfs 文件创建 每个分支对应驱动必须实现的一组回调函数 移植时需逐一填充,缺失会导致功能异常

4.4 移植适配关键点

常见陷阱:
  • driver_features 遗漏:未设置 DRIVER_MODESET 会导致 modeset 功能不可用,用户空间无法设置显示模式。
  • fops 未正确赋值:直接使用 drm_driver_fops 模板即可,不要自定义 unlocked_ioctl,否则会破坏 DRM 核心的 IOCTL 分发机制。
  • GEM 对象未实现:即使只做简单 framebuffer,也必须实现 dumb_create,否则 modetest 等工具无法分配显存。
  • 中断处理不完整:VBLANK 中断必须调用 drm_crtc_handle_vblank(),否则页面翻转会卡死。

4.4.1 从 fbdev 到 DRM 的成员映射

fbdev 成员 DRM 对应成员 迁移要点
fb_ops.fb_setcolreg drm_mode_config_funcs.fb_create 颜色表管理由 DRM 核心接管,驱动只需提供 framebuffer 创建
fb_ops.fb_pan_display drm_crtc_funcs.page_flip 页面翻转通过 atomic 或 legacy page_flip 实现
fb_var_screeninfo drm_display_mode 显示时序参数结构体不同,需转换
fb_info.screen_base drm_gem_object.vaddr 显存映射从固定地址改为 GEM 对象管理
fb_info.fix.smem_start drm_gem_object.dma_addr 物理地址通过 GEM 的 dma_buf 获取

4.5 注册流程代码骨架

以下是一个完整的 probe() 函数骨架,展示了 DRM 驱动注册的标准写法:

static int xxx_drm_probe(struct platform_device *pdev)
{
    struct drm_device *drm;
    int ret;

    /* 1. 分配 DRM 设备 */
    drm = drm_dev_alloc(&xxx_drm_driver, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    platform_set_drvdata(pdev, drm);

    /* 2. 硬件初始化:CRTC、Encoder、Connector、Plane */
    ret = xxx_display_init(drm);
    if (ret)
        goto err_free;

    /* 3. 注册 DRM 设备 */
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_deinit;

    /* 4. 创建 fbdev 兼容层 */
    drm_fbdev_generic_setup(drm, 32);

    dev_info(&pdev->dev, "XXX DRM driver loaded successfully\n");
    return 0;

err_deinit:
    xxx_display_deinit(drm);
err_free:
    drm_dev_put(drm);
    return ret;
}
经验总结: DRM 驱动的注册流程比 fbdev 更加规范化,但灵活性也更高。核心原则是“先分配、再初始化、后注册”。drm_dev_alloc 与 drm_dev_register 必须配对使用,错误路径中调用 drm_dev_put 释放资源。fbdev 兼容层建议放在最后,即使失败也不应阻塞主驱动的注册。