3. Linux显示子系统演进与DRM定位

在嵌入式Linux图形显示的发展过程中,FBDEV(Framebuffer Device)与DRM/KMS(Direct Rendering Manager / Kernel Mode Setting)代表了两个不同时代的内核显示抽象。理解它们之间的演进逻辑,是掌握STM32MP平台上显示工程实践的基础。FBDEV的设计目标非常朴素:为上层提供一个线性帧缓冲区,应用程序通过/dev/fb0映射内存后直接写入像素数据,内核负责把这块内存扫描输出到显示控制器。这种模型在单图层、单分辨率、无硬件合成需求的场景下足够简单高效,但它把显示模式设置、图层管理、内存管理和同步机制全部推给了用户空间或驱动私有实现,导致多图层叠加、页面翻转、垂直同步、热插拔检测等功能难以标准化。

DRM/KMS的出现正是为了解决这些结构性问题。KMS负责显示控制器侧的硬件抽象,包括CRTC、Encoder、Connector、Plane等对象的建模与模式设置;DRM则负责GPU与显示共享的内存管理、命令提交、同步对象和权限控制。两者合在一起,形成了现代Linux显示栈的核心。与FBDEV相比,DRM/KMS把“谁在什么位置显示什么内容”变成了一组可原子提交的状态对象,支持多图层、原子模式设置、显式同步和动态连接器管理。对于STM32MP这类带有LTDC显示控制器和GPU的SoC,DRM/KMS能够更自然地表达硬件能力。

从迁移原因看,FBDEV的局限主要体现在四个方面。第一,模式设置接口不统一,不同驱动通过ioctl私有命令设置分辨率,用户空间难以通用。第二,多图层支持薄弱,FBDEV通常只暴露一个帧缓冲,硬件图层能力被隐藏。第三,缺少页面翻转和垂直同步的标准机制,视频播放和游戏容易出现撕裂。第四,内存管理分散,显示缓冲与GPU缓冲难以统一分配和同步。DRM/KMS通过atomic提交、drm_plane、drm_crtc、dma_fence等机制,把这些问题纳入统一框架。因此,新平台和新驱动通常优先采用DRM/KMS,FBDEV更多作为兼容层存在。

STM32MP 显示栈:FBDEV 兼容层与 DRM/KMS 主路径 用户空间 应用 / Qt / GStreamer libdrm / Mesa fbdev 兼容应用 /dev/fb0 节点 内核空间 DRM/KMS 核心 CRTC · Plane · Connector Atomic 提交 · Fence 同步 STM32 LTDC 驱动 stm32-drm / ltdc 图层混合 · 时序配置 FBDEV 兼容层 drm_fbdev_generic 将 DRM 帧缓冲映射为 fb0 GEM / DMA-BUF 显存分配与共享 V4L2 / GPU 节点 /dev/video · /dev/dri/renderD128 DMA-BUF 导入导出 零拷贝跨设备共享 STM32MP 硬件 LTDC 显示控制器 2 图层 + 背景色 DSI / RGB 接口 时序输出 GPU / DDR 渲染与带宽 主路径:应用 → libdrm → DRM/KMS → LTDC;兼容路径:fbdev 应用 → /dev/fb0 → drm_fbdev_generic → DRM

在DRM框架内部,职责划分可以按对象来理解。drm_crtc代表显示控制器的一次扫描输出,负责时序生成和像素流;drm_plane代表硬件图层,负责从帧缓冲中取像素并做格式转换、缩放和混合;drm_encoder把CRTC的像素流转换为接口信号,例如DSI或RGB;drm_connector代表物理输出接口和显示设备,负责EDID读取、热插拔检测和连接状态。用户空间通过libdrm或Mesa操作这些对象,提交atomic请求,内核在垂直消隐期间原子地切换状态。STM32MP的LTDC驱动正是按这个模型注册CRTC、Plane和Connector,把两个硬件图层暴露为可独立配置的DRM Plane。

在STM32MP平台上,fbdev与DRM并不是非此即彼的关系。实际工程中常见三种共存与切换策略。第一种是纯DRM路径,内核配置CONFIG_DRM_STM32并关闭CONFIG_FB,用户空间使用/dev/dri/card0和libdrm,适合新项目。第二种是DRM主路径加fbdev兼容层,启用CONFIG_DRM_FBDEV_EMULATION,由drm_fbdev_generic把DRM帧缓冲映射为/dev/fb0,让旧应用无需修改即可运行,但会占用一个额外的帧缓冲对象。第三种是双驱动并存,内核同时加载stm32fb和stm32-drm,通过设备树status属性或内核启动参数选择实际生效的驱动。需要注意的是,LTDC硬件只有一套,两个驱动不能同时控制同一个显示控制器,因此并存通常意味着编译期共存、运行期二选一。

切换策略的关键在于设备树和内核配置。以STM32MP157为例,若使用DRM,设备树中ltdc节点保持status = "okay",并确保stm32-drm驱动匹配;若使用fbdev,则可能需要关闭DRM节点或使用video=参数指定模式。启动参数video=HDMI-A-1:1280x720@60或video=800x480@60可以影响KMS的模式选择。对于需要同时兼容新旧应用的场景,推荐启用fbdev仿真,让/dev/fb0由DRM后端提供,这样既保留DRM的原子提交能力,又给旧程序留出接口。

分析典型启动日志,可以清晰看到DRM初始化流程。以下是一段STM32MP平台启动日志的示例片段,用于说明各阶段顺序:

[    0.000000] Linux version 5.15.67-stm32mp1
[    1.234567] stm32-dsi 5a000000.dsi: supply phy not found, using dummy regulator
[    1.345678] stm32-drm 5a001000.ltdc: bound 5a000000.dsi (ops stm32_dsi_ops)
[    1.456789] [drm] Initialized stm32 1.0.0 20201110 for 5a001000.ltdc on minor 0
[    1.567890] stm32-drm 5a001000.ltdc: [drm] fb0: stm32drmfb frame buffer device
[    1.678901] [drm] Initialized stm32-dsi 1.0.0 20201110 for 5a000000.dsi on minor 1
[    1.789012] Console: switching to colour frame buffer device 100x30
[    2.345678] stm32-drm 5a001000.ltdc: [drm] Connector DSI-1: connected
[    2.456789] stm32-drm 5a001000.ltdc: [drm] CRTC 0: mode 800x480@60

从日志顺序看,初始化大致分为五个阶段。第一阶段是DSI PHY和DSI控制器探测,日志中stm32-dsi先出现,说明接口驱动先于显示控制器完成绑定。第二阶段是LTDC与DSI绑定,bound 5a000000.dsi表示encoder与connector建立连接。第三阶段是DRM设备注册,Initialized stm32 ... on minor 0表示/dev/dri/card0创建成功。第四阶段是帧缓冲创建,fb0: stm32drmfb说明fbdev仿真层已经生成/dev/fb0。第五阶段是连接器状态检测和模式设置,Connector DSI-1: connected与CRTC 0: mode 800x480@60表明显示链路已经就绪。如果日志中缺少connected或出现not connected,则应检查DSI线序、背光使能和面板供电。

在调试时,可以用 modetest -M stm32 查看CRTC、Plane和Connector的当前状态;用 cat /sys/class/drm/card0-DSI-1/status 读取连接器状态;用 dmesg | grep -i drm 过滤DRM初始化日志。若/dev/fb0存在但/dev/dri/card0不存在,通常说明只加载了fbdev驱动,需要检查内核配置和设备树节点状态。

综合来看,从FBDEV到DRM/KMS的迁移不是简单的接口替换,而是显示模型从“一块内存”升级为“一组可原子提交的状态对象”。在STM32MP平台上,理解LTDC、DSI、CRTC、Plane、Connector之间的对应关系,掌握fbdev仿真层的启用条件和切换方法,再结合启动日志判断初始化卡在哪一阶段,就能在工程中快速定位显示问题,并为后续的图层配置、页面翻转和GPU协同打下基础。