4. GPU驱动注册流程:pci_driver / platform_driver 的 probe 回调、drm_dev_alloc 与 drm_dev_register 的调用链

GPU 驱动在内核中的注册流程是显示子系统初始化的核心环节。无论是独立显卡(PCIe 设备)还是嵌入式 GPU(平台设备),驱动都必须通过标准的内核驱动模型完成设备探测、DRM 核心对象分配以及设备注册。本章深入剖析 pci_driver / platform_driver 的 probe 回调、drm_dev_alloc 与 drm_dev_register 的完整调用链,揭示从硬件发现到 DRM 子系统的桥梁构建过程。

4.1 驱动注册入口:pci_driver 与 platform_driver

Linux 内核使用统一的设备驱动模型,GPU 驱动根据总线类型注册为 pci_driver(独立显卡)或 platform_driver(SoC 集成 GPU)。两者的核心结构体均包含 probe 回调函数,当内核发现匹配的设备时自动调用。

总线类型 驱动结构体 典型场景 probe 触发条件
PCI struct pci_driver 独立显卡(NVIDIA/AMD/Intel dGPU) PCI 子系统枚举到匹配 VID/PID 的设备
Platform struct platform_driver 嵌入式 GPU(ARM Mali/Qualcomm Adreno) 设备树(DT)或 ACPI 匹配到 compatible 属性
关键点: probe 回调是 GPU 驱动生命周期的起点,所有后续的 DRM 对象分配、内存管理初始化、中断注册等操作均在此函数中串联。

4.2 probe 回调的标准执行流程

无论是 PCI 还是平台设备,probe 回调通常遵循以下标准步骤,这些步骤构成了 GPU 驱动初始化的骨架:

  1. 设备启用与资源获取:启用 PCI 设备(pci_enable_device)、获取 MMIO 基地址(pci_iomap / platform_get_resource)、注册中断(request_irq / devm_request_irq)。
  2. DRM 设备分配:调用 drm_dev_alloc 分配 struct drm_device 对象,并传入驱动私有结构体的大小。
  3. 驱动私有数据初始化:初始化 GPU 核心状态,包括命令处理器、内存管理器(TTM / GEM)、显示控制器、编解码引擎等。
  4. DRM 设备注册:调用 drm_dev_register 将设备注册到 DRM 子系统,暴露给用户空间。
  5. 错误处理与清理:任何步骤失败时,回滚已分配的资源并返回错误码。
最佳实践: 现代 GPU 驱动推荐使用 devm_drm_dev_alloc 替代 drm_dev_alloc,前者利用设备资源管理(devres)自动处理内存释放,减少内存泄漏风险。

4.3 drm_dev_alloc:DRM 核心对象的诞生

drm_dev_alloc 是 DRM 子系统的核心分配函数,原型如下:

struct drm_device *drm_dev_alloc(struct drm_driver *driver,
                                 struct device *parent);

该函数执行以下关键操作:

  • 分配 struct drm_device 内存,并根据 driver->driver_features 初始化内部标志位。
  • 初始化 DRM 框架内部数据结构:drm_mode_config、drm_file 管理、drm_vblank 等。
  • 如果驱动定义了 driver->load 回调(旧接口),则调用它;新驱动应使用 drmm_add_action 或 devm_drm_dev_alloc 进行资源管理。
  • 返回 struct drm_device 指针,驱动可将其转换为包含私有数据的容器结构体。
注意: drm_dev_alloc 仅分配对象并初始化框架内部状态,不会 将设备暴露给用户空间。设备对用户可见需要后续调用 drm_dev_register。

4.4 drm_dev_register:设备对外可见

drm_dev_register 是驱动注册的最后一步,它将 DRM 设备注册到字符设备层和 sysfs,使得用户空间可以通过 /dev/dri/cardN 访问 GPU:

int drm_dev_register(struct drm_device *dev, unsigned long flags);

注册过程包含以下子步骤:

步骤 操作 说明
1 字符设备注册 调用 register_chrdev 或 cdev 接口,创建设备节点
2 sysfs 创建 在 /sys/class/drm/ 下创建 cardN 目录及属性文件
3 connector 注册 遍历所有 drm_connector,注册到 sysfs 并触发 hotplug 事件
4 render node 创建 如果支持,创建 /dev/dri/renderD* 节点用于计算任务
5 回调通知 调用 driver->load(如果存在)或 drm_driver->postregister

4.5 完整调用链 SVG 架构图

下图展示了从内核设备模型到 DRM 子系统的完整调用链,涵盖 PCI 和平台两种总线路径:

GPU驱动注册完整调用链 GPU 驱动注册调用链:从总线探测到 DRM 注册 PCI 设备枚举 pci_driver.probe() 设备树匹配 platform_driver.probe() 设备启用 & 资源获取 drm_dev_alloc() 分配 drm_device + 私有数据 驱动私有数据初始化 GEM/TTM/显示控制器/IRQ drm_dev_register() 字符设备 + sysfs + connector ⚠ 关键时序 drm_dev_alloc 必须在 drm_dev_register 之前完成

4.6 PCI 驱动 probe 回调代码示例

以下代码展示了典型的 PCI GPU 驱动 probe 回调实现,突出 drm_dev_alloc 与 drm_dev_register 的调用位置:

static int gpu_pci_probe(struct pci_dev *pdev,
                         const struct pci_device_id *ent)
{
    struct drm_device *dev;
    struct my_gpu_private *priv;
    int ret;

    // 1. 启用 PCI 设备
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    // 2. 分配 DRM 设备 + 私有数据
    dev = drm_dev_alloc(&my_gpu_driver, &pdev->dev);
    if (IS_ERR(dev)) {
        pci_disable_device(pdev);
        return PTR_ERR(dev);
    }

    priv = to_my_gpu_private(dev);
    priv->pdev = pdev;
    pci_set_drvdata(pdev, dev);

    // 3. 初始化 GPU 核心(MMIO、IRQ、GEM、显示等)
    ret = my_gpu_hw_init(priv);
    if (ret)
        goto err_free;

    // 4. 注册 DRM 设备
    ret = drm_dev_register(dev, 0);
    if (ret)
        goto err_hw_fini;

    drm_info(dev, "GPU probe success\n");
    return 0;

err_hw_fini:
    my_gpu_hw_fini(priv);
err_free:
    drm_dev_put(dev);
    pci_disable_device(pdev);
    return ret;
}

4.7 平台驱动 probe 回调差异

平台设备驱动的 probe 回调与 PCI 版本结构相似,但资源获取方式不同:

操作 PCI 驱动 Platform 驱动
设备启用 pci_enable_device() platform_device_enable() 或直接使用 devm
MMIO 获取 pci_iomap(pdev, bar, len) platform_get_resource(pdev, IORESOURCE_MEM, 0)
中断注册 pci_alloc_irq_vectors() + request_irq() platform_get_irq() + devm_request_irq()
DMA 设置 pci_set_dma_mask() dma_set_mask_and_coherent()

4.8 错误处理与资源回滚

GPU 驱动注册过程中,任何步骤失败都必须正确回滚已分配的资源。以下是常见的错误处理模式:

  • 使用 goto 标签:按初始化逆序释放资源,避免重复代码。
  • 利用 devres 框架:使用 devm_ 系列函数(如 devm_ioremap_resource、devm_request_irq),在 probe 失败或设备移除时自动释放。
  • drm_dev_put 与 drm_dev_unregister:drm_dev_alloc 分配的对象必须通过 drm_dev_put 释放;已注册的设备需先调用 drm_dev_unregister 再释放。
推荐模式: 使用 devm_drm_dev_alloc 替代 drm_dev_alloc,该函数将 drm_device 的生命周期绑定到父设备,probe 失败或设备移除时自动调用 drm_dev_put,显著简化错误处理逻辑。

4.9 注册流程中的常见陷阱

陷阱 1: 在 drm_dev_register 之前暴露设备给用户空间。用户空间可能在注册完成前打开设备节点,导致竞态条件。解决方案:确保所有内部状态初始化完成后再调用 drm_dev_register。
陷阱 2: 忽略 drm_dev_register 的返回值。该函数可能因 sysfs 创建失败、内存不足等原因返回错误,驱动必须检查并处理。
陷阱 3: 在 probe 中执行耗时操作(如固件加载、长时硬件初始化)。这会导致内核启动延迟,甚至触发 hung task 检测。建议将耗时操作移至 drm_driver->load 回调或工作队列中异步执行。

4.10 总结:注册流程的关键时序

GPU 驱动注册流程的核心时序可归纳为以下三点:

  1. 先分配,后注册:drm_dev_alloc 必须在 drm_dev_register 之前调用,确保 DRM 核心对象完全初始化。
  2. 先硬件,后框架:硬件资源(MMIO、IRQ、内存管理器)的初始化应在 drm_dev_register 之前完成,避免注册后设备状态不一致。
  3. 先注册,后暴露:drm_dev_register 完成后,设备才对用户空间可见,驱动应确保此时所有功能已就绪。