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 驱动初始化的骨架:
- 设备启用与资源获取:启用 PCI 设备(
pci_enable_device)、获取 MMIO 基地址(pci_iomap/platform_get_resource)、注册中断(request_irq/devm_request_irq)。 - DRM 设备分配:调用
drm_dev_alloc分配struct drm_device对象,并传入驱动私有结构体的大小。 - 驱动私有数据初始化:初始化 GPU 核心状态,包括命令处理器、内存管理器(TTM / GEM)、显示控制器、编解码引擎等。
- DRM 设备注册:调用
drm_dev_register将设备注册到 DRM 子系统,暴露给用户空间。 - 错误处理与清理:任何步骤失败时,回滚已分配的资源并返回错误码。
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 和平台两种总线路径:
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 注册流程中的常见陷阱
drm_dev_register 之前暴露设备给用户空间。用户空间可能在注册完成前打开设备节点,导致竞态条件。解决方案:确保所有内部状态初始化完成后再调用 drm_dev_register。
drm_dev_register 的返回值。该函数可能因 sysfs 创建失败、内存不足等原因返回错误,驱动必须检查并处理。
drm_driver->load 回调或工作队列中异步执行。
4.10 总结:注册流程的关键时序
GPU 驱动注册流程的核心时序可归纳为以下三点:
- 先分配,后注册:
drm_dev_alloc必须在drm_dev_register之前调用,确保 DRM 核心对象完全初始化。 - 先硬件,后框架:硬件资源(MMIO、IRQ、内存管理器)的初始化应在
drm_dev_register之前完成,避免注册后设备状态不一致。 - 先注册,后暴露:
drm_dev_register完成后,设备才对用户空间可见,驱动应确保此时所有功能已就绪。