第3章:调试环境搭建——内核配置、串口日志与动态调试
做USB OTG驱动调试,环境没搭好,后面全是白忙活。
我见过不少新手,代码改了一堆,结果日志打不出来,或者内核根本没把OTG功能编译进去。
今天咱们就把这三件事儿一次性搞定:内核配置选项、串口日志输出、动态调试(debugfs)启用。
3.1 先搞定内核配置:CONFIG_USB_OTG 及相关选项
这一步是基础,说白了就是让内核知道,你要用OTG功能。
我个人习惯,在正式调试前,先把内核 .config 文件翻一遍,确保相关宏都开了。
核心选项如下表:
| 配置宏 | 意义 | 建议值 |
|---|---|---|
| CONFIG_USB_OTG | USB OTG 核心支持 | y |
| CONFIG_USB_OTG_FSM | OTG 有限状态机(驱动核心逻辑) | y |
| CONFIG_USB_OTG_WHITELIST | OTG 设备白名单过滤 | 按需选 y |
| CONFIG_USB_OTG_BLACKLIST_HUB | 禁止 OTG 端口接 Hub | y(建议启用) |
| CONFIG_USB_OTG_PRODUCTINFO | 产品信息字符串传递 | y |
| CONFIG_USB_GADGET | USB Gadget 框架(设备侧) | y 或 m |
| CONFIG_USB_DWC3 | DWC3 控制器驱动(常见平台) | y 或 m |
| CONFIG_USB_DWC3_OTG | DWC3 OTG 模式支持 | y |
核心要点:
- CONFIG_USB_OTG_FSM 不开,状态机不跑,HNP/SRP 协议直接罢工。
- CONFIG_USB_OTG_BLACKLIST_HUB 建议开启,我遇到过因为插了 Hub 导致角色协商失败的情况,排查了好久才发现是 Hub 干扰了 ID 中断时序。
配置方法(以 dwc3 平台为例):
# 进入内核源码目录
make menuconfig
# 依次进入
Device Drivers → USB support → USB Gadget Support
→ [*] USB Gadget Drivers
→ <*> DesignWare USB3 Core Support
→ [*] DesignWare USB3 DRD Mode Support
# OTG 相关通常在
Device Drivers → USB support
→ [*] USB OTG Support
→ [*] USB OTG FSM
嗯,这里要注意:不同厂商的 SoC(比如 TI、NXP、Rockchip)可能还有自己的平台配置宏。
建议在 defconfig 或 fragment 里一次性加好,避免每次 menuconfig 漏选。
小技巧:
我习惯在 .config 文件里直接搜索“OTG”、“DWC3”、“GADGET”,确认所有相关项都被编入内核(=y)。如果是模块(=m),记得确保开机时能正确装载,否则调试时可能发现驱动没在运行。
3.2 串口日志输出:调试第一通道
OTG 驱动调试,串口日志是救命稻草。
为什么呢?因为 OTG 角色切换、ID 中断、VBUS 变化这些事件,其它接口(比如网络、adb)可能根本不可用。
说白了,串口是你最后还能看到“系统在干什么”的窗口。
我一般这么配置:
-
内核日志级别调低,让更多信息刷到串口:
# 在 kernel cmdline 里加: loglevel=8 console=ttyS0,115200n8 -
开启 USB 子系统日志:
# 在 /etc/sysctl.conf 或 sysctl -w 中设置: kernel.printk = 7 4 1 7 # 动态调整: echo "file drivers/usb/* +p" > /sys/kernel/debug/dynamic_debug/control -
针对 Gadget/OTG 专用日志开关:
# 在 drivers/usb/gadget/ 下,可以通过 module_param 打开 verbose: modprobe dwc3.ko debug=1 # 或写入 /sys/module/dwc3/parameters/debug
避坑指南:
我曾经遇到过一款平台,串口被其他模块(比如蓝牙)复用了。导致 OTG 驱动在角色切换时,串口突然失效,日志全丢。最后不得不通过 GPIO 拉出第二个串口,才抓到关键信息。
所以,调试前一定确认串口线路没有被其它外设抢占。
常用的 USB 相关日志标签(dmesg 过滤用):
# 过滤 OTG 相关
dmesg | grep -iE "otg|fsm|hnp|srp|id|vbus"
# 过滤 Gadget/UDC
dmesg | grep -iE "gadget|udc|dwc3"
# 实时追踪
watch -n 0.5 "dmesg | tail -50 | grep -iE 'otg|role|switch'"
3.3 动态调试(dynamic debug / debugfs)
内核的动态调试框架,是调试驱动的神器。
你不用重新编译内核,就能打开或关闭某个文件的调试信息。
尤其适合像 OTG 这种状态机复杂的驱动,你根据现场现象,动态决定要看哪段代码的 log。
启用 debugfs:
# 确保内核选项已开
CONFIG_DEBUG_FS=y
CONFIG_DYNAMIC_DEBUG=y
# 挂载 debugfs(通常自动挂载,手动也可以)
mount -t debugfs none /sys/kernel/debug
常用动态调试命令示例:
# 打开 drivers/usb/dwc3/core.c 里所有 pr_debug/dev_dbg 输出
echo "file drivers/usb/dwc3/core.c +p" > /sys/kernel/debug/dynamic_debug/control
# 打开 drivers/usb/gadget/ 目录下所有文件的调试日志
echo "file drivers/usb/gadget/* +p" > /sys/kernel/debug/dynamic_debug/control
# 只打开 otg_fsm.c 里包含 "state" 的调试语句
echo "file drivers/usb/common/otg_fsm.c line * state* +p" > /sys/kernel/debug/dynamic_debug/control
# 查看当前已打开哪些动态调试规则
cat /sys/kernel/debug/dynamic_debug/control | grep dwc3
我个人的排查习惯:
- 先挂载 debugfs,确认路径存在。
- 打开待调文件的所有动态调试。
- 如果日志太多,再配合 grep -v 过滤噪声。
- 对于 OTG 状态机,我通常会同时打开 otg_fsm.c 和 dwc3_otg.c 的调试,因为它们处理的是同一事件的不同层级。
为什么要特别强调 dynamic debug?
因为在 OTG 调试中,时序就是一切。
ID 中断来了,状态机做出反应,VBUS 变化,然后又触发中断。
如果每个细节都 printk,你会被信息淹没;如果都不打,你什么都看不到。
动态调试让你精准定位,只在你想看的代码路径上输出信息。
3.4 本章知识体系总览
下面这张图,是我梳理的整套调试环境依赖关系:
有了这三样东西——正确配置的内核、可靠的串口、动态调试能力——
你就有了“看清 OTG 内部运转”的眼睛。
后面章节讲的所有具体调试案例,都是在这个环境基础上展开的。
另外,别小看 debugfs 的动态调试功能。
我在调试某厂商平台的 OTG HNP 切换时,就是靠动态调试临时打开了 dwc3 中断处理函数的日志,才抓到了 ID 中断丢失的问题。
如果没有动态调试,我要么重新编译内核,要么加一堆全局 printk 然后还得删掉——太折腾了。