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设备注册的完整调用链:
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失败的原因。