本文约2600字,本文对项目上一个驱动加载问题进行分析总结。
关注公众号, 即可获得与Linux相关的电子书籍(含《硬件架构的艺术》)以及常用开发工具,文末有文档清单,本公众号提供的电子书均只可作为个人学习使用,不可用做商业用途。
现象重现
最近在调试某 WiFi 网卡驱动(hgicf.ko,基于 SDIO 接口)时,遇到了一个“经典”问题:第一次 insmod hgicf.ko 驱动加载成功,设备正常工作;执行 rmmod hgicf 卸载驱动,再次 insmod 时,内核日志出现以下错误:
[ 51.763243] ** HUGE-IC WLAN Card Driver(fmac) v2.2.1-24338[ 51.768859] hgic_sdio_init:1001::Enter, max_pkt_len = 8192[ 51.774472] hgic_sdio_wlan: probe of mmc1:0001:1 failed with error -110[ 51.781162] hgic_sdio_init:1007::Leave[ 51.785397] leave hgicf_init错误码 -110 即 -ETIMEDOUT,表示 SDIO 设备探测超时。为什么第一次加载成功,卸载后再加载就超时?本文将从原理层面剖析原因,并给出系统性的排查思路。
一 错误码 -110 与 SDIO 驱动加载流程
1.1 -110 是什么
在 Linux 内核中,-110 定义为 ETIMEDOUT(连接超时)。在 SDIO 驱动中,通常发生在:
发送 CMD5(查询 SDIO 设备)或 CMD3(设置 RCA)等初始化命令时,设备未在预期时间内响应。 读取设备的 CIS(卡信息结构)或功能寄存器时超时。 执行 SDIO 读写操作( sdio_readb/sdio_writeb)时卡住。
1.2 SDIO 驱动的加载流程
一个典型的 SDIO 驱动(如 WiFi 网卡)加载时,内核会经历以下步骤:
MMC/SDIO 核心检测到设备插入(或通过 mmc_rescan扫描总线)。发送初始化命令序列(CMD5→CMD3→CMD7→CMD9 等),获取设备信息。 读取 CIS 表,识别设备厂商 ID 和产品 ID,匹配对应驱动。 调用驱动 probe 函数(本例为 hgic_sdio_wlan),进行硬件初始化:使能 SDIO 功能(F0/F1) 配置时钟、电源、中断 下载固件(如有) 驱动注册网络设备,完成加载。
上述任一步骤超时,都会返回 -ETIMEDOUT,导致 probe 失败。
二 为什么第一次加载成功,卸载后再加载就失败?
这是问题的核心。硬件状态未完全复位 是最常见的原因。
2.1 驱动卸载时做了什么?
一个典型的 SDIO 驱动 remove 函数会执行:
停止网络设备,释放中断。 关闭 SDIO 功能(如禁用 F1 的 I/O 通道)。 可能调用 sdio_disable_func或sdio_release_irq。释放设备内存、注销设备号等。
但 关键点:很多驱动 不会主动对 SDIO 设备执行硬件复位(比如发送 CMD52 复位寄存器,或通过 GPIO 拉低复位引脚)。它们只做软件层面的清理。
2.2 设备在卸载后处于什么状态?
SDIO 设备(WiFi 芯片)可能仍处于 上电工作状态,内部寄存器保留之前的配置。 MMC 控制器(主机端)可能仍保留着与设备通信的时钟、电压设置。 当再次加载驱动时,MMC 核心会重新执行初始化流程,但此时设备可能: 仍处于 睡眠模式,对初始化命令不响应。 内部状态机卡在某个非期望状态,无法正确回复 CMD5。 时钟或电压不匹配(因上次卸载时未恢复默认值)。
2.3 本例的深层原因
从日志 probe of mmc1:0001:1 failed with error -110 看,MMC 核心已经成功识别到 SDIO 设备(mmc1:0001),但调用驱动 probe 时,驱动内部初始化(如读取寄存器)超时。这通常意味着:
设备电源/时钟未正确恢复。 设备固件下载失败(若上次卸载未复位,固件可能仍在运行,导致二次下载冲突)。 SDIO 主机控制器未重新初始化(如未重新设置总线宽度、频率)。
三 故障排查思路(实战篇)
3.1 第一步:确认卸载是否彻底
先检查是否所有依赖模块都已卸载。本例中,hgicf.ko 依赖 sdhi_axrea.ko(SDIO 主机控制器驱动)。卸载顺序应为:
rmmod hgicf # 先卸载上层驱动rmmod sdhi_axrea # 再卸载底层主机驱动如果只卸载 hgicf 而不卸载 sdhi_axrea,主机控制器状态可能残留,导致重新加载时异常。务必确认 lsmod | grep -E "hgic|sdhi" 无任何相关模块。
3.2 第二步:检查硬件复位是否有效
最简单的方法:卸载驱动后,执行一次 硬件复位。
若有物理复位按键,按一下再加载。 若有 GPIO 控制复位,可通过 sysfs 操作: echo 0 > /sys/class/gpio/gpioXX/value # 拉低复位sleep 1echo 1 > /sys/class/gpio/gpioXX/value # 拉高复位若板卡支持,可尝试 关闭再开启 SDIO 电源(通过 /sys/bus/mmc/devices/.../下的电源控制)。
若硬件复位后加载成功,说明问题就是设备未复位。
3.3 第三步:查看完整 dmesg 时序
不仅仅是最后的错误信息,要看 整个初始化过程:
dmesg | grep -E "mmc|sdio|hgic|sdhi" | tail -50观察:
第一次加载时,MMC 核心是否打印了 mmc1: new SDIO card at address 0001。第二次加载时,是否同样出现了 new SDIO card,还是直接在 probe 中超时。是否有电源/时钟相关的 warning(如 clk_enable failed)。
3.4 第四步:检查内核配置和驱动代码
如果硬件复位无法解决(或不便操作),则需检查驱动代码:
probe 函数中是否先执行了硬件复位?许多 SDIO 驱动会在 probe 开始时通过写特定寄存器复位设备。 remove 函数中是否保存了设备状态?或者是否调用了 sdio_claim_host/sdio_release_host不匹配导致死锁?超时时间是否过短?可在驱动中尝试增大超时值(如 msecs_to_jiffies(1000)改为msecs_to_jiffies(2000))。
3.5 第五步:检查 MMC 控制器状态
MMC 主机控制器(本例 sdhi_axrea.ko)本身可能需要重新初始化:
卸载 sdhi_axrea再加载(modprobe sdhi_axrea),观察是否恢复。检查控制器寄存器是否有错误位(需硬件手册)。
3.6 第六步:终极武器——重启
重启系统可保证硬件完全复位,如果重启后加载成功,基本确认是复位问题。
四 为什么有些驱动卸载后再加载没问题?
这取决于驱动设计质量。良好的驱动卸载函数应做到:
将设备置于已知的初始状态(如发送复位命令到设备)。 释放所有资源,包括中断、DMA、时钟。 通知 MMC 核心设备已移除(通过 mmc_remove_host或sdio_unregister_driver)。
而 主机控制器驱动(如 sdhi_axrea) 的 remove 函数应:
关闭 SDIO 时钟。 释放中断。 将控制器寄存器恢复默认值。
若两者都做到位,卸载后再加载就不会出现状态残留问题。
五 预防与设计建议
在 probe 开始时执行设备软复位:通过 SDIO 命令写复位寄存器,或拉低 GPIO 再拉高。 在 remove 时执行设备软复位,使设备回到初始状态,以便下次加载。 将电源管理(时钟、电源)与设备状态解耦:确保卸载后电源可被独立控制。 使用 devm_系列 API,简化资源释放,减少遗漏。设置合理的超时时间,并增加重试机制(如失败后重试 3 次)。
六 总结
modprobe -r 递归卸载 | ||
对于 -110 超时,请记住:
日志:dmesg 中的具体超时位置(哪个命令/哪次读写)。 硬件:电源、时钟、复位信号是否正常。 软件:驱动 remove 是否干净,probe 是否做了充分的初始化等待。
终极手段:重启系统,强制硬件复位,验证是否为软件问题。
往期文章(欢迎订阅技术分享栏目全部文章):

嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助
夜雨聆风