ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

显示驱动调试实战:DRM debugfs与modetest从点屏到故障排查

显示驱动调试实战:DRM debugfs与modetest从点屏到故障排查 1. 显示驱动调试的底层逻辑与工具选型思路做显示驱动这行的人都有一个共识代码写完了只是开始真正的战场在调试。一块屏幕从点亮到画面正常输出中间要经过图层合成、时序控制、接口协议传输、面板初始化等一长串环节任何一个环节出问题表现可能是黑屏、花屏、闪屏、颜色偏差甚至系统直接崩溃。这时候如果没有一套趁手的调试工具基本等于盲人摸象。显示驱动的调试工具链大致可以分成三个层次。最底层是内核态的调试接口比如 DRMDirect Rendering Manager子系统暴露出来的 debugfs 节点能直接看到 CRTC、Encoder、Connector、Plane 这些硬件抽象对象的状态。中间层是用户态的命令行工具最典型的就是modetest它能枚举显示资源、设置显示模式、测试不同分辨率。最上层是应用层的验证手段比如 Android 系统里的SurfaceFlingerdump、dumpsys SurfaceFlinger命令以及各种 GPU 渲染分析工具。为什么要把这三个层次分清楚因为不同阶段遇到的问题需要用不同层次的工具来定位。举个例子如果你连屏幕都没点亮那肯定要先从内核态查起看看 Connector 有没有检测到显示器、CRTC 有没有正常配置时序。如果屏幕亮了但画面撕裂那可能是图层合成或者 vsync 同步的问题得往上走看 SurfaceFlinger 的状态。如果画面正常但颜色不对那可能要查色彩空间转换或者 gamma 校正的配置。我见过不少新手一上来就用modetest去点屏点不亮就懵了完全不知道下一步该查什么。其实正确的思路是自底向上先确认硬件连接和电源正常再确认内核驱动加载成功并且 DRM 设备节点存在然后用modetest验证基本的显示通路最后才去排查上层合成和渲染的问题。这个顺序不能乱乱了就是浪费时间。工具选型上还有一点要注意不同平台的 DRM 驱动实现差异很大。比如 Rockchip 的 DRM 驱动和 i.MX 的 DRM 驱动在 debugfs 节点的命名和内容上就有区别。所以你在网上找到的教程不一定完全适用你的平台关键是要理解 DRM 框架的通用概念然后结合具体平台的文档去适配。这也是为什么我建议每个做显示驱动的人都应该花时间读一遍 DRM 子系统的核心代码至少把drm_crtc.c、drm_connector.c、drm_encoder.c这几个文件的关键流程搞清楚。2. DRM 框架核心概念与 debugfs 调试节点详解2.1 DRM 五大核心对象到底是什么关系DRM 框架里有五个核心对象CRTC、Encoder、Connector、Plane、Framebuffer。很多人刚开始学的时候被这几个概念绕晕我用一个生活化的类比来解释。把显示输出想象成一条快递配送链路。Framebuffer就是你要寄的包裹里面装的是像素数据。Plane是打包台你可以把多个包裹多个图层叠在一起比如视频层叠在 UI 层下面。CRTC是配送站它负责从打包台取货然后按照指定的时间表显示时序往外发。Encoder是运输车它把配送站的货转换成特定的运输格式比如 HDMI 的 TMDS 信号、MIPI DSI 的差分信号。Connector是收件人门口的快递柜它代表物理接口负责检测收件人显示器在不在、支持什么规格。这五个对象的关系是一个 CRTC 可以接一个 Encoder一个 Encoder 可以接一个 Connector。Plane 挂在 CRTC 上Framebuffer 挂在 Plane 上。当你执行modetest设置显示模式的时候实际上就是在配置这条链路上的各个对象。理解了这个关系你再看 debugfs 里的信息就不会晕了。在/sys/kernel/debug/dri/目录下通常会有0、1这样的编号目录每个对应一个 DRM 设备。进去之后你会看到一堆文件# 查看 DRM 设备的基本信息 cat /sys/kernel/debug/dri/0/name # 输出类似rockchip-drm devff900000.vop # 查看所有 Connector 的状态 cat /sys/kernel/debug/dri/0/connectorsconnectors文件里会列出每个 Connector 的 ID、状态connected/disconnected、支持的显示模式列表。这个信息非常关键因为如果 Connector 状态是 disconnected那后面怎么调都没用得先查硬件连接和 EDID 读取。2.2 用 debugfs 快速定位显示通路故障我平时排查显示问题第一步永远是看 debugfs。具体操作流程是这样的# 挂载 debugfs有些系统默认没挂 mount -t debugfs none /sys/kernel/debug # 进入 DRM 调试目录 cd /sys/kernel/debug/dri/0 # 查看当前 CRTC 配置 cat crtc-0/state # 查看 Plane 状态 cat plane-0/state # 查看 Encoder 状态 cat encoder-0/statecrtc-0/state文件里会显示当前 CRTC 的使能状态、绑定的 Connector ID、显示模式分辨率、刷新率、输出格式等。如果你发现activeno说明 CRTC 根本没使能那屏幕肯定是黑的。如果activeyes但mode是空的说明时序没配置对。这里有个经验很多黑屏问题的根源是 CRTC 没有正确绑定 Connector。在 DRM 驱动初始化的时候如果 Connector 检测失败或者 Encoder 没有正确关联CRTC 就不会被激活。这时候你需要检查设备树里的ports和endpoints配置确保 CRTC、Encoder、Connector 之间的链路是通的。还有一个容易忽略的点是Plane 的格式配置。如果 Framebuffer 的像素格式和 Plane 支持的格式不匹配画面会花屏或者直接显示异常。在plane-0/state里可以看到format字段常见的格式有XR24XRGB8888、AR24ARGB8888、NV12YUV 视频格式等。如果你用modetest测试时指定了错误的格式画面就会出问题。2.3 不同平台的 debugfs 差异与适配技巧前面说了不同平台的 DRM 驱动实现有差异这里具体展开一下。Rockchip 平台的 VOPVideo Output Processor驱动在 debugfs 里会额外暴露vop相关的寄存器信息你可以直接读寄存器值来确认硬件状态# Rockchip 平台查看 VOP 寄存器 cat /sys/kernel/debug/dri/0/vop0/regsi.MX 平台的 IPUImage Processing Unit驱动则会在 debugfs 里提供ipu相关的调试节点能看到 DMA 通道的状态和中断统计。这些平台特有的节点在通用教程里很少提到但实际调试时非常有用。我的建议是拿到一个新平台先把/sys/kernel/debug/dri/下面所有文件都cat一遍把输出内容记下来。然后对照 DRM 框架的通用文档理解每个字段的含义。这样下次遇到问题你就能快速定位到是哪个对象的状态不对。3. modetest 实战从枚举资源到点亮屏幕的完整流程3.1 modetest 的编译与部署modetest是libdrm包自带的一个测试工具源码在libdrm/tests/modetest/目录下。很多嵌入式系统默认不带这个工具需要自己交叉编译。编译步骤大致如下# 下载 libdrm 源码 git clone https://gitlab.freedesktop.org/mesa/drm.git cd drm # 配置交叉编译以 ARM64 为例 meson build \ --cross-file cross_file.txt \ -Dteststrue \ -Dlibkmsfalse # 编译 ninja -C build # 产物在 build/tests/modetest/modetest交叉编译文件cross_file.txt的内容大概是这样[binaries] c aarch64-linux-gnu-gcc cpp aarch64-linux-gnu-g ar aarch64-linux-gnu-ar strip aarch64-linux-gnu-strip [host_machine] system linux cpu_family aarch64 cpu aarch64 endian little编译完成后把modetest推到开发板上加上可执行权限就能用了。如果不想自己编译很多发行版的libdrm-tests包里已经包含了这个工具直接安装即可。注意有些平台的 DRM 驱动不支持modetest的某些操作比如 atomic commit。如果执行时报Operation not supported可以加-a参数试试非 atomic 模式。3.2 枚举显示资源搞清楚手头有什么modetest最常用的功能就是枚举当前系统的显示资源。执行modetest -M rockchip -c-M指定 DRM 驱动名称-c表示列出 Connector。输出大概长这样Connectors: id encoder status name size (mm) modes encoders 36 35 connected HDMI-A-1 520x290 3 35 40 39 connected eDP-1 340x190 1 39这里能看到两个 ConnectorHDMI-A-1 和 eDP-1都是 connected 状态。HDMI 支持 3 个显示模式eDP 支持 1 个。encoders字段显示的是这个 Connector 当前绑定的 Encoder ID。再看 Encoder 和 CRTCmodetest -M rockchip -e modetest -M rockchip -p-e列出 Encoder-p列出 CRTC 和 Plane。这些信息组合起来你就能画出当前的显示拓扑图哪个 CRTC 接了哪个 Encoder哪个 Encoder 又接了哪个 Connector。3.3 设置显示模式并点亮屏幕枚举完资源后就可以尝试设置显示模式了。基本命令格式modetest -M rockchip \ -s connector_id:crtc_id:mode比如要把 HDMI-A-1ID 36接到 CRTC 0 上使用 1920x1080 模式modetest -M rockchip -s 36:0:1920x1080执行成功后屏幕应该会显示一个彩条测试图案。如果屏幕没反应先检查 CRTC ID 是否正确。有时候 Connector 只能绑定到特定的 CRTC 上绑错了就不会有输出。modetest还支持更复杂的测试比如指定像素格式和刷新率# 指定 60Hz 刷新率和 XRGB8888 格式 modetest -M rockchip -s 36:0:1920x108060 -f XR24如果要点亮多个屏幕可以多次执行-s参数modetest -M rockchip -s 36:0:1920x1080 -s 40:1:1920x1080这里 CRTC 0 给 HDMICRTC 1 给 eDP两个屏幕同时输出。实操心得modetest设置的模式是临时的重启后就恢复了。如果你想持久化配置需要在显示管理服务比如 Android 的 SurfaceFlinger 或者 Linux 的 Weston里配置。另外有些平台的 CRTC 数量有限如果两个 Connector 抢同一个 CRTC后设置的会覆盖前面的。3.4 用 modetest 做压力测试和边界验证除了基本的点屏modetest还能做很多有价值的测试。比如验证不同分辨率的兼容性# 遍历所有支持的模式 for mode in $(modetest -M rockchip -c | grep -oP \dx\d?\d*); do echo Testing mode: $mode modetest -M rockchip -s 36:0:$mode sleep 2 done这个脚本会依次尝试所有显示模式你可以观察哪些模式能正常显示哪些会花屏或黑屏。对于调试 EDID 解析问题特别有用。还可以测试 Plane 的叠加能力# 在 CRTC 0 上叠加两个 Plane modetest -M rockchip -s 36:0:1920x1080 \ -P 400:1920x108000XR24 \ -P 410:640x480100100AR24这个命令会在主画面上叠加一个小窗口用来验证硬件图层合成是否正常。如果叠加后画面异常可能是 Plane 的格式或位置配置有问题。4. Android 显示调试SurfaceFlinger 与 dumpsys 实战4.1 SurfaceFlinger 在显示链路中的角色Android 的显示架构和纯 Linux 有本质区别。在 Android 里应用不直接操作 DRM而是通过 SurfaceFlinger 这个系统服务来合成画面。SurfaceFlinger 从各个应用收集 Surface图层用 GPU 或者硬件合成器HWC把它们合成一张最终的 Framebuffer然后通过 DRM 提交给显示硬件。这个架构带来的好处是合成效率高、功耗低但调试复杂度也上去了。因为问题可能出在应用层、SurfaceFlinger 层、HWC 层或者 DRM 驱动层你需要一层一层往下查。Android 提供了dumpsys SurfaceFlinger命令来查看 SurfaceFlinger 的状态。这个命令的输出信息量非常大我挑几个最常用的部分来说。# 查看显示设备列表 adb shell dumpsys SurfaceFlinger --display-id # 查看所有图层的合成状态 adb shell dumpsys SurfaceFlinger --list # 查看详细的合成统计 adb shell dumpsys SurfaceFlingerdumpsys SurfaceFlinger的完整输出里有几个关键段落需要重点关注。Display 段落会列出每个物理显示器的分辨率、刷新率、当前活跃图层数。Layer 段落会列出每个图层的名称、Z-order、可见区域、合成方式Client 还是 Device。HWC 段落会显示硬件合成器的能力比如支持多少个图层、支持哪些格式。4.2 用 dumpsys 定位画面撕裂和掉帧问题画面撕裂是 Android 显示调试里最常见的问题之一。根本原因通常是 vsync 同步没做好或者合成时间超过了 vsync 周期。排查思路是这样的先看 SurfaceFlinger 的 vsync 统计信息adb shell dumpsys SurfaceFlinger --latency layer_name这个命令会输出该图层的帧时间戳包括应用绘制时间、SurfaceFlinger 合成时间、显示提交时间。如果发现某段时间的合成时间明显超过 16.6ms60Hz 下的 vsync 周期那就是掉帧了。再看 HWC 的合成策略adb shell dumpsys SurfaceFlinger | grep -A 20 Hardware Composer如果发现大量图层走了 GPU 合成Client Composition而不是硬件合成Device Composition那可能是图层数量超过了 HWC 的能力或者图层格式不被 HWC 支持。GPU 合成虽然灵活但功耗高、延迟大容易导致掉帧。避坑技巧有些平台的 HWC 实现有 bug在某些分辨率下会错误地报告合成能力。这时候可以强制使用 GPU 合成来对比验证adb shell setprop debug.sf.hwc.force_gpu 1。如果强制 GPU 合成后问题消失那基本可以确定是 HWC 的问题。4.3 Android 显示调试的常用属性开关Android 系统里有一堆debug.sf开头的属性可以在运行时动态调整 SurfaceFlinger 的行为对调试非常有用。我整理了一个常用属性表属性名作用常用值debug.sf.hwc.force_gpu强制 GPU 合成0/1debug.sf.disable_hwc禁用 HWC0/1debug.sf.enable_hwc_vds启用 HWC 虚拟显示0/1debug.sf.latch_unsignaled允许未同步的帧提交0/1debug.sf.dump启用 SurfaceFlinger dump0/1debug.sf.dump.glesdump GLES 状态0/1设置方式adb shell setprop debug.sf.hwc.force_gpu 1 adb shell stop adb shell start注意有些属性需要重启 SurfaceFlinger 才能生效最简单的办法是重启系统服务或者直接重启设备。还有一个非常实用的命令是adb shell dumpsys SurfaceFlinger --timestats它能输出一段时间内的帧率统计包括平均帧率、掉帧次数、卡顿次数。做性能优化的时候这个数据比肉眼观察靠谱得多。5. 常见显示问题排查速查与实战经验5.1 黑屏问题从电源到时序的逐层排查黑屏是显示调试里最让人头疼的问题因为可能的原因太多了。我总结了一套逐层排查的方法按顺序来基本不会漏。第一层硬件连接和电源。先确认屏幕背光是否点亮用手电筒照屏幕看有没有隐约画面确认排线是否插紧确认电源电压是否正常。这一步看似简单但实际工作中至少有 30% 的黑屏问题是硬件连接导致的。第二层内核驱动加载。检查 DRM 驱动是否成功 probedmesg | grep -i drm dmesg | grep -i vop dmesg | grep -i dsi如果看到probe failed或者timeout之类的错误那就是驱动初始化有问题。常见原因包括时钟配置错误、电源域没打开、PHY 初始化失败等。第三层Connector 检测。用modetest -c看 Connector 状态。如果是disconnected检查 EDID 读取是否正常。对于 HDMI可以用cat /sys/class/drm/card0-HDMI-A-1/edid | hexdump -C看 EDID 数据。如果 EDID 全是 0 或者读取失败那可能是 DDC 通道有问题。第四层CRTC 配置。用modetest -s手动设置模式。如果设置成功但屏幕还是不亮检查 CRTC 的时序参数是否正确。有些屏幕对时序要求很严格行场同步极性搞反了就不显示。第五层背光控制。如果前面都正常但屏幕还是黑的检查背光电路。Linux 下背光通常通过 PWM 或者 GPIO 控制# 查看背光设备 ls /sys/class/backlight/ # 手动设置背光亮度 echo 255 /sys/class/backlight/backlight/brightness5.2 花屏和闪屏时序与格式的常见陷阱花屏通常和像素格式、时序参数、内存带宽有关。我遇到过的花屏问题里最常见的原因是Framebuffer 格式和 Plane 格式不匹配。比如 Framebuffer 是 ARGB8888但 Plane 配置成了 RGB565那颜色就会错乱。排查方法是用modetest指定明确的格式# 用 XRGB8888 格式测试 modetest -M rockchip -s 36:0:1920x1080 -f XR24 # 用 RGB565 格式测试 modetest -M rockchip -s 36:0:1920x1080 -f RG16如果某个格式正常另一个格式花屏那问题就定位到了格式配置上。闪屏问题则更多和时序有关。检查 CRTC 的时序参数cat /sys/kernel/debug/dri/0/crtc-0/state重点关注mode字段里的clock、hdisplay、hsync_start、hsync_end、htotal、vdisplay、vsync_start、vsync_end、vtotal这些值。它们必须和屏幕规格书里的一致。特别是htotal和vtotal如果设小了会导致数据传输带宽不够画面就会闪。还有一个隐蔽的闪屏原因是DDR 带宽不足。高分辨率高刷新率下显示控制器从 DDR 读 Framebuffer 需要很大的带宽。如果和其他模块比如 GPU、VPU抢带宽就会出现闪屏。这时候可以用devfreq或者ddr的调试节点查看带宽占用情况。5.3 显示问题排查速查表为了方便快速定位问题我整理了一张速查表现象可能原因排查工具解决方法完全黑屏电源/背光/驱动未加载dmesg, 万用表检查硬件连接和驱动 probe有背光无画面CRTC 未使能/时序错误modetest, debugfs手动设置模式检查时序参数花屏格式不匹配/内存问题modetest -f统一 Framebuffer 和 Plane 格式闪屏时序参数错误/带宽不足debugfs crtc state修正时序降低分辨率或刷新率颜色偏差色彩空间/gamma 配置色彩分析仪检查 CSC 和 gamma 表画面撕裂vsync 同步问题dumpsys SurfaceFlinger检查 HWC 合成策略和 vsync 配置掉帧卡顿合成超时/带宽不足dumpsys --timestats优化图层数量启用硬件合成这张表是我多年调试经验的浓缩基本上覆盖了 80% 的常见问题。当然实际场景可能更复杂但按照这个思路去排查至少不会毫无头绪。5.4 几个容易被忽略的调试细节最后分享几个我在实际工作中踩过的坑都是文档里不会写的。第一个坑debugfs 节点权限问题。有些系统默认把 debugfs 挂载成noexec或者权限很严导致modetest无法访问。解决办法是重新挂载mount -o remount,rw /sys/kernel/debug。第二个坑多个 DRM 设备混淆。有些平台有多个 DRM 设备比如一个给显示一个给 GPUmodetest默认操作第一个。如果操作错了设备怎么调都没反应。用modetest -M driver_name明确指定驱动名称。第三个坑Android 的 SELinux 限制。在 Android 上直接跑modetest可能会被 SELinux 拦截。临时关闭 SELinux 用setenforce 0但正式调试时应该配置正确的 SELinux 策略而不是一关了之。第四个坑热插拔检测延迟。HDMI 热插拔后Connector 状态更新可能有延迟。如果modetest -c显示的还是旧状态可以手动触发检测echo detect /sys/class/drm/card0-HDMI-A-1/status。第五个坑时钟精度问题。有些平台的 PLL 时钟精度不够导致实际刷新率和设定值有偏差。用modetest设置 60Hz实际可能是 59.94Hz。对于大多数场景没问题但对帧同步要求高的场景比如视频播放就会有影响。这时候需要微调 PLL 参数。这些细节看起来不起眼但实际调试时往往就是这些地方卡住你半天。显示驱动调试这件事说到底就是理论加经验理论让你知道查什么经验让你知道怎么查得快。多动手、多记录、多总结慢慢就能形成自己的调试方法论了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进