1. 显示驱动调试的痛点与工具选型逻辑搞显示驱动的朋友大概都有过这种体验屏幕点不亮串口里刷出一堆看不懂的日志改了几个寄存器还是黑屏最后发现是某个时钟没使能。显示子系统涉及的东西太多了——CRTC、Encoder、Connector、Bridge、Panel、背光、电源域、时钟树任何一个环节出问题都表现为屏幕不亮或花屏。这时候如果没有一套趁手的调试工具基本就是盲人摸象。我做了几年显示驱动从早期的FBDEV到现在的DRM/KMS框架踩过的坑不计其数。这篇文章不讲大道理就聊聊实际工作中真正用得上的几个调试工具以及它们各自适合解决什么问题。核心关键词就几个显示驱动、调试工具、DRM、modetest、dmesg。如果你正在做Linux显示驱动开发或者刚接手一个点屏项目这些内容应该能帮你省下不少时间。先说清楚一个前提显示驱动的调试工具大致分三类。第一类是内核日志类比如dmesg、printk、ftrace用来追踪驱动内部的执行流程和状态变化第二类是用户态验证类比如modetest、kmscube用来验证KMS管道的配置是否正确第三类是硬件寄存器类比如devmem、i2c-tools用来直接读写显示控制器和外部芯片的寄存器。这三类工具不是替代关系而是配合使用——先用dmesg看驱动有没有报错再用modetest验证显示管道最后用寄存器工具确认硬件状态。为什么工具选型这么重要因为显示驱动的问题定位成本很高。一个屏幕不亮的问题可能涉及五六个模块每个模块又有几十个寄存器。如果没有系统性的调试方法很容易陷入改一个参数试一次的低效循环。我见过有人为了调一个MIPI DSI的时序反复烧录固件几十次最后发现是lane数配置错了。如果一开始就用对工具这个问题五分钟就能定位。还有一个容易被忽略的点不同阶段的调试重点不一样。Bring-up阶段最关心的是能不能点亮这时候dmesg和寄存器读写是主力功能验证阶段关心的是显示是否正确modetest和kmscube更合适性能优化阶段关心的是带宽和延迟这时候需要用到ftrace和perf。所以工具选型不是一成不变的要根据当前所处的阶段来调整。下面我会按照实际调试流程逐个拆解这些工具的使用方法、适用场景和踩坑经验。每个工具都会给出具体的命令和参数以及我在实际项目中总结的注意事项。2. dmesg与内核日志显示驱动调试的第一现场2.1 为什么dmesg是显示驱动调试的起点显示驱动跑在内核态用户态的工具再强大也看不到驱动内部的执行细节。dmesg是内核日志的环形缓冲区驱动里所有的dev_info、dev_err、dev_dbg输出都会进到这里。屏幕不亮的时候第一件事就是敲dmesg看看驱动有没有probe成功、有没有报错、有没有超时。但很多人用dmesg的方式不对。直接敲dmesg会输出几千行日志里面混杂着USB、网络、文件系统的信息根本找不到显示相关的。正确的做法是配合grep过滤dmesg | grep -iE drm|panel|dsi|hdmi|backlight|vop|mipi这个命令会把所有跟显示相关的日志过滤出来。我一般还会加上时间戳方便定位问题发生的顺序dmesg -T | grep -iE drm|panel|dsi|hdmi-T参数会把内核时间戳转成人类可读的格式。不过要注意有些嵌入式平台的RTC没校准转换出来的时间可能不准这时候还是用原始时间戳更可靠。2.2 显示驱动probe失败的典型日志模式显示驱动的问题大部分在probe阶段就能看出来。我整理了几种常见的失败模式以及对应的日志特征日志特征可能原因排查方向probe failed with error -517EPROBE_DEFER依赖未就绪检查panel、backlight、regulator的probe顺序failed to get gpioGPIO资源未定义或冲突检查DTS中的gpio配置failed to get clock时钟未使能或未注册检查clock provider和clock-namestimeout waiting for...硬件无响应检查电源、复位、I2C/SPI通信unsupported format像素格式不匹配检查panel和controller的format配置-517这个错误码特别常见它是EPROBE_DEFER意思是我依赖的东西还没准备好等会儿再试。显示驱动依赖的东西很多——panel、背光、电源 regulator、时钟任何一个没就绪都会导致defer。这时候要看的是probe的调用顺序而不是驱动本身的代码。我遇到过一个案例MIPI DSI controller一直probe失败日志显示-517。查了半天发现是panel的驱动注册晚了因为panel依赖一个I2C的背光芯片而那个I2C芯片又依赖一个GPIO扩展器。整条依赖链是GPIO扩展器 → I2C背光 → Panel → DSI Controller。这种链式依赖在DTS里很难一眼看出来只能通过日志里的probe顺序来推断。2.3 动态调试让驱动输出更多细节有时候dmesg里的信息不够驱动只输出了probe success但你想知道具体的寄存器配置值。这时候可以用动态调试dynamic debug在不重新编译驱动的情况下打开dev_dbg输出。首先确认内核编译时开启了CONFIG_DYNAMIC_DEBUG然后# 查看当前支持的动态调试节点 cat /sys/kernel/debug/dynamic_debug/control | grep drm # 打开某个文件的debug输出 echo file drivers/gpu/drm/panel/panel-simple.c p /sys/kernel/debug/dynamic_debug/control # 打开某个模块的所有debug输出 echo module dw_mipi_dsi p /sys/kernel/debug/dynamic_debug/controlp表示打开printk输出还可以用f打开函数名l打开行号。这个功能在追踪驱动内部流程时非常有用尤其是当你怀疑某个函数没被调用或者某个分支没走到的时候。不过要注意动态调试打开后日志量会暴增尤其是在中断处理函数里加printk可能导致系统卡顿甚至看门狗复位。我的经验是只在必要时打开定位到问题后立即关闭。关闭的方法是把p改成-p。2.4 printk的替代方案trace_printk和ftrace如果动态调试还不够或者你需要在中断上下文里输出日志printk可能会有性能问题。这时候可以考虑trace_printk它写入的是ftrace的环形缓冲区开销比printk小得多。#include linux/kernel.h // 在驱动代码中 trace_printk(dsi lane rate: %d\n, lane_rate);然后通过tracefs读取cat /sys/kernel/debug/tracing/tracetrace_printk的缺点是会污染ftrace的缓冲区而且官方文档不太推荐在生产驱动里长期保留。但在调试阶段它确实比printk更轻量。3. modetest验证KMS管道的瑞士军刀3.1 modetest能做什么不能做什么dmesg解决的是驱动有没有跑起来的问题modetest解决的是显示管道配置对不对的问题。它是libdrm自带的一个测试工具可以枚举显示资源、设置显示模式、测试页面翻转。先看基本用法# 枚举所有显示资源 modetest -M rockchip # 查看特定connector的状态 modetest -M rockchip -c # 在指定connector上显示测试图案 modetest -M rockchip -s 4235:1920x1080这里的4235表示connector ID 42CRTC ID 35。-s是set mode的意思后面跟分辨率和刷新率。modetest最实用的功能是枚举资源。当你不知道系统里有哪些CRTC、Encoder、Connector以及它们的对应关系时modetest -M driver一敲所有信息一目了然。输出里会显示每个connector支持的模式列表、当前连接状态、支持的像素格式等。但modetest也有局限。它只能验证KMS层面的配置不能验证panel的初始化序列、背光控制、电源时序这些底层的东西。如果modetest能显示图案但屏幕还是黑的问题大概率在panel侧不在KMS侧。3.2 用modetest定位花屏和偏移问题花屏和图像偏移是显示调试中的常见问题。modetest的测试图案功能可以帮你快速判断问题出在哪个环节。# 显示 SMPTE 彩条图案 modetest -M rockchip -s 4235:1920x1080 -P 3540:1920x1080AR24-P参数用来指定plane的配置格式是plane_idcrtc_id:宽x高格式。如果彩条显示正常说明KMS管道和panel的时序都没问题花屏可能是应用层或GPU的问题。如果彩条本身就花那问题在显示控制器或panel的时序配置上。我遇到过一个典型的偏移问题图像整体向右偏移了64个像素。用modetest显示彩条发现彩条也偏移说明不是应用层的问题。最后查出来是panel的hback-porch参数配错了实际值应该是64DTS里写成了0。这种问题用modetest一眼就能看出来比反复改应用代码高效得多。3.3 modetest的原子提交模式较新的内核支持原子atomic模式modetest也提供了对应的参数# 原子模式设置 modetest -M rockchip -a -s 4235:1920x1080-a表示使用atomic API。原子模式的好处是所有配置一次性提交要么全部生效要么全部不生效不会出现中间状态。在调试多图层叠加、缩放、旋转这些复杂场景时原子模式更可靠。不过要注意不是所有驱动都完整支持原子模式。有些老驱动只实现了legacy接口用-a参数会报错。这时候去掉-a用legacy模式就行。3.4 modetest之外的验证工具kmscube和kmstestmodetest显示的是静态图案如果想验证动态渲染和页面翻转可以用kmscube。它是一个基于OpenGL ES的旋转立方体演示程序能直观地看出帧率和撕裂情况。kmscube -D /dev/dri/card0如果kmscube能流畅运行说明GPU渲染、KMS提交、页面翻转这条链路都是通的。如果卡顿或撕裂可能是vsync配置有问题或者GPU和显示控制器的带宽不够。还有一个工具叫kmstest是modetest的简化版用法更简单适合快速验证kmstest -c 42 -r 1920x10804. 寄存器级调试devmem与i2c-tools的实战用法4.1 什么时候需要直接读写寄存器dmesg和modetest都是高层工具它们看到的是驱动抽象后的结果。但有些问题必须深入到寄存器层面才能定位比如显示控制器的某个中断状态位没有清除panel的初始化序列没有正确写入时钟频率配置错误导致时序偏差电源域的状态寄存器显示异常这时候就需要devmem和i2c-tools这类工具。4.2 devmem读写显示控制器寄存器devmem可以直接读写物理地址空间用法很简单# 读取一个32位寄存器 devmem 0xfe0c0000 32 # 写入一个32位寄存器 devmem 0xfe0c0000 32 0x00000001但直接操作物理地址有风险写错地址可能导致系统崩溃。我的经验是先用devmem读确认地址和值符合预期后再写。而且写之前最好把原值记下来方便回滚。在显示驱动调试中devmem最常用的场景是查看VOPVideo Output Processor或DSI控制器的状态寄存器。比如Rockchip平台的VOP寄存器基地址是0xfe0c0000可以通过读取0xfe0c0000 offset来查看各个模块的状态。不过要注意不同平台的寄存器基地址和偏移量都不一样必须查对应的TRM技术参考手册。而且有些寄存器是只读的写进去也没用有些寄存器有写保护需要先解锁。4.3 i2c-tools调试panel和背光芯片很多panel的初始化序列是通过I2C或SPI写入的背光芯片也通常挂在I2C总线上。i2c-tools是调试这类外设的必备工具。# 列出所有I2C总线 i2cdetect -l # 扫描指定总线上的设备 i2cdetect -y 3 # 读取指定设备的寄存器 i2cget -y 3 0x45 0x00 # 写入指定设备的寄存器 i2cset -y 3 0x45 0x00 0x1fi2cdetect -y 3会扫描I2C总线3上的所有地址显示哪些地址有设备响应。如果panel的地址没有出现在列表里说明要么panel没上电要么I2C线路有问题。我遇到过一个背光不亮的问题i2cdetect能扫到背光芯片的地址但i2cget读出来的值全是0xFF。后来发现是背光芯片的使能引脚没有拉高芯片处于掉电状态虽然I2C能响应但内部寄存器不可访问。这种问题光看dmesg是看不出来的必须用i2c-tools实际读写才能发现。4.4 寄存器调试的安全注意事项直接操作寄存器是有风险的我总结了几个必须遵守的原则注意写寄存器前必须确认地址和位定义错误的写入可能导致显示控制器锁死甚至影响其他模块。先读后写写之前先读原值确认当前状态最小改动只改需要改的位不要整个寄存器覆盖记录操作每次读写都记录下来方便回溯准备回滚关键寄存器写之前准备好恢复脚本避免中断上下文不要在中断处理函数里用devmem可能引起竞态还有一个实用技巧可以把常用的寄存器读写操作写成脚本避免每次手动敲命令。比如#!/bin/bash # vop_reg_dump.sh - 导出VOP关键寄存器 BASE0xfe0c0000 for offset in 0x0000 0x0004 0x0008 0x0010 0x0014; do addr$(printf 0x%x $((BASE offset))) val$(devmem $addr 32) echo VOP$offset $val done5. 显示驱动调试的完整排查链路5.1 从屏幕不亮到定位根因的标准化流程前面分别讲了dmesg、modetest、devmem、i2c-tools这几个工具但实际调试中它们是配合使用的。我总结了一套标准化的排查流程按照这个顺序走大部分问题都能定位到第一步确认驱动probe状态dmesg -T | grep -iE drm|panel|dsi|hdmi|vop看驱动有没有probe成功有没有报错。如果有-517检查依赖顺序如果有timeout检查硬件通信。第二步确认显示资源枚举modetest -M driver -c看connector的状态是connected还是disconnected。如果是disconnected说明HPD热插拔检测没检测到显示器检查HPD引脚和EDID读取。第三步确认KMS管道配置modetest -M driver -s connector_idcrtc_id:mode如果这一步能显示图案说明KMS层面没问题问题在panel或背光。如果不能显示检查CRTC和Encoder的绑定关系。第四步确认panel初始化i2cdetect -y bus i2cget -y bus addr reg检查panel的I2C通信是否正常初始化序列是否写入成功。第五步确认寄存器状态devmem vop_base offset 32检查显示控制器的关键寄存器比如使能位、中断状态、时序配置。5.2 一个真实的排查案例MIPI DSI点屏失败说一个我最近处理的案例。平台是RK3568屏幕是1280x800的MIPI DSI panel现象是背光亮但屏幕全白。按照流程走第一步dmesg显示DSI controller和panel都probe成功没有报错。这说明驱动加载没问题。第二步modetest枚举资源connector状态是connected支持的模式列表里有1280x800。这说明EDID读取正常。第三步modetest设置模式屏幕还是全白。这说明KMS配置可能有问题或者panel没有正确初始化。第四步用i2c-tools检查panel的初始化寄存器。发现panel的0x0A寄存器读出来是0x00而根据datasheet正常初始化后应该是0x1C。这说明panel的初始化序列没有正确执行。第五步查驱动代码发现panel的初始化序列是在prepare回调里执行的但prepare的调用时机在enable之前。而DSI controller的enable又依赖panel的prepare完成。这里有一个时序竞争如果DSI controller先enablepanel的初始化序列还没写完就会导致panel处于未初始化状态。解决方案是调整panel驱动的prepare和enable回调确保初始化序列在DSI controller使能之前完成。具体做法是在prepare里加一个msleep(50)等待panel内部状态机稳定后再写初始化序列。这个问题从表面看是屏幕全白但根因是驱动回调的时序问题。如果没有按照流程一步步排查很容易误判为硬件问题。5.3 常见问题的快速对照表为了方便查阅我把显示驱动调试中的常见问题和对应的排查工具整理成表现象首选工具排查重点屏幕完全不亮dmesgprobe状态、电源、时钟背光亮但无图像modetestKMS管道、panel初始化花屏modetest像素格式、时序参数图像偏移modetesthback-porch、vback-porch闪烁dmesg ftracevsync、中断频率颜色异常modetest像素格式、色彩空间分辨率不对modetestEDID、mode列表触摸不工作i2c-toolsI2C通信、中断引脚6. 调试效率提升的实战经验6.1 日志分级与过滤技巧显示驱动的日志量很大如果不加过滤关键信息很容易被淹没。我的做法是在驱动里对日志分级dev_err只用于真正的错误比如probe失败、通信超时dev_warn用于异常但可恢复的情况比如重试成功dev_info用于关键状态变化比如probe成功、模式切换dev_dbg用于详细的寄存器读写和流程追踪然后在调试时用不同的过滤级别# 只看错误 dmesg -T | grep -iE drm|panel | grep -iE error|fail|timeout # 看错误和警告 dmesg -T | grep -iE drm|panel | grep -iE error|fail|timeout|warn # 看所有显示相关日志 dmesg -T | grep -iE drm|panel|dsi|vop这样可以根据问题的严重程度快速定位到关键日志。6.2 用脚本自动化重复操作显示调试中有很多重复操作比如反复读同一组寄存器、反复设置同一个模式。把这些操作写成脚本能省下大量时间。比如一个自动点屏脚本#!/bin/bash # auto_display_test.sh DRIVERrockchip CONNECTOR42 CRTC35 MODE1280x800 echo 检查驱动状态 dmesg -T | grep -iE drm|panel | tail -20 echo 枚举显示资源 modetest -M $DRIVER -c echo 设置显示模式 modetest -M $DRIVER -s ${CONNECTOR}${CRTC}:${MODE} echo 检查寄存器状态 devmem 0xfe0c0000 32 devmem 0xfe0c0004 32这个脚本把排查流程固化下来每次调试时跑一遍就能快速获取所有关键信息。6.3 显示驱动调试中的常见误区最后说几个我踩过的坑希望能帮你避开误区一只看dmesg不看寄存器。有些问题驱动不会报错但寄存器状态已经异常了。比如中断状态位没有清除驱动不会报错但会导致后续中断丢失。误区二modetest能显示就认为没问题。modetest显示的是测试图案不经过GPU和视频解码器。如果实际应用中是视频播放花屏但modetest正常问题可能在GPU或解码器侧。误区三忽略电源和时钟。显示子系统的电源域和时钟树很复杂很多问题其实是电源没开或时钟频率不对。调试时先用万用表确认电源再用示波器确认时钟能排除一大半硬件问题。误区四改参数不记录。显示调试经常需要反复改参数如果不记录每次改了什么、结果如何很容易陷入混乱。我的习惯是每改一个参数就在笔记本上记一笔包括改之前的值、改之后的值、现象变化。误区五忽视panel的datasheet。很多panel的初始化序列、时序参数、电源要求都在datasheet里写得很清楚但很多人不看凭经验瞎调。我见过有人调了一个星期的时序最后发现datasheet里明确写了hback-porch的最小值是46而他一直设的是40。显示驱动调试确实是个细致活工具只是辅助关键还是对显示子系统的整体理解。把dmesg、modetest、devmem、i2c-tools这几个工具用熟再配合标准化的排查流程大部分问题都能在半小时内定位到根因。剩下的就是查datasheet、改代码、验证这个过程没有捷径只能靠积累。