乐于分享
好东西不私藏

Linux下驱动加载成功后卸载再加载就出错?-110超时背后的原理与排查方法

Linux下驱动加载成功后卸载再加载就出错?-110超时背后的原理与排查方法
Hello,大家好,我是程序媛MM。
本文约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 网卡)加载时,内核会经历以下步骤:

  1. MMC/SDIO 核心检测到设备插入(或通过 mmc_rescan 扫描总线)。
  2. 发送初始化命令序列(CMD5→CMD3→CMD7→CMD9 等),获取设备信息。
  3. 读取 CIS 表,识别设备厂商 ID 和产品 ID,匹配对应驱动。
  4. 调用驱动 probe 函数(本例为 hgic_sdio_wlan),进行硬件初始化:
    • 使能 SDIO 功能(F0/F1)
    • 配置时钟、电源、中断
    • 下载固件(如有)
  5. 驱动注册网络设备,完成加载。

上述任一步骤超时,都会返回 -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 第六步:终极武器——重启

重启系统可保证硬件完全复位,如果重启后加载成功,基本确认是复位问题。


四 为什么有些驱动卸载后再加载没问题?

这取决于驱动设计质量。良好的驱动卸载函数应做到

  1. 将设备置于已知的初始状态(如发送复位命令到设备)。
  2. 释放所有资源,包括中断、DMA、时钟
  3. 通知 MMC 核心设备已移除(通过 mmc_remove_host 或 sdio_unregister_driver)。

而 主机控制器驱动(如 sdhi_axrea) 的 remove 函数应:

  • 关闭 SDIO 时钟。
  • 释放中断。
  • 将控制器寄存器恢复默认值。

若两者都做到位,卸载后再加载就不会出现状态残留问题。


五 预防与设计建议

  1. 在 probe 开始时执行设备软复位:通过 SDIO 命令写复位寄存器,或拉低 GPIO 再拉高。
  2. 在 remove 时执行设备软复位,使设备回到初始状态,以便下次加载。
  3. 将电源管理(时钟、电源)与设备状态解耦:确保卸载后电源可被独立控制。
  4. 使用 devm_ 系列 API,简化资源释放,减少遗漏。
  5. 设置合理的超时时间,并增加重试机制(如失败后重试 3 次)。

六 总结

现象
根本原因
排查方向
第一次加载成功,卸载后再加载 -110
设备或主机控制器未完全复位,导致初始化命令超时
检查卸载顺序、硬件复位、驱动 remove 函数
卸载时报错
模块依赖关系未处理(如 sdhi_axrea 未卸载)
使用 modprobe -r 递归卸载
重新加载 Unknown symbol
依赖模块未先加载
按依赖顺序加载

对于 -110 超时,请记住:

  • 日志:dmesg 中的具体超时位置(哪个命令/哪次读写)。
  • 硬件:电源、时钟、复位信号是否正常。
  • 软件:驱动 remove 是否干净,probe 是否做了充分的初始化等待。

终极手段:重启系统,强制硬件复位,验证是否为软件问题。

往期文章(欢迎订阅技术分享栏目全部文章):

【从零开始撸内核驱动源码】:以ttyserial(串口驱动)为例,串联字符设备驱动基础知识点的学习计划
Linux内核源码顶层 Makefile分析并单独编译调试内核自带的驱动
【从零开始撸内核驱动源码】:ttynull驱动
Linux内核驱动安装失败问题调试及解决方法
Linux内核驱动源码走读之编译内核及外部驱动实操指南
谢谢你看到这里

嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计

嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计

分享读书心得、工作经验,自我成长和生活方式。

希望我的文字能对你有所帮助