第三章 Linux DRM框架基础:DRM/KMS核心概念与驱动初始化
好,咱们进入第三章。这一章我打算聊聊DRM框架,尤其是KMS那部分。说实话,DRM这玩意儿刚接触时挺绕的,但搞懂了CRTC、Encoder、Connector、Plane这几个核心对象,后面写驱动就顺了。
3.1 DRM/KMS到底是个啥?
DRM全称是Direct Rendering Manager,直接渲染管理器。KMS是Kernel Mode Setting,内核模式设置。说白了,DRM负责图形渲染和显示管理,KMS负责显示模式的切换和配置。
我在MTK平台上做多屏显示时,最深的体会是:DRM把显示硬件抽象成了几个标准组件。你不需要关心底层寄存器怎么配,只需要操作这几个组件就行。
核心思想:DRM/KMS将显示硬件抽象为CRTC、Encoder、Connector、Plane四个对象,驱动开发者只需实现对应的回调函数。
3.2 四大核心对象详解
咱们一个一个来看。这四个对象,我习惯叫它们“显示四件套”。
3.2.1 CRTC(阴极射线管控制器)
CRTC这个名字有点历史感,但功能很实在。它负责生成显示时序,控制扫描输出。说白了,就是告诉显示器“什么时候该刷新哪一行像素”。
- 管理显示模式(分辨率、刷新率)
- 控制帧缓冲区的读取
- 处理垂直同步(VBlank)中断
我在MT8678项目里遇到过一个问题:双屏显示时,两个CRTC的时序必须独立配置。如果共用同一个时序源,第二块屏就会出现撕裂。嗯,这个坑我踩过。
3.2.2 Encoder(编码器)
Encoder负责把CRTC输出的像素数据转换成特定接口的信号。比如HDMI、DP、MIPI DSI这些。
- 将并行数据转为串行信号
- 处理不同接口的电气特性
- 管理热插拔检测
你想想看,MTK平台上的DisplayPort和eDP其实都走同一个Encoder硬件模块,但驱动里要区分不同的Encoder类型。我建议你在dts里就把Encoder的type配清楚,不然后面调试起来很痛苦。
3.2.3 Connector(连接器)
Connector代表物理接口,比如HDMI插座、DP口、LVDS排线。它负责检测显示器是否连接、读取EDID信息。
| Connector类型 | 典型应用 | 注意事项 |
|---|---|---|
| HDMI-A | 电视、外接显示器 | 支持音频传输 |
| DisplayPort | 高分辨率显示器 | 支持多流传输 |
| DSI | 内置LCD屏 | MIPI接口,功耗低 |
| eDP | 笔记本内置屏 | 支持PSR省电 |
我记得有一次调试HDMI输出,死活检测不到显示器。后来发现是Connector的status检测回调没实现,内核以为没插显示器。这个坑,新手很容易掉进去。
3.2.4 Plane(图层)
Plane是DRM/KMS里比较新的概念。它代表一个独立的显示图层。每个Plane可以有自己的帧缓冲区、位置、大小、透明度。
- 主平面(Primary Plane):默认的显示图层
- 叠加平面(Overlay Plane):用于画中画、字幕等
- 光标平面(Cursor Plane):硬件光标
MT8678支持最多4个硬件Plane。我在做多屏异显时,就是利用不同Plane输出不同内容到不同屏幕。说白了,每个屏幕绑定一个CRTC,每个CRTC可以挂多个Plane。
个人经验:Plane的alpha混合功能很强大,但要注意硬件限制。有些Plane不支持全局alpha,只能做像素级混合。写驱动前一定要看datasheet。
3.3 对象之间的连接关系
这四个对象不是孤立的。它们通过“管道”连接在一起:
Plane → CRTC → Encoder → Connector → 显示器
数据流向是这样的:
- Plane提供像素数据(从帧缓冲区读取)
- CRTC生成时序,控制扫描输出
- Encoder将数据编码为接口信号
- Connector将信号送到物理接口
你想想看,这就像一条流水线。每个环节出了问题,显示都会异常。我在项目中遇到过Encoder和Connector不匹配的情况——Encoder输出的是LVDS信号,但Connector配成了HDMI,结果屏幕一片黑。
3.4 drm_device初始化流程
好,理论讲完了,咱们看看实际代码。drm_device的初始化,我总结为“三步走”:
3.4.1 第一步:分配和注册drm_device
struct drm_device *drm_dev;
int ret;
// 分配drm_device
drm_dev = drm_dev_alloc(&mtk_drm_driver, dev);
if (IS_ERR(drm_dev))
return PTR_ERR(drm_dev);
// 设置驱动私有数据
drm_dev->dev_private = mtk_priv;
// 注册drm_device
ret = drm_dev_register(drm_dev, 0);
if (ret)
goto err_free;
这里要注意:drm_dev_alloc和drm_dev_register是分开的。我习惯在probe函数里先alloc,等所有资源都准备好了再register。这样如果中间出错,可以直接释放,不会留下半残的设备。
3.4.2 第二步:初始化mode_config
// 设置mode_config的回调函数
drm_mode_config_init(drm_dev);
drm_dev->mode_config.funcs = &mtk_mode_config_funcs;
drm_dev->mode_config.min_width = 320;
drm_dev->mode_config.max_width = 4096;
drm_dev->mode_config.min_height = 240;
drm_dev->mode_config.max_height = 4096;
mode_config是KMS的核心配置结构。它定义了显示能力的上下限。我曾经把max_width设小了,结果4K屏死活点不亮。调试了两天才发现是这里的问题。
3.4.3 第三步:创建CRTC、Encoder、Connector、Plane
// 创建CRTC
struct drm_crtc *crtc;
crtc = mtk_crtc_create(drm_dev);
if (IS_ERR(crtc))
goto err_mode_config;
// 创建Encoder
struct drm_encoder *encoder;
encoder = mtk_encoder_create(drm_dev);
if (IS_ERR(encoder))
goto err_crtc;
// 创建Connector
struct drm_connector *connector;
connector = mtk_connector_create(drm_dev);
if (IS_ERR(connector))
goto err_encoder;
// 创建Plane
struct drm_plane *plane;
plane = mtk_plane_create(drm_dev, DRM_PLANE_TYPE_PRIMARY);
if (IS_ERR(plane))
goto err_connector;
创建完这些对象后,还要把它们连接起来:
// 将Plane附加到CRTC
drm_crtc_init_with_planes(drm_dev, crtc, plane, NULL, &mtk_crtc_funcs, NULL);
// 将Encoder连接到CRTC
encoder->possible_crtcs = drm_crtc_mask(crtc);
// 将Connector连接到Encoder
connector->encoder_ids[0] = encoder->base.id;
connector->encoder = encoder;
注意:possible_crtcs这个位掩码很容易配错。如果配成0,Encoder就找不到可用的CRTC,显示会失败。我建议用drm_crtc_mask()自动生成。
3.5 完整的初始化流程总结
我把整个流程画成一张表,方便你对照检查:
| 步骤 | 操作 | 关键函数/结构 |
|---|---|---|
| 1 | 分配drm_device | drm_dev_alloc() |
| 2 | 初始化mode_config | drm_mode_config_init() |
| 3 | 创建CRTC | drm_crtc_init_with_planes() |
| 4 | 创建Encoder | drm_encoder_init() |
| 5 | 创建Connector | drm_connector_init() |
| 6 | 创建Plane | drm_universal_plane_init() |
| 7 | 连接对象 | 设置possible_crtcs、encoder_ids |
| 8 | 注册drm_device | drm_dev_register() |
嗯,到这里DRM框架的基础就讲完了。说白了,DRM/KMS就是一套标准化的显示管理框架。你只要把CRTC、Encoder、Connector、Plane这四个对象创建好、连接好,剩下的工作内核框架帮你搞定。
下一章咱们会深入MTK平台的具体实现,看看这些对象在MT8678上是怎么适配的。到时候我会分享一些实际调试中的经验,包括怎么用modetest工具验证你的驱动是否正常工作。
课后练习:试着在开发板上运行modetest -M mtk_drm,看看能不能列出CRTC、Encoder、Connector的信息。如果报错,检查一下你的drm_device初始化是否完整。