跳转至

主线内核调试技巧:那些让你少走弯路的方法

前言:调试是嵌入式开发的日常

说实话,移植主线内核没有一次就成功的。至少我的经历是这样的:编译通过了 → 烧录 → 启动 → 报错 → 改代码 → 再编译 → 再报错……循环好几次,直到所有问题都解决。

调试的过程其实是理解系统的过程。每解决一个问题,你就对内核的工作原理多了一分理解。这篇文章总结了我在调试主线内核时常用的方法,希望能帮你少走一些弯路。

第一类技巧——dmesg 日志分析

dmesg 是你最好的朋友。内核的几乎所有重要信息都会通过 printk 输出到 dmesg,学会正确阅读 dmesg,调试就成功了一半。

基本用法

# 查看完整日志
dmesg

# 清空日志
dmesg -c

# 实时跟踪日志
dmesg -w

# 只看 ERROR 级别的日志
dmesg -l err

# 只看 WARN 级别的日志
dmesg -l warn

# 带时间戳查看
dmesg -T

按子系统过滤

# 显示相关
dmesg | grep -E "drm|fb|panel|lcdif|mxsfb"
# 输出:
# [    0.912345] mxsfb 21c8000.lcdif: Initializing
# [    0.987654] panel-dpi: panel enabled
# [    1.123456] mxsfb 21c8000.lcdif: bound panel-dpi

# 网络相关
dmesg | grep -E "fec|net|phy|eth"

# 触摸相关
dmesg | grep -E "touch|goodix|input"

# 设备树相关
dmesg | grep -E "of_|dtb|fdt"

理解日志级别

内核日志有 8 个级别:

级别 数值 说明
KERN_EMERG 0 系统不可用
KERN_ALERT 1 需要立即处理
KERN_CRIT 2 严重情况
KERN_ERR 3 错误
KERN_WARNING 4 警告
KERN_NOTICE 5 正常但需要注意
KERN_INFO 6 信息
KERN_DEBUG 7 调试信息

如果你觉得日志太多,可以在内核命令行加 loglevel=4,只显示 WARN 及以上级别的日志。

常见报错模式

# 驱动 probe 失败
dmesg | grep "probe.*failed"

# 资源冲突
dmesg | grep "conflict"

# 设备不存在
dmesg | grep "ENODEV"

# 内存分配失败
dmesg | grep "alloc.*failed"

第二类技巧——设备树验证

设备树写错了,驱动就 probe 不了。但问题是你怎么知道是设备树的问题?以下方法可以帮你验证。

方法一:/proc/device-tree

内核会把设备树解析后的内容放到 /proc/device-tree 目录下,你可以直接查看:

# 列出所有节点
ls /proc/device-tree/

# 查看 lcdif 节点
ls /proc/device-tree/soc/bus@2100000/lcdif@21c8000/

# 读取 lcdif 的 compatible 属性
cat /proc/device-tree/soc/bus@2100000/lcdif@21c8000/compatible

# 读取 lcdif 的 status 属性
cat /proc/device-tree/soc/bus@2100000/lcdif@21c8000/status

如果 status 是 okay,说明设备已启用;如果是 disabled,说明设备被禁用;如果文件不存在,说明节点定义有问题。

方法二:dtc 反编译

你可以把运行中的设备树导出成 DTS 源文件:

# 反编译整个设备树
dtc -I fs /proc/device-tree -O dts -o /tmp/running.dts

# 查看某个节点
dtc -I fs /proc/device-tree -O dts 2>/dev/null | grep -A 20 "lcdif"

对比你写的 DTS 源文件和运行中的 DTS,能发现很多问题。

方法三:检查 OF graph 连接

对于 DRM 显示系统,OF graph 的连接很重要:

# 查看 panel 的 remote-endpoint
cat /proc/device-tree/panel-dpi/port/panel_in/remote-endpoint

# 查看 lcdif 的 remote-endpoint
cat /proc/device-tree/soc/bus@2100000/lcdif@21c8000/port/lcdif_out/remote-endpoint

这两个 remote-endpoint 应该互相指向。如果一个是空的,说明连接有问题。

第三类技巧——DRM 调试接口

DRM 子系统提供了丰富的调试接口,都在 /sys/kernel/debug/dri/ 下(需要 debugfs 挂载)。

挂载 debugfs

mount -t debugfs none /sys/kernel/debug

查看 DRM 状态

# 列出所有 DRM 设备
ls /sys/kernel/debug/dri/

# 查看 connector 状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 输出:connected 或 disconnected

# 查看 connector 支持的模式
cat /sys/class/drm/card0-HDMI-A-1/modes
# 输出:1024x600 或其他分辨率

# 查看 framebuffer 信息
cat /sys/class/graphics/fb0/mode
# 输出:1024x600p-60

modetest 工具

如果安装了 modetest

# 列出所有设备
modetest

# 只测试 mxsfb
modetest -M mxsfb

# 测试模式设置
modetest -M mxsfb -s 1024x600@RG24

modetest 会显示所有 connector、encoder、CRTC 的信息,还能直接设置模式测试显示。

第四类技巧——内核配置检查

有时候问题不是代码,而是配置。你需要确认内核是否开启了必要的功能。

检查 .config

# 查看当前配置
cat /proc/config.gz | gunzip

# 或者在源码目录
cat .config

# 搜索特定配置
cat /proc/config.gz | gunzip | grep DRM
cat /proc/config.gz | gunzip | grep PANEL

关键配置检查清单

# DRM 相关
zcat /proc/config.gz | grep -E "CONFIG_DRM|CONFIG_FB"
# 应该看到:
# CONFIG_DRM=y
# CONFIG_DRM_MXSFB=y
# CONFIG_DRM_PANEL=y
# CONFIG_DRM_PANEL_SIMPLE=y
# CONFIG_FB_MXS is not set  ← 必须是 not set

# 网络相关
zcat /proc/config.gz | grep -E "CONFIG_FEC|CONFIG_MICREL"
# 应该看到:
# CONFIG_FEC=y
# CONFIG_MICREL_PHY=y

# 触摸相关
zcat /proc/config.gz | grep -E "CONFIG_TOUCHSCREEN|CONFIG_INPUT"
# 应该看到:
# CONFIG_TOUCHSCREEN_GOODIX=y
# CONFIG_INPUT_TOUCHSCREEN=y

第五类技巧——硬件调试

有时候问题在硬件层面,需要用示波器、万用表等工具。

GPIO 状态检查

# 查看 GPIO 的 sysfs 接口
ls /sys/class/gpio/

# 导出 GPIO
echo 49 > /sys/class/gpio/export
cat /sys/class/gpio/gpio49/direction
cat /sys/class/gpio/gpio49/value

# 设置 GPIO 方向和值
echo out > /sys/class/gpio/gpio49/direction
echo 1 > /sys/class/gpio/gpio49/value

I2C 设备检查

# 安装 i2c-tools
apt install i2c-tools

# 扫描 I2C 总线
i2cdetect -y 0
i2cdetect -y 1

# 读取 I2C 寄存器
i2cget -y 1 0x5d 0x00

# 写入 I2C 寄存器
i2cset -y 1 0x5d 0x00 0x01

GT9147 的 I2C 地址是 0x5d,如果 i2cdetect -y 1 能在 0x5d 位置看到设备,说明 I2C 通信正常。

网络接口检查

# 查看接口状态
ip link show eth0
ethtool eth0

# 查看 PHY 寄存器
cat /sys/bus/mdio_bus/devices/2188000.ethernet:02/phy_id

# 抓包分析
tcpdump -i eth0 -w /tmp/capture.pcap

第六类技巧——trace 和 latency

如果你需要分析性能问题或时序问题,可以用 ftrace:

# 挂载 debugfs
mount -t debugfs none /sys/kernel/debug

# 查看可用的 tracer
cat /sys/kernel/debug/tracing/available_tracers

# 启用 function tracer
echo function > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace

# 只跟踪某个函数
echo drm_atomic_helper_commit > /sys/kernel/debug/tracing/set_ftrace_filter
cat /sys/kernel/debug/tracing/trace

ftrace 能帮你分析函数调用关系和执行时间,对理解内核行为很有帮助。

常见问题速查表

现象 可能原因 检查方法
LCD 不亮,背光也不亮 背光供电问题 检查 cat /sys/class/backlight/*/brightness
LCD 不亮,背光亮 时序参数错误 用示波器测 LCD 接口信号
触摸没反应 I2C 通信失败 i2cdetect -y 1 检查 0x5d
网口不通 PHY 链路问题 ethtool eth0 检查链接状态
dmesg 里全是 defer probe 依赖设备未就绪 等待几秒后检查,或检查设备树顺序
编译报头文件找不到 依赖没装 检查 make menuconfig 配置

完整调试流程示例

假设你遇到 LCD 不显示的问题,完整的调试流程可能是:

# 1. 检查 dmesg
dmesg | grep -E "drm|panel|lcdif"
# 如果看到 "Cannot connect bridge (-ENODEV)",说明 panel 没找到

# 2. 检查设备树
ls /proc/device-tree/panel-dpi/
# 如果目录不存在,说明 panel 节点没定义

# 3. 检查内核配置
zcat /proc/config.gz | grep PANEL_SIMPLE
# 如果是 n,说明驱动没编译进内核

# 4. 检查 DRM 状态
cat /sys/class/drm/card0-HDMI-A-1/status
# 如果是 disconnected,说明 connector 没初始化

# 5. 检查背光
cat /sys/class/backlight/*/brightness
echo 255 > /sys/class/backlight/*/brightness
# 如果屏幕变亮,说明背光是好的

# 6. 用示波器测 LCD 接口信号
# 如果信号正常,说明驱动工作正常,问题在屏幕硬件

下一章预告

到这里,你应该掌握了主线内核调试的基本方法。有了这些工具,遇到问题就能快速定位。

最后一篇文章,我们会汇总常见问题和解决方案:

  • 报错速查表
  • GPIO 冲突解决
  • 时钟配置问题
  • 电源管理问题
  • 启动失败排查

掌握了调试方法和常见问题的解决思路,你就基本具备了独立移植主线内核的能力。我们最后一章见。


参考命令速查

# dmesg 分析
dmesg | grep -E "drm|panel"
dmesg -l err
dmesg -T

# 设备树验证
ls /proc/device-tree/panel-dpi/
dtc -I fs /proc/device-tree -O dts -o running.dts

# DRM 调试
cat /sys/class/drm/card0-HDMI-A-1/status
modetest -M mxsfb

# 内核配置检查
zcat /proc/config.gz | grep DRM
zcat /proc/config.gz | grep PANEL

# I2C 检查
i2cdetect -y 1
i2cget -y 1 0x5d 0x00

# 网络检查
ethtool eth0
ip link show

延伸阅读