4、显示驱动框架:DRM/KMS框架介绍、Framebuffer、Plane、CRTC、Encoder、Connector
好,咱们今天来啃一块硬骨头——显示驱动框架。说实话,很多做8155开发的朋友,一开始都被这套东西绕晕过。我当年第一次接触DRM/KMS时,看着那一堆英文缩写,心里直犯嘀咕:这玩意儿到底是干嘛的?
其实说白了,DRM/KMS就是Linux内核里负责管理显示的那一套机制。你想想看,8155要驱动三块屏幕,每块屏幕可能分辨率不同、刷新率不同、甚至接口类型也不同。如果没有一个统一的框架来管这事,那代码得写成什么样?
DRM全称是Direct Rendering Manager,KMS全称是Kernel Mode Setting。这两个东西合在一起,就是咱们今天要聊的主角。
4.1 DRM/KMS框架概览
先说说DRM。它最初是为图形加速设计的,后来慢慢把显示控制这部分也收了进来。KMS呢,就是负责显示模式设置的那部分——分辨率、刷新率、颜色深度这些。
我个人习惯把DRM/KMS理解成一个「显示流水线」。数据从应用程序下来,经过内核,最终打到屏幕上。这条流水线上有五个关键角色:
- Framebuffer:存放像素数据的内存区域
- Plane:可以叠加的图像层
- CRTC:负责扫描输出,生成时序信号
- Encoder:将像素数据编码成特定接口信号
- Connector:物理接口,比如HDMI、DP、DSI
这五个角色是怎么配合的?我画个简单的流程给你看:
应用程序 → Framebuffer → Plane → CRTC → Encoder → Connector → 显示器
嗯,这里要注意,实际硬件上可能不止一套这样的流水线。8155这种SoC,内部通常集成了多个显示控制器,每个控制器可以独立驱动一块屏幕。
4.2 Framebuffer:像素数据的家
Framebuffer,直译过来就是「帧缓冲」。它本质上是一块内存区域,里面存着每个像素的颜色值。应用程序把画面画到Framebuffer里,显示硬件再从里面读出来显示。
我在项目中遇到过一个问题:Framebuffer的分配位置很讲究。如果分配在DDR里,访问延迟高,影响刷新率。如果分配在SRAM里,空间又不够。8155的解决方案是——用DDR,但通过硬件优化来降低延迟。
创建Framebuffer的典型代码:
struct drm_framebuffer *fb;
uint32_t handles[4], pitches[4], offsets[4];
/* 分配内存并获取handle */
drmModeAddFB(fd, width, height, depth, bpp, stride, &fb_id);
/* 或者用更现代的API */
drmModeAddFB2(fd, width, height, pixel_format,
handles, pitches, offsets, &fb_id, 0);
这里有个坑:drmModeAddFB只支持有限的像素格式,而drmModeAddFB2支持更多格式。我建议你直接用后者,省得后面踩坑。
4.3 Plane:图层叠加的艺术
Plane,你可以把它理解成一个「透明胶片」。每张胶片上画着一层内容,把它们叠在一起,就是最终画面。
为什么需要Plane?你想想看,车载系统上经常要同时显示导航地图、音乐播放器、倒车影像。如果每次都要把所有内容合成一张图再显示,那CPU/GPU的负担得多重?有了Plane,每个应用只管自己的图层,硬件自动完成叠加。
Plane有三种类型:
| 类型 | 用途 | 典型场景 |
|---|---|---|
| Primary Plane | 主图层,必须存在 | 桌面背景、主界面 |
| Overlay Plane | 叠加图层,可选 | 视频播放、悬浮窗 |
| Cursor Plane | 光标图层 | 鼠标指针、触摸反馈 |
我曾经在调试8155双屏显示时,发现副屏的画面总是闪烁。查了半天,原来是副屏的Overlay Plane没有正确设置Z-order。说白了就是图层叠放顺序搞反了,底层画面盖住了上层。
4.4 CRTC:显示时序的指挥官
CRTC,全称Cathode Ray Tube Controller。名字虽然古老,但功能依然核心——它负责生成显示时序信号。
什么是时序信号?就是告诉显示器:「现在该扫描第几行第几列了」。CRTC内部有一个计数器,按照设定的分辨率、刷新率,精确地产生行同步、场同步信号。
在8155上,每个显示接口都对应一个CRTC。比如:
- CRTC0 → 主屏(LVDS或eDP)
- CRTC1 → 副屏(HDMI)
- CRTC2 → 第三屏(DP或DSI)
配置CRTC时,最关键的是mode参数。mode里包含了分辨率、刷新率、时钟频率等信息。我习惯用drmModeGetConnector获取显示器支持的mode列表,然后选一个最合适的。
drmModeConnector *connector = drmModeGetConnector(fd, connector_id);
drmModeModeInfo *mode = &connector->modes[0]; // 选第一个,通常是首选模式
drmModeSetCrtc(fd, crtc_id, fb_id, x, y, &connector_id, 1, mode);
嗯,这里要注意:drmModeSetCrtc这个调用会直接切换显示模式。如果在切换过程中没有处理好Framebuffer的更新,可能会出现短暂的黑屏。我建议你在切换前先准备好新的Framebuffer,然后用drmModePageFlip做无缝切换。
4.5 Encoder:信号转换的桥梁
Encoder,编码器。它的任务是把CRTC输出的并行像素数据,转换成特定接口需要的串行信号。
不同的接口需要不同的Encoder:
- LVDS Encoder:把RGB信号转成LVDS差分对
- HDMI Encoder:把RGB信号转成TMDS信号,还要嵌入音频
- DP Encoder:把RGB信号转成DisplayPort的微包架构
- DSI Encoder:把RGB信号转成MIPI DSI的差分信号
在8155上,Encoder通常是硬件IP核,驱动里只需要配置几个寄存器就行。但有一个细节容易忽略——Encoder的电源管理。我曾经遇到过一个问题:系统休眠后唤醒,副屏不亮了。查到最后发现是Encoder的时钟没有重新使能。
4.6 Connector:物理接口的管家
Connector,连接器。它代表的是物理接口本身——HDMI插座、DP插座、LVDS排线座等等。
Connector负责检测显示器是否连接、读取EDID信息(显示器参数)、管理热插拔事件。在8155的多屏系统中,每个Connector对应一块屏幕。
获取Connector信息的典型流程:
/* 1. 获取所有Connector */
drmModeRes *res = drmModeGetResources(fd);
for (int i = 0; i < res->count_connectors; i++) {
drmModeConnector *conn = drmModeGetConnector(fd, res->connectors[i]);
/* 2. 检查连接状态 */
if (conn->connection == DRM_MODE_CONNECTED) {
/* 3. 读取EDID */
drmModePropertyBlob *edid = get_edid(fd, conn);
parse_edid(edid);
}
drmModeFreeConnector(conn);
}
drmModeFreeResources(res);
这里有个实用技巧:对于固定连接的屏幕(比如LVDS内屏),Connector的状态永远是DRM_MODE_CONNECTED。但对于HDMI外接屏,你需要监听热插拔事件。我一般用drmHandleEvent来处理:
/* 监听热插拔事件 */
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if (select(fd + 1, &fds, NULL, NULL, NULL) > 0) {
drmEventContext evctx = {
.version = DRM_EVENT_CONTEXT_VERSION,
.vblank_handler = NULL,
.page_flip_handler = handle_page_flip,
};
drmHandleEvent(fd, &evctx);
}
4.7 多屏异显的实战要点
好了,五个角色都介绍完了。咱们回到8155多屏异显的场景,看看它们是怎么配合的。
假设你要驱动三块屏幕:
- 主屏(LVDS,1920x1080)
- 副屏(HDMI,1280x720)
- 第三屏(DSI,800x480)
在DRM/KMS框架下,你需要:
- 为每块屏幕分配独立的Framebuffer
- 为每块屏幕配置独立的CRTC
- 为每块屏幕选择合适的Encoder和Connector
- 确保每个CRTC的时钟独立配置,互不干扰
我个人的经验是:先让单屏跑通,再逐步增加屏幕。每增加一块屏幕,都要验证时序是否独立、Framebuffer是否隔离、热插拔是否正常。
modetest工具可以快速验证DRM/KMS配置。命令格式:modetest -M msm -c(msm是8155的DRM驱动名)。它会列出所有Connector、CRTC、Plane的信息,非常直观。
最后说一句:DRM/KMS的学习曲线确实陡峭。但一旦你理解了这五个核心概念,8155的多屏异显就不再神秘。说白了,就是给每块屏幕配一套「Framebuffer→Plane→CRTC→Encoder→Connector」的流水线,然后让它们各自独立运行。
下一章,咱们会深入8155的显示控制器硬件,看看这些概念在芯片层面是怎么实现的。