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驱动,入口函数通常执行以下步骤:
- 平台驱动注册:调用
platform_driver_register()将驱动注册到平台总线。 - 设备匹配:内核根据设备树或ACPI表匹配设备。
- 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。
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的硬件抽象与编程模型。