錯(cuò)誤分析與實(shí)戰(zhàn)排查)
1. 先搞清楚 -110 是誰遞出來的做嵌入式 Linux 的尤其是做 WiFi 模塊適配的基本都會(huì)在某塊板子上撞見mmc0: error -110 whilst initialising SDIO card這一行日志。我第一次見到它是在一塊國產(chǎn) SoC 的評(píng)估板上WiFi 模塊型號(hào)剛換驅(qū)動(dòng)也剛合進(jìn)去開機(jī)跑十次能成三次剩下七次全部停在枚舉階段dmesg 里翻來覆去就是這一句。當(dāng)時(shí)第一反應(yīng)是驅(qū)動(dòng)的問題折騰了兩天把驅(qū)動(dòng)換回舊版本、換了固件、換了 nvram 配置最后發(fā)現(xiàn)是板子上 SDIO 的 1.8V 電源軌在瞬時(shí)負(fù)載下塌了。這件事之后我才算真正把wifi sdio -110 錯(cuò)誤分析這套東西從頭捋了一遍。這篇內(nèi)容想做的事情很明確把-110這個(gè)錯(cuò)誤碼從它的誕生點(diǎn)一路拆到最終現(xiàn)象講清楚它是誰返回的、什么時(shí)候返回、在 SDIO 通信的哪一環(huán)觸發(fā)然后給出一套我自己在項(xiàng)目里反復(fù)用過的分層排查方法。不管你是剛接手 WiFi 模塊適配的新人還是已經(jīng)在調(diào) SDIO 時(shí)序的老手都能從里面找到能直接抄的操作。整篇偏實(shí)戰(zhàn)涉及設(shè)備樹、內(nèi)核配置、調(diào)試開關(guān)、硬件量測這幾塊都會(huì)給到具體命令和參數(shù)。1.1 從錯(cuò)誤碼到調(diào)用棧-110 就是 ETIMEDOUTLinux 內(nèi)核里的錯(cuò)誤碼沿用了一套固定的負(fù)數(shù)體系-110對(duì)應(yīng)的就是ETIMEDOUT。你可以在內(nèi)核頭文件里直接查到grep -n ETIMEDOUT /usr/include/asm-generic/errno.h # define ETIMEDOUT 110注意這里有個(gè)容易混淆的點(diǎn)用戶態(tài)errno是正數(shù) 110內(nèi)核態(tài)函數(shù)返回值是負(fù)數(shù)-110。所以在內(nèi)核日志里看到的error -110翻譯成人話就是等超時(shí)了。那到底是誰在等、等什么、等了多久順著 MMC 子系統(tǒng)往下扒最短的一條鏈路大概是這樣上層驅(qū)動(dòng)比如brcmfmac、rtl8xxxu、aic8800之類調(diào)用sdio_readb()或sdio_writel()SDIO 公共層把它封裝成一個(gè)或多個(gè)mmc_request交給mmc_wait_for_req()mmc_wait_for_req()把請求下發(fā)給 host 控制器驅(qū)動(dòng)常見的是sdhci然后wait_for_completion()等一個(gè)完成量Host 控制器發(fā)命令、發(fā)數(shù)據(jù)理應(yīng)產(chǎn)生中斷中斷服務(wù)程序里complete()喚醒等待者如果指定時(shí)間內(nèi)沒有中斷到來超時(shí)分支觸發(fā)把mrq-cmd-error或mrq-data-error置成-ETIMEDOUT也就是-110。在sdhci這顆最常見的 host 控制器里超時(shí)打印通常有兩處。命令超時(shí)走定時(shí)器sdhci_timeout_timer打印mmc0: Timeout waiting for hardware interrupt.數(shù)據(jù)超時(shí)走sdhci_timeout_data_timer打印內(nèi)容類似但緊接著會(huì) 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 章會(huì)專門講怎么讀它。1.2 為什么偏偏是 SDIO 接口的 WiFi 模塊高發(fā)同樣掛在 SDIO 上的設(shè)備比如 SD 卡、eMMC、SDIO 藍(lán)牙穩(wěn)定性通常比 WiFi 模塊好一大截。原因不復(fù)雜WiFi 是這幾個(gè)里面最不像存儲(chǔ)設(shè)備的那一個(gè)。SD 卡和 eMMC 的數(shù)據(jù)流是塊設(shè)備模型讀寫有明確的分界請求之間相對(duì)規(guī)整。而 WiFi 模塊在 SDIO 上跑的是三層?xùn)|西命令層CMD52讀寫寄存器、數(shù)據(jù)層CMD53做塊傳輸、帶外中斷DAT1 上的 SDIO 中斷。一次 WiFi 掃描可能觸發(fā)幾十上百次 CMD53中間還夾著固件下載、nvram 寫入、中斷上報(bào)。請求密度高、突發(fā)性強(qiáng)、時(shí)序敏感任何一環(huán)抖一下都可能超時(shí)。更要命的是功耗。WiFi 模塊在發(fā)射瞬間的電流峰值可以到 300mA 甚至更高如果電源設(shè)計(jì)余量不足、退耦電容放得不夠電壓跌落會(huì)直接反映到 SDIO 的信號(hào)電平上。而很多硬件同事在畫板子的時(shí)候是拿平均功耗去算電源的峰值這一下就被忽略了。再疊加一層因素SDIO 的時(shí)鐘頻率。為了追吞吐很多人上來就寫max-frequency 50000000甚至開 UHS-I 模式跑到 100MHz 以上。頻率越高對(duì)走線阻抗、端接、驅(qū)動(dòng)能力的要求就越苛刻邊界條件下出-110的概率也就越大。所以你會(huì)看到一種很典型的現(xiàn)象——同一份軟件換一塊板子就好了或者在實(shí)驗(yàn)室好好的一到產(chǎn)線批量就零星報(bào)-110。2. SDIO 一次讀寫到底走過了哪些環(huán)節(jié)想把-110定位到具體環(huán)節(jié)得先把 SDIO 一次完整的傳輸拆開看。不然你只能看到超時(shí)了看不到在哪一步超時(shí)了排查就會(huì)變成碰運(yùn)氣。2.1 命令、響應(yīng)、數(shù)據(jù)的三段式時(shí)序SDIO 協(xié)議在物理層上和 SD 卡共享同一套機(jī)制一共用到三根信號(hào)線組CLK時(shí)鐘、CMD命令線、DAT0~DAT3數(shù)據(jù)線。一次操作大致分三段。第一段是命令段。Host 在CMD線上發(fā) 48 位的命令幀卡在收到后于規(guī)定時(shí)間內(nèi)回響應(yīng)。命令分兩類無響應(yīng)比如 CMD0、有響應(yīng)48 位短響應(yīng)或者 136 位長響應(yīng)。SDIO 的寄存器讀寫用的是 CMD52一個(gè)命令幀里就能帶地址和數(shù)據(jù)非常輕量塊傳輸用 CMD53命令幀里帶塊長度、塊數(shù)量、寄存器地址。第二段是響應(yīng)段??ū仨氃谝?guī)范規(guī)定的窗口內(nèi)把響應(yīng)拉回來這個(gè)窗口按時(shí)鐘周期計(jì)算。如果 host 在規(guī)定周期里沒采到響應(yīng)起始位硬件層面就是響應(yīng)超時(shí)軟件層看到的就是-110。第三段是數(shù)據(jù)段只有 CMD53 有。數(shù)據(jù)傳輸方向由命令里的 bit 決定讀的時(shí)候卡驅(qū)動(dòng) DAT 線寫的時(shí)候 host 驅(qū)動(dòng)。數(shù)據(jù)段結(jié)束靠 CRC 狀態(tài)和結(jié)束位判定。數(shù)據(jù)線在傳輸期間是 busy 的卡會(huì)通過拉低 DAT0 表示我還沒準(zhǔn)備好這時(shí)候 host 必須等。三段之間還有間隙規(guī)范里叫 Ncr、Nrc 之類的時(shí)序參數(shù)都是按CLK周期定義的。這里有個(gè)關(guān)鍵結(jié)論時(shí)鐘頻率越高這些以周期計(jì)數(shù)的窗口在絕對(duì)時(shí)間上就越短對(duì)硬件邊沿質(zhì)量的要求就越嚴(yán)。這也是為什么降速能解決很多-110——它把時(shí)間窗口按比例放大了。2.2 誰在計(jì)時(shí)硬件超時(shí)與軟件超時(shí)是兩套東西很多人排查時(shí)會(huì)誤以為只有一個(gè)超時(shí)其實(shí) SDIO 上有兩層超時(shí)在同時(shí)工作搞清楚它們的區(qū)別非常關(guān)鍵。第一層是硬件超時(shí)由 host 控制器和卡之間的時(shí)序規(guī)定決定。SDHCI 這類控制器里有個(gè) Data Timeout Counter值是根據(jù)當(dāng)前SDCLK頻率算出來的。核心代碼大致是/* drivers/mmc/host/sdhci.c 里的思路 */ count sdhci_calc_timeout(host, cmd, too_big);計(jì)算邏輯是拿期望的超時(shí)時(shí)間去除以時(shí)鐘周期再取 2 的冪次。這里有個(gè)容易踩的坑timeout 寄存器的值域很小最大只能表示到 2 的某次冪。如果當(dāng)前時(shí)鐘頻率太低、而要等的絕對(duì)時(shí)間又太長算出來的 count 就會(huì)溢出內(nèi)核會(huì)打印mmc0: Too large timeout 0x%x requested for CMD%d!看到這行說明你的時(shí)鐘低到了不正常的程度或者有人手動(dòng)改了超時(shí)參數(shù)。第二層是軟件超時(shí)在 MMC 核心層。mmc_wait_for_req_done()會(huì)用一個(gè)固定的毫秒數(shù)去wait_for_completion_timeout()等不到就返回失敗。這一層是兜底防止硬件層面徹底死掉的時(shí)候內(nèi)核永遠(yuǎn)卡住。所以你在日志里可能看到兩種措辭Timeout waiting for hardware interrupt主機(jī)控制器定時(shí)器先到和request timeout核心層軟件等待先到。這兩個(gè)指向的排查方向不完全一樣。2.3 不同階段超時(shí)的日志長相日志是分階段定位的第一手證據(jù)。我把常見的幾種面貌列一下方便你對(duì)照。枚舉階段的失敗通常長這樣mmc1: new high speed SDIO card at address 0001 mmc1: error -110 whilst initialising SDIO card有時(shí)候前面會(huì)先有一行mmc1: Timeout waiting for hardware interrupt。這種情況說明卡已經(jīng)被識(shí)別到address 0001拿到了但是在后續(xù)功能初始化的時(shí)候掛了。固件下載階段的失敗往往在 WiFi 驅(qū)動(dòng)的日志里brcmfmac: brcmf_sdio_download_firmware: dongle nvram file download failed brcmfmac: brcmf_sdio_bus_rxctl: resumed on timeout mmc1: Timeout waiting for hardware interrupt.這類錯(cuò)誤的特點(diǎn)是枚舉過了接口通了但是大塊數(shù)據(jù)傳輸?shù)臅r(shí)候崩。運(yùn)行階段隨機(jī)抽風(fēng)日志零散且沒有規(guī)律mmc1: card 0001 removed mmc1: new high speed SDIO card at address 0001 wlan0: firmware crashcard removed后面緊跟new ... card是卡被重新枚舉了一次說明 host 控制器或者驅(qū)動(dòng)判定卡掉線了。這種通常和電源瞬態(tài)或者信號(hào)完整性有關(guān)。休眠喚醒階段日志會(huì)出現(xiàn)在 resume 之后mmc1: Timeout waiting for hardware interrupt. mmc1: error -110 whilst initialising SDIO card這類問題八成和電源管理有關(guān)尤其是mmc-pwrseq和keep-power-in-suspend沒配好的時(shí)候。3. 按階段切分把問題圈死在某一層有了上面的日志分類第一步就不是急著改代碼而是先確定問題落在哪個(gè)階段。這個(gè)判斷做完能省掉一大半無效嘗試。3.1 枚舉階段CMD5 之后就不動(dòng)了SDIO 卡的枚舉流程是CMD0 復(fù)位 → CMD5 查詢 IO 能力 → CMD3 拿到 RCA → CMD7 選中 → CMD52 讀寫 CCCR 寄存器 → 使能功能。如果-110出現(xiàn)在枚舉階段重點(diǎn)懷疑兩個(gè)方向。一是CMD5 的響應(yīng)超時(shí)。CMD5 是 SDIO 獨(dú)有的命令卡必須在規(guī)定時(shí)間內(nèi)回響應(yīng)。如果卡本身沒上電、復(fù)位沒釋放、或者 3.3V 電源還沒穩(wěn)那 CMD5 必然超時(shí)。這時(shí)候你要做的第一件事是量電源而不是改代碼。我在項(xiàng)目里遇到過一次mmc-pwrseq里的post-power-on-delay-ms只寫了 10ms而模塊手冊要求至少 100ms結(jié)果就是十次里成功三次。二是CMD52 讀寫 CCCR 失敗。這一步已經(jīng)在用 1 位總線通信了如果還超時(shí)說明信號(hào)質(zhì)量或者時(shí)鐘已經(jīng)出了問題。此時(shí)可以嘗試把max-frequency降到 400kHz 再試一次。400kHz 是 SD 規(guī)范里定義的識(shí)別階段默認(rèn)時(shí)鐘絕大多數(shù)硬件在這個(gè)頻率下都能通。如果 400kHz 還通不過那基本可以判定是硬件問題軟件怎么調(diào)都白搭。3.2 固件下載階段寫塊寫到一半崩WiFi 模塊尤其是 Broadcom、Realtek 那幾家的方案上電后需要 host 把固件和配置寫進(jìn)模塊內(nèi)存再啟動(dòng) CPU。這個(gè)過程通常幾萬到幾十萬字節(jié)靠 CMD53 分塊傳輸完成。這一階段出-110通常有三個(gè)原因第一個(gè)是塊大小設(shè)置不合理。SDIO 的塊大小受 CCCR 里的 FBRFunction Basic Register限制最大一般是 512 字節(jié)。但實(shí)際能跑多大還要看 host 控制器的能力和板級(jí)信號(hào)質(zhì)量。有些驅(qū)動(dòng)默認(rèn)用 512實(shí)測在邊界板子上必須降到 256 甚至 128 才穩(wěn)。第二個(gè)是總線寬度。初始化階段通常用 1 位總線功能使能后切到 4 位。切換動(dòng)作本身涉及寫 CCCR 的 Bus Interface Control 寄存器如果切換失敗或者切完沒生效后續(xù)大塊傳輸會(huì)異常。第三個(gè)是電源瞬態(tài)。固件下載是一個(gè)長時(shí)間高負(fù)載過程模塊電流持續(xù)在幾十毫安到上百毫安如果用的 LDO 響應(yīng)慢中間可能掉一下表現(xiàn)就是傳到一半超時(shí)。/* 一個(gè)比較保守的 WiFi 節(jié)點(diǎn)配置示例 */ 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; };這套配置我一般拿來做基線。確認(rèn)能穩(wěn)定跑起來之后再逐項(xiàng)往上加頻率、加特性。3.3 運(yùn)行階段隨機(jī)抽風(fēng)最難纏運(yùn)行階段出-110是最讓人頭疼的因?yàn)椴豢蓮?fù)現(xiàn)??赡芘芤徽鞗]事也可能半小時(shí)來一次。這類問題我總結(jié)下來絕大多數(shù)不落在協(xié)議層而落在環(huán)境上。一個(gè)典型的場景是中斷風(fēng)暴。WiFi 模塊在 SDIO 的 DAT1 線上發(fā)中斷如果 host 這邊的 IRQ 處理不當(dāng)或者模塊固件在高負(fù)載下瘋狂上報(bào)DAT1 會(huì)長時(shí)間處于低電平導(dǎo)致數(shù)據(jù)線被占住。這種時(shí)候你會(huì)看到寄存器 dump 里 DAT0~DAT3 的狀態(tài)位一直不變。另一個(gè)場景是并發(fā)沖突。有些方案藍(lán)牙和 WiFi 共用一根 SDIO 總線SDIO 上掛兩個(gè) function如果兩個(gè)驅(qū)動(dòng)的 runtime PM 沒有協(xié)調(diào)好一個(gè)在做 suspend 另一個(gè)在傳輸就會(huì)出現(xiàn)請求超時(shí)。這種情況要去看mmc_pm的日志或者干脆先關(guān)掉 runtime PM 驗(yàn)證一次。最后一個(gè)高頻原因是供電紋波。前面提過 WiFi 發(fā)射瞬間的電流峰值如果此時(shí) SDIO 恰好也在傳輸電壓跌落會(huì)直接打亂時(shí)序。這個(gè)問題在實(shí)驗(yàn)室輕負(fù)載下看不出來一上產(chǎn)線跑壓力測試就批量冒出來。3.4 休眠喚醒階段resume 后第一槍就啞火休眠喚醒階段的-110有個(gè)很明顯的特征——系統(tǒng)睡下去之前一切正常醒來之后第一次訪問模塊就超時(shí)。根因基本集中在兩處。第一處是電源在 suspend 期間被切掉了但驅(qū)動(dòng)層面不知道卡已經(jīng)掉電醒來后直接發(fā)命令卡當(dāng)然不回。解決辦法是配好keep-power-in-suspend保持供電或者配好mmc-pwrseq讓它在 resume 時(shí)重新走一遍上電時(shí)序。第二處是resume 時(shí)序競爭。電源恢復(fù)了但模塊內(nèi)部還在啟動(dòng)此時(shí) host 已經(jīng)開始發(fā)命令。這個(gè)窗口期要靠post-power-on-delay-ms拉開。我一般會(huì)把初期調(diào)試的值直接給到 200ms確認(rèn)穩(wěn)定后再往下壓到手冊標(biāo)稱的最小值加 30% 余量。4. 硬件側(cè)電源、時(shí)鐘、走線這三件事軟件調(diào)到底還是解決不了的時(shí)候就要回到硬件。根據(jù)我的經(jīng)驗(yàn)-110里真正根因在硬件的比例比大多數(shù)人想象的要高得多。4.1 供電與上電時(shí)序先看三個(gè)電源參數(shù)VDD模塊主電源通常 3.3V、VDDIOIO 電源可能 3.3V 或 1.8V、以及復(fù)位信號(hào)。量測方法很直接把示波器調(diào)到電源軌上觸發(fā)條件設(shè)成下降沿閾值設(shè)在標(biāo)稱值的 90%然后讓 WiFi 跑起來。觀察有沒有瞬時(shí)跌落。判定標(biāo)準(zhǔn)不是平均電壓對(duì)不對(duì)而是最低點(diǎn)有沒有跌破模塊手冊的 minimum 值。我見過一個(gè)典型案例3.3V 軌在 WiFi 發(fā)射瞬間掉到 2.9V掉的時(shí)間只有幾微秒萬用表完全看不出來但 SDIO 時(shí)序已經(jīng)被打亂了。解決辦法是在模塊電源腳旁邊補(bǔ)一顆 22uF 陶瓷電容加一顆 1uF 高頻電容位置盡量靠近引腳。上電時(shí)序也是重災(zāi)區(qū)。模塊手冊一般會(huì)規(guī)定VDD穩(wěn)定后至少 N 毫秒才能釋放復(fù)位復(fù)位釋放后至少 M 毫秒才能通信。這兩段時(shí)間在設(shè)備樹里分別對(duì)應(yīng)post-power-on-delay-ms和mmc-pwrseq的時(shí)序控制。千萬不要憑感覺寫一個(gè) 10ms 就算完一定要查手冊或者實(shí)測。注意有些模塊的復(fù)位腳是低有效有些是高有效設(shè)備樹里GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH寫反了現(xiàn)象就是永遠(yuǎn)枚舉不過。這個(gè)坑很基礎(chǔ)但很常見。4.2 時(shí)鐘頻率與驅(qū)動(dòng)能力max-frequency是設(shè)備樹里最能直接影響穩(wěn)定性的一項(xiàng)。它的取值要和三個(gè)東西匹配host 控制器的能力、模塊支持的最高頻率、以及板級(jí)走線質(zhì)量。調(diào)試策略是從低往高試試跑頻率適用場景說明400kHz最小驗(yàn)證識(shí)別階段默認(rèn)頻率幾乎必通12.5MHz保底可用吞吐偏低但穩(wěn)定性最好25MHz常規(guī)選擇大多數(shù)板子在 4 位總線下能跑通50MHz高性能需要走線阻抗控制良好100MHzUHS-I對(duì)硬件和電源要求都極高除了頻率還有一項(xiàng)容易被忽略的是時(shí)鐘相位phase和驅(qū)動(dòng)能力drive strength。不少 SoC 的 SDIO 控制器支持調(diào)節(jié)這兩項(xiàng)通過 pinctrl 或者專門的寄存器配置。當(dāng)走線比較長超過 5cm的時(shí)候適當(dāng)調(diào)整采樣相位往往能救回一批板子。/* 調(diào)整時(shí)鐘相位的典型寫法具體屬性名取決于 SoC */ sdmmc1 { pinctrl-names default, state_uhs; pinctrl-0 sdmmc1_b4_pins_a; pinctrl-1 sdmmc1_b4_od_pins_a; };4.3 走線與信號(hào)完整性走線這塊軟件工程師通常插不上手但你有必要知道該向硬件提什么要求。CLK是最關(guān)鍵的一根。它是單向的從 host 到卡全程應(yīng)該做阻抗控制參考地完整。CLK上的過沖和振鈴會(huì)直接導(dǎo)致采樣錯(cuò)誤。CMD和DAT0~3是雙向的需要上下拉。SDIO 規(guī)范里要求 host 側(cè)提供上拉如果板子上漏了信號(hào)在空閑態(tài)就會(huì)浮空表現(xiàn)是隨機(jī)超時(shí)。等長也很重要。4 位模式下DAT0~3加CMD這五根線的長度差要控制住具體數(shù)值看你的目標(biāo)頻率。25MHz 以下可以放寬到 5mm 以內(nèi)50MHz 就要壓到 2mm 級(jí)別。最后一個(gè)實(shí)用技巧如果你懷疑信號(hào)完整性問題可以先降頻驗(yàn)證再飛線驗(yàn)證。降頻能讓問題消失基本就鎖定是信號(hào)問題如果再換一塊 PCB 就好了那就是板廠工藝波動(dòng)。5. 軟件側(cè)調(diào)參與設(shè)備樹逐項(xiàng)過硬件排查完還是一頭霧水的時(shí)候軟件側(cè)還有不少能擰的旋鈕。這一章把設(shè)備樹屬性和內(nèi)核調(diào)試手段挨個(gè)過一遍。5.1 降速驗(yàn)證法最省事的第一刀不管問題出在哪我建議第一刀永遠(yuǎn)是降速。理由很簡單它能用最小的代價(jià)把時(shí)序類問題和邏輯類問題分開。# 修改設(shè)備樹 max-frequency 后重新編譯 dtb # 從 50MHz 降到 25MHz再降到 12.5MHz make dtbs降速后如果問題消失了說明是時(shí)序或者信號(hào)相關(guān)如果降速后問題依舊那多半是邏輯配置問題比如 pwrseq 時(shí)序不對(duì)、功能沒使能、固件路徑錯(cuò)。這一刀的性價(jià)比極高我?guī)缀趺看味枷茸?。降速?yàn)證做完之后還有第二個(gè)驗(yàn)證動(dòng)作關(guān)掉 SDIO 中斷。sdmmc1 { /* 注釋掉這一行 */ /* cap-sdio-irq; */ };cap-sdio-irq打開時(shí)模塊通過 DAT1 發(fā)中斷給 host關(guān)掉之后走輪詢模式。如果關(guān)掉中斷后就不超時(shí)了說明問題出在中斷線路上重點(diǎn)去查 DAT1 的走線和上拉。5.2 設(shè)備樹里那些和 SDIO 穩(wěn)定性強(qiáng)相關(guān)的屬性我把實(shí)際項(xiàng)目里最常調(diào)的幾項(xiàng)整理成表附上取值邏輯。屬性常用取值影響bus-width1 或 44 位吞吐高但要求四根 DAT 都合格max-frequency25000000 起調(diào)直接決定時(shí)序余量non-removable加上避免 host 反復(fù)做卡檢測cap-sdio-irq視情況關(guān)掉可排除中斷線路問題keep-power-in-suspend視電源設(shè)計(jì)保持供電避免 resume 異常no-1-8-v視 IO 電平禁掉 1.8V 電壓切換mmc-pwrseq必配控制復(fù)位和上電延時(shí)disable-wp加上避免寫保護(hù)檢測干擾關(guān)于no-1-8-v多說一句。這一項(xiàng)是禁掉 1.8V 信令電壓切換。默認(rèn)情況下UHS-I 模式會(huì)把 IO 電壓從 3.3V 切到 1.8V 來換更高的速度。如果你的板子沒有做 1.8V 供電或者模塊不支持就一定要加上這一項(xiàng)。沒加的話host 會(huì)嘗試切換電壓切完通信直接崩日志就是一堆-110。5.3 抓現(xiàn)場debugfs 與 dynamic debug設(shè)備樹的配置是一回事跑起來之后當(dāng)前狀態(tài)是另一回事。想確認(rèn)實(shí)際生效的參數(shù)看 debugfs。# 掛載 debugfs如果還沒掛 mount -t debugfs none /sys/kernel/debug # 查看當(dāng)前總線狀態(tài)時(shí)鐘、總線寬度、時(shí)序模式 cat /sys/kernel/debug/mmc0/ios # 輸出示例 # clock: 50000000 Hz # vdd: 21 (3.3 ~ 3.4 V) # bus mode: 2 (push-pull) # chip select: 0 (dont 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)這個(gè)輸出很有用。如果signal voltage顯示 1.80V 而你的板子其實(shí)是 3.3V說明電壓切換出問題了。如果bus width顯示 1 位說明 4 位切換沒成功。再看 MMC 層的動(dòng)態(tài)調(diào)試。只要內(nèi)核編譯時(shí)開了CONFIG_DYNAMIC_DEBUG就可以在運(yùn)行時(shí)打開某個(gè)文件或模塊的調(diào)試輸出# 打開 sdhci 的所有調(diào)試信息 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 # 查看當(dāng)前已打開的調(diào)試點(diǎn) grep -c p /sys/kernel/debug/dynamic_debug/control打開之后配合dmesg -w實(shí)時(shí)看日志能拿到比默認(rèn)詳細(xì)得多的信息包括每一次請求的地址、長度、方向。這個(gè)手段在做壓力測試抓偶發(fā)問題的時(shí)候特別有用。還有個(gè)更有針對(duì)性的一招用strace或者自己寫個(gè)腳本反復(fù)觸發(fā) WiFi 操作把復(fù)現(xiàn)概率拉高。#!/bin/sh # 壓力腳本反復(fù)掃描和連接放大偶發(fā)問題 while true; do iw dev wlan0 scan /dev/null 21 sleep 0.5 ping -c 2 -W 1 192.168.1.1 /dev/null 21 sleep 0.5 done我之前遇到一個(gè)半天才復(fù)現(xiàn)一次的-110用這個(gè)腳本跑了二十分鐘就穩(wěn)定復(fù)現(xiàn)了。6. 常見問題速查表與我的踩坑記錄前面幾章講的是方法論這一章把實(shí)際高頻問題整理成速查表再補(bǔ)幾個(gè)印象比較深的案例。6.1 常見問題速查表現(xiàn)象大概率原因快速驗(yàn)證方法處理方向每次開機(jī)必報(bào)-110上電時(shí)序或復(fù)位查 pwrseq 的 delay 值加大post-power-on-delay-ms十次里成幾次電源余量不足示波器量電源軌最低點(diǎn)補(bǔ)退耦電容、換 LDO降速后正常信號(hào)完整性把頻率降到 12.5MHz查走線、調(diào)相位、調(diào)驅(qū)動(dòng)能力關(guān)中斷后正常DAT1 中斷線路去掉cap-sdio-irq查 DAT1 上拉和走線只在高溫下報(bào)錯(cuò)器件溫漂加熱臺(tái)升溫測試換器件或降低工作頻率resume 后報(bào)錯(cuò)電源被切但驅(qū)動(dòng)不知情看 suspend 期間電流配keep-power-in-suspend大塊傳輸才報(bào)錯(cuò)塊大小或總線寬度把 block size 降到 128調(diào) CCCR 配置或驅(qū)動(dòng)參數(shù)并發(fā)時(shí)隨機(jī)報(bào)錯(cuò)runtime PM 競爭關(guān)掉 runtime PM 測試協(xié)調(diào)兩個(gè) function 的 PM6.2 幾個(gè)印象深刻的坑第一個(gè)坑是關(guān)于mmc-pwrseq的。有一次項(xiàng)目上換了新模塊代碼完全照搬舊模塊的設(shè)備樹結(jié)果新模塊十次開機(jī)只成功兩次。查了兩天最后發(fā)現(xiàn)舊模塊的復(fù)位是高有效新模塊是低有效設(shè)備樹里那個(gè)GPIO_ACTIVE_LOW沒改導(dǎo)致復(fù)位電平一直是反的。改完之后問題立刻消失。教訓(xùn)是換模塊的第一步是逐項(xiàng)核對(duì)硬件差異表不要想當(dāng)然地復(fù)用配置。第二個(gè)坑是關(guān)于-110和塊大小的關(guān)系。某方案默認(rèn)塊大小 512 字節(jié)實(shí)驗(yàn)室跑了三天沒問題。一到客戶現(xiàn)場同樣的板子開始零星報(bào)-110而且都是運(yùn)行一段時(shí)間之后。后來發(fā)現(xiàn)是客戶環(huán)境溫度比實(shí)驗(yàn)室高十幾度高溫下信號(hào)裕量變小512 字節(jié)的長傳輸更容易在中間出錯(cuò)。把塊大小降到 256問題解決。這類環(huán)境差異觸發(fā)的邊界問題最容易被忽略因?yàn)樗桓淖冘浖壿嬛皇前延布A繅旱搅伺R界點(diǎn)。第三個(gè)坑關(guān)于寄存器 dump 的讀法。很多人看到Timeout waiting for hardware interrupt后面那一大坨 dump 就直接跳過其實(shí)里面信息量很大。重點(diǎn)看Present這一行也就是 PRNSTS 寄存器。里面有幾個(gè)關(guān)鍵位如果CMD Inhibit位一直是 1說明命令還在發(fā)host 沒收到響應(yīng)如果DAT Inhibit位一直是 1說明數(shù)據(jù)線還被占著卡沒釋放總線如果Command Complete位沒置起來說明卡壓根沒回響應(yīng)如果這幾個(gè)位都在合理狀態(tài)但中斷沒來那可能是控制器自己的中斷屏蔽或者路由問題。我有一次就是靠讀Present寄存器發(fā)現(xiàn) DAT0 一直是低電平最后定位到是模塊的某個(gè) GPIO 復(fù)用配置錯(cuò)了把 DAT0 拉死了。第四個(gè)坑關(guān)于降速的假陽性。降速之后問題消失很容易讓人得出硬件沒問題軟件降速就行的結(jié)論。但如果這是批量產(chǎn)品降速意味著吞吐下降可能根本達(dá)不到產(chǎn)品要求。**降速只是定位手段不是最終方案。**正確的做法是降到能穩(wěn)定的頻率然后往上找到臨界點(diǎn)再回頭去優(yōu)化硬件讓臨界點(diǎn)提高上來。6.3 一份可以直接抄的排查流程最后把我自己用的排查順序整理出來從零開始遇到-110可以照這個(gè)走。第一步抓完整日志。dmesg -w全程掛著復(fù)現(xiàn)一次把從卡識(shí)別到報(bào)錯(cuò)的所有行完整保存。特別注意報(bào)錯(cuò)前的最后幾條那是關(guān)鍵。第二步判斷階段??词敲杜e階段、固件下載階段、運(yùn)行階段還是 resume 階段。這一步?jīng)Q定后面往哪個(gè)方向走。第三步降速驗(yàn)證。把max-frequency降到 12.5MHz重啟測試。如果好了走信號(hào)完整性方向如果還壞走配置方向。第四步關(guān)中斷驗(yàn)證。去掉cap-sdio-irq重啟測試。用來區(qū)分是數(shù)據(jù)通道問題還是中斷通道問題。第五步量電源。示波器掛上主電源軌觸發(fā)在下降沿跑壓力測試??醋畹忘c(diǎn)和持續(xù)時(shí)間。第六步看寄存器 dump。重點(diǎn)看Present和Error兩個(gè)狀態(tài)寄存器判斷是命令階段掛還是數(shù)據(jù)階段掛。第七步換板對(duì)比。同一份軟件換一塊板子如果換板就好那基本可以定性為硬件批次差異走硬件方向。這七步走完絕大多數(shù)-110都能有個(gè)明確的歸屬。剩下的極少數(shù)通常是多因素疊加比如電源和信號(hào)問題同時(shí)存在那就需要一項(xiàng)一項(xiàng)隔離工作量會(huì)大一些但思路是一樣的。我自己踩過的這些坑告訴我一件事-110這個(gè)錯(cuò)誤碼本身不含任何指向性它只是說超時(shí)了。真正有價(jià)值的線索全在它前后的日志、寄存器狀態(tài)和你能測到的物理信號(hào)里。與其花時(shí)間猜不如花時(shí)間把這些證據(jù)收齊。收齊了答案往往就擺在那里。