1. 先搞清楚 -110 是谁递出来的
做嵌入式 Linux 的,尤其是做 WiFi 模块适配的,基本都会在某块板子上撞见mmc0: error -110 whilst initialising SDIO card这一行日志。我第一次见到它是在一块国产 SoC 的评估板上,WiFi 模块型号刚换,驱动也刚合进去,开机跑十次能成三次,剩下七次全部停在枚举阶段,dmesg 里翻来覆去就是这一句。当时第一反应是驱动的问题,折腾了两天把驱动换回旧版本、换了固件、换了 nvram 配置,最后发现是板子上 SDIO 的 1.8V 电源轨在瞬时负载下塌了。这件事之后我才算真正把wifi sdio -110 错误分析这套东西从头捋了一遍。
这篇内容想做的事情很明确:把-110这个错误码从它的诞生点一路拆到最终现象,讲清楚它是谁返回的、什么时候返回、在 SDIO 通信的哪一环触发,然后给出一套我自己在项目里反复用过的分层排查方法。不管你是刚接手 WiFi 模块适配的新人,还是已经在调 SDIO 时序的老手,都能从里面找到能直接抄的操作。整篇偏实战,涉及设备树、内核配置、调试开关、硬件量测这几块,都会给到具体命令和参数。
1.1 从错误码到调用栈:-110 就是 ETIMEDOUT
Linux 内核里的错误码沿用了一套固定的负数体系,-110对应的就是ETIMEDOUT。你可以在内核头文件里直接查到:
grep -n "ETIMEDOUT" /usr/include/asm-generic/errno.h # define ETIMEDOUT 110注意这里有个容易混淆的点:用户态errno是正数 110,内核态函数返回值是负数-110。所以在内核日志里看到的error -110,翻译成人话就是"等超时了"。
那到底是谁在等、等什么、等了多久?顺着 MMC 子系统往下扒,最短的一条链路大概是这样:
- 上层驱动(比如
brcmfmac、rtl8xxxu、aic8800之类)调用sdio_readb()或sdio_writel(); - SDIO 公共层把它封装成一个或多个
mmc_request,交给mmc_wait_for_req(); mmc_wait_for_req()把请求下发给 host 控制器驱动(常见的是sdhci),然后wait_for_completion()等一个完成量;- Host 控制器发命令、发数据,理应产生中断,中断服务程序里
complete()唤醒等待者; - 如果指定时间内没有中断到来,超时分支触发,把
mrq->cmd->error或mrq->data->error置成-ETIMEDOUT,也就是-110。
在sdhci这颗最常见的 host 控制器里,超时打印通常有两处。命令超时走定时器sdhci_timeout_timer,打印:
mmc0: Timeout waiting for hardware interrupt.数据超时走sdhci_timeout_data_timer,打印内容类似,但紧接着会 dump 一整块寄存器:
mmc0: sdhci: ============ SDHCI REGISTER DUMP =========== mmc0: sdhci: Sys addr: 0x00000000 | Version: 0x00001002 mmc0: sdhci: Blk size: 0x00000200 | Blk cnt: 0x00000008 mmc0: sdhci: Argument: 0x00000... | Trn mode: 0x00000012 mmc0: sdhci: Present: 0x01f70000 | Host ctl: 0x00000007 ...这块 dump 是排查的核心线索之一,后面第 4 章会专门讲怎么读它。
1.2 为什么偏偏是 SDIO 接口的 WiFi 模块高发
同样挂在 SDIO 上的设备,比如 SD 卡、eMMC、SDIO 蓝牙,稳定性通常比 WiFi 模块好一大截。原因不复杂,WiFi 是这几个里面"最不像存储设备"的那一个。
SD 卡和 eMMC 的数据流是块设备模型,读写有明确的分界,请求之间相对规整。而 WiFi 模块在 SDIO 上跑的是三层东西:命令层(CMD52)读写寄存器、数据层(CMD53)做块传输、带外中断(DAT1 上的 SDIO 中断)。一次 WiFi 扫描可能触发几十上百次 CMD53,中间还夹着固件下载、nvram 写入、中断上报。请求密度高、突发性强、时序敏感,任何一环抖一下都可能超时。
更要命的是功耗。WiFi 模块在发射瞬间的电流峰值可以到 300mA 甚至更高,如果电源设计余量不足、退耦电容放得不够,电压跌落会直接反映到 SDIO 的信号电平上。而很多硬件同事在画板子的时候,是拿"平均功耗"去算电源的,峰值这一下就被忽略了。
再叠加一层因素:SDIO 的时钟频率。为了追吞吐,很多人上来就写max-frequency = <50000000>,甚至开 UHS-I 模式跑到 100MHz 以上。频率越高,对走线阻抗、端接、驱动能力的要求就越苛刻,边界条件下出-110的概率也就越大。所以你会看到一种很典型的现象——同一份软件,换一块板子就好了;或者在实验室好好的,一到产线批量就零星报-110。
2. SDIO 一次读写到底走过了哪些环节
想把-110定位到具体环节,得先把 SDIO 一次完整的传输拆开看。不然你只能看到"超时了",看不到"在哪一步超时了",排查就会变成碰运气。
2.1 命令、响应、数据的三段式时序
SDIO 协议在物理层上和 SD 卡共享同一套机制,一共用到三根信号线组:CLK时钟、CMD命令线、DAT0~DAT3数据线。一次操作大致分三段。
第一段是命令段。Host 在CMD线上发 48 位的命令帧,卡在收到后于规定时间内回响应。命令分两类:无响应(比如 CMD0)、有响应(48 位短响应或者 136 位长响应)。SDIO 的寄存器读写用的是 CMD52,一个命令帧里就能带地址和数据,非常轻量;块传输用 CMD53,命令帧里带块长度、块数量、寄存器地址。
第二段是响应段。卡必须在规范规定的窗口内把响应拉回来,这个窗口按时钟周期计算。如果 host 在规定周期里没采到响应起始位,硬件层面就是响应超时,软件层看到的就是-110。
第三段是数据段(只有 CMD53 有)。数据传输方向由命令里的 bit 决定,读的时候卡驱动 DAT 线,写的时候 host 驱动。数据段结束靠 CRC 状态和结束位判定。数据线在传输期间是 busy 的,卡会通过拉低 DAT0 表示"我还没准备好",这时候 host 必须等。
三段之间还有间隙,规范里叫 Ncr、Nrc 之类的时序参数,都是按CLK周期定义的。这里有个关键结论:时钟频率越高,这些以周期计数的窗口在绝对时间上就越短,对硬件边沿质量的要求就越严。这也是为什么"降速能解决很多-110"——它把时间窗口按比例放大了。
2.2 谁在计时:硬件超时与软件超时是两套东西
很多人排查时会误以为只有一个超时,其实 SDIO 上有两层超时在同时工作,搞清楚它们的区别非常关键。
第一层是硬件超时,由 host 控制器和卡之间的时序规定决定。SDHCI 这类控制器里有个 Data Timeout Counter,值是根据当前SDCLK频率算出来的。核心代码大致是:
/* drivers/mmc/host/sdhci.c 里的思路 */ count = sdhci_calc_timeout(host, cmd, &too_big);计算逻辑是拿期望的超时时间去除以时钟周期,再取 2 的幂次。这里有个容易踩的坑:timeout 寄存器的值域很小,最大只能表示到 2 的某次幂。如果当前时钟频率太低、而要等的绝对时间又太长,算出来的 count 就会溢出,内核会打印:
mmc0: Too large timeout 0x%x requested for CMD%d!看到这行说明你的时钟低到了不正常的程度,或者有人手动改了超时参数。
第二层是软件超时,在 MMC 核心层。mmc_wait_for_req_done()会用一个固定的毫秒数去wait_for_completion_timeout(),等不到就返回失败。这一层是兜底,防止硬件层面彻底死掉的时候内核永远卡住。所以你在日志里可能看到两种措辞:Timeout waiting for hardware interrupt(主机控制器定时器先到)和request timeout(核心层软件等待先到)。这两个指向的排查方向不完全一样。
2.3 不同阶段超时的日志长相
日志是分阶段定位的第一手证据。我把常见的几种面貌列一下,方便你对照。
枚举阶段的失败,通常长这样:
mmc1: new high speed SDIO card at address 0001 mmc1: error -110 whilst initialising SDIO card有时候前面会先有一行mmc1: Timeout waiting for hardware interrupt。这种情况说明卡已经被识别到(address 0001拿到了),但是在后续功能初始化的时候挂了。
固件下载阶段的失败,往往在 WiFi 驱动的日志里:
brcmfmac: brcmf_sdio_download_firmware: dongle nvram file download failed brcmfmac: brcmf_sdio_bus_rxctl: resumed on timeout mmc1: Timeout waiting for hardware interrupt.这类错误的特点是:枚举过了,接口通了,但是大块数据传输的时候崩。
运行阶段随机抽风,日志零散且没有规律:
mmc1: card 0001 removed mmc1: new high speed SDIO card at address 0001 wlan0: firmware crashcard removed后面紧跟new ... card是卡被重新枚举了一次,说明 host 控制器或者驱动判定卡掉线了。这种通常和电源瞬态或者信号完整性有关。
休眠唤醒阶段,日志会出现在 resume 之后:
mmc1: Timeout waiting for hardware interrupt. mmc1: error -110 whilst initialising SDIO card这类问题八成和电源管理有关,尤其是mmc-pwrseq和keep-power-in-suspend没配好的时候。
3. 按阶段切分:把问题圈死在某一层
有了上面的日志分类,第一步就不是急着改代码,而是先确定问题落在哪个阶段。这个判断做完,能省掉一大半无效尝试。
3.1 枚举阶段:CMD5 之后就不动了
SDIO 卡的枚举流程是:CMD0 复位 → CMD5 查询 IO 能力 → CMD3 拿到 RCA → CMD7 选中 → CMD52 读写 CCCR 寄存器 → 使能功能。
如果-110出现在枚举阶段,重点怀疑两个方向。
一是CMD5 的响应超时。CMD5 是 SDIO 独有的命令,卡必须在规定时间内回响应。如果卡本身没上电、复位没释放、或者 3.3V 电源还没稳,那 CMD5 必然超时。这时候你要做的第一件事是量电源,而不是改代码。我在项目里遇到过一次,mmc-pwrseq里的post-power-on-delay-ms只写了 10ms,而模块手册要求至少 100ms,结果就是十次里成功三次。
二是CMD52 读写 CCCR 失败。这一步已经在用 1 位总线通信了,如果还超时,说明信号质量或者时钟已经出了问题。此时可以尝试把max-frequency降到 400kHz 再试一次。400kHz 是 SD 规范里定义的识别阶段默认时钟,绝大多数硬件在这个频率下都能通。如果 400kHz 还通不过,那基本可以判定是硬件问题,软件怎么调都白搭。
3.2 固件下载阶段:写块写到一半崩
WiFi 模块(尤其是 Broadcom、Realtek 那几家的方案)上电后需要 host 把固件和配置写进模块内存,再启动 CPU。这个过程通常几万到几十万字节,靠 CMD53 分块传输完成。
这一阶段出-110,通常有三个原因:
第一个是块大小设置不合理。SDIO 的块大小受 CCCR 里的 FBR(Function Basic Register)限制,最大一般是 512 字节。但实际能跑多大,还要看 host 控制器的能力和板级信号质量。有些驱动默认用 512,实测在边界板子上必须降到 256 甚至 128 才稳。
第二个是总线宽度。初始化阶段通常用 1 位总线,功能使能后切到 4 位。切换动作本身涉及写 CCCR 的 Bus Interface Control 寄存器,如果切换失败或者切完没生效,后续大块传输会异常。
第三个是电源瞬态。固件下载是一个长时间高负载过程,模块电流持续在几十毫安到上百毫安,如果用的 LDO 响应慢,中间可能掉一下,表现就是传到一半超时。
/* 一个比较保守的 WiFi 节点配置示例 */ &sdmmc1 { status = "okay"; bus-width = <4>; max-frequency = <25000000>; non-removable; cap-sdio-irq; keep-power-in-suspend; no-1-8-v; mmc-pwrseq = <&wifi_pwrseq>; vmmc-supply = <&vcc_wifi>; vqmmc-supply = <&vcc_io>; }; wifi_pwrseq: wifi-pwrseq { compatible = "mmc-pwrseq-simple"; reset-gpios = <&gpio1 29 GPIO_ACTIVE_LOW>; post-power-on-delay-ms = <200>; power-off-delay-us = <20000>; };这套配置我一般拿来做基线。确认能稳定跑起来之后,再逐项往上加频率、加特性。
3.3 运行阶段:随机抽风最难缠
运行阶段出-110是最让人头疼的,因为不可复现。可能跑一整天没事,也可能半小时来一次。这类问题我总结下来,绝大多数不落在协议层,而落在"环境"上。
一个典型的场景是中断风暴。WiFi 模块在 SDIO 的 DAT1 线上发中断,如果 host 这边的 IRQ 处理不当,或者模块固件在高负载下疯狂上报,DAT1 会长时间处于低电平,导致数据线被"占住"。这种时候你会看到寄存器 dump 里 DAT0~DAT3 的状态位一直不变。
另一个场景是并发冲突。有些方案蓝牙和 WiFi 共用一根 SDIO 总线(SDIO 上挂两个 function),如果两个驱动的 runtime PM 没有协调好,一个在做 suspend 另一个在传输,就会出现请求超时。这种情况要去看mmc_pm的日志,或者干脆先关掉 runtime PM 验证一次。
最后一个高频原因是供电纹波。前面提过 WiFi 发射瞬间的电流峰值,如果此时 SDIO 恰好也在传输,电压跌落会直接打乱时序。这个问题在实验室轻负载下看不出来,一上产线跑压力测试就批量冒出来。
3.4 休眠唤醒阶段:resume 后第一枪就哑火
休眠唤醒阶段的-110有个很明显的特征——系统睡下去之前一切正常,醒来之后第一次访问模块就超时。
根因基本集中在两处。第一处是电源在 suspend 期间被切掉了,但驱动层面不知道卡已经掉电,醒来后直接发命令,卡当然不回。解决办法是配好keep-power-in-suspend(保持供电)或者配好mmc-pwrseq让它在 resume 时重新走一遍上电时序。
第二处是resume 时序竞争。电源恢复了,但模块内部还在启动,此时 host 已经开始发命令。这个窗口期要靠post-power-on-delay-ms拉开。我一般会把初期调试的值直接给到 200ms,确认稳定后再往下压到手册标称的最小值加 30% 余量。
4. 硬件侧:电源、时钟、走线这三件事
软件调到底还是解决不了的时候,就要回到硬件。根据我的经验,-110里真正根因在硬件的比例,比大多数人想象的要高得多。
4.1 供电与上电时序
先看三个电源参数:VDD(模块主电源,通常 3.3V)、VDDIO(IO 电源,可能 3.3V 或 1.8V)、以及复位信号。
量测方法很直接:把示波器调到电源轨上,触发条件设成下降沿,阈值设在标称值的 90%,然后让 WiFi 跑起来。观察有没有瞬时跌落。判定标准不是"平均电压对不对",而是"最低点有没有跌破模块手册的 minimum 值"。
我见过一个典型案例:3.3V 轨在 WiFi 发射瞬间掉到 2.9V,掉的时间只有几微秒,万用表完全看不出来,但 SDIO 时序已经被打乱了。解决办法是在模块电源脚旁边补一颗 22uF 陶瓷电容加一颗 1uF 高频电容,位置尽量靠近引脚。
上电时序也是重灾区。模块手册一般会规定:VDD稳定后至少 N 毫秒才能释放复位,复位释放后至少 M 毫秒才能通信。这两段时间在设备树里分别对应post-power-on-delay-ms和mmc-pwrseq的时序控制。千万不要凭感觉写一个 10ms 就算完,一定要查手册或者实测。
注意:有些模块的复位脚是低有效,有些是高有效,设备树里
GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH写反了,现象就是永远枚举不过。这个坑很基础但很常见。
4.2 时钟频率与驱动能力
max-frequency是设备树里最能直接影响稳定性的一项。它的取值要和三个东西匹配:host 控制器的能力、模块支持的最高频率、以及板级走线质量。
调试策略是"从低往高试":
| 试跑频率 | 适用场景 | 说明 |
|---|---|---|
| 400kHz | 最小验证 | 识别阶段默认频率,几乎必通 |
| 12.5MHz | 保底可用 | 吞吐偏低,但稳定性最好 |
| 25MHz | 常规选择 | 大多数板子在 4 位总线下能跑通 |
| 50MHz | 高性能 | 需要走线阻抗控制良好 |
| 100MHz+ | UHS-I | 对硬件和电源要求都极高 |
除了频率,还有一项容易被忽略的是时钟相位(phase)和驱动能力(drive strength)。不少 SoC 的 SDIO 控制器支持调节这两项,通过 pinctrl 或者专门的寄存器配置。当走线比较长(超过 5cm)的时候,适当调整采样相位往往能救回一批板子。
/* 调整时钟相位的典型写法,具体属性名取决于 SoC */ &sdmmc1 { pinctrl-names = "default", "state_uhs"; pinctrl-0 = <&sdmmc1_b4_pins_a>; pinctrl-1 = <&sdmmc1_b4_od_pins_a>; };4.3 走线与信号完整性
走线这块,软件工程师通常插不上手,但你有必要知道该向硬件提什么要求。
CLK是最关键的一根。它是单向的,从 host 到卡,全程应该做阻抗控制,参考地完整。CLK上的过冲和振铃会直接导致采样错误。
CMD和DAT0~3是双向的,需要上下拉。SDIO 规范里要求 host 侧提供上拉,如果板子上漏了,信号在空闲态就会浮空,表现是随机超时。
等长也很重要。4 位模式下DAT0~3加CMD这五根线的长度差要控制住,具体数值看你的目标频率。25MHz 以下可以放宽到 5mm 以内,50MHz 就要压到 2mm 级别。
最后一个实用技巧:如果你怀疑信号完整性问题,可以先降频验证,再飞线验证。降频能让问题消失,基本就锁定是信号问题;如果再换一块 PCB 就好了,那就是板厂工艺波动。
5. 软件侧调参与设备树逐项过
硬件排查完还是一头雾水的时候,软件侧还有不少能拧的旋钮。这一章把设备树属性和内核调试手段挨个过一遍。
5.1 降速验证法:最省事的第一刀
不管问题出在哪,我建议第一刀永远是降速。理由很简单:它能用最小的代价把"时序类问题"和"逻辑类问题"分开。
# 修改设备树 max-frequency 后重新编译 dtb # 从 50MHz 降到 25MHz,再降到 12.5MHz make dtbs降速后如果问题消失了,说明是时序或者信号相关;如果降速后问题依旧,那多半是逻辑配置问题,比如 pwrseq 时序不对、功能没使能、固件路径错。这一刀的性价比极高,我几乎每次都先做。
降速验证做完之后,还有第二个验证动作:关掉 SDIO 中断。
&sdmmc1 { /* 注释掉这一行 */ /* cap-sdio-irq; */ };cap-sdio-irq打开时,模块通过 DAT1 发中断给 host;关掉之后走轮询模式。如果关掉中断后就不超时了,说明问题出在中断线路上,重点去查 DAT1 的走线和上拉。
5.2 设备树里那些和 SDIO 稳定性强相关的属性
我把实际项目里最常调的几项整理成表,附上取值逻辑。
| 属性 | 常用取值 | 影响 |
|---|---|---|
bus-width | 1 或 4 | 4 位吞吐高,但要求四根 DAT 都合格 |
max-frequency | 25000000 起调 | 直接决定时序余量 |
non-removable | 加上 | 避免 host 反复做卡检测 |
cap-sdio-irq | 视情况 | 关掉可排除中断线路问题 |
keep-power-in-suspend | 视电源设计 | 保持供电避免 resume 异常 |
no-1-8-v | 视 IO 电平 | 禁掉 1.8V 电压切换 |
mmc-pwrseq | 必配 | 控制复位和上电延时 |
disable-wp | 加上 | 避免写保护检测干扰 |
关于no-1-8-v,多说一句。这一项是禁掉 1.8V 信令电压切换。默认情况下,UHS-I 模式会把 IO 电压从 3.3V 切到 1.8V 来换更高的速度。如果你的板子没有做 1.8V 供电,或者模块不支持,就一定要加上这一项。没加的话,host 会尝试切换电压,切完通信直接崩,日志就是一堆-110。
5.3 抓现场:debugfs 与 dynamic debug
设备树的配置是一回事,跑起来之后当前状态是另一回事。想确认实际生效的参数,看 debugfs。
# 挂载 debugfs(如果还没挂) mount -t debugfs none /sys/kernel/debug # 查看当前总线状态:时钟、总线宽度、时序模式 cat /sys/kernel/debug/mmc0/ios # 输出示例 # clock: 50000000 Hz # vdd: 21 (3.3 ~ 3.4 V) # bus mode: 2 (push-pull) # chip select: 0 (don't care) # power mode: 2 (on) # bus width: 2 (4 bits) # timing spec: 2 (sd high-speed) # signal voltage: 0 (3.30 V) # driver type: 0 (driver type B)这个输出很有用。如果signal voltage显示 1.80V 而你的板子其实是 3.3V,说明电压切换出问题了。如果bus width显示 1 位,说明 4 位切换没成功。
再看 MMC 层的动态调试。只要内核编译时开了CONFIG_DYNAMIC_DEBUG,就可以在运行时打开某个文件或模块的调试输出:
# 打开 sdhci 的所有调试信息 echo 'module sdhci +p' > /sys/kernel/debug/dynamic_debug/control # 打开 MMC 核心层 echo 'file core.c +p' > /sys/kernel/debug/dynamic_debug/control # 打开 sdio 层 echo 'file sdio.c +p' > /sys/kernel/debug/dynamic_debug/control # 查看当前已打开的调试点 grep -c "=p" /sys/kernel/debug/dynamic_debug/control打开之后,配合dmesg -w实时看日志,能拿到比默认详细得多的信息,包括每一次请求的地址、长度、方向。这个手段在做压力测试抓偶发问题的时候特别有用。
还有个更有针对性的一招:用strace或者自己写个脚本反复触发 WiFi 操作,把复现概率拉高。
#!/bin/sh # 压力脚本:反复扫描和连接,放大偶发问题 while true; do iw dev wlan0 scan > /dev/null 2>&1 sleep 0.5 ping -c 2 -W 1 192.168.1.1 > /dev/null 2>&1 sleep 0.5 done我之前遇到一个半天才复现一次的-110,用这个脚本跑了二十分钟就稳定复现了。
6. 常见问题速查表与我的踩坑记录
前面几章讲的是方法论,这一章把实际高频问题整理成速查表,再补几个印象比较深的案例。
6.1 常见问题速查表
| 现象 | 大概率原因 | 快速验证方法 | 处理方向 |
|---|---|---|---|
每次开机必报-110 | 上电时序或复位 | 查 pwrseq 的 delay 值 | 加大post-power-on-delay-ms |
| 十次里成几次 | 电源余量不足 | 示波器量电源轨最低点 | 补退耦电容、换 LDO |
| 降速后正常 | 信号完整性 | 把频率降到 12.5MHz | 查走线、调相位、调驱动能力 |
| 关中断后正常 | DAT1 中断线路 | 去掉cap-sdio-irq | 查 DAT1 上拉和走线 |
| 只在高温下报错 | 器件温漂 | 加热台升温测试 | 换器件或降低工作频率 |
| resume 后报错 | 电源被切但驱动不知情 | 看 suspend 期间电流 | 配keep-power-in-suspend |
| 大块传输才报错 | 块大小或总线宽度 | 把 block size 降到 128 | 调 CCCR 配置或驱动参数 |
| 并发时随机报错 | runtime PM 竞争 | 关掉 runtime PM 测试 | 协调两个 function 的 PM |
6.2 几个印象深刻的坑
第一个坑是关于mmc-pwrseq的。有一次项目上换了新模块,代码完全照搬旧模块的设备树,结果新模块十次开机只成功两次。查了两天,最后发现旧模块的复位是高有效,新模块是低有效,设备树里那个GPIO_ACTIVE_LOW没改,导致复位电平一直是反的。改完之后问题立刻消失。教训是:换模块的第一步是逐项核对硬件差异表,不要想当然地复用配置。
第二个坑是关于-110和块大小的关系。某方案默认块大小 512 字节,实验室跑了三天没问题。一到客户现场,同样的板子开始零星报-110,而且都是运行一段时间之后。后来发现是客户环境温度比实验室高十几度,高温下信号裕量变小,512 字节的长传输更容易在中间出错。把块大小降到 256,问题解决。这类"环境差异触发的边界问题"最容易被忽略,因为它不改变软件逻辑,只是把硬件裕量压到了临界点。
第三个坑关于寄存器 dump 的读法。很多人看到Timeout waiting for hardware interrupt后面那一大坨 dump 就直接跳过,其实里面信息量很大。重点看Present这一行,也就是 PRNSTS 寄存器。里面有几个关键位:
- 如果CMD Inhibit位一直是 1,说明命令还在发,host 没收到响应;
- 如果DAT Inhibit位一直是 1,说明数据线还被占着,卡没释放总线;
- 如果Command Complete位没置起来,说明卡压根没回响应;
- 如果这几个位都在合理状态但中断没来,那可能是控制器自己的中断屏蔽或者路由问题。
我有一次就是靠读Present寄存器发现 DAT0 一直是低电平,最后定位到是模块的某个 GPIO 复用配置错了,把 DAT0 拉死了。
第四个坑关于降速的"假阳性"。降速之后问题消失,很容易让人得出"硬件没问题,软件降速就行"的结论。但如果这是批量产品,降速意味着吞吐下降,可能根本达不到产品要求。**降速只是定位手段,不是最终方案。**正确的做法是:降到能稳定的频率,然后往上找到临界点,再回头去优化硬件,让临界点提高上来。
6.3 一份可以直接抄的排查流程
最后把我自己用的排查顺序整理出来,从零开始遇到-110可以照这个走。
第一步,抓完整日志。dmesg -w全程挂着,复现一次,把从卡识别到报错的所有行完整保存。特别注意报错前的最后几条,那是关键。
第二步,判断阶段。看是枚举阶段、固件下载阶段、运行阶段还是 resume 阶段。这一步决定后面往哪个方向走。
第三步,降速验证。把max-frequency降到 12.5MHz,重启测试。如果好了,走信号完整性方向;如果还坏,走配置方向。
第四步,关中断验证。去掉cap-sdio-irq,重启测试。用来区分是数据通道问题还是中断通道问题。
第五步,量电源。示波器挂上主电源轨,触发在下降沿,跑压力测试。看最低点和持续时间。
第六步,看寄存器 dump。重点看Present和Error两个状态寄存器,判断是命令阶段挂还是数据阶段挂。
第七步,换板对比。同一份软件换一块板子,如果换板就好,那基本可以定性为硬件批次差异,走硬件方向。
这七步走完,绝大多数-110都能有个明确的归属。剩下的极少数,通常是多因素叠加,比如电源和信号问题同时存在,那就需要一项一项隔离,工作量会大一些,但思路是一样的。
我自己踩过的这些坑告诉我一件事:-110这个错误码本身不含任何指向性,它只是说"超时了"。真正有价值的线索全在它前后的日志、寄存器状态和你能测到的物理信号里。与其花时间猜,不如花时间把这些证据收齐。收齐了,答案往往就摆在那里。