ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux图形显示链路全解析:DRM/KMS、MIPI DSI与点屏调试实战

Linux图形显示链路全解析:DRM/KMS、MIPI DSI与点屏调试实战 嵌入式分享断更好久了这次想认真聊一个几乎每个做板子的人都会撞上的主题Linux图形显示。先交代个背景我这些年被问得最多的求助十个里有八个最后都栽在同一件事上——不是驱动写错了也不是设备树配错了而是对整条图形显示链路缺一张全景图。明明参考设计都抄对了编译烧录一切顺利屏幕就是黑着偶尔亮了分辨率又不对画面还偏到一边去。这篇文章就是把链路上每一个环节拆开讲清楚从应用层怎么把像素交出去到内核DRM怎么调度显示控制器再到MIPI DSI、LVDS这些物理接口的差异最后聊到实际点屏调试的顺序。看过之后你再回头面对一块不亮的屏幕至少能知道该从哪一层开始查。1. 先画一张地图一个像素从App走到屏幕中间要过多少道关卡1.1 链路全景与每一层的职责很多人一说Linux图形显示就直接想到驱动、想到设备树其实那只是链条中比较靠后的部分。一条完整的显示链路大概是这样的应用层Qt、GTK、Chromium→ 图形库OpenGL ES、Vulkan、Cairo→ 窗口系统/合成器X11、Wayland、EGLFS→ libdrm 用户态接口 → /dev/dri/card0 → 内核 DRM/KMS 框架 → SoC 厂商的显示驱动 → 显示控制器VOP、DCSS、DISPC这类硬件模块→ 物理接口DSI、LVDS、RGB、HDMI、eDP→ 面板时序与背光 → 最终的光点。每一级都很重要但出错后的表现往往是同一个字黑、花、偏、闪。为什么这么难查因为从软件的角度你很难直观判断像素到底走到哪一步了。应用层以为自己把画面渲染出来了合成器以为自己把画面提交上去了内核以为自己的 flip 已经完成了可屏幕就是不亮。所以我习惯把这条链路分成三个大段用户态渲染段、内核 KMS 调度段、硬件接口与面板段。排查时从物理那头往回推或者从内核那一层往外推都比一上来就翻 UI 代码靠谱。举个实际的例子。一个跑 Qt 的应用通过 EGLFS 或 Wayland 把渲染结果交给合成器时合成器最终要做的只是一个 DRM 的 page-flip 调用把一张已经渲染好的帧缓冲交给 CRTC。CRTC 负责按固定的扫描时序把数据一行行发出去encoder 再把数据编码成对应接口的电气信号。到了面板那边初始化序列、复位时序、背光电压任何一个没满足条件前面做得再好也是白搭。1.2 黑屏问题的定位思路从哪一层开始切我在实际项目里定位黑屏第一步从来不是看代码而是先确认一个问题内核到底认没认出这块 panel。如果 /sys/class/drm/ 下面连对应的 connector 都没出现那应用层再努力也没用问题在设备树、驱动加载或者面板供电上。如果 connector 出现了但手工用 modetest 都点不亮那问题就在内核驱动到面板之间的时序或初始化序列。如果 modetest 能点亮进应用就黑这才轮到渲染库、合成器、窗口系统背锅。这个顺序非常关键它把黑屏这种模糊症状拆成了三个相对独立的排查区间避免你在一个已经阵亡的环节后面瞎折腾半天。2. fbdev 与 DRM/KMS 的两代架构为什么新代码全都往 DRM 里写2.1 fbdev 模型简单但它只是一块可写内存老一代嵌入式工程师对 /dev/fb0 肯定不陌生。fbdev 的思路非常直白内核开一块内存映射给用户态应用拿到地址后往里面填像素填完屏幕就显示了。开发最早期我确实爱用这一套因为代码极简单一个 open、一个 mmap然后再 memset 一下屏幕就能有颜色。但它的天花板也很明显。fbdev 只管帧缓冲不管扫描时序怎么调不管什么时候到了 vblank也不管撕裂怎么避免。它就像一个只给你一张画纸、不给画架也不给投影仪的画家你可以随时往纸上涂但涂的时候别人正好在翻页纸上就会出现两条错位。更要命的是多个进程都想画系统没法协调画着画着就会互相覆盖。所以现在的 Linux 内核里fbdev 基本只剩下一个兼容层比如用 DRM 的 simpledrm 配合 tinyDRM 来模拟 /dev/fb0给那些只认 fbdev 的旧程序一个台阶下。很多新手拿着老教程在 /dev/fb0 上折腾半天最后发现这接口在新板卡上要么不存在要么就是走了一层转换。这个方向本身就不利于做现代图形栈。2.2 DRM/KMS 的核心抽象把显示当成一台状态机DRMDirect Rendering Manager一开始是为了解决 GPU 渲染时的多进程安全问题后来 KMSKernel Mode Setting把模式设置的活也收进了内核。两者合在一起就是我们常说的 DRM/KMS。这个框架里定义了几个关键对象CRTC、encoder、connector、plane、framebuffer。CRTC负责产生扫描时序相当于行扫描大脑。encoder把 CRTC 输出的并行像素数据编码成某种接口的信号。connector代表一个物理输出口比如 HDMI、DSI、LVDS对应 /sys/class/drm 里的目录。plane显示控制器里的叠加层可以在屏幕上叠加多个不同 alpha 的图层。framebuffer一块具体的内存里面存着要显示的像素。KMS 最关键的设计是引入原子状态。每次模式设置或翻页内核会构造一个新状态校验通过后一次性提交。如果中途出问题整个状态回滚屏幕不会出现一半新一半旧的尴尬局面。这种设计在嵌入式上非常珍贵因为我们的显示控制器往往直接连着真实面板现场没法像台式机那样随便重启一个桌面。2.3 两种架构在实际开发里的取舍说句实在话新板子的 BSP 里基本不会有人再为 fbdev 写详细驱动了。内核社区的态度很明确新显示驱动一律走 DRMfbdev 只保留兼容层。所以你在看新内核的时候会发现很多厂商的显示驱动文件都放在 drivers/gpu/drm/厂商名/ 下面而不是 drivers/video/fbdev/。从开发者的角度我建议即使你的应用只需要刷一张图也优先走 DRM 的用户态接口比如用 libdrm 写一个几十行的小程序通过 ioctl 分配 dumb buffer、map 到用户态、填充像素、再 page-flip。这样程序在新老内核上都能跑而且后续想加多图层、旋转、裁剪这些功能都不必重写。我用下表总结一下两者差异方便你选型的时候对号入座对比项fbdevDRM/KMS接口文件/dev/fb0/dev/dri/card0模式设置基本不管内核统一管理并校验多图层支持无有 plane 概念防撕裂没有标准手段通过 vblank 与 page-flip 机制多进程协调很差有 DRM master 权限管理新驱动支持兼容层主流方向3. 面板那一头MIPI DSI、LVDS、RGB 这些接口协议背后的实际差异3.1 显示控制器和接口之间的关系同一个源不同的出口在嵌入式 SoC 里显示控制器和接口不是一回事。以瑞芯微、全志、NXP 的芯片为例VOP/DCSS 这类显示控制器负责把内存里的像素数据按一定时序读出来输出的是一组并行 RGB 信号再加同步时钟。这个信号要变成 MIPI DSI、LVDS、HDMI就得靠相应的 encoder 模块转换。这也是为什么你会看到一个 SoC 能同时支持多种屏幕接口核心的显示控制器是同一个但引脚复用pinmux决定最终从哪组引脚出去。选型时除了看 SoC 支不支持更要看你的屏幕规格、PCB 布线难度和产品形态。MIPI DSI 引脚少、差分信号、适合手机和平板类设备LVDS 走线较长、抗干扰强、适合 10 寸以上的一体机并行 RGB 走线多、频率低、适合小尺寸廉价屏。3.2 MIPI DSI 的细节lane、时钟和初始化序列是三个坑MIPI DSI 是用 D-PHY 实现的串行差分接口常见的 lane 配置是 1/2/4 对数据 lane 加 1 对时钟 lane。因为是差分信号PCB 上好走线而且频率可以做得很高。那怎么估算需要多少 lane一条很实用的公式lane_rate(Mbps) pixel_clock(MHz) × bpp / lane_count / 2除以 2 是因为 D-PHY 是 DDR 双沿采样一个时钟周期传两个 bit。举个例子720×128060 的屏假设 htotal880、vtotal1320像素时钟大约是 880×1320×60 ≈ 69.7MHz。RGB888 的话 bpp 取 24四 lane 下的每条 lane 速率就是69.7 × 24 / 4 / 2 ≈ 209 Mbps/lane实际留余量选 250 到 300Mbps/lane 附近一般都能稳定跑。DSI 还有一个容易踩的坑command mode 和 video mode。video mode 就是主机像流水一样持续把像素发到面板面板边收边显示command mode 则是把一整帧数据写进面板内部的 RAM面板自己刷新。低功耗小尺寸屏常见 command mode但驱动和帧缓冲的配合逻辑完全不同。如果驱动初始化和面板配置不对常出现背光亮但没有画面的情况。再说初始化序列这是我见过最多人翻车的地方。面板厂商给的 datasheet 里通常有一串 DCS 写寄存器命令比如 0x39 写多字节、0x05 写单字节中间还夹着各种 delay_ms。这些序列要一字不差地照抄到 panel 驱动里顺序不能乱电源域不能缺。很多背光亮屏幕黑的案例查到最后就是某一条初始化命令的 delay 被删掉或缩短了导致面板的 TCON 没起来。3.3 LVDS 与并行 RGB老协议反而更皮实LVDS 是全差分信号一般由 4 对数据 lane 加 1 对时钟 lane 组成单通道带宽大概能扛到 135MHz 像素时钟左右。像素时钟再高就得用双通道也就是两组 LVDS 信号同时传输。常见做法是1024×60060、1280×80060 这类屏用单通道足够1080p 及以上看面板规格上双通道。LVDS 的优势是长距离传输能力强、抗干扰好、时序相对宽松很多工控、医疗设备喜欢用。调试时只要确认 pixel clock、porch 参数和 DE 极性跟面板规格书一致基本能亮。反观并行 RGB虽然原理最简单但 24 根数据线加时钟的扇出在稍高分辨率下对走线和电平要求就很苛刻走线不等长就容易出现花屏或偏色。我做几个常见接口的快速对比方便你心里有个谱接口类型信号特点典型应用调试注意点MIPI DSI串行差分、lane 可配手机、平板、中尺寸屏lane 速率、初始化序列、command/video 模式LVDS并行差分对工控、医疗、一体机单/双通道配置、pixel clock、porchRGB 并行单端并行信号小尺寸 LCD电平匹配、走线等长、DE 极性HDMITMDS 差分多媒体、扩展显示EDID 协商、时钟余量4. 渲染结果怎么走到屏幕CPU、GPU、硬件 plane 和合成的分工4.1 从 framebuffer 到 scanoutdumb buffer、GEM 和 DMA-BUF到了驱动这层所有要显示的内容最终都要存到内存里内核才能用显示控制器去扫屏。DRM 把内存对象抽象成 GEMGraphics Execution Manager而最简单的一种叫 dumb buffer。dumb 的意思就是不加速、不转码就是一块普通的可映射内存适合纯 CPU 绘制的场景。流程大概是应用通过 ioctl 让内核分配一块 dumb buffermmap 到自己进程空间填像素然后调用 atomic commit 把它作为扫描源提交。这个流程写起来不复杂但理解它很重要因为所有上层库最后都在做这件事。GPU 渲染则是另一条路GPU 渲染到一块由 GEM 管理的内存再用 DMA-BUF 机制共享给显示控制器。不同进程之间传 buffer 时依靠 DMA-BUF/PRIME 的 fd 传递不需要拷贝数据。实际开发里常见的一个误区是以为显示控制器读取 framebuffer 是拷贝一次。其实现在的显示控制器都是通过 DMA 直接从内存地址读数据显示控制器和 CPU 的读写是并行的。所以如果 CPU 正在往 buffer 里画图显示控制器正好扫到了同一块区域就会出现撕裂。要避免撕裂必须按照 vblank 的节奏在两次刷新之间提交新的 buffer而不是原地改同一块。4.2 合成不是可有可无的硬件 plane 能省下成吨的 CPU如果你的设备只有一个图层那合成的问题基本不存在。但嵌入式产品常常要同时显示 UI、视频、OSD 信息。这时有两种做法一种是让 CPU 在软件层把三层内容合成到一张图里再整体显示另一种是直接把三个 buffer 分别配置到显示控制器的不同 plane 上让硬件自动叠加显示。硬件 plane 方案的效果非常明显。视频播放器可以把解码后的帧丢到视频 planeUI 放 primary planeOSD 放 overlay plane三块内存互不干扰显示控制器按 zpos 顺序完成叠加CPU 只负载极小的配置工作。反过来如果每次画面变化都要软件合成一整帧分辨率一高纯软件合成的耗电和帧率都很难看。所以选 SoC 时我会优先关注它的 display controller 支持几个 plane、是否支持 scaled 和 rotation、plane 之间是否能 alpha 混合。很多中高端嵌入式 SoC 能提供 2 到 4 个可用 plane利用好它们比在用户态堆优化省心得多。4.3 无 GPU 的场景怎么办软渲染依然是重要拼图并不是所有嵌入式设备都有 GPU。低成本方案里无 GPU 或 GPU 驱动不成熟很常见。这时候渲染就得靠 CPUMesa 的软渲染llvmpipe、softpipe、swrast可以用 CPU 模拟 OpenGL ESweston 合成器也有 pixman-rendererQt 还有 linuxfb 这种直接把像素画到 fb 的古老后端。我在一个低端主控项目上是这么做的app 直接用 Cairo/Pixman 画到一块 dumb buffer然后用 DRM/KMS 做 page-flip。虽然没有 GPU 的流畅度但静态界面刷新没什么压力。要注意的是CPU 渲染时频繁的 cache flush 会拖慢速度画完后尽量集中在切换 buffer 前做一次 flush不要边画边刷屏。另外分辨率越高软件合成越吃力所以无 GPU 场景尽量别上 1080p 以上的复杂动态界面。5. 点屏调试实战从 dmesg 到 modetest 的手工点亮流程5.1 通电之后第一眼该看什么拿到一块换了新 panel 的开发板别急着跑 app。我会按下面这个顺序做硬件基础检查dmesg | grep -i -E drm|dsi|panel|vop看驱动有没有 probe 成功有没有报时序或链路错误。ls /sys/class/drm/看有没有生成 card0-DSI-1、card0-HDMI-A-1 之类的节点。cat /sys/class/drm/card0-DSI-1/status如果能看到 connected说明 panel 的识别链路基本正常。ls /sys/class/backlight/确认背光节点存在试着写 brightness 看背光能不能亮。如果这些都没有大概率是设备树没配好或 panel 驱动没编进去或者寄存器的供电/复位没就绪。此时先查硬件连接再查 dts。一个非常实用的技巧是看内核日志里的 probe 顺序有些 SoC 的 DSI host 要等 panel 驱动注册后才会发起初始化如果你看到类似 failed to attach dsi 的报错说明主机和面板的 bridge 没连接上。5.2 用 modetest 绕过所有上层直接点亮modetest 是 libdrm 自带的测试工具也是我实测最好用的链路探针。它直接操作内核 KMS不经过任何窗口系统。基本用法是这样modetest -M rockchip -p这条命令会列出所有 connector、encoder、CRTC、plane 和可用的 mode。你要做的是先找到对应屏的 connector id。假设它显示 DSI-1 的 id 是 32然后执行modetest -M rockchip -s 32:720x128060 -d这里的 -s 表示 set mode后面的 32 是 connector id720x128060 是分辨率和刷新率。-d 是 dump 当前模式信息。如果命令执行后屏幕能亮起来并显示测试图案那么恭喜从内核到面板的整条链路是通的问题基本可以锁定在上层用户态。如果这叫都点不亮就回头查设备树、panel 驱动、时序和硬件连线别浪费时间折腾 Qt。这招之所以好用就是因为它把应用层的锅和内核驱动的锅一刀切开了。我每次做新的显示适配都会先让 modetest 能点亮再动上层。这个顺序千万不要反。5.3 三个最容易翻车的点时序、复位、背光我列一个故障对照表都是实际项目里反复出现的典型问题现象最常见原因背光亮、屏幕没图像panel 初始化序列没完整执行或 DSI lane 数与 dts 不一致花屏、画面撕裂错位porch/sync 参数与 panel 规格不符或像素时钟偏离画面整体偏左/偏上hback porch、vback porch 配置错误HSYNC/VSYNC 极性反了颜色不对、偏色严重RGB 颜色通道映射错误或 bpp 配置和 panel 不一致闪烁、随机黑闪电源供电或复位时序不稳定缺少 TE/Tearing Effect 同步特别强调复位和时序。很多 DSI 屏的规格书上都会给出 reset 引脚的要求拉低至少 10ms、拉高后等待多少毫秒才能发 DCS 命令。设备树里如果没有对应的 reset-gpios 或者电源顺序不对面板的 TCON 根本没就绪后面命令全白搭。时序则要严格按 panel 的 datasheet 来hsync、hback porch、hfront porch、vsync、vback porch、vfront porch一个都不能随手填。有些通用的 720×1280 配置往往换一块屏就花就是因为每个面板的实际 blanking 并不一样。设备树里一块 DSI panel 的典型结构大致长这样示意一下就行具体 compatible 和节点以对应内核版本和面板型号为准mipi_dsi { status okay; panel0 { compatible example,720p-video-panel, simple-panel; reg 0; backlight backlight; reset-gpios gpio4 10 GPIO_ACTIVE_LOW; enable-gpios gpio4 12 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 mipi_dsi_panel_pins; ports { port { panel_in: endpoint { remote-endpoint mipi_dsi_out; }; }; }; }; };配置完 dts 重新编译内核或设备树在日志里确认 panel 驱动成功 probe。整个过程看起来简单但电源、复位、初始化序列、时序每一项都是魔鬼细节。6. 上层图形栈怎么选X11、Wayland 与无桌面的轻量组合6.1 X11 在嵌入式上的历史包袱与现代处境X11 在 Linux 桌面上统治了几十年但它本质上是一个远程显示协议 窗口管理的老架构所有绘制都要经过 X server绘制路径长、合成效率低、还引入大量历史兼容负担。嵌入式设备上如果只是为了跑一两个全屏应用X11 往往是不划算的。不过现实中仍然有不少老产品在用因为既有软件栈全绑死在 X11 上迁移成本太高。Xorg 的 modesetting 驱动可以直接跑在 DRM/KMS 上所以如果你的产品必须跑 X11 应用技术上完全可行。只是不要指望它在合成和性能上有多少优势。新的嵌入式项目我基本不推荐从零选 X11。6.2 Wayland 的价值和成本Wayland 把合成器compositor提升到核心位置客户端的绘制结果通过共享内存或 DMA-BUF 提交给合成器由合成器统一合成后交给 DRM/KMS。这里最典型的是 Weston它专门支持 DRM backend 和 pixman-renderer。有 GPU 时用 GL 渲染没 GPU 时退化为 CPU 合成。Wayland 相比 X11 的优点是协议更精简、画面提交更直接合成器还能利用 DRM 的 plane 做硬件合成。但代价是你至少需要一个合成器进程在跑内存、闪存、CPU 占用都比裸奔 DRM 直连要高。如果只是一块低端板子跑一个全屏应用Wayland 带来的额外复杂度可能并不值当。6.3 我个人的选型建议按 GPU、CPU、应用数量三个维度决定这个选型没有银弹完全取决于你的硬件资源。我给几个常见组合有 GPU、需要同时跑多个应用优先 Wayland。选择成熟合成器、Qt 或 GTK 的 Wayland 后端能获得较好的窗口管理和合成性能。有 GPU、只跑一个全屏应用直接用 Qt EGLFS 或者裸 DRM跳过 X11/Wayland省一层是一层。全屏单应用本来就不需要窗口管理器。无 GPU、单应用最稳的组合是 DRM/KMS dumb buffer 软件渲染。Qt 也可以选 linuxfb 后端但本质是纯 CPU 画图交互稍复杂就容易掉帧。无 GPU、多应用只能上合成器比如 Weston 的 pixman 后端但要做好 CPU 占用高的心理准备复杂界面建议降分辨率。我见过很多团队在这上面反复折腾。其实核心就一句话每个额外引入的抽象层都要用你这块板子的算力去换。资源紧的时候宁可让应用直接面对 DRM/KMS也不要为了标准硬套一个窗口系统。另外还有一个常被忽略的细节运行 DRM 直连应用时进程需要获得 DRM master 权限。如果调试阶段遇到 Device or resource busy多半是另一个进程已经占用了 DRM master关闭桌面或合成器再试。很多奇奇怪怪的显示问题最后都是这类权限粒度上的小摩擦导致的。7. 一个实际排障思路的复盘从黑屏到点亮的那几步作为收尾我很想给你还原一次真实的排障过程。朋友拿了一块新板子MIPI DSI 屏烧完镜像后 dmesg 没有明显报错但屏幕一直黑着。这种问题我见过太多次常规动作是第一件事查 /sys/class/drm/ 下面的 connector 是否存在再查 /sys/class/backlight/ 下面有没有背光节点。结果背光节点在、写入亮度能亮但 DRM 里没有 DSI connector。这就直接把范围缩小到了设备树或 panel 驱动注册上。打开 dmesg搜索 panel 和 dsi果然有条 failed to attach panel 的日志。再查 dts发现 reset-gpios 指定的引脚和实际硬件接的引脚差了两位。改完 pinmux重新加载内核connector 出现了。接着用 modetest 手工点亮画面能出测试图。再到应用层跑 Qt立刻正常显示。整个过程没有动过一行渲染代码——问题本来也不在那。这段经历我想表达的意思是Linux 图形显示看着复杂但只要把链路分层装进脑子里配合 modetest 和 dmesg 这两个工具绝大多数问题都能快速缩小到某一层。我到现在做新的显示适配依然坚持先手工点亮、再谈框架的原则。你把这条经验带进项目里至少能少熬几个通宵。
返回列表