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,其执行流程可概括为以下关键步骤:

  1. 检查设备状态: 确认设备尚未被注册,且主次设备号已分配。
  2. 创建设备节点: 通过 drm_minor_alloc 为 primary 和 render 节点分配 minor 结构。
  3. 注册字符设备: 调用 drm_sysfs_device_add 在 sysfs 中创建设备节点。
  4. 初始化客户端列表: 初始化 filelist 等管理结构。
  5. 调用驱动回调: 执行 dev->driver->load 或 dev->driver->late_register 钩子函数。
  6. 标记注册完成: 设置 dev->registered 标志位。
关键数据结构: struct drm_device 中的 registered 字段是一个原子位标志,用于防止重复注册和并发访问问题。
drm_dev_register 核心流程 drm_dev_register 入口 检查设备状态与 minor 分配 drm_sysfs_device_add drm_minor_register 调用 late_register 钩子 注册完成 / 设备可见 说明:每个步骤都可能失败,失败时调用 drm_dev_unregister 进行回滚清理

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
错误处理 失败时自动回滚已执行步骤 通常不处理错误,强制清理
drm_dev_register 与 drm_dev_unregister 对称架构 drm_dev_register ① drm_minor_alloc ② drm_sysfs_device_add ③ drm_minor_register ④ late_register 回调 ⑤ 设置 registered 标志 drm_dev_unregister ① 清除 registered 标志 ② early_unregister 回调 ③ drm_minor_unregister ④ drm_sysfs_device_remove ⑤ drm_minor_free 虚线连接表示逆序对应关系:后注册的先注销

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 放在第一步。这样可以确保在设备对外可见之前,所有内部状态都已就绪。