4. KMS驱动框架初始化

KMS(Kernel Mode Setting)驱动的初始化是整个显示子系统启动的基石。本章深入剖析 drm_driver 结构体的定义、模块加载/卸载宏、设备对象的分配与注册,以及设备与驱动的匹配机制。理解这些环节,是编写稳定、可移植的KMS驱动的第一步。

4.1 drm_driver 结构体:驱动的“身份证”

每个DRM/KMS驱动都必须定义一个 struct drm_driver 实例,它向内核描述驱动的能力、回调函数和私有数据。该结构体是驱动与DRM核心层交互的唯一接口。

字段 类型/说明 典型值/作用
driver_features u32 位掩码,声明驱动支持的特性(如 DRIVER_MODESET、DRIVER_ATOMIC)
name const char * 驱动名称,用于 /sys/bus/platform/drivers/ 目录标识
desc const char * 驱动描述,在 /proc/devices 中显示
date const char * 驱动版本日期
major int 主设备号,通常设为0由内核动态分配
minor int 次设备号起始值
fops const struct file_operations * 用户空间文件操作回调(open、ioctl、mmap等)
gem_create_object 回调函数 创建GEM对象,用于内存管理
prime_handle_to_fd 回调函数 PRIME缓冲共享,用于DMA-BUF导出
irq_handler 回调函数 中断处理,处理VBLANK等事件
关键提示: 对于KMS驱动,driver_features 必须包含 DRIVER_MODESET 和 DRIVER_ATOMIC(如果使用原子API)。缺少这些标志会导致CRTC、Encoder等核心对象无法注册。

4.2 module_init / module_exit:驱动的生命周期

Linux内核使用 module_init() 和 module_exit() 宏来管理驱动的加载与卸载。对于DRM驱动,入口函数通常执行以下步骤:

  1. 平台驱动注册:调用 platform_driver_register() 将驱动注册到平台总线。
  2. 设备匹配:内核根据设备树或ACPI表匹配设备。
  3. probe函数触发:匹配成功后,调用驱动的 probe 回调。
// 典型的DRM驱动模块入口/出口
static int __init mykms_init(void)
{
    return platform_driver_register(&mykms_platform_driver);
}

static void __exit mykms_exit(void)
{
    platform_driver_unregister(&mykms_platform_driver);
}

module_init(mykms_init);
module_exit(mykms_exit);

MODULE_AUTHOR("KMS Expert");
MODULE_DESCRIPTION("Sample KMS DRM Driver");
MODULE_LICENSE("GPL");
最佳实践: 使用 module_platform_driver() 宏可以简化上述代码,它自动生成 module_init 和 module_exit 函数。

4.3 drm_dev_alloc 与 drm_dev_register:设备对象的创建与发布

在驱动的 probe 函数中,核心任务是分配并注册DRM设备对象。这两个函数分工明确:

  • drm_dev_alloc():在内存中创建 struct drm_device 实例,初始化内部数据结构(如IDR、列表头),但不对外可见。
  • drm_dev_register():将设备注册到字符设备层,创建设备节点(如 /dev/dri/card0),使设备对用户空间可用。
// probe函数中的典型流程
static int mykms_probe(struct platform_device *pdev)
{
    struct drm_device *drm;
    int ret;

    // 1. 分配DRM设备
    drm = drm_dev_alloc(&mykms_driver, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    platform_set_drvdata(pdev, drm);

    // 2. 初始化硬件(设置MMIO、中断、时钟等)
    ret = mykms_hw_init(drm);
    if (ret)
        goto err_free;

    // 3. 注册DRM设备
    ret = drm_dev_register(drm, 0);
    if (ret)
        goto err_hw_fini;

    return 0;

err_hw_fini:
    mykms_hw_fini(drm);
err_free:
    drm_dev_put(drm);
    return ret;
}
注意: 在 drm_dev_register() 之前,必须完成所有硬件初始化(包括中断注册)。因为注册后用户空间可能立即打开设备并触发操作。

4.4 设备与驱动匹配:设备树与平台总线的桥梁

设备与驱动的匹配是Linux设备模型的核心。对于嵌入式KMS驱动,匹配通常通过设备树(Device Tree)完成。驱动需要提供 of_match_table,内核根据设备树节点的 compatible 属性进行匹配。

匹配方式 数据结构 典型场景
设备树(OF) of_device_id ARM/ARM64嵌入式平台
ACPI acpi_device_id x86/ARM64服务器平台
平台名称 platform_device_id 非设备树传统平台
// 设备树匹配表
static const struct of_device_id mykms_of_match[] = {
    { .compatible = "vendor,mykms-display", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mykms_of_match);

// 平台驱动定义
static struct platform_driver mykms_platform_driver = {
    .probe  = mykms_probe,
    .remove = mykms_remove,
    .driver = {
        .name = "mykms",
        .of_match_table = mykms_of_match,
        .pm = &mykms_pm_ops,  // 电源管理回调
    },
};
匹配流程: 内核启动时解析设备树 → 创建 platform_device → 总线遍历驱动列表 → 调用 of_match_table 比较 compatible 字符串 → 匹配成功则调用 probe。
KMS驱动初始化流程 module_init() platform_driver_register() 设备树匹配 (of_match) probe() 回调 drm_dev_alloc() 硬件初始化 drm_dev_register() /dev/dri/card0 可用 说明:drm_dev_alloc() 和 drm_dev_register() 之间必须完成所有硬件初始化,否则用户空间访问可能导致系统崩溃。

4.5 完整初始化序列总结

将上述环节串联起来,一个完整的KMS驱动初始化序列如下表所示。每个步骤都有明确的职责和依赖关系。

步骤 函数/宏 关键动作 常见错误
1 module_init() 注册平台驱动 忘记调用 platform_driver_register()
2 内核总线匹配 根据 compatible 匹配设备 设备树节点与驱动表不匹配
3 probe() 分配DRM设备、初始化硬件 资源获取失败未回滚
4 drm_dev_alloc() 创建 drm_device 实例 未检查返回值
5 硬件初始化 MMIO映射、中断注册、时钟使能 中断注册顺序错误
6 drm_dev_register() 设备对外可见 注册后修改关键配置
7 module_exit() 注销驱动、释放资源 忘记注销导致内存泄漏
调试技巧: 在 probe 函数中添加 dev_info() 打印,可以跟踪初始化进度。使用 drm_dev_dbg() 可以输出DRM核心层的调试信息。

掌握上述四个核心环节,你就具备了编写KMS驱动初始化代码的基础能力。下一环节将深入CRTC与Plane的硬件抽象与编程模型。