这是显示驱动调试系列的第5篇。干这么多年,我最大的体会是:显示问题很少是“单纯一个函数写错”,而是一连串状态没对上。你拿到的可能是一句“屏幕花了”,但背后可能是时序、时钟、通道、格式、缓冲任何一个环节出了问题。这篇我按实际排查顺序聊聊真正用得上的显示驱动调试工具:日志类怎么开、状态类怎么读、帧内容类怎么用、性能类怎么定位,以及它们怎么组合才能最快找到根因。如果你刚接触嵌入式Linux显示驱动、MIPI/HDMI/DP接口调试,或者被某个怎么都查不出来的花屏、闪屏、黑屏折磨过,这篇应该能帮你在工具选择上少走一段弯路。
1. 先搞明白:显示驱动调试到底在调什么
1.1 把显示链路拆成五段再下手
很多人拿到显示bug就开始翻驱动代码,这个习惯我不太推荐。显示链路太长,从应用生成一帧画面到它真正在屏幕上亮起来,中间至少隔着五段:用户态渲染与buffer生成、DRM/KMS提交与合成、显示控制器scanout、物理链路传输、屏端接收。每一段出错都可能表现出“屏有问题”,但归属完全不同。第一段的问题通常是应用和GPU驱动,第二段是framebuffer和compositor,第三段是显示控制器的寄存器与中断,第四段才是我们常说的PHY、时钟、lane、时序,第五段涉及屏的EDID、背光和面板配置。
我为什么强调拆段?因为不同段对应不同的调试工具链。第一段可以用GPU厂商的Profile和抓帧工具,第二段用DRM状态和modetest,第三段用debugfs和寄存器dump,第四段主要用devmem加示波器,第五段则要读EDID和屏参。如果你不拆段,很可能出现一种尴尬情况:明明是DSI时序的bug,你却一直用kmscube反复切分辨率,切了半天现象不变,人也懵了。拆段之后,调试路径很清楚:先确定段,再选工具,效率能高一倍。
1.2 五类调试工具,分别对治五类问题
| 工具类别 | 代表工具 | 主要解决什么 |
|---|---|---|
| 日志和断言 | dmesg、printk、drm.debug、ETW | probe失败、超时、中断异常、错误码 |
| 状态视图 | debugfs、sysfs、modetest | mode、connector、crtc、plane状态 |
| 帧内容输出 | kmscube、weston、igt-gpu-tools、采集卡 | 花屏、黑屏、偏色、撕裂的归因 |
| 信号与时序 | devmem、示波器、协议分析仪、i2c工具 | DSI/HDMI/DP的信号完整性和timing |
| 性能剖析 | ftrace、perf、厂商Profiler | 帧率、vblank抖动、buffer带宽 |
这张表是我自己常用的分类,不是标准答案。核心思路是对症下药:先确认现象落在哪一段,再选对应层级的工具。如果你用帧内容工具去查物理信号问题,或者拿示波器去追渲染bug,就是在浪费自己时间。工具选型也不是越多越好,关键是每类里有一两个你用得滚瓜烂熟,知道它的输出怎么解读。我见过有人装了全套性能工具,最后连perf report都不会看,不如先把dmesg和modetest玩明白。
1.3 调试路径:日志、状态、帧输出三步走
我的调试路径永远是三步。第一步抓日志,看驱动有没有报错;第二步读状态,让内核告诉你它认为的mode、分辨率、连接状态;第三步做帧输出测试,绕开复杂应用,直接向屏幕推已知的测试画面。三步走完,基本能划出结论:链路根本没有输出、输出了但内容不对、内容对但信号质量差。这三个大类和工具一一对应。实际项目里,很多问题在第一步就能定位,比如某个GPIO申请失败导致背光不亮;只花半分钟,根本不需要后续工具。而像雪花闪烁这种模糊问题,才需要走完三步。
2. 核心调试工具清单与使用要点
2.1 日志类:dmesg、printk 与 drm.debug
嵌入式Linux下最常用的是dmesg。显示驱动probe失败、DSI host超时、HDMI hotplug中断异常,一般都会留下内核日志。但默认日志往往不够细,需要打开DRM子系统的动态调试参数。你可以临时执行echo 0x1f > /sys/module/drm/parameters/debug,也可以启动参数加drm.debug=0x1f。0x1f对应DRM_UT_CORE、DRIVER、KMS、MODE、ATOMIC几类日志,对驱动开发和集成够用。想看更细的DSI或具体IC日志,还需要在驱动里用dev_dbg自己留点。这个开关是全局的,生产环境别随便开,日志量会很大。
# 临时打开 DRM 调试日志 echo 0x1f > /sys/module/drm/parameters/debug # 实时观察日志 dmesg -w | grep -E "drm|dsi|hdmi|error"我踩过的一个坑是:有些平台会把DRM日志编译成release级关掉,dmesg里永远看不到。这时候先检查内核config,比如CONFIG_DRM_DEBUG_SELFTEST、CONFIG_DEBUG_FS,以及驱动自身的DEBUG宏,而不是死盯代码。还有一点,日志不是看得越多越好,建议先grep error/warn,再打开对应功能模块。比如DSI问题,就grep dsi、dsi_phy、timing;整屏问题才开全量DRM日志。否则日志刷得飞快,反而把关键报错冲没了。
2.2 状态类:debugfs、sysfs 与 modetest
debugfs里的dri目录是金矿。cat /sys/kernel/debug/dri/0/state 能看到每个crtc、plane、connector的当前状态,包括是否enable、mode、format、fb大小。比你自己在驱动结构体里翻找快得多。state文件适合看“现在”的状态,而sysfs里的drm属性,比如/sys/class/drm/card0-DSI-1/status和modes,则适合看“内核对外暴露”的状态。还有一个容易被忽略的点:state文件输出里包含每个plane的src和dest坐标,花屏时如果坐标对不上,问题很可能在合成层,而不是物理层。
cat /sys/kernel/debug/dri/0/state cat /sys/class/drm/card0-DSI-1/status cat /sys/class/drm/card0-DSI-1/modesmodetest是libdrm自带的工具,执行modetest -M platform -p可以打印所有connector和支持的mode;用modetest -M platform -s : 可以把某个mode直接推到屏幕上。这是判断“驱动能不能点亮屏”最直接的办法。注意不同平台-M参数不一样,-M platform对应DRM master设备;有的SoC要在内核命令行指定video或者DRM master选择。先用modetest -M help确认设备名。
提示:modetest需要root权限或DRM master权限,普通账号下面可能什么设备都看不到。
2.3 帧内容类:kmscube、weston 与 igt-gpu-tools
日志和状态都正常后,屏幕上可能还是花的。这时要用已知的测试画面隔离问题。kmscube做一个简单的旋转立方体,不依赖桌面环境,非常适合快速验证。weston用来验证合成器,跑一个带多个窗口的Wayland环境,能暴露buffer翻转、tearing这类合成相关问题。igt-gpu-tools是Intel/AMD社区的工具集,里面kms_*测试用例覆盖大部分显示路径,尤其适合做回归测试;在ARM平台也能移植,很多厂商把它作为标准测试集。实际项目里我常用kmscube加modetest组合:先推纯色块,确认色彩和格式;再跑动画,确认帧更新正常。这样能把“花屏是静态格式错还是动态buffer错”分开。
比如纯红色输出正确,但跑kmscube时出现横向撕裂,那基本可以怀疑vsync和buffer切换,而不是RGB格式问题。如果纯色块本身就偏绿,则优先查format。这个试验几秒钟就能出结论,但很多人会忽略它,上来就抓PHY寄存器,思路就跑偏了。
2.4 信号时序类:devmem、寄存器 dump 与 I2C 工具
当内容输出正确但屏幕上出现闪烁、条纹、边沿锯齿,往往是物理时序问题。MIPI DSI的lan count、clock频率、porch参数、PHY模式都写在dts和寄存器里。busybox的devmem读寄存器非常直接,比如读DSI控制器状态寄存器,确认PHY有没有进入HS mode。如果板子上的寄存器地址是0x1a000000,偏移0x88,可以这样读:
devmem 0x1a000088这个命令会直接把物理地址的32位寄存器的值打印出来。读之前一定要拿芯片手册确认寄存器地址和位域定义,否则读出来的数字没有任何意义。配合示波器看clock lane和数据lane更准确。驱动调试阶段,我还会用i2c工具读EDID,例如i2cdump -y 0 0x50,确认屏自己上报的分辨率和timing是否与驱动配置一致。经验是不要一上来就抓波形,先把所有寄存器值dump下来存成文件,和正常板卡逐位对比,很多时序问题就是某个bit被配置错了。
2.5 性能类:ftrace、perf 与厂商Profile
显示驱动调试不只是通断和颜色,还有帧率。帧率不够、画面撕裂、vsync抖动,本质是时序和buffer调度问题。ftrace可以跟踪drm的vblank事件:
trace-cmd record -e drm:drm_vblank_event -e drm:drm_vblank_work -- sleep 5 trace-cmd report回放时能看到vblank周期是否稳定。perf stat可以看CPU中断负载,如果vblank中断频繁丢失,多半是中断线程被优先级更高的事件抢占。厂商工具各有各的用处:Mali的Streamline、Adreno的Snapdragon Profiler、Intel的GPU Top等,都能看到GPU任务占用和buffer带宽。工具很多,关键还是先确认瓶颈在CPU、GPU还是显示控制器。我见过一个案例,帧率不足查了半天GPU,最后发现是DRM提交线程被实时优先级任务一直抢占,vblank事件晚到几十毫秒,perf schedule事件一眼就暴露了。
3. 实操:一个屏幕雪花闪烁问题的排查全过程
3.1 现场记录:先把现象和条件固定下来
先交代背景:一块嵌入式Linux板子,MIPI DSI接1080p LCD,用户反馈开机后大概30秒开始雪花闪烁,有时重启会好,有时又复现。我的习惯是先不碰代码,把现象录下来,记录环境温度、内核版本、屏型号、是否用了长排线、供电方式。这个项目最终发现和排线长度、电源纹波都有关联,如果一开始不记录,很难判断是哪一次改动引入了问题。记录表不需要复杂,就是列出时间、现象、操作、结论四列,每次改动后回填一行。工具上只需要手机录像和一张纸。
这个步骤看起来“不技术”,但显示调试最容易被坑的地方就是复现不稳定。如果你连“什么时候发生、持续多久、什么操作能触发”都不知道,后面所有工具都像盲人摸象。我的习惯是先在A4纸上写下:亮屏正常持续多少秒,出现雪花前有没有声音或其他外设事件,用固定画面还是会动画面,触摸屏幕会不会加重。这些信息能帮你判断是不是热噪声、电源跌落还是软件事件触发。绝大多数项目的首份有效结论,不是从示波器里出来的,而是从现场记录里推出来的。
3.2 第一步:抓日志、确认顶层状态
复现问题的同时,我打开dmesg -w。出现雪花前,抓到了几行DSI PHY timeout。注意,这些日志不是每次启动都出现,所以dmesg要一直开着。执行cat /sys/kernel/debug/dri/0/state,发现connector一直正常enabled,crtc也在刷新,但clocks显示有些抖动。这基本说明内核上层认为自己在正常输出,问题不在framebuffer提交,而更可能在物理层或时钟配置。
dmesg -w | grep -E "dsi|phy|timeout|error" cat /sys/kernel/debug/dri/0/state这里有个判断逻辑:如果state文件显示crtc没有enable,那问题大概率在DRM/KMS提交层;如果crtc和connector都enable了,但屏幕没反应,那就是后端通路问题。实操中很多人会跳过state文件直接改dts,结果改了半天发现问题是HDMI线材接触不良。先看状态可以避免这种无效劳动。state文件里的字段的确比较绕,比如“crtc 0: DSI-1”表示这个crtc连接的是DSI接口,“plane 0”下面有fb_id和format,表示当前正在scanout的buffer。我会把关键几行截图存起来,和出现雪花后的文件做diff,差异处经常就是根因所在。
3.3 第二步:用测试画面做通路隔离
为了确认是不是上层应用的问题,我先停掉界面,直接用modetest强制推一个纯色画面。具体命令是modetest -M platform -s 32:1920x1080-60,然后观察屏幕。纯色画面下雪花依然出现,说明问题在显示控制器到屏这一段。再用weston跑一个简单动画做纵向撕裂观察,撕裂也伴随雪花,进一步排除“应用画错buffer”。这个环节的关键是保证已知输入:已知画面都异常,链路后端基本锁定;如果已知画面正常而应用画面异常,那问题就回到GPU驱动或应用侧,根本不用动DSI寄存器。
modetest -M platform -s 32:1920x1080-60 weston --idle-time=0 &我特别强调“屏上是什么内容”要在测试时固定下来。如果你一边测试一边用手机看日志,很难判断雪花到底是何时出现的。用modetest推纯红色画面,正常情况整屏均匀红色;如果出现雪花、黑条、偏色,说明scanout输出格式或后端处理有疑点。再用kmscube跑旋转立方体,如果红色纯色块正常而动画时异常,优先级自然转移到了buffer更新和vsync。这个两步法,能在一分钟之内把问题域缩小一半。
3.4 第三步:读寄存器、量时序,锁定根因
既然锁定在DSI物理层,我用busybox devmem读PHY状态寄存器和时钟寄存器,对比正常板卡,发现HS准备时间和clock lane的上升沿参数与屏规格书不一致。这个板子换过一版屏,兼容模式下默认使用了偏小的setup时间。打开完整时序后,再结合示波器确认clock和data lane的transition满足要求。修改dts中的DSI timing参数,主要是hback porch和hs-traffic参数。这个案例的根因是时序参数不匹配,工具链里起决定性作用的是日志加状态加寄存器dump,而不是玄学。
devmem 0x1a0000a0 devmem 0x1a0000a4具体对应哪一位配置,需要看芯片手册。我只能给出排查思路:第一确认PHY是否报HS timeout,第二确认时钟lane和数据lane的setup/hold时间,第三确认porch和blanking参数是否在屏规格书允许范围内。雪花闪烁很多时候是时序margin太小,在温度变化后被触发。比如正常25度没问题,温度升高或屏幕老化后开始闪,八成是clock lane的setup/hold余量不足。dts里的driving current、pre-emphasis这些参数你也可以尝试,但一次只改一个变量,改完跑完整复现流程,否则你根本不知道是哪一项修好的。
3.5 验证与存档:最少要跑三个场景
修复后不能只复现原问题,还要验证三个场景:冷启动、反复休眠唤醒、长时间播放。我把当时的dmesg、state文件、寄存器dump、每帧时间戳都归档到一个目录,命名带上日期和屏型号。这种档案在下次换屏、换内核时就是最值钱的参考资料。常见项目里,排线松、电源纹波大、时序参数错、驱动buffer越界,这几类问题的现象相似,靠工具链是可以逐步区分的。做完这些,才算真正把问题关闭。
4. 辅助工具组合与避坑经验
4.1 把日志搬到网络上:nc、串口与日志服务
很多小板子没有串口,或者串口日志太长刷屏。我的做法是用nc把日志实时转发到PC:PC端监听nc -l -p 5555 > display.log,板子上执行dmesg -w | nc 192.168.1.10 5555。这样不仅能保留完整日志,还能在PC上方便地grep。nc全名叫netcat,作为网络调试工具,它的定位就是一个轻量TCP/UDP瑞士军刀。除了转发日志,也可以用它快速搭一个临时命令通道,把一段shell脚本用管道送进板子执行,省去U盘拷贝和来回插拔的功夫。
# 在 PC 上监听 nc -l -p 5555 > display.log # 在板子上发送日志 dmesg -w | nc 192.168.1.10 5555要注意nc传输没有加密和校验,传输中断时日志可能丢尾部,所以关键日志我会在板端同时存一份。另外别把端口暴露到公网,只在隔离的调试网段使用。使用这个组合时,我通常再开一个终端跑tcpdump,查看日志传输本身有没有问题;如果网络不佳,宁可降级用串口。网络日志的最大价值是解放双手,你可以坐在PC前一边看日志一边操作板子,不用拿放大镜盯串口终端。
4.2 用 Lua 调试器调试自动化检查脚本
显示驱动调试里经常要写脚本批量读寄存器、循环切分辨率、检查EDID。Lua在嵌入式环境里常被用来做这类自动化,脚本本身出错也会耽误事。我建议本地用ZeroBrane Studio这类带断点、watch窗口的IDE调试脚本,在线环境里用基础的print和断言库。比如写一个脚本轮询/sys/kernel/debug/dri/0/state,判断connector状态;脚本里加一个状态不变量检查,异常就打印上下文。别小看脚本调试,一个误判可能让你在错误方向上多花大半天。
local f = io.open("/sys/kernel/debug/dri/0/state", "r") local content = f:read("*a") f:close() if not content:find("connector%[32%]") then error("connector 32 not found") end这里的关键是把“自动检查脚本”当作驱动代码一样对待:要加日志、要可重复、要在修完bug后回归跑一遍。我见过太多临时脚本,功能正确但没有任何错误处理,一旦某次读文件失败就静默跳过,导致误判“状态正常”。如果你用ZeroBrane Studio在PC上调试,建议把目标板上的状态文件拉到本地,用同一份输入反复跑测试,确保脚本逻辑正确后再放回板子。这样能隔离“脚本问题”和“驱动问题”,避免自摆乌龙。
4.3 Modbus 工具在工业显示与控制联调中的角色
如果你调的是带触摸屏的HMI或者工业网关屏,显示异常不一定在显示链路。很多设备用Modbus和PLC通信,屏上显示的数据来自寄存器,数据错乱会让界面看起来像显示bug。可以用Modbus Poll这类工具模拟主站,读PLC寄存器,和屏上显示内容逐项对比。常见坑是寄存器地址偏移和大小端,比如32位浮点数在A、B两块屏上存法不同,屏上数据自然不一样。这时候你拿显示驱动工具查半天也没用,正确工具是Modbus调试工具和协议文档。
我举一个真实例子:客户报“屏上温度偶尔跳成负值”,工程团队查了三天驱动,发现DSI时序完全正常。后来用Modbus工具一读,发现PLC某个保持寄存器被另一个任务周期性改写,屏只是在忠实显示错误数据。所以说,调试工具选型永远要和“数据从哪来、显示经过哪些环节”对应起来。Modbus调试工具还有一个用途是模拟屏端,让PLC认为屏在线,从而把PLC逻辑和屏硬件隔离开来。整个联调过程里,它不算显示驱动工具,但缺了它,显示问题容易变成无头案。
4.4 动态分析类工具我为什么不优先推荐
前几年有朋友问我,能不能对驱动二进制做动态dump分析,快速查看内存里的数据结构。我的看法在显示驱动调试里比较保守:驱动bug的根因九成能靠日志、状态、测试画面三类工具定位,没必要一上来用重型动态分析。如果一定要分析自研模块,也要在合规授权、只针对自己代码的范围内进行,并保留完整的证据链。显示驱动是内核和硬件打交道的薄弱边界,任何未受控的操作都有可能让问题从“驱动bug”变成“系统崩溃”,排查成本反而更高。
这里我不是否认动态调试技术的价值,而是强调适用边界。驱动开发中,我们有ftrace、kprobe、perf这些被内核社区支持的动态观测机制,它们有明确的事件语义,知道自己在观测什么。相比之下,单纯对二进制做内存级dump,拿到的信息往往是某个时刻的快照,缺少上下文,很难定位一个持续发生的时序问题。我一直坚持工具选型“够用就好”:先把观测类工具吃透,再考虑是否需要更底层的手段。多数显示问题根本不需要走到那一步。
4.5 常见问题速查表
| 现象 | 优先工具 | 典型排查方向 |
|---|---|---|
| 黑屏无背光 | dmesg、GPIO导出、电源测量 | 背光PWM、GPIO方向、电压轨 |
| 开机能亮但无画面 | modetest、state文件 | plane/connector enable、fb格式 |
| 花屏/雪花 | kmscube、modetest、devmem | DSI时序、lane数、时钟、缓存越界 |
| 闪屏/跳动 | dmesg、weston、ftrace | vsync、buffer刷新、tearing |
| 分辨率不对 | i2c读EDID、modetest | EDID解析、mode偏好、缩放 |
| 颜色偏色/偏绿 | kmscube纯色、format dump | RGB/YUV格式、色彩矩阵、位宽 |
| 帧率不足 | perf、trace-cmd、厂商Profiler | GPU渲染瓶颈、带宽、中断抢占 |
这张表不是标准答案,但能让你不至于一上来就拿错工具。我每次调试都会先对照第一列现象,在第二列选两个工具,然后按第三列的思路推进。第三列的很多方向看起来像是硬件问题,实际上驱动代码同样能引起,比如GPIO方向配置错导致背光不亮,这在软件侧就可以定位。表格里每个现象都强调“优先工具”而不是“唯一工具”,就是因为显示问题经常是多因叠加,只用一种工具很容易形成幸存者偏差。
最后说点个人习惯。我现在拿到一个新的显示bug,第一件事不是打开代码找函数,而是先打开dmesg和debugfs,把“设备认为自己在输出什么”和“屏实际显示什么”放在一起对照。显示驱动调试最怕的是只盯着一点,明明时钟已经偏了,还反复改dts里的偏移量。工具不在多,把日志、状态、帧输出这三类吃透,你就能解决绝大多数问题。每次调完,记得把当时的寄存器值和截图存档,下次遇到相似现象能省半天时间。先写到这,欢迎在评论里聊聊你遇到过最诡异的显示问题。