4. DRM 设备注册与初始化

DRM 子系统的设备注册与初始化是驱动开发者必须掌握的核心流程。本章将深入剖析三种关键机制:PCI/平台驱动注册、DRM 设备对象的创建与注册,以及 组件化驱动模型 的解耦设计。理解这些流程,是编写稳定、可扩展显示驱动的基石。

核心要点:DRM 设备生命周期分为“分配(alloc)→ 初始化(init)→ 注册(register)”三个阶段。驱动必须按序调用,不可颠倒。

4.1 PCI 驱动与平台驱动注册

DRM 驱动首先是一个标准的 Linux 总线驱动。根据硬件接口类型,驱动开发者需要选择 pci_driver 或 platform_driver 结构体进行注册。

4.1.1 PCI 驱动注册流程

对于独立显卡(如 AMD、NVIDIA、Intel 独显),通常使用 PCI 接口。驱动通过 module_pci_driver() 宏或手动调用 pci_register_driver() 完成注册。

// 定义 PCI ID 表
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x1234, 0x5678) },
    { }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

// PCI 驱动结构体
static struct pci_driver my_pci_driver = {
    .name       = "my_drm_driver",
    .id_table   = my_pci_ids,
    .probe      = my_pci_probe,
    .remove     = my_pci_remove,
};

// 模块入口
module_pci_driver(my_pci_driver);

当 PCI 子系统检测到匹配设备时,调用 probe 函数。在 probe 中,驱动需要完成以下步骤:

  1. 启用设备:调用 pci_enable_device()。
  2. 设置总线主控:调用 pci_set_master() 启用 DMA。
  3. 获取 MMIO 资源:使用 pci_iomap() 映射 BAR 空间。
  4. 分配 IRQ:调用 pci_request_irq() 注册中断处理函数。
  5. 进入 DRM 初始化:调用 drm_dev_alloc() 创建 DRM 设备。

4.1.2 平台驱动注册流程

对于嵌入式 SoC 中的显示控制器(如 Rockchip、Allwinner、i.MX),通常使用 platform_driver。其注册方式与 PCI 类似,但资源获取方式不同。

// 平台驱动结构体
static struct platform_driver my_platform_driver = {
    .probe  = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my_drm_driver",
        .of_match_table = my_of_match,
    },
};

// 模块入口
module_platform_driver(my_platform_driver);

平台驱动的 probe 函数中,通过 platform_get_resource() 获取 I/O 内存和中断号,然后进入 DRM 初始化流程。

特性 PCI 驱动 平台驱动
总线类型 PCI 总线 平台总线(Device Tree)
设备发现 PCI 枚举 + ID 表匹配 Device Tree 节点匹配
资源获取 pci_iomap() platform_get_resource()
电源管理 PCI 标准 PM 回调 Runtime PM + 系统 PM
典型场景 独立显卡 嵌入式 SoC 显示控制器
经验之谈:对于平台驱动,务必在 probe 中检查 of_match_table 的兼容性字符串,确保与设备树节点严格匹配。常见的错误是兼容字符串拼写错误导致 probe 不被调用。

4.2 DRM 设备分配与注册

总线驱动 probe 成功后,核心任务就是创建并注册 DRM 设备。内核提供了两个关键 API:drm_dev_alloc() 和 drm_dev_register()。

4.2.1 drm_dev_alloc:分配与初始化

drm_dev_alloc() 负责分配 struct drm_device 内存,并完成基础初始化。其原型如下:

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

该函数执行的关键操作:

  • 分配 struct drm_device 对象。
  • 初始化内部锁、链表、IDR 映射。
  • 设置 driver 和 dev 指针。
  • 创建 drm_minor 结构(主设备号)。
  • 初始化 mode_config 的默认值。

分配完成后,驱动需要手动设置 drm_device 的 irq、irq_enabled 等字段,并调用 dmm_mode_config_init() 初始化模式配置。

4.2.2 drm_dev_register:对外暴露

当所有内部初始化完成后,调用 drm_dev_register() 将设备注册到用户空间可见的字符设备接口。

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

注册过程包括:

  1. 创建设备节点 /dev/dri/cardN。
  2. 注册 drm_minor 到文件系统。
  3. 调用 driver->load() 回调(如果定义了)。
  4. 注册 drm_connector 和 drm_encoder 到 sysfs。
  5. 创建 debugfs 入口。
注意:在调用 drm_dev_register() 之前,必须确保所有 CRTC、Encoder、Connector 和 Plane 已经创建并添加到 mode_config 中。否则用户空间可能看到不完整的显示管线。

4.2.3 完整初始化序列

现代 DRM 驱动推荐使用 devm_drm_dev_alloc() 进行资源管理分配,并结合 drmm_add_action() 实现自动清理。典型序列如下:

static int my_drm_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 设备(使用 devm 版本自动管理生命周期)
    drm = devm_drm_dev_alloc(&pdev->dev, &my_drm_driver,
                             struct my_drm_private, drm);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    // 3. 初始化模式配置
    drmm_mode_config_init(drm);

    // 4. 创建 CRTC、Encoder、Connector、Plane
    // ... (具体实现见后续章节)

    // 5. 注册 DRM 设备
    ret = drm_dev_register(drm, 0);
    if (ret)
        return ret;

    // 6. 设置驱动私有数据
    pci_set_drvdata(pdev, drm);

    return 0;
}
DRM 设备初始化完整流程 总线 Probe 触发 pci/platform 启用 获取 MMIO / IRQ drm_dev_alloc / devm_drm_dev_alloc drmm_mode_config_init 创建 CRTC / Encoder / Connector / Plane drm_dev_register → 创建设备节点 /dev/dri/card0 可用 关键顺序:总线探测 → 资源获取 → drm_dev_alloc → 管线创建 → drm_dev_register

4.3 组件化驱动模型

在复杂的 SoC 显示系统中,显示管线可能由多个独立的硬件模块组成(如显示控制器、HDMI 发送器、DSI 桥接芯片)。这些模块可能由不同的驱动文件管理,且存在依赖关系。组件化驱动模型(Component Helper)正是为了解决这种解耦与同步问题而设计的。

4.3.1 核心数据结构

组件模型的核心是 struct component 和 struct component_master。每个硬件模块作为一个 component,而负责协调的驱动作为 master。

// 组件结构体(每个硬件模块实现一个)
struct component_ops {
    int (*bind)(struct device *dev, struct device *master, void *data);
    void (*unbind)(struct device *dev, struct device *master, void *data);
};

// 主控器结构体(负责协调的驱动实现)
struct component_master_ops {
    int (*bind)(struct device *dev);
    void (*unbind)(struct device *dev);
};

4.3.2 工作流程

组件化模型的工作流程分为注册、匹配、绑定三个阶段:

阶段 组件(Component) 主控器(Master)
注册 调用 component_add() 注册到全局列表 调用 component_master_add() 注册并声明依赖
匹配 等待 master 调用 component_match_add() 添加匹配规则 遍历所有已注册组件,检查是否满足依赖条件
绑定 调用 component_ops.bind() 完成子模块初始化 调用 master_ops.bind() 完成整体管线组装

4.3.3 实际应用示例

以 i.MX8 显示子系统为例,其组件化驱动实现如下:

// 主控器 probe 函数
static int imx_drm_master_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct component_match *match = NULL;
    int ret;

    // 添加组件匹配规则(通过设备树节点)
    component_match_add(dev, &match, imx_drm_match_component, NULL);

    // 注册主控器
    ret = component_master_add_with_match(dev, &imx_drm_master_ops, match);
    if (ret)
        return ret;

    return 0;
}

// 主控器 bind 回调:组装完整 DRM 设备
static int imx_drm_master_bind(struct device *dev)
{
    struct drm_device *drm;
    int ret;

    drm = drm_dev_alloc(&imx_drm_driver, dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    // 此时所有子组件(HDMI、DSI、DC)已经 bind 完成
    // 可以安全地访问它们的私有数据

    ret = drm_dev_register(drm, 0);
    if (ret) {
        drm_dev_put(drm);
        return ret;
    }

    dev_set_drvdata(dev, drm);
    return 0;
}

// 子组件(如 HDMI 发送器)的 bind 回调
static int imx_hdmi_bind(struct device *dev, struct device *master, void *data)
{
    // 初始化 HDMI 硬件
    // 注册为 DRM 桥接(bridge)
    return 0;
}
设计优势:组件化模型解决了“鸡生蛋蛋生鸡”的依赖问题。HDMI 驱动不需要等待显示控制器驱动就绪,两者可以独立 probe。当所有组件都注册后,master 的 bind 回调统一执行,确保依赖关系正确。

4.3.4 组件匹配的三种方式

内核提供了多种组件匹配方式,以适应不同的硬件拓扑:

  • 设备树节点匹配:通过 component_match_add() 传入比较函数,遍历设备树子节点。
  • 设备名称匹配:通过 component_match_add_release() 指定设备名称。
  • 自定义数据匹配:通过 component_match_add_fwnode() 使用 fwnode 句柄。
组件化驱动模型架构 Master (主控器) Component A (DC) Component B (HDMI) Component C (DSI) component_ops.bind() component_ops.bind() component_ops.bind() Master.bind() → drm_dev_alloc + register 所有组件 bind 完成后,Master 的 bind 才被调用,确保依赖关系正确 组件可以来自不同的驱动文件,通过设备树或名称匹配

4.4 本章小结

DRM 设备注册与初始化是显示驱动开发的骨架。开发者需要掌握:

  • 总线驱动层:根据硬件接口选择 PCI 或平台驱动,在 probe 中完成硬件启用和资源获取。
  • DRM 设备层:严格遵循 drm_dev_alloc → 管线创建 → drm_dev_register 的三步流程。
  • 组件化模型:利用 component helper 解决多模块依赖,实现解耦和有序初始化。
常见错误:
  1. 在 drm_dev_register 之后才创建 CRTC/Connector,导致用户空间看到空管线。
  2. 组件化模型中,master 的 bind 回调未检查所有子组件是否已 bind,导致空指针访问。
  3. PCI 驱动中忘记调用 pci_set_master(),导致 DMA 操作失败。