ARTICLE DETAIL

资讯详情

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

Linux显示驱动调试工具与实战:分层定位黑屏花屏问题

Linux显示驱动调试工具与实战:分层定位黑屏花屏问题 做过几年底层显示驱动的开发调试我一直觉得这个方向有个特别实在的规律屏幕亮了不算本事屏幕不亮能定位到具体环节才算入门。显示驱动调试工具说到底就是帮你在茫茫多的寄存器、时序参数、帧缓冲地址、中断状态里快速圈出问题所在的那条“追踪链”。前面几篇聊了DRM框架、fbdev、panel驱动这些基础知识这篇就专门把调试工具摊开来讲清楚——不是罗列工具名单而是告诉你每个工具在哪个环节用、怎么读数、怎么判断。这篇内容适合正在搞Linux DRM/KMS驱动、Android显示HAL或者在做MIPI DSI、eDP、RGB屏调试的朋友参考。不管你是刚接触显示驱动的初学者还是已经被花屏、闪屏折磨了几天的老手看完应该都能建立一套自己的调试思路。1. 为什么显示驱动调试极其依赖工具搞显示驱动和搞其他内核驱动最大的区别是大部分情况下你根本看不到代码在哪一步出错。网络驱动出问题你还能 ping 一下、看个丢包率存储驱动出错IO 统计直接给你答案。但显示驱动一旦出问题现象往往只有几种黑屏、白屏、花屏、闪屏、偏色。屏幕上显示的那几个像素根本不会告诉你“我是因为时序不对才花掉的”。所以做显示驱动基本上就是“黑盒调试”你只能靠外部工具去观察每一个环节到底工作没有。我习惯把显示链路拆成四层来看层级对应内容出问题时的表现主要工具应用/合成层SurfaceFlinger、Wayland合成器图层错乱、合成性能差dumpsys、glmark2内核KMS层DRM驱动、atomic配置、fb设置黑屏、分辨率错误、模式设置失败dmesg、modetest、debugfs面板控制层panel时序、背光、上下电序列白屏、闪烁、背光不亮dmesg、逻辑分析仪物理信号层MIPI/eDP信号质量、时钟频率花屏、抖动、显示异常示波器、逻辑分析仪这类分层思路很像网络调试。用过网络调试工具nc的人都知道排查网络问题先要搞清楚是链路层不通、IP层不通、还是应用层的问题显示驱动也是一样的逻辑先定位到层再定位到点最后才去抠具体参数。换句话说显示驱动调试没有像nc那样一个命令通吃全场的“万能工具”你能做的就是手上备齐一套工具按层去验证。另一个核心原因是显示驱动涉及的时序和状态太多了。一个面板的点屏时序就有几十个参数一个atomic commit里面涉及crtc、plane、connector三个对象的状态联动。靠肉眼“读代码找bug”大概率会漏但工具不会骗你——它把真实硬件状态摆在你面前比对一下就知道驱动代码有没有按预期走。2. 内核态调试工具从日志到运行追踪这一层是显示驱动调试的主战场。内核态的工具有个共同特点它们反映的是驱动程序自身运行时的真实状态不是靠猜而是直接读内核的数据结构、跟踪函数的执行轨迹。2.1 drm.debug与dynamic_debug让内核开口说话DRM源码里到处是DRM_DEBUG、DRM_DEV_DEBUG、DRM_ATOMIC_PRINT这些打印默认情况下这些调试信息是关闭的因为全开的话日志刷屏非常严重。你需要通过drm.debug参数来控制输出级别。drm.debug参数是按位图工作的比较常用的位0x01 DRM_UT_CORE核心消息0x02 DRM_UT_DRIVER驱动通用消息0x04 DRM_UT_KMSKMS相关0x08 DRM_UT_PRIMEDMA-BUF/PRIME相关0x10 DRM_UT_ATOMICatomic操作相关0x20 DRM_UT_VBLANK垂直消隐相关0x40 DRM_UT_STATE对象状态相关实际操作中像我排查modeset问题会这样加载modprobe drm drm.debug0x1f0x1f就是0x01到0x10全开正好覆盖了core、driver、KMS、prime、atomic。这组日志会详细打印atomic事务中plane、crtc的更新过程是定位“为什么模式设置失败”的第一手资料。如果内核模块已经加载了也可以运行时调echo 0x1f /sys/module/drm/parameters/debug比我更常用的是dynamic_debug机制它可以在不重启的情况下精确打开某个驱动的某一类打印echo file drivers/gpu/drm/specific_driver.c line 320 p /sys/kernel/dynamic_debug/control echo module my_display_driver p /sys/kernel/dynamic_debug/control这条命令的意思是打开某个文件的某一行或者整个模块的动态打印。排查驱动初始化流程时把module级别的打印全开再配合dmesg看执行顺序基本能定位是probe卡死、资源申请失败还是panel没就绪。看日志也有技巧。dmesg默认输出很多时候被其他日志挤掉了我会习惯性地先做一件事dmesg -c清空当前日志然后复现问题这样后面打印出来的就是本次操作产生的完整日志不会混入系统启动历史。复现问题后再用dmesg回看配合时间戳和调用顺序判断执行路径。一个小经验是显示驱动的初始化日志往往在启动阶段就已经刷过去了等到你进系统发现问题再来看dmesg只能看到交互阶段的信息。所以真正排查初始化问题时要在启动参数里加上loglevel8并且用串口或内核日志抓取工具把启动过程全程记录下来。2.2 /sys/kernel/debug/dri文件系统直接读驱动内部状态dmesg是“驱动主动告诉你发生了什么”而debugfs下的文件是“你自己去看当前驱动是什么状态”。后者在排查问题的时候往往更硬核因为它直接反映内核对象在某一时刻的真实取值。先挂载debugfsmount -t debugfs none /sys/kernel/debug然后进到DRM相关目录cd /sys/kernel/debug/dri/0 ls里面会有state、framebuffers、connectors、clients等一堆文件。我最常看的是state它会把这个DRM设备下所有对象的状态完整打印出来包括crtc的enable/active状态、当前mode、plane的fb关联、connector的连接状态。算是“一键查看驱动全家桶状态”。举个例子排查黑屏时需要确认crtc到底有没有被正确使能直接看state文件里的内容cat /sys/kernel/debug/dri/0/state输出里会看到类似这样的关键信息crtc是enabled还是disabled、active是1还是0、connector是否connected。如果应用层和内核都认为crtc是enable的但屏幕依然黑屏那问题就不在KMS状态配置上而是要去查panel本身的上电时序和信号输出。这个判断在调试中省了我大量时间因为它能直接把“驱动状态没问题”这一块排除掉。framebuffers文件也很有用cat /sys/kernel/debug/dri/0/framebuffers它会列出当前创建的framebuffer对象包括句柄、宽度、高度、stride行字节数、像素格式。很多花屏问题追到最后就是stride计算不匹配。比如用户空间应用按1080宽、每像素4字节对齐生成了4420字节的stride驱动却按4320字节去计算扫描地址那从第二行开始就会错位表现出来就是肉眼可见的花屏条纹。这类问题从debugfs里一眼就能看出来。2.3 ftrace与perf把驱动运行轨迹录下来dmesg和debugfs能看到“是什么状态”但想看“执行了哪些函数、耗时多少”就得靠ftrace和perf这两个性能与追踪大杀器。我排查驱动长时间才点亮屏幕的问题时最常用的就是ftrace的函数图模式echo 0 /sys/kernel/debug/tracing/tracing_on echo function_graph /sys/kernel/debug/tracing/current_tracer echo drm_atomic_commit_tail /sys/kernel/debug/tracing/set_graph_function echo 1 /sys/kernel/debug/tracing/tracing_on cat /sys/kernel/debug/tracing/trace这段操作的意思是打开function_graph追踪只追踪drm_atomic_commit_tail这个函数及其调用的子函数然后读取追踪结果。输出里会看到密密麻麻的函数调用缩进能直观看出内核在哪个子函数里消耗了大量时间。用trace-cmd更省事trace-cmd record -p function_graph -l drm_atomic* trace-cmd reporttrace-cmd会自动管理追踪缓冲区的开关和读取比手写echo命令可维护性强得多。我一般会配合一个小脚本把重复的trace流程自动化思路有点像写lua脚本去批量执行调试操作——把固定的操作序列固化成脚本然后每次复现问题时只改目标函数名节省下来的时间很可观。perf在大规模耗时分析上更强。比如驱动整体性能下降用perf找热点函数是最快的perf top这个命令会实时显示CPU使用率最高的内核函数。如果看到某个锁函数、某个list遍历函数占了很高的比例那性能问题大概率就在那里。做显示驱动性能调优时我还会用perf record抓一段实际点屏或刷屏的调用链再通过perf report看annotate视图。很多“屏幕刷新一卡一卡”的问题最后都是通过这个方式定位到驱动里某个不必要的忙等待循环。3. 用户态测试工具验证链路功能内核态工具解决“驱动有没有按预想工作”的问题用户态工具解决的是“整个显示链路能不能跑通、跑得多快”的问题。这是两条腿缺一条都不行。3.1 modetestKMS功能的照妖镜modetest是libdrm目录下的经典测试工具凡是做Linux显示驱动的人一定都认识它。它用最朴素的方式调用DRM KMS接口把当前的connector、encoder、crtc、plane信息全部枚举出来这在验证显示驱动最基本功能时极其有用。先看基础用法# 查看card0下所有connector的状态 modetest -M card0 -c # 查看所有plane及其支持的像素格式 modetest -M card0 -p # 在connector 32上设置一个1080x192060Hz的模式 modetest -M card0 -s 32:1080x1920-60-c参数输出每个connector的连接状态、物理尺寸、支持的mode列表。如果内核已经成功解析了EDID或者panel驱动里预设了mode这里就能看到mode列表。如果这个列表是空的说明驱动没有正确向KMS暴露模式屏幕当然点不亮。-p参数会列出所有plane的尺寸范围、格式列表和当前关联的fb是排查花屏问题的重要入口。最硬核的用法是直接在屏幕上输出测试彩条modetest -M card0 -s 32:1080x1920 -v这个命令在目标connector上创建一个framebuffer用一段动态变化的彩条填充并输出。如果你能看到流畅变化的彩条说明从KMS到panel整条链路没有任何问题剩下的问题就出在应用或合成层。如果彩条是花的、错位的、或者根本没画面那就回头去查内核态。有台式机环境跑modetest时有个注意点如果X/Wayland正占着显卡modetest会拿不到设备。最简单的办法是切到纯文本终端下执行或者用DRM主设备权限直接跑。很多新手说modetest没输出排查一番后发现是权限问题。用root或者把用户加入video组可以避免绝大部分权限坑。3.2 kmscube与glmark2从GPU到屏幕的完整链路modetest验证的是“不经过GPU、直接KMS输出”的链路但很多花屏、撕裂问题是GPU渲染和显示扫描之间的配合出了问题。这时候需要kmscube和glmark2这类工具拉通全链路验证。kmscube在支持EGL/GLE的平台上跑一个旋转的立方体场景同时会把渲染结果通过KMS直接提交到显示层。它既能验证GPU能不能正常渲染又能验证渲染结果能不能正确显示。我在排查“GPU渲染输出后屏幕显示空白”这类现象时第一个跑的就是kmscube。它能帮我快速区分是GPU没有渲染出内容还是渲染出来了但没有被正确扫描出。glmark2则是标准OpenGL ES基准测试工具它跑的是大量场景组合测完会给出一个综合帧率和每项得分。对显示驱动调试来说glmark2的价值在于压力测试和性能基线如果帧率明显低于正常水平或者画面在切换场景时出现撕裂那就是驱动性能优化不到位的信号。配套的还有一个eglinfo工具eglinfo它能输出EGL版本、扩展列表、surface格式、原生窗口类型等。排查EGL配置时很有用尤其是确认默认surface格式是不是被工作负载意外改了。比如某个app跑完后EGL surface格式从RGBA8888变成RGB565就会导致画面颜色发紫或闪一下这个状态用eglinfo查一遍马上就能确认。3.3 逻辑分析仪与示波器物理层最后一道防线内核态和用户态工具都查完了问题还是存在那就说明大概率是物理信号层面的问题。这时候必须上硬件工具逻辑分析仪用来抓MIPI DSI、eDP的包命令和时序示波器用来量时钟频率、上升沿质量和电压幅值。用逻辑分析仪抓屏过程最核心的是确认一点panel初始化序列到底有没有按预期发出去。我调试一块MIPI DSI屏时屏幕白屏但背光亮内核日志显示panel驱动probe正常。这时我接上逻辑分析仪抓取MIPI DSI的command lane发现驱动根本没有往panel发送display on指令因为reset GPIO的时序反了panel一直停留在sleep模式。这个问题从日志上看完全无解只靠逻辑分析仪抓波形才能定位。示波器主要用于测量时钟频率和信号质量。点屏后如果屏幕闪得很厉害优先用示波器量一下pixel clock或MIPI clock的实际频率。很多时候驱动里配置的频率和PLL实际算出来的频率不一致导致刷新率偏移你以为是驱动导致闪屏实际上是时钟参数算错了。测量要点是直接在时钟lane上量频率然后反推刷新率和modetest里设置的模式做比对相差在1%以内才算正常。4. 实操一次花屏问题的完整排查路径工具讲再多都是纸面功夫真正有价值的是把工具串起来的实战流程。我拿最近调试的一块1080x1920 RGB LCD屏为例完整走一遍排查思路你可以直接参考这个路径。现象是内核启动到显示阶段后屏幕有画面但画面明显花屏表现为横向彩色条纹每隔一段距离就错位一次。这是显示驱动调试里非常典型的症状——数据有输出但帧缓冲被错误扫描了。第一步看内核日志确认驱动有没有报错dmesg | grep -i drm排查结果没有任何error/warning级别的信息驱动probe成功panel也正常初始化。这说明问题不在于驱动崩溃或panel没起来。第二步用modetest确认KMS对象状态modetest -M card0 -c modetest -M card0 -p查出connector状态正常mode列表里有1080x192060plane也处于正常状态。这说明KMS层的模式配置是通的。此时我心里已经有一半把握问题出在plane和framebuffer的尺寸/stride匹配上。第三步直接看debugfs确认framebuffer的真实参数cat /sys/kernel/debug/dri/0/framebuffers重点看stride和pixel_format两个字段。对比modetest输出后发现实际framebuffer的stride是4420字节而plane配置时计算出来的stride值却是4320字节。两者差了正好100字节每行。这样每扫描一行地址就会往后偏移100字节扫描到屏幕中间就会累积出一大截错位看起来就是横向条纹花屏。第四步反向追踪stride为什么不对。检查应用层的buffer分配逻辑发现它按1080宽度乘以一个不当的alignment硬编码了stride而KMS驱动的atomic check里没有校验stride是否匹配导致错误配置一路绿灯直到显示。修复方式是在atomic_check中增加stride一致性校验同时修正应用层的分配代码。这个过程看起来简单但如果没有第二步和第三步的工具支撑我大概只能靠反复改驱动打印来定位那会慢得多。调试工具的意义就在于此每一步都能直接排除一类可能逐步缩小范围到具体对象。我复盘时总跟新人说花屏问题先还原到“KMS输出彩条”这一步如果是彩条也花就说明问题在驱动或物理链路如果彩条正常就去看应用层buffer。这条分界的价值极高能让你少走一半弯路。5. 常见问题速查表与排查技巧实录做多了显示驱动调试发现很多问题是有规律可循的。我把这些年积累的问题现象、排查路径和对应工具整理成一张速查表遇到问题直接对照着查现象优先排查路径关键工具黑屏无背光背光控制GPIO/PWM是否配置、背光供电时序dmesg、示波器黑屏有背光panel上下电时序、reset/STB引脚电平、初始化命令逻辑分析仪、dmesg白屏panel是否进入sleep模式、display on是否发出逻辑分析仪花屏错位fb的stride/pixel_format/地址对齐debugfs、modetest花屏雪花MIPI lane数配置、clock频率逻辑分析仪、示波器闪屏刷新率是否正确、vblank中断是否稳定modetest、ftrace、示波器偏色pixel_format、色域空间、clutmodetest、debugfs撕裂vsync等待缺失、buffer切换不同步glmark2、dmesg触摸时画面卡中断优先级、共享锁竞争perf、ftrace排查时的几个核心技巧值得单独写出来首先复现问题时一定要保留现场日志。drm.debug全开后再去复现问题不要复现完才想起来没开日志。另外很多显示问题需要冷启动才能复现一定要在开机日志里抓到关键信息否则就白跑一次。其次先用软件工具排除软件问题再动硬件工具。每次看到花屏就去接示波器这是最大的误区。正确的顺序是dmesg看错误、modetest看状态、debugfs看对象、ftrace看调用链最后才是接逻辑分析仪量波形。很多软件问题在前三步就能定位。第三改mode之前先备份当前配置。尤其带EDID的屏在调试时随意切换一个错误模式可能触发面板保护。我之前调试时在连接状态下设置了一个不支持的timing结果面板直接进入了保护状态需要断电一段时间才能恢复。第四关注fb的对齐规则。DRM驱动对framebuffer的stride和起始地址一般有对齐要求常见的是64位或者256字节对齐。用户空间分配buffer的alignment若不满足驱动要求扫描就会出现周期性的错位。debugfs里看到的stride与实际分配buffer的size对不上基本就是这个原因。第五分清vblank和vsync。vblank是硬件事件vsync是vblank同步到用户空间后的逻辑。看dmesg里vblank中断的频率如果中断频繁丢失或者触发异常那就不是应用层vsync的问题而是内核驱动对vblank处理有bug。很多帧率上不去的问题追到后来都是vblank中断被关掉了。6. 一些调试环境方面的细节补充工具选好了、流程理顺了调试环境本身也有不少讲究。我最早做调试时吃过不少亏这里多说几句。建议搞一整套稳定的串口日志方案。显示驱动和别的驱动不同屏幕本身就是被调试对象你没法在屏幕上打印信息观察系统状态。如果板子没有串口或者串口没引出调试效率会直线下降。所以我做显示驱动开发时第一件事就是把串口拉通而且日志级别开到最高确保无论屏幕亮不亮都能看到内核的完整运行过程。另外手边要有一套稳定的测试画面。我会准备几个原始色彩画面纯色、渐变、网格用脚本固定输出到指定分辨率。这些画面的好处是纯色能看出屏有没有坏点、偏色渐变能看出8bit/10bit色深切换是否正确网格能精确判断错位在屏幕的哪个区域。比随便放一张照片再观察“看起来不对劲”要可靠得多。调试工具的自动化脚本也值得投入时间。显示驱动调试经常需要反复切换分辨率、检查状态、比对日志。把这些操作写成shell脚本或者用python脚本调用modetest和debugfs自动比对预期结果可以解放大量重复劳动。就像写lua调试脚本一样把操作序列固化下来出问题跑一遍就能得到结构化结果比手动一条条敲命令高效得多。最后聊一下工具版本的匹配问题。modetest和debugfs接口在不同内核版本上差异不小老内核的modetest拿到新内核上跑可能解析不了新的DRM对象类型。我用的一直是随内核版本配套的libdrm工具尽量保持工具版本和内核版本一致避免因为工具本身解析错误产生误导。毕竟调试工具自己如果不可靠它给出的结论也就不可信了。回头说一点个人体会。我见过很多人桌上堆满了高端示波器和逻辑分析仪遇到显示问题却还是两眼一抹黑。工具的多少从来不等于调试能力强真正的差距在于你有没有建立一套“分层定位”的思维。先把问题框到某一个层再用这一层对应的工具去细化最后才回到代码层面改东西。这套方法论内化之后你会发现显示驱动调试其实没有那么多玄学大部分问题都是“工具用对了答案自己就出来了”。显示器亮起来的那一瞬间永远是我做这块驱动最爽的时刻。而为了那一次点亮前面无数次把dmesg翻到底、把modetest输出对齐、把波形量到小数点后几位——这些功夫一点都不会白费。
返回列表