2、驱动模型概览:Linux DRM/KMS框架介绍,驱动在操作系统中的位置与作用
好,我们正式开始聊驱动模型。这一章,我会带你看看DRM/KMS这个框架到底长什么样,以及你的驱动代码在操作系统里到底扮演什么角色。
说实话,很多初学者一上来就盯着寄存器手册看,结果越看越懵。为什么?因为你不知道这些寄存器操作最终要服务于谁。驱动不是孤立的,它是操作系统的一部分。你得先看清全局,再动手写代码。
2.1 驱动在操作系统中的位置
先问个问题:你的GPU驱动代码,跑在什么权限级别?
答案是内核态。嗯,这一点很重要。用户空间的应用程序,比如一个游戏或者一个桌面合成器,不能直接操作硬件。它们通过系统调用,经过内核,再到达你的驱动。
我画个简单的分层给你看:
+---------------------+
| 用户空间应用 | (游戏、Wayland合成器、X11)
+---------------------+
| 系统调用接口 | (open, ioctl, mmap)
+---------------------+
| DRM核心层 | (drm.ko)
+---------------------+
| GPU驱动 | (你的代码,比如 rockchip-drm.ko)
+---------------------+
| 硬件 | (GPU、Display Controller)
+---------------------+
你的驱动就在这个位置。它向上对接DRM核心层,向下操作硬件寄存器。说白了,你是个翻译官——把内核的标准请求,翻译成硬件能懂的时序和指令。
核心要点:驱动不是万能的。它只负责一件事:让硬件能被内核管理。至于上层应用怎么用,那是DRM核心和libdrm的事。
2.2 DRM/KMS框架是什么?
DRM全称是Direct Rendering Manager,直接渲染管理器。KMS是Kernel Mode Setting,内核模式设置。这两个东西经常一起出现,但侧重点不同。
我个人习惯这样理解:
- DRM:负责GPU的渲染和内存管理。比如分配显存、提交命令缓冲区。
- KMS:负责显示输出。比如设置分辨率、刷新率、连接显示器。
你想想看,一个嵌入式设备,GPU可能很弱,但显示控制器是必须有的。所以很多嵌入式GPU驱动,其实主要工作在KMS部分。渲染部分可能只是简单实现,甚至直接走CPU软渲染。
我的经验:我在做全志平台驱动时,发现它的GPU(Mali 400)渲染能力有限,但显示控制器很灵活。所以我把大部分精力花在了KMS的crtc和plane配置上。渲染部分,反而用了现成的Mali驱动。
2.3 DRM核心对象模型
DRM/KMS框架定义了几个核心对象。你写驱动,本质上就是实现这些对象的回调函数。它们分别是:
| 对象 | 作用 | 对应硬件 |
|---|---|---|
| CRTC | 控制显示时序,生成扫描信号 | Display Controller |
| Encoder | 将像素数据编码为特定接口信号 | HDMI、LVDS、DSI发送器 |
| Connector | 代表物理连接器,检测显示器插拔 | HDMI接口、VGA接口 |
| Plane | 代表一个显示层,支持硬件叠加 | 硬件图层(Overlay) |
| FB | 帧缓冲,存放像素数据的内存区域 | 显存中的一块区域 |
这些对象之间怎么连接?我举个例子:
FB (帧缓冲) --> Plane (图层) --> CRTC (控制器) --> Encoder (编码器) --> Connector (接口) --> 显示器
数据流就是这样走的。你配置好每个环节,图像就能从内存一路跑到屏幕上。
注意:不是所有硬件都有独立的Plane。很多低端嵌入式芯片只有一个主图层,那你就只需要实现一个primary plane。别被框架吓到,按需实现即可。
2.4 驱动初始化流程
写一个DRM驱动,入口函数通常是这样的:
static struct platform_driver my_gpu_driver = {
.probe = my_gpu_probe,
.remove = my_gpu_remove,
.driver = {
.name = "my-gpu",
.of_match_table = my_gpu_of_match,
},
};
module_platform_driver(my_gpu_driver);
在probe函数里,你需要做几件事:
- 分配并初始化
drm_device结构体 - 设置DRM驱动回调(
drm_driver) - 调用
drm_dev_register注册设备 - 配置KMS对象(CRTC、Encoder、Connector等)
我曾经犯过一个低级错误:在probe里直接操作硬件寄存器,但忘了检查时钟是否已经使能。结果系统直接挂死。嗯,从那以后我养成了一个习惯——先确认基础资源(时钟、电源、复位)都到位了,再碰寄存器。
2.5 避坑指南:框架理解上的常见误区
我见过不少新手,包括我自己早期,容易陷入几个误区:
- 误区一:以为DRM驱动必须实现完整的GPU渲染。其实很多嵌入式设备只做显示输出,渲染交给CPU或单独的GPU内核模块。
- 误区二:把KMS和framebuffer设备搞混。framebuffer是老的接口,KMS是新的。现在内核推荐用KMS,别再用fbdev那套了。
- 误区三:忽略设备树。嵌入式平台下,你的驱动依赖设备树来获取寄存器地址、中断号、时钟频率。设备树配错了,驱动写得再好也没用。
我的建议:刚开始接触DRM/KMS时,别急着写完整驱动。先在内核源码里找一个简单的DRM驱动,比如drivers/gpu/drm/tiny/下的那些小驱动,读一遍代码。它们麻雀虽小五脏俱全,能帮你快速理解框架。
2.6 本章小结
这一章我们聊了:
- 驱动在内核中的位置——它是硬件和内核之间的桥梁
- DRM/KMS框架的分工——DRM管渲染和内存,KMS管显示输出
- 五个核心对象——CRTC、Encoder、Connector、Plane、FB
- 驱动初始化的大致流程
下一章,我们会深入drm_device和drm_driver这两个核心结构体,看看它们到底有哪些字段需要你填充。准备好了吗?