3. KMS组件初始化入口:drm_dev_register与drm_dev_unregister流程分析
在Linux内核显示子系统中,drm_dev_register 与 drm_dev_unregister 是KMS组件初始化的核心入口与出口。前者负责将已分配并初始化的DRM设备对象注册到内核框架中,使其对外可见并开始提供服务;后者则执行逆操作,安全地移除设备并释放资源。理解这两个函数的内部流程,是掌握KMS驱动加载与卸载机制的关键。
核心概念: DRM设备的生命周期分为三个阶段:分配(Allocate) → 初始化(Init) → 注册(Register)。drm_dev_register 标志着设备从“内部可用”转变为“系统可见”。
3.1 drm_dev_register 整体流程
drm_dev_register 函数位于 drivers/gpu/drm/drm_drv.c,其执行流程可概括为以下关键步骤:
- 检查设备状态: 确认设备尚未被注册,且主次设备号已分配。
- 创建设备节点: 通过
drm_minor_alloc为 primary 和 render 节点分配 minor 结构。 - 注册字符设备: 调用
drm_sysfs_device_add在 sysfs 中创建设备节点。 - 初始化客户端列表: 初始化
filelist等管理结构。 - 调用驱动回调: 执行
dev->driver->load或dev->driver->late_register钩子函数。 - 标记注册完成: 设置
dev->registered标志位。
关键数据结构: struct drm_device 中的
registered 字段是一个原子位标志,用于防止重复注册和并发访问问题。
3.2 drm_dev_unregister 逆操作流程
drm_dev_unregister 是注册的逆过程,用于在驱动卸载或设备热拔出时安全地移除设备。其执行顺序与注册流程严格相反,体现了“后注册的先注销”原则:
| 步骤 | 函数/操作 | 说明 |
|---|---|---|
| 1 | drm_lastclose | 关闭所有打开的文件描述符,释放用户态资源 |
| 2 | drm_legacy_dev_reinit | 清理遗留的DMA和AGP资源(若存在) |
| 3 | drm_minor_unregister | 注销 primary 和 render 节点的字符设备 |
| 4 | drm_sysfs_device_remove | 从 sysfs 中移除设备节点 |
| 5 | drm_minor_free | 释放 minor 结构体内存 |
| 6 | dev->registered = 0 | 清除注册标志,允许后续重新注册 |
注意: 在调用 drm_dev_unregister 之前,驱动应确保所有正在进行的提交(commit)和页面翻转(page flip)操作已经完成或取消,否则可能导致内核崩溃或显示异常。
3.3 注册与注销的对称性分析
从软件架构角度看,drm_dev_register 与 drm_dev_unregister 构成了一个完整的生命周期管理对。下表对比了二者在关键维度上的对应关系:
| 维度 | drm_dev_register | drm_dev_unregister |
|---|---|---|
| 触发时机 | 驱动 probe 成功、设备初始化完成后 | 驱动 remove、设备热拔出、模块卸载时 |
| 设备可见性 | 从不可见 → 可见 | 从可见 → 不可见 |
| sysfs 操作 | 创建设备目录和属性文件 | 删除设备目录和属性文件 |
| 字符设备 | 注册主次设备号,创建设备节点 | 注销设备号,删除设备节点 |
| 驱动回调 | 调用 late_register | 调用 early_unregister |
| 错误处理 | 失败时自动回滚已执行步骤 | 通常不处理错误,强制清理 |
3.4 关键代码路径解析
以下代码展示了 drm_dev_register 的核心实现片段,重点关注其错误处理与回滚机制:
int drm_dev_register(struct drm_device *dev, unsigned long flags)
{
int ret;
if (dev->registered)
return -EBUSY;
ret = drm_minor_alloc(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_primary;
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret)
goto err_render;
ret = drm_sysfs_device_add(dev);
if (ret)
goto err_sysfs;
ret = drm_minor_register(dev, DRM_MINOR_PRIMARY);
if (ret)
goto err_minor_primary;
ret = drm_minor_register(dev, DRM_MINOR_RENDER);
if (ret)
goto err_minor_render;
if (dev->driver->late_register) {
ret = dev->driver->late_register(dev);
if (ret)
goto err_late;
}
dev->registered = 1;
return 0;
err_late:
drm_minor_unregister(dev, DRM_MINOR_RENDER);
err_minor_render:
drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
err_minor_primary:
drm_sysfs_device_remove(dev);
err_sysfs:
drm_minor_free(dev, DRM_MINOR_RENDER);
err_render:
drm_minor_free(dev, DRM_MINOR_PRIMARY);
err_primary:
return ret;
}
EXPORT_SYMBOL(drm_dev_register);
设计模式分析: 上述代码采用了典型的 分层回滚(Layered Rollback) 模式。每个错误标签(err_xxx)只清理到当前步骤为止,确保资源不会泄漏。这种模式在Linux内核驱动中广泛使用。
3.5 驱动开发中的注意事项
- 注册顺序依赖: 驱动应在所有KMS组件(如CRTC、Encoder、Connector、Plane)初始化完成后再调用
drm_dev_register,否则用户态可能在设备节点出现时看到不完整的显示管线。 - 并发安全:
drm_dev_register内部未加锁,调用者需确保在注册期间没有其他线程同时访问drm_device结构。 - 热插拔场景: 对于支持热插拔的显示设备(如USB显示器),
drm_dev_unregister必须在设备断开连接时立即调用,且不能阻塞等待用户态操作完成。 - 调试技巧: 可以通过
cat /sys/kernel/debug/dri/*/name查看当前已注册的DRM设备列表,验证注册是否成功。
最佳实践: 在驱动 probe 函数中,建议将 drm_dev_register 放在最后一步,而在 remove 函数中将 drm_dev_unregister 放在第一步。这样可以确保在设备对外可见之前,所有内部状态都已就绪。