3. DRM设备注册流程:platform_driver与pci_driver的注册、drm_dev_alloc与drm_dev_register的调用链

DRM子系统的设备注册是显示驱动初始化的核心环节。根据硬件总线类型的不同,Linux内核提供了两种主要的驱动模型:platform_driver(用于SoC集成显示控制器)和pci_driver(用于独立显卡)。无论采用哪种总线,最终都会汇聚到DRM核心层的drm_dev_alloc与drm_dev_register两个关键函数,完成DRM设备的创建与对外可见性注册。

核心理解:总线驱动模型负责“发现硬件”,DRM核心层负责“管理显示资源”。两者通过struct drm_driver桥接,形成完整的设备注册链路。

3.1 两种总线驱动模型的注册入口

下表对比了platform_driver与pci_driver在DRM上下文中的注册差异:

对比维度 platform_driver pci_driver
适用硬件 SoC集成显示控制器(如i.MX、Rockchip、Mediatek) 独立PCIe显卡(如Intel、AMD、NVIDIA)
匹配方式 设备树(Device Tree)compatible字符串 PCI vendor/device ID表
核心结构体 struct platform_driver struct pci_driver
probe回调原型 platform_driver.probe(struct platform_device *) pci_driver.probe(struct pci_dev *, const struct pci_device_id *)
注册函数 platform_driver_register() pci_register_driver()
DRM桥接方式 在probe中调用drm_dev_alloc并绑定platform_device 在probe中调用drm_dev_alloc并绑定pci_dev
关键点:两种驱动模型在probe回调中最终都会调用drm_dev_alloc,区别仅在于传入的父设备指针类型不同——platform驱动传入&pdev->dev,PCI驱动传入&pdev->dev(两者最终都是struct device *)。

3.2 drm_dev_alloc:DRM设备的创建与初始化

drm_dev_alloc是DRM设备对象的分配器,负责在内存中创建struct drm_device实例,并完成基础字段的初始化。其调用链如下:

drm_dev_alloc(struct drm_driver *driver, struct device *parent)
  ├── kzalloc(sizeof(*dev), GFP_KERNEL)          // 分配drm_device内存
  ├── dev->driver = driver                        // 绑定驱动回调
  ├── dev->dev = parent                           // 绑定父设备
  ├── idr_init(&dev->ctx_idr)                     // 初始化上下文ID分配器
  ├── mutex_init(&dev->struct_mutex)              // 初始化结构体互斥锁
  ├── drm_mode_config_init(dev)                   // 初始化模式配置(KMS核心)
  ├── dev->anon_inode = anon_inode_getfile()      // 创建匿名inode用于用户态交互
  └── return dev

该函数执行完毕后,DRM设备对象已在内存中建立,但尚未对外可见。此时设备处于“静默”状态,驱动可以继续完成私有数据、中断、I/O内存映射等初始化工作。

注意事项:drm_dev_alloc不会分配设备号或创建字符设备节点。这些工作由后续的drm_dev_register完成。在alloc和register之间,驱动应完成所有必要的硬件初始化,避免注册后出现未就绪状态。

3.3 drm_dev_register:DRM设备的对外注册

drm_dev_register将之前分配的DRM设备注册到系统中,使其对用户空间可见。其核心调用链如下:

drm_dev_register(struct drm_device *dev, unsigned long flags)
  ├── drm_minor_alloc(dev, DRM_MINOR_PRIMARY)     // 分配主设备号(minor)
  ├── drm_minor_alloc(dev, DRM_MINOR_RENDER)      // 分配渲染设备号(minor)
  ├── drm_sysfs_device_add(dev)                   // 在sysfs中创建设备节点
  ├── drm_debugfs_init(dev)                       // 初始化debugfs调试接口
  ├── dev->driver->load(dev)                      // 调用驱动的load回调(可选)
  ├── drm_setup(dev)                              // 设置文件操作表
  ├── list_add_tail(&dev->driver_item, ...)       // 加入全局DRM设备链表
  └── drm_minor_register(dev, DRM_MINOR_PRIMARY)  // 注册字符设备到VFS

注册完成后,用户空间可以通过/dev/dri/card0、/dev/dri/renderD128等设备节点访问DRM服务。

3.4 完整注册流程SVG架构图

下图展示了从总线驱动匹配到DRM设备注册的完整调用链:

DRM设备注册完整调用链 platform_driver_register() 设备树匹配 → probe() pci_register_driver() vendor/device匹配 → probe() drm_dev_alloc() 驱动私有初始化 drm_dev_register() /dev/dri/card0 对外可见 platform_driver与pci_driver在probe后汇聚到同一DRM核心路径

3.5 驱动实例中的注册代码模式

以下展示两种总线驱动在probe函数中调用DRM核心API的典型代码模式:

platform_driver 模式(SoC显示控制器)

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

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

    // 2. 驱动私有初始化(I/O内存、中断、CRTC/Encoder/Connector)
    platform_set_drvdata(pdev, drm);
    ret = my_drm_hw_init(drm, pdev);
    if (ret)
        goto err_free;

    // 3. 注册DRM设备,对外可见
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_hw_fini;

    return 0;

err_hw_fini:
    my_drm_hw_fini(drm);
err_free:
    drm_dev_put(drm);
    return ret;
}

pci_driver 模式(独立显卡)

static int my_drm_pci_probe(struct pci_dev *pdev,
                            const struct pci_device_id *ent)
{
    struct drm_device *drm;
    int ret;

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

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

    // 3. PCI专用初始化(BAR映射、MSI中断)
    pci_set_drvdata(pdev, drm);
    ret = my_drm_pci_init(drm, pdev);
    if (ret)
        goto err_free;

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

    return 0;

err_pci_fini:
    my_drm_pci_fini(drm);
err_free:
    drm_dev_put(drm);
    return ret;
}
最佳实践:现代DRM驱动推荐使用drm_dev_alloc + drm_dev_register两步走模式,而非旧版drm_get_pci_dev等封装函数。两步模式让驱动在alloc和register之间拥有完整的硬件初始化窗口,避免注册过早导致用户空间访问未就绪设备。

3.6 注册流程中的关键数据结构关系

下表总结了注册过程中涉及的核心数据结构及其职责:

数据结构 分配时机 核心字段/作用
struct drm_device drm_dev_alloc driver(驱动回调)、dev(父设备)、mode_config(KMS配置)、filelist(文件句柄链表)
struct drm_minor drm_dev_register minor(次设备号)、dev(字符设备)、index(索引,用于card0/renderD128命名)
struct drm_driver 静态定义 load/unload/open/release/ioctl等回调函数表,驱动开发者填充
struct drm_mode_config drm_mode_config_init min_width/max_width(分辨率限制)、funcs(模式配置回调)、fb_base(帧缓冲基址)

3.7 注册流程中的常见问题与避坑

  • alloc与register分离不足:部分驱动在alloc后立即register,导致用户空间在硬件未完全初始化时就能打开设备。应在alloc后完成所有硬件准备再调用register。
  • 错误路径资源泄漏:register失败后必须调用drm_dev_put释放alloc分配的内存,否则造成内存泄漏。
  • PCI驱动遗漏pci_enable_device:在调用drm_dev_alloc前必须先使能PCI设备,否则后续I/O操作会失败。
  • platform驱动设备树匹配错误:compatible字符串必须与设备树节点完全一致,否则probe不会被调用。
  • 多实例设备注册冲突:同一系统存在多个DRM设备时,drm_dev_register会自动分配递增的minor号,驱动无需手动管理。
调试技巧:注册完成后,检查/sys/class/drm/目录下的符号链接,确认card0、renderD128等节点已创建。若节点缺失,检查dmesg中drm核心层的错误输出,通常能定位到register失败的原因。