4. DRM 设备注册与初始化
DRM 子系统的设备注册与初始化是驱动开发者必须掌握的核心流程。本章将深入剖析三种关键机制:PCI/平台驱动注册、DRM 设备对象的创建与注册,以及 组件化驱动模型 的解耦设计。理解这些流程,是编写稳定、可扩展显示驱动的基石。
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 中,驱动需要完成以下步骤:
- 启用设备:调用
pci_enable_device()。 - 设置总线主控:调用
pci_set_master()启用 DMA。 - 获取 MMIO 资源:使用
pci_iomap()映射 BAR 空间。 - 分配 IRQ:调用
pci_request_irq()注册中断处理函数。 - 进入 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);
注册过程包括:
- 创建设备节点
/dev/dri/cardN。 - 注册
drm_minor到文件系统。 - 调用
driver->load()回调(如果定义了)。 - 注册
drm_connector和drm_encoder到 sysfs。 - 创建 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;
}
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;
}
4.3.4 组件匹配的三种方式
内核提供了多种组件匹配方式,以适应不同的硬件拓扑:
- 设备树节点匹配:通过
component_match_add()传入比较函数,遍历设备树子节点。 - 设备名称匹配:通过
component_match_add_release()指定设备名称。 - 自定义数据匹配:通过
component_match_add_fwnode()使用 fwnode 句柄。
4.4 本章小结
DRM 设备注册与初始化是显示驱动开发的骨架。开发者需要掌握:
- 总线驱动层:根据硬件接口选择 PCI 或平台驱动,在 probe 中完成硬件启用和资源获取。
- DRM 设备层:严格遵循
drm_dev_alloc→ 管线创建 →drm_dev_register的三步流程。 - 组件化模型:利用 component helper 解决多模块依赖,实现解耦和有序初始化。
- 在
drm_dev_register之后才创建 CRTC/Connector,导致用户空间看到空管线。 - 组件化模型中,master 的 bind 回调未检查所有子组件是否已 bind,导致空指针访问。
- PCI 驱动中忘记调用
pci_set_master(),导致 DMA 操作失败。