1. 课程导入

在嵌入式Linux设备中,“把画面显示出来”看似是一件理所当然的事情,但在这背后,内核需要完成显存管理、图层合成、时序控制、背光调节、格式转换等一系列复杂工作。早期方案以 FBDEV(Framebuffer Device) 为主,它用一块线性帧缓冲描述屏幕,简单直接;而现代方案则普遍转向 DRM/KMS(Direct Rendering Manager / Kernel Mode Setting),用更结构化的对象模型管理显示管线。理解这两者的定位差异,是后续驱动移植工作的起点。

一、显示子系统的演进脉络

嵌入式Linux显示子系统大致经历了三个阶段。第一阶段是裸帧缓冲阶段,应用直接操作 /dev/fb0,通过 mmap 映射显存后写入像素数据,内核只负责把这块内存扫描输出到屏幕。第二阶段是FBDEV 多图层扩展阶段,部分 SoC 厂商在 FBDEV 框架上增加 ioctl 来支持硬件叠加层(Overlay)、YUV 图层和色彩键,但这些扩展缺乏统一标准,移植性差。第三阶段是DRM/KMS 统一阶段,内核用 CRTC、Encoder、Connector、Plane 等抽象描述显示硬件,用户态通过 libdrm 或 Wayland 合成器统一管理,支持原子提交、多平面合成和动态模式设置。

这一演进并非单纯“新替代旧”,而是显示需求从“能显示”走向“高效、可组合、可扩展”的必然结果。尤其在多屏、高分辨率、视频播放和 GPU 合成场景下,FBDEV 的线性模型逐渐力不从心。

嵌入式Linux显示子系统演进与FBDEV/DRM定位对比 阶段一:裸帧缓冲 /dev/fb0 + mmap 单块线性显存 无图层合成概念 代表:早期 S3C2410 阶段二:FBDEV扩展 厂商私有 ioctl Overlay / YUV / ColorKey 接口不统一,移植困难 代表:Freescale IPU 阶段三:DRM/KMS CRTC/Encoder/Connector Plane 多图层 + 原子提交 统一用户态接口 代表:i.MX8 / RK3588 FBDEV 定位 • 面向“一块显存”的简单模型 • 用户态直接 mmap 写像素 • 无统一多图层/原子提交 • 适合简单 UI、低分辨率场景 • 内核中逐步被标记为 legacy DRM/KMS 定位 • 面向“显示管线”的对象模型 • Plane 合成 + CRTC 时序输出 • 原子提交保证状态一致性 • 支持多屏、GPU 合成、Wayland • 现代嵌入式显示的主流方向

二、FBDEV 与 DRM/KMS 的核心差异

从驱动开发者视角看,两者最本质的差异在于抽象层次。FBDEV 把显示硬件抽象成“一块可写的帧缓冲”,驱动只需实现 fb_ops 中的 fb_fillrect、fb_copyarea、fb_imageblit 等回调,以及 fb_check_var、fb_set_par 等模式设置接口。而 DRM/KMS 把显示硬件拆解为多个可独立配置的对象,驱动需要分别实现 Plane、CRTC、Encoder、Connector 的初始化与状态提交。

下表从多个维度对比两者的定位差异,便于在移植前建立整体判断。

对比维度 FBDEV DRM/KMS
核心抽象 单块线性帧缓冲 Plane / CRTC / Encoder / Connector
用户态接口 /dev/fbX + ioctl + mmap /dev/dri/cardX + libdrm / atomic ioctl
多图层支持 厂商私有扩展,不统一 Plane 原生支持,可叠加合成
模式设置 fb_set_par,粒度粗 Atomic Commit,状态一致
与 GPU 协同 弱,通常各管各的 强,GEM/DMA-BUF 共享缓冲
典型场景 简单 UI、低分辨率、老平台 多屏、视频、Wayland、现代 SoC

三、一个典型场景:从 FBDEV 到 DRM 的切换动因

假设你在一款 i.MX6 平台上维护旧产品,系统使用 FBDEV 显示 Qt 界面,运行稳定。现在产品升级到 i.MX8,需要支持双屏异显、视频播放和 GPU 合成。此时若继续沿用 FBDEV,会遇到几个硬伤:双屏需要两套独立帧缓冲和私有 ioctl;视频图层无法与 UI 图层统一合成;GPU 渲染结果难以直接送入显示管线。而 DRM/KMS 天然支持多 CRTC、多 Plane 和 DMA-BUF 共享,能够把 GPU、VPU 和显示控制器串联成一条完整管线。

移植判断要点:当项目出现多屏、多图层、GPU 合成、Wayland 或高分辨率需求时,应优先评估 DRM/KMS 方案;若只是简单单屏 UI 且平台老,FBDEV 仍可短期维持,但需关注内核社区对 FBDEV 的维护状态。

四、常见误区与复盘

  • 误区一:DRM 只是“更复杂的 FBDEV”。实际上 DRM 的对象模型和提交机制与 FBDEV 有本质区别,不能简单套用 fb_ops 的思路。
  • 误区二:移植就是改设备树。设备树只描述硬件连接,驱动中的 CRTC/Plane 初始化、时钟、电源域、桥接芯片配置都需要逐一适配。
  • 误区三:用户态不用改。从 /dev/fb0 切换到 /dev/dri/card0,Qt、SDL、GStreamer 的显示后端都需要重新配置或替换。
  • 误区四:忽略原子提交的异步性。DRM 的 atomic commit 是异步生效的,调试时要关注 flip 完成事件和 fence,而不是写完寄存器就认为已经生效。
学习建议:先建立“显示管线”的整体图景,再逐层深入。把 CRTC 理解为“扫描输出引擎”,Encoder 理解为“信号编码器”,Connector 理解为“物理接口”,Plane 理解为“图层”,这样后续看驱动代码时不会迷失在结构体里。

五、本课程整体框架与学习目标

本课程围绕“从 FBDEV 到 KMS 切换”这一主线展开,整体分为四个部分。第一部分是基础认知,包括显示子系统演进、FBDEV 与 DRM/KMS 定位差异、DRM 对象模型与数据流。第二部分是驱动移植实操,涵盖设备树配置、CRTC/Encoder/Connector 驱动适配、Plane 与格式协商、时钟与电源管理。第三部分是用户态切换,包括 libdrm 编程、Qt/SDL 显示后端替换、Wayland 合成器对接。第四部分是调试与优化,涉及 dmesg 日志分析、modetest 验证、性能调优和常见故障排查。

完成本课程后,你应该能够:读懂一个 SoC 的 DRM 驱动结构;根据硬件手册完成显示管线适配;把原有 FBDEV 应用迁移到 DRM/KMS;并使用标准工具验证显示状态和排查问题。

# 后续章节会反复用到的验证命令示例
modetest -M imx-drm -c          # 查看 Connector 状态
modetest -M imx-drm -p          # 查看 CRTC 与 Plane 能力
cat /sys/kernel/debug/dri/0/state  # 查看当前原子状态
dmesg | grep -i drm             # 查看 DRM 驱动初始化日志

显示子系统的切换不是一次简单的 API 替换,而是从“写显存”到“管理管线”的思维转变。理解 FBDEV 与 DRM/KMS 各自的定位,明确移植的动因与边界,才能在实际项目中做出合理的技术选型,并为后续的驱动适配和用户态迁移打下坚实基础。