驱动 probe 失败是内核 bring-up 里最常见的问题之一。它看起来很直接:驱动没有起来,日志里有一个错误码。但真正排查时,经常会变成漫长的试错。
原因在于 probe 不是单个动作,而是一条资源依赖链:设备匹配、内存资源、中断、时钟、复位、电源、pinctrl、DMA、PHY、子设备、固件。链条里任何一环失败,最后都可能表现成 probe 失败。
先看错误码
错误码是第一条线索。几个常见错误码含义不同:
-ENODEV:设备不存在、不支持、匹配后主动拒绝-EINVAL:设备树属性、参数或配置格式错误-ENOMEM:内存分配失败,也可能是资源管理路径错误-ENXIO:资源不存在或无法映射-EPROBE_DEFER:依赖资源尚未 ready-ETIMEDOUT:硬件状态等待超时-EIO:底层访问失败,常见于总线或寄存器访问
如果驱动只打印:
probe failed: -22
这对排查帮助有限。更好的日志应该告诉你哪个资源失败:
failed to get core clock: -ENOENT
failed to deassert reset: -ETIMEDOUT
failed to request irq 42: -EBUSY
错误码要和失败位置一起看。只有错误码,没有上下文,价值会打折。
EPROBE_DEFER 不是错误,但可能掩盖错误
-EPROBE_DEFER 的意思是依赖资源还没有准备好,驱动请求稍后再 probe。比如 regulator、clock、GPIO、PHY、backlight、panel 等资源还没注册。
合理的 defer 是正常机制。但长期 defer 就是问题。
可以检查:
cat /sys/kernel/debug/devices_deferred
如果看到设备一直在 deferred 列表里,要看后面的原因。有些内核版本会显示具体等待的资源,有些只显示设备名。
常见原因:
- provider 驱动没有编进内核或模块没加载
- 设备树 phandle 指向错误
- clock/reset/regulator 名称和驱动期望不一致
- provider 自己 probe 失败
- probe 顺序被模块加载策略影响
不要看到 -EPROBE_DEFER 就放过。它是“稍后再试”,不是“没有问题”。
devm 简化释放,不简化状态机
现代驱动大量使用 devm_ API,例如:
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
base = devm_platform_ioremap_resource(pdev, 0);
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(dev, irq, handler, 0, name, priv);
devm_ 能减少错误路径释放代码,但它不能替你设计正确的状态机。
比如:
- request_irq 之前是否已经初始化 handler 需要的数据
- 中断打开之前硬件状态是否清干净
- workqueue 是否可能在 probe 失败后仍然运行
- runtime PM 是否在资源准备前启用
- remove 时是否需要先停止硬件再释放资源
devm_ 负责生命周期释放,不负责并发顺序。很多 use-after-free 和空指针问题,恰恰出现在“资源会自动释放”的错觉之后。
probe 日志应该放在关键边界
驱动日志太少,排查会盲;日志太多,长期维护又会污染输出。比较实用的方式是在关键边界打日志:
- match 成功后
- 解析设备树关键属性后
- 获取 clock/reset/regulator 后
- 资源映射后
- 中断申请后
- 硬件初始化前后
- 注册子系统对象前后
例如:
dev_dbg(dev, "mapped regs %pr\n", res);
dev_dbg(dev, "irq=%d\n", irq);
dev_dbg(dev, "rate=%lu\n", clk_get_rate(priv->clk));
这些日志不一定默认打开,但在 dynamic debug 下非常有用。
echo 'file drivers/foo/* +p' > /sys/kernel/debug/dynamic_debug/control
一个可维护的驱动,不应该依赖临时 printk 才能知道 probe 到哪一步。
硬件 timeout 要回到手册
-ETIMEDOUT 是 bring-up 里最容易被误判的错误。驱动等待某个位变成 1,最后超时:
timeout waiting for controller ready
这个日志本身只能说明硬件没按预期响应。原因可能很多:
- clock 没开
- reset 没释放
- 电源域没上电
- 寄存器地址错
- pinmux 错
- 固件没加载
- 外部 PHY 没 ready
- 需要先清状态位
- 写寄存器顺序不符合手册
遇到 timeout,应该回到硬件初始化序列,而不是只加大 timeout。加大 timeout 只有在硬件确实慢、手册允许、且能解释时才合理。
错误路径也要测试
probe 成功路径通常会被反复测试,错误路径却容易被忽视。错误路径不干净,会导致二次 probe、模块卸载、热插拔、suspend/resume 时出问题。
可以主动构造失败:
- 注释某个 clock 节点
- 改错 regulator 名称
- 让
platform_get_irq()失败 - 模拟 firmware 加载失败
- 强制某个资源返回
-EPROBE_DEFER
观察驱动是否:
- 留下已打开的 clock
- 留下已释放但仍会使用的对象
- 注册了未注销的子对象
- 开了中断但没有关闭
- 启用了 runtime PM 但没有 disable
很多“偶现 panic”其实是 probe 错误路径长期未测试。
probe 和 remove 要成对思考
写 probe 时就应该想 remove。初始化顺序和释放顺序应该成镜像关系:
probe:
allocate priv
map regs
get clock
deassert reset
init hardware
request irq
register subsystem object
remove:
unregister subsystem object
disable irq or stop hardware
assert reset
disable clock
release managed resources
并不是所有资源都需要手工释放,但逻辑状态一定要关闭。尤其是硬件还可能发中断、DMA 还可能写内存、work 还可能排队时,不能只依赖 devm。
一份 probe 排查模板
遇到 probe 失败,可以按这个模板记录:
设备:
节点路径、compatible、运行时 dtb 是否存在
匹配:
驱动 of_match_table 是否匹配
失败位置:
具体 API、错误码、日志上下文
资源:
reg、irq、clock、reset、regulator、pinctrl、dma、phy
依赖:
是否 EPROBE_DEFER,provider 是否 ready
硬件状态:
clock rate、reset 状态、寄存器读写是否有效
下一步:
加 dynamic debug、验证 dtb、对照手册初始化序列
模板的意义是逼自己把“驱动没起来”拆成可验证的链条。
结论
probe 失败不是一个错误点,而是资源依赖链断在某处。高效排查依赖三个东西:准确的错误码、清晰的失败位置、对资源生命周期的理解。
驱动能 probe 成功只是第一步。真正可靠的驱动,还要在 probe defer、错误路径、remove、runtime PM 和并发中断下都保持状态一致。
评论