2. 显示子系统基础

在嵌入式Linux与桌面Linux中,显示子系统负责把应用层绘制的像素数据,经过内存、显示控制器、时序编码与物理接口,最终送到LCD、HDMI、DP或MIPI DSI等显示设备上。理解这一链路,是后续进行KMS组件初始化、驱动移植与显示问题排查的基础。Linux显示子系统经历了从早期Framebuffer(fbdev)到DRM/KMS的演进,现代SoC与GPU驱动几乎都围绕DRM/KMS构建。

一、从Framebuffer到DRM/KMS:显示子系统的演进

Framebuffer是Linux最早的显示抽象,核心思想是:内核暴露一个/dev/fb0设备,用户空间通过mmap把显存映射到进程地址空间,直接写入像素即可显示。它简单直接,适合固定分辨率、单图层、无硬件加速的场景。但Framebuffer只描述“一块显存”,不描述显示时序、图层合成、多屏管理、页面翻转等现代显示能力。

DRM(Direct Rendering Manager)最初用于管理GPU渲染与显存,KMS(Kernel Mode Setting)则把显示模式设置从用户空间X Server下沉到内核。DRM/KMS共同提供了:显示模式枚举与设置、多图层合成、页面翻转(Page Flip)、垂直同步、多屏输出、原子提交(Atomic Commit)等能力。其用户空间接口是/dev/dri/card0,常用库为libdrm,现代合成器如Weston、Android SurfaceFlinger、Xorg modesetting都基于它。

核心区别:Framebuffer是“一块显存”的抽象;DRM/KMS是“显示管线”的抽象。前者适合简单显示,后者适合多图层、多屏、高刷新率与硬件合成。

二、DRM/KMS核心对象与数据流

KMS把显示管线拆成若干可独立配置的对象,它们通过“属性(Property)”和“状态(State)”描述,最终由原子提交一次性生效。最核心的对象包括:

  • Framebuffer(FB):描述一块可供扫描输出的像素缓冲区,包含格式、宽高、pitch、GEM handle等信息。它是“图像内容”的载体。
  • Plane(平面):图层抽象,负责把某个Framebuffer按位置、缩放、旋转、混合方式叠加到CRTC上。常见类型有Primary、Overlay、Cursor。
  • CRTC:显示控制器通道,负责从Plane取像素,按显示时序(hdisplay/vdisplay、hsync/vsync、前后沿)扫描输出,并产生像素时钟。一个CRTC通常对应一个物理显示通道。
  • Encoder:把CRTC输出的并行像素信号转换为特定接口格式,如RGB、LVDS、HDMI TMDS、MIPI DSI、DP等。
  • Connector:代表物理输出接口与连接状态,如HDMI-A-1、eDP-1、DSI-1。它负责EDID读取、热插拔检测、链路训练与模式枚举。

它们的关系可以概括为:Framebuffer提供图像,Plane负责图层叠加,CRTC负责时序扫描,Encoder负责信号编码,Connector负责物理连接与显示设备。用户空间通过drmModeSetCrtc或原子提交接口,把FB、Plane、CRTC、Connector绑定成一条完整显示链路。

DRM/KMS 显示管线对象关系 Framebuffer 像素缓冲区 Plane 图层叠加/缩放 CRTC 时序扫描/像素时钟 Encoder 信号编码 Connector HDMI/eDP/DSI 显示设备 LCD / 显示器 扫描输出 合成 编码 物理连接 热插拔/EDID 用户空间配置路径 libdrm / 合成器 drmModeSetCrtc Atomic Commit 属性/状态提交 内核 DRM 驱动回调 典型调用顺序: 1. 枚举资源 drmModeGetResources 2. 选择 Connector/CRTC/Plane 3. 创建 FB 并提交显示模式

三、典型应用场景与对象映射

不同显示场景对KMS对象的组合方式不同。下面表格给出常见场景与关键对象的对应关系,便于在驱动开发与调试中快速定位。

应用场景 Framebuffer Plane CRTC Encoder Connector
单屏桌面显示 主帧缓冲 Primary CRTC0 HDMI/DVI HDMI-A-1
视频播放叠加字幕 视频帧 + 字幕帧 Overlay + Primary CRTC0 HDMI HDMI-A-1
双屏扩展 FB0 + FB1 Primary×2 CRTC0 + CRTC1 HDMI + eDP HDMI-A-1 + eDP-1
MIPI DSI 面板 主帧缓冲 Primary CRTC0 DSI DSI-1
硬件光标 光标位图 Cursor CRTC0 HDMI HDMI-A-1

四、驱动开发中的关键流程与代码片段

在KMS驱动初始化中,通常需要注册CRTC、Plane、Encoder、Connector,并建立它们之间的绑定关系。以下伪代码展示了核心注册顺序:

/* 1. 初始化 CRTC */
drm_crtc_init_with_planes(drm, crtc, primary_plane, cursor_plane,
                          &my_crtc_funcs, NULL);

/* 2. 初始化 Encoder */
drm_encoder_init(drm, encoder, &my_encoder_funcs,
                 DRM_MODE_ENCODER_TMDS, NULL);

/* 3. 初始化 Connector */
drm_connector_init(drm, connector, &my_connector_funcs,
                   DRM_MODE_CONNECTOR_HDMI);

/* 4. 绑定 Encoder 与 Connector */
drm_connector_attach_encoder(connector, encoder);

/* 5. 注册模式与属性 */
drm_mode_connector_attach_encoder(connector, encoder);
drm_connector_attach_edid_property(connector);

用户空间设置显示模式时,典型命令与日志如下:

$ modetest -M my_drm -c
Connectors:
id  encoder status      name        size (mm)  modes  encoders
36  35      connected   HDMI-A-1    520x290    3      35
  modes:
    #0 1920x1080 60.00 1920 2008 2052 2200 1080 1084 1089 1125

$ modetest -M my_drm -s 36@35:1920x1080-60
setting mode 1920x1080-60Hz on connectors 36, crtc 35

五、常见误区与排障复盘

误区1:把Framebuffer当成DRM/KMS的全部。fbdev只是兼容层,现代驱动中/dev/fb0往往由DRM的fbdev模拟提供,真正控制显示的是KMS对象。

误区2:只配置Connector,忽略CRTC与Plane的绑定。显示链路必须完整:FB→Plane→CRTC→Encoder→Connector,缺一不可。

误区3:认为Encoder和Connector一一对应。一个Encoder可以连接多个Connector,一个Connector也可以切换不同Encoder,具体由驱动与硬件拓扑决定。

排障建议:遇到“黑屏但系统正常”时,按以下顺序检查:
1. modetest -c 确认Connector状态是否为connected;
2. 检查CRTC是否被占用、Plane是否绑定正确;
3. 查看内核日志dmesg | grep -i drm,关注EDID读取、链路训练、时序设置失败信息;
4. 确认Framebuffer格式与Plane支持的格式匹配,避免因格式不支持导致提交失败。

掌握Framebuffer、Plane、CRTC、Encoder、Connector这五个核心对象及其数据流,就抓住了Linux显示子系统的骨架。后续无论是编写KMS驱动、移植MIPI DSI面板,还是排查HDMI无输出问题,都可以沿着这条链路逐级定位。