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函数里,你需要做几件事:

  1. 分配并初始化drm_device结构体
  2. 设置DRM驱动回调(drm_driver
  3. 调用drm_dev_register注册设备
  4. 配置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_devicedrm_driver这两个核心结构体,看看它们到底有哪些字段需要你填充。准备好了吗?