絡(luò)調(diào)試:Ethernet PHY軟件復(fù)位失效根因與解決實(shí)踐)
搞嵌入式網(wǎng)絡(luò)開(kāi)發(fā)的朋友應(yīng)該都撞過(guò)這個(gè)場(chǎng)景板子上電代碼初始化 PHY執(zhí)行了軟件復(fù)位但寄存器讀出來(lái)還是老樣子或者鏈路死活起不來(lái)。Ethernet PHY 的軟件復(fù)位不生效看起來(lái)是個(gè)很小的點(diǎn)但牽扯到 MDIO 時(shí)序、PHY 地址、strap 管腳、甚至驅(qū)動(dòng)初始化順序問(wèn)題藏得深的時(shí)候能折騰好幾天。我調(diào)過(guò)不少帶 PHY 的板卡從 RTL8211、KSZ9031 到 DP83848 都踩過(guò)坑這篇文章就專門(mén)聊聊 Software reset 失效這件事哪些原因會(huì)導(dǎo)致它失效、怎么一步步定位、以及驅(qū)動(dòng)側(cè)怎么把軟復(fù)位寫(xiě)得足夠穩(wěn)。1. 軟復(fù)位的第一課寄存器行為與正常時(shí)序1.1 軟件復(fù)位到底在復(fù)位什么以太網(wǎng) PHY 通過(guò) MDIOManagement Data Input/Output接口與 MAC 或 CPU 通信這個(gè)接口專門(mén)用于讀寫(xiě) PHY 內(nèi)部寄存器。軟件復(fù)位一般指往 BMCRBasic Mode Control Register寄存器地址 0x00的 bit15 寫(xiě) 1 來(lái)觸發(fā) PHY 內(nèi)部復(fù)位。這個(gè)復(fù)位位是自清零的PHY 復(fù)位完成后硬件會(huì)自動(dòng)把 bit15 清 0所以“復(fù)位是否完成”可以通過(guò)輪詢 bit15 判斷。PHY 軟復(fù)位會(huì)做幾件事把大部分 MII 寄存器恢復(fù)成默認(rèn)值、重新初始化 PHY 內(nèi)部狀態(tài)機(jī)、中斷當(dāng)前自協(xié)商并重新啟動(dòng)。這也就意味著如果復(fù)位后你原本配置的自協(xié)商使能、速度、雙工等參數(shù)沒(méi)有重新寫(xiě)一遍PHY 會(huì)回到默認(rèn)配置鏈路可能和你的預(yù)期不一致。很多“軟復(fù)位不工作”的現(xiàn)象表面上是 PHY 沒(méi)有反應(yīng)其實(shí)是復(fù)位之后配置被清掉了但又被誤讀成“沒(méi)復(fù)位成功”。1.2 寫(xiě) 1 不等于執(zhí)行讀 0 才算數(shù)不少人在代碼里寫(xiě)了一個(gè)mdio_write(phy_addr, 0, 0x8000)就以為復(fù)位完成了。這個(gè)想法在多數(shù) PHY 上會(huì)出問(wèn)題。因?yàn)閺?fù)位需要時(shí)間不同 PHY 的復(fù)位時(shí)間差別很大快的幾十微秒慢的能到幾百毫秒甚至更久。如果你寫(xiě)完立刻讀 0x00很有可能會(huì)讀到 0x8000這并不代表復(fù)位失敗而是復(fù)位還沒(méi)執(zhí)行完。正確的做法是寫(xiě) 0x8000 后循環(huán)讀取 BMCR直到 bit15 變?yōu)?0同時(shí)設(shè)置一個(gè)超時(shí)值防止卡死。比如int phy_soft_reset(int phy_addr) { int ret; uint16_t val; int timeout 100; ret mdio_write(phy_addr, 0x00, 0x8000); if (ret 0) return ret; while (timeout--) { ret mdio_read(phy_addr, 0x00, val); if (ret 0) return ret; if ((val 0x8000) 0) return 0; usleep(1000); } return -1; }1.3 怎么證明“軟復(fù)位真的執(zhí)行了”調(diào)試時(shí)不要只盯著 BMCR 的 bit15還要從多個(gè)角度確認(rèn)復(fù)位確實(shí)發(fā)生過(guò)。首先是讀 PHY ID 寄存器地址 0x02 和 0x03正常復(fù)位后 ID 應(yīng)該保持不變。其次是觀察鏈路狀態(tài)復(fù)位期間 PHY 的 link 會(huì)掉線自協(xié)商重新開(kāi)始所以如果你用示波器或 ethtool 持續(xù)觀察會(huì)看到 link down 又 up 的過(guò)程。最后是核對(duì)配置寄存器比如 BMCR 的 bit15 清零后bit12自協(xié)商使能、bit13速度選擇等會(huì)恢復(fù)到默認(rèn)值。如果復(fù)位前后這些值完全沒(méi)變化那基本可以確定軟復(fù)位沒(méi)有真正執(zhí)行。2. 根因分類(lèi)為什么軟復(fù)位會(huì)失效2.1 MDIO 總線層面數(shù)據(jù)根本沒(méi)寫(xiě)進(jìn)去最常見(jiàn)的軟復(fù)位失效原因不是 PHY 本身的問(wèn)題而是 MDIO 上的寫(xiě)操作根本沒(méi)成功。MDIO 的通信時(shí)序有規(guī)范要求MDC 時(shí)鐘頻率在 IEEE 802.3 標(biāo)準(zhǔn)里最大是 2.5 MHz部分 PHY 支持更高的 25 MHz 模式但需要特定寄存器開(kāi)啟而且不是所有 PHY 都保證在高時(shí)鐘下穩(wěn)定工作。排查時(shí)先看 MDC 頻率。有的系統(tǒng)為了趕速度把 MDC 配到了 10 MHz 甚至更高結(jié)果 PHY 對(duì)數(shù)據(jù)采樣出錯(cuò)寄存器寫(xiě)入不生效。我遇到過(guò)一塊板子讀 PHY ID 一直正常但寫(xiě) BMCR 復(fù)位位后讀回還是 0x0000最后用示波器抓波形才發(fā)現(xiàn)寫(xiě)幀的數(shù)據(jù)段完全亂了把 MDC 降到 2.5 MHz 之后立刻恢復(fù)正常。MDIO 幀格式也值得了解。一個(gè)完整的 MDIO 寫(xiě)幀包含 32 位 preamble、起始碼、操作碼01 表示寫(xiě)、5 位 PHY 地址、5 位寄存器地址、2 位 turnaround 和 16 位數(shù)據(jù)。如果你用邏輯分析儀或者示波器抓幀可以按這個(gè)格式手動(dòng)解碼確認(rèn) CPU 發(fā)出的 PHY 地址、寄存器地址和實(shí)際想要訪問(wèn)的是否一致。上拉電阻也是常見(jiàn)問(wèn)題點(diǎn)。MDIO 數(shù)據(jù)線是雙向的讀操作時(shí)由 PHY 驅(qū)動(dòng)如果 MDIO 缺少上拉電阻讀數(shù)據(jù)時(shí)高電平可能不夠?qū)е伦x到全 0 或隨機(jī)值寫(xiě)操作倒是正常。判斷方法很簡(jiǎn)單只讀 PHY ID如果每次讀出來(lái)都不一樣優(yōu)先檢查上拉電阻和 IO 電平。2.2 你操作的不是你想要的 PHYPHY 地址由 PHYAD strap 管腳在上電復(fù)位時(shí)鎖存范圍是 0 到 31。MDIO 寫(xiě)幀里的 PHY 地址必須和 PHY 實(shí)際鎖存的地址一致否則 PHY 根本不應(yīng)答。很多板卡的 PHY 地址在硬件設(shè)計(jì)階段已經(jīng)確定比如通過(guò)上下拉電阻把 PHYAD[4:0] 配成 0x01但如果你在代碼里寫(xiě)死了 0x00軟復(fù)位自然無(wú)效。在多 PHY 場(chǎng)景下更容易踩坑。比如帶交換芯片的板卡CPU 通過(guò) MDIO 總線接 switchswitch 內(nèi)部又管理多個(gè) PHY這種時(shí)候 PHY 地址往往不是簡(jiǎn)單的 0-4可能包含頁(yè)選擇或者內(nèi)部端口映射。最穩(wěn)妥的做法是上電后先遍歷地址 0-31讀取每個(gè)地址的 PHY ID和 datasheet 上的期望值比對(duì)確認(rèn)哪個(gè)地址對(duì)應(yīng)哪顆 PHY再做復(fù)位操作。2.3 寄存器訪問(wèn)路徑或間接訪問(wèn)陷阱有些 PHY 芯片的寄存器訪問(wèn)不是簡(jiǎn)單的直接訪問(wèn)。比如擴(kuò)展寄存器需要先寫(xiě)頁(yè)寄存器再訪問(wèn)目標(biāo)地址或者 PHY 掛在 MDIO mux 后面需要先切換通道。如果驅(qū)動(dòng)把地址配置錯(cuò)軟復(fù)位命令寫(xiě)到了錯(cuò)誤的寄存器PHY 當(dāng)然不會(huì)有反應(yīng)。還有一個(gè)隱蔽的坑有些 PHY 在低功耗或者隔離模式下MDIO 接口仍然能夠響應(yīng)但寄存器寫(xiě)入可能被忽略或者只有特定命令才能喚醒。如果你把一個(gè) PHY 配置成 power down 之后再去寫(xiě)軟復(fù)位會(huì)發(fā)現(xiàn) bit15 寫(xiě)進(jìn)去了但 PHY 狀態(tài)不對(duì)鏈路也起不來(lái)。這種時(shí)候先檢查 BMCR 的 bit11power down和 bit10isolate是否被意外置位。2.4 被后面的代碼覆蓋最常見(jiàn)也最隱蔽這是我見(jiàn)過(guò)最多的“軟復(fù)位失效”原因現(xiàn)象非常具有迷惑性單獨(dú)執(zhí)行軟復(fù)位是好的但接入完整驅(qū)動(dòng)流程后復(fù)位好像沒(méi)發(fā)生過(guò)。問(wèn)題出在初始化順序上。比如驅(qū)動(dòng)流程是寫(xiě)軟復(fù)位、延時(shí)固定時(shí)間、配置自協(xié)商、等待自協(xié)商完成。如果延時(shí)時(shí)間不夠PHY 的復(fù)位還沒(méi)走完后面的自協(xié)商配置寫(xiě)入就可能被復(fù)位過(guò)程覆蓋掉。最終效果就是 BMCR 里的自協(xié)商使能位不正確link 建立不起來(lái)看起來(lái)像是“復(fù)位把配置清了”實(shí)際上是復(fù)位還沒(méi)完成配置寫(xiě)得過(guò)早。排查方法在復(fù)位完成后延時(shí)幾毫秒把 BMCR 寄存器讀出來(lái)再對(duì)比你后面代碼寫(xiě)入的預(yù)期值。不一致就說(shuō)明寫(xiě)入順序有時(shí)序問(wèn)題解決方法是把軟復(fù)位改成輪詢等待自清零完成再繼續(xù)配置。2.5 strap 管腳在復(fù)位釋放時(shí)鎖定嚴(yán)格來(lái)說(shuō)軟件復(fù)位不會(huì)重新采樣 strap 管腳strap 只在硬件復(fù)位釋放或者上電過(guò)程中鎖存。但很多“軟復(fù)位不生效”的板子根因反而在硬件復(fù)位和 strap 上。如果硬件復(fù)位釋放瞬間 strap 管腳電平?jīng)]有穩(wěn)定PHY 會(huì)鎖存到錯(cuò)誤的地址或者模式后續(xù)軟復(fù)位即使執(zhí)行成功PHY 的工作模式也是錯(cuò)的。比如有的電路用 RC 延時(shí)給 strap 管腳做默認(rèn)電平如果 RC 時(shí)間常數(shù)太大復(fù)位釋放時(shí)電平還沒(méi)穩(wěn)定PHY 就把不確定的電平鎖進(jìn)去了。這種問(wèn)題在低功耗設(shè)計(jì)、IO 電壓切換的板子上尤其常見(jiàn)。排查時(shí)需要示波器看復(fù)位釋放時(shí)刻 strap 管腳的電平是否符合設(shè)計(jì)預(yù)期。2.6 時(shí)鐘沒(méi)起來(lái)狀態(tài)機(jī)不轉(zhuǎn)PHY 需要一個(gè)參考時(shí)鐘才能維持內(nèi)部狀態(tài)機(jī)運(yùn)行一般是外接晶振或由 MAC 提供 25 MHz 時(shí)鐘。如果時(shí)鐘沒(méi)起振或者頻率不對(duì)MDIO 接口可能還能讀 ID因?yàn)樽x ID 不需要完整的狀態(tài)機(jī)但軟復(fù)位后的自協(xié)商完全不會(huì)進(jìn)行鏈路也就起不來(lái)。我調(diào)試時(shí)有個(gè)習(xí)慣碰到復(fù)位不生效先測(cè) PHY 時(shí)鐘輸出或者晶振波形。用示波器探一下 XI 或 CLK_OUT 管腳確認(rèn)頻率和幅度符合 datasheet 要求。很多時(shí)候軟復(fù)位本身沒(méi)問(wèn)題問(wèn)題在于 PHY 的數(shù)據(jù)通路根本沒(méi)工作時(shí)鐘就是第一嫌疑。下面是失效現(xiàn)象和優(yōu)先排查方向的速查表方便現(xiàn)場(chǎng)對(duì)照定位失效現(xiàn)象優(yōu)先排查項(xiàng)常見(jiàn)原因?qū)憦?fù)位后 BMCR 仍為 0x8000MDC 頻率、MDIO 波形總線時(shí)序不滿足 PHY 要求讀 PHY ID 全 0 或全 1PHY 地址、上拉電阻地址不匹配或總線無(wú)應(yīng)答軟復(fù)位單測(cè)正常加上配置代碼后失效初始化順序配置寫(xiě)入早于復(fù)位完成復(fù)位后 link 始終起不來(lái)時(shí)鐘、strap、硬件復(fù)位PHY 數(shù)據(jù)通路未工作PHY 地址與實(shí)際硬件不符PHYAD strap 管腳strap 鎖存錯(cuò)誤3. 現(xiàn)場(chǎng)排查把問(wèn)題“逼”出來(lái)3.1 先讀 PHY ID 建立基線排查任何 PHY 問(wèn)題前先把 PHY ID 讀出來(lái)。PHY ID 由寄存器 0x02OUI 高 16 位和 0x03OUI 低 6 位 型號(hào) 6 位 版本 4 位組成。每個(gè) PHY 型號(hào)有固定 ID你可以對(duì)照 datasheet 確認(rèn)讀取結(jié)果是否正確。如果讀到 0x0000 或者 0xffff說(shuō)明 MDIO 總線上根本沒(méi)有 PHY 響應(yīng)或者地址不對(duì)。建議寫(xiě)一個(gè)掃描腳本遍歷 PHY 地址 0-31把所有能讀到合法 ID 的地址打印出來(lái)。以 mdio-tools 為例# 遍歷地址0-31讀寄存器2和3 for i in $(seq 0 31); do id1$(mdio read phy 0 $i 2 2/dev/null | awk {print $NF}) id2$(mdio read phy 0 $i 3 2/dev/null | awk {print $NF}) if [ $id1 ! 0xffff ] [ $id1 ! 0x0000 ]; then echo addr$i id$id1$id2 fi done腳本的目的是找到所有存活的 PHY 地址。正常板卡上結(jié)果里應(yīng)該能看到 datasheet 對(duì)應(yīng)的 ID如果找不到先別糾結(jié)軟復(fù)位把 MDIO 總線基本問(wèn)題解決再說(shuō)。3.2 示波器抓 MDIO/MDC觀察幀波形軟件層面的寄存器讀寫(xiě)最終要通過(guò) IO 波形體現(xiàn)。用示波器同時(shí)測(cè) MDC 和 MDIO觸發(fā)方式選 MDC 上升沿抓一次寫(xiě) BMCR 0x8000 的完整幀。觀察幾個(gè)關(guān)鍵點(diǎn)MDC 頻率是否在 PHY 允許范圍內(nèi)MDIO 數(shù)據(jù)是否在 MDC 下降沿后變化、上升沿前穩(wěn)定幀里的 PHY 地址和寄存器地址是否和代碼一致。如果發(fā)現(xiàn)數(shù)據(jù)線在 MDC 上升沿附近還在跳變說(shuō)明時(shí)序余量不夠。這時(shí)候可以降低 MDC 頻率或者調(diào)整 MDIO 驅(qū)動(dòng)能力優(yōu)先保證通信穩(wěn)定。還有一種情況MDIO 線上有電平競(jìng)爭(zhēng)。比如多個(gè) MDIO 主機(jī)同時(shí)操作同一根總線或者 PHY 的 MDIO 引腳被復(fù)用為其他功能讀寫(xiě)數(shù)據(jù)就會(huì)錯(cuò)亂。用示波器看波形時(shí)如果發(fā)現(xiàn) MDIO 高電平被拉低大概率是總線沖突。3.3 讀回 BMCR確認(rèn)寫(xiě)操作落地寫(xiě)軟復(fù)位后立即讀 BMCR有兩種典型情況需要區(qū)分第一種讀到 0x8000說(shuō)明復(fù)位還在進(jìn)行中繼續(xù)輪詢等待自清零。第二種讀到 0x0000或者其他值但 bit15 已經(jīng)為 0說(shuō)明 PHY 接受了復(fù)位并完成。如果此時(shí)你看到 bit12 自協(xié)商使能、bit13 速度選擇等配置寄存器都恢復(fù)默認(rèn)了證明復(fù)位執(zhí)行過(guò)。如果寫(xiě)完 0x8000 后無(wú)論怎么輪詢 bit15 都一直為 1而且 PHY ID 能正常讀到那問(wèn)題基本在 PHY 內(nèi)部狀態(tài)。建議檢查 PHY 的電源、時(shí)鐘、復(fù)位管腳是否正常。3.4 檢查硬件復(fù)位和 strap 信號(hào)軟件復(fù)位不工作很多人忽略了硬件復(fù)位管腳。PHY 的 RST# 管腳如果在軟件復(fù)位過(guò)程中被外部拉低會(huì)導(dǎo)致 PHY 一直在硬件復(fù)位狀態(tài)任何 MDIO 寫(xiě)操作都不會(huì)被真正執(zhí)行。檢查 RST# 電平是否穩(wěn)定在高電平如果有周期性拉低要追蹤是誰(shuí)在操作這個(gè)管腳。strap 信號(hào)檢查要在復(fù)位釋放瞬間做。示波器觸發(fā)在 RST# 上升沿看 PHYAD、ANEG、MODE 等 strap 管腳的電平是否已經(jīng)穩(wěn)定。如果 strap 管腳在復(fù)位釋放后才慢慢爬升PHY 鎖存的就不是你想要的配置這對(duì)后續(xù)軟復(fù)位的“無(wú)效”判斷會(huì)產(chǎn)生誤導(dǎo)。3.5 最小化復(fù)現(xiàn)腳本把問(wèn)題從完整驅(qū)動(dòng)里剝離出來(lái)用最小化腳本復(fù)現(xiàn)。在嵌入式 Linux 下可以直接在 shell 里讀寫(xiě) MDIO 寄存器在裸機(jī)環(huán)境寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試函數(shù)只做三件事讀 PHY ID、寫(xiě)軟復(fù)位、輪詢 BMCR。下面是一個(gè)最小化的 C 示例#include stdio.h #include unistd.h int main(int argc, char *argv[]) { int addr argc 1 ? atoi(argv[1]) : 1; uint16_t id1, id2, bmcr; mdio_read(addr, 2, id1); mdio_read(addr, 3, id2); printf(PHY ID: 0x%04x%04x\n, id1, id2); mdio_write(addr, 0, 0x8000); for (int i 0; i 100; i) { mdio_read(addr, 0, bmcr); if (!(bmcr 0x8000)) { printf(reset done after %d ms\n, i); break; } usleep(1000); } printf(BMCR after reset: 0x%04x\n, bmcr); return 0; }如果這個(gè)最小化腳本能正常復(fù)位說(shuō)明硬件和 MDIO 總線沒(méi)問(wèn)題問(wèn)題在完整驅(qū)動(dòng)的代碼邏輯。如果腳本都復(fù)現(xiàn)不了軟復(fù)位失效那就回到硬件和總線排查。4. 三個(gè)真實(shí)案例復(fù)盤(pán)4.1 案例 AMDC 頻率太高導(dǎo)致寫(xiě)操作丟失現(xiàn)象板卡上調(diào)試讀取 PHY ID 正常但寫(xiě)軟復(fù)位寄存器后再讀 BMCR 一直是 0x0000bit15 始終沒(méi)有置位PHY 也沒(méi)有任何 link 變化。當(dāng)時(shí)第一反應(yīng)是 PHY 壞了換了芯片還是一樣。排查用示波器抓 MDC 波形發(fā)現(xiàn) MDC 頻率被一個(gè)共用驅(qū)動(dòng)的代碼配置成了 12.5 MHz。再抓 MDIO 寫(xiě)幀發(fā)現(xiàn)數(shù)據(jù)段在 MDC 上升沿采樣時(shí)不穩(wěn)定PHY 把數(shù)據(jù)采樣錯(cuò)了相當(dāng)于寫(xiě)進(jìn)去的數(shù)據(jù)不是 0x8000而是隨機(jī)值。解決把 MDC 頻率降到 2.5 MHz重新測(cè)試軟復(fù)位正常生效。后面我養(yǎng)成了一個(gè)習(xí)慣只要能調(diào)低 MDC 頻率就先用低頻率跑通功能再去考慮性能優(yōu)化。4.2 案例 B多 PHY 板卡地址認(rèn)錯(cuò)對(duì)象現(xiàn)象一塊帶交換芯片的板卡交換芯片內(nèi)部有 5 個(gè) PHY。代碼里固定往 PHY 地址 0 執(zhí)行軟復(fù)位結(jié)果交換機(jī)某個(gè)端口的 link 總是異常另一個(gè)端口倒是正常。排查用掃描腳本遍歷 0-31發(fā)現(xiàn) PHY 地址分布和代碼預(yù)期完全不同。交換芯片把內(nèi)部 PHY 映射到了以 0x04 起始的地址區(qū)間地址 0 上根本沒(méi)有 PHY 響應(yīng)。也就是說(shuō)軟復(fù)位命令一直被發(fā)到了總線上一個(gè)不存在的 PHY那“不工作”就是必然的。解決修改驅(qū)動(dòng)中的 PHY 地址表按掃描到的實(shí)際地址配置軟復(fù)位和后續(xù)鏈路配置都正常。4.3 案例 C復(fù)位成功但配置被覆蓋現(xiàn)象最小化腳本測(cè)試軟復(fù)位是好的但跑完整驅(qū)動(dòng)后PHY 的自協(xié)商配置總是不對(duì)link 起不來(lái)。當(dāng)時(shí)排查了很久一度以為 PHY 有問(wèn)題。排查在軟復(fù)位函數(shù)返回后加延時(shí)讀取 BMCR發(fā)現(xiàn)自協(xié)商使能位為 0而代碼里明明在后面寫(xiě)了配置。再細(xì)看代碼發(fā)現(xiàn)驅(qū)動(dòng)里軟復(fù)位后有一個(gè)usleep(1000)的固定延時(shí)但實(shí)際 PHY 復(fù)位需要 5 ms 左右。后面的自協(xié)商配置寫(xiě)入太早被復(fù)位過(guò)程清掉了。解決把固定延時(shí)改成輪詢 bit15 自清零清零后再寫(xiě)自協(xié)商配置問(wèn)題消失。同時(shí)加了超時(shí)保護(hù)防止 PHY 異常時(shí)卡死初始化。案例復(fù)盤(pán)匯總案例現(xiàn)象定位手段修復(fù)方式MDC 頻率過(guò)高寫(xiě)復(fù)位后 BMCR 不置位示波器抓 MDC/MDIO 波形MDC 降到 2.5 MHzPHY 地址錯(cuò)誤復(fù)位命令發(fā)到不存在的 PHY掃描 0-31 地址讀 PHY ID改用實(shí)際 PHY 地址配置寫(xiě)入過(guò)早復(fù)位單測(cè)正常驅(qū)動(dòng)整體異常復(fù)位后讀回 BMCR 比對(duì)輪詢自清零后再配置5. 驅(qū)動(dòng)側(cè)穩(wěn)妥實(shí)現(xiàn)一份可以直接落地的軟復(fù)位代碼5.1 封裝 MDIO 讀寫(xiě)原語(yǔ)不同平臺(tái)的 MDIO 讀寫(xiě)實(shí)現(xiàn)差別很大但接口可以統(tǒng)一。建議封裝兩個(gè)函數(shù)返回 0 表示成功負(fù)值表示錯(cuò)誤。代碼里不直接操作硬件寄存器方便后續(xù)適配或用 mock 測(cè)試int mdio_read(uint8_t phy_addr, uint8_t reg_addr, uint16_t *val); int mdio_write(uint8_t phy_addr, uint8_t reg_addr, uint16_t val);底層實(shí)現(xiàn)里注意 MDC 頻率必須控制在 PHY 支持范圍內(nèi)MDIO 讀寫(xiě)幀要按照 IEEE 802.3 格式構(gòu)造幀間隔最好留幾個(gè) MDC 時(shí)鐘周期避免連續(xù)操作時(shí) PHY 來(lái)不及處理。5.2 軟復(fù)位實(shí)現(xiàn)的四個(gè)關(guān)鍵點(diǎn)一個(gè)可靠的phy_soft_reset函數(shù)要有四個(gè)關(guān)鍵點(diǎn)寫(xiě) 0x8000 到 BMCR、輪詢 bit15 自清零、超時(shí)保護(hù)、復(fù)位完成后延遲讓內(nèi)部狀態(tài)機(jī)穩(wěn)定。參考實(shí)現(xiàn)#define PHY_REG_BMCR 0x00 #define BMCR_RESET 0x8000 #define BMCR_ANENABLE 0x1000 #define BMCR_SPEED100 0x2000 #define BMCR_DUPLEX_FULL 0x0100 int phy_soft_reset(uint8_t phy_addr) { uint16_t val; int ret; int timeout 200; ret mdio_write(phy_addr, PHY_REG_BMCR, BMCR_RESET); if (ret 0) return ret; while (timeout--) { ret mdio_read(phy_addr, PHY_REG_BMCR, val); if (ret 0) return ret; if ((val BMCR_RESET) 0) break; usleep(1000); } if (timeout 0) return -1; /* 給PHY內(nèi)部狀態(tài)機(jī)一點(diǎn)時(shí)間 */ usleep(1000); return 0; }注意這里的usleep(1000)是給狀態(tài)機(jī)啟動(dòng)用的不同 PHY 可能有差異如果后面緊接著要配置自協(xié)商建議再加一個(gè) 10 ms 級(jí)別的延時(shí)或者輪詢 PHY 狀態(tài)寄存器確認(rèn)自協(xié)商已經(jīng)啟動(dòng)。5.3 復(fù)位 自協(xié)商 鏈路檢測(cè)的編排軟復(fù)位之后不能直接等 link需要重新配置自協(xié)商參數(shù)。推薦的初始化順序是讀取 PHY ID確認(rèn)訪問(wèn)對(duì)象正確。執(zhí)行軟復(fù)位等待自清零。寫(xiě) ANAR寄存器 0x04配置本端支持的速率和工作模式。寫(xiě) BMCR使能自協(xié)商并重啟自協(xié)商bit12 1bit9 1。輪詢基本狀態(tài)寄存器寄存器 0x01的 bit5自協(xié)商完成和 bit2link 狀態(tài)。很多驅(qū)動(dòng)把第 2 步和第 4 步混在一起先寫(xiě) BMCR 使能自協(xié)商再寫(xiě)軟復(fù)位結(jié)果復(fù)位把自協(xié)商使能又清掉了。順序上一定要先復(fù)位復(fù)位完成后再寫(xiě)配置。5.4 多 PHY 場(chǎng)景的復(fù)位策略一塊板子上有多個(gè) PHY 時(shí)盡量逐個(gè)復(fù)位、逐個(gè)確認(rèn)。同時(shí)對(duì)所有 PHY 發(fā)軟復(fù)位雖然省時(shí)間但多個(gè) PHY 同時(shí)重啟自協(xié)商可能帶來(lái)電源沖擊也可能讓 MDIO 總線出現(xiàn)短時(shí)間異常。逐個(gè)復(fù)位的代價(jià)是初始化時(shí)間變長(zhǎng)但如果把每個(gè)復(fù)位之間的等待時(shí)間優(yōu)化好實(shí)際影響很小。另外多 PHY 場(chǎng)景下一定要維護(hù)一張 PHY 地址映射表把“邏輯端口號(hào)”和“MDIO PHY 地址”對(duì)應(yīng)起來(lái)不要把 PHY 地址硬編碼散落在各處。調(diào)試時(shí)打印復(fù)位前后每個(gè) PHY 的狀態(tài)能省非常多時(shí)間。6. 調(diào)試軟復(fù)位的三條硬件經(jīng)驗(yàn)6.1 新板卡先測(cè)時(shí)鐘和復(fù)位釋放拿到新板卡的第一件事不是燒代碼而是用示波器測(cè)三樣?xùn)|西PHY 參考時(shí)鐘是否起振、頻率是否準(zhǔn)確、RST# 復(fù)位釋放時(shí)間是否滿足 datasheet 要求。這三樣如果沒(méi)問(wèn)題軟件層面的排查才有意義。我見(jiàn)過(guò)因?yàn)榫д裉摵笇?dǎo)致 PHY 軟復(fù)位不生效的也見(jiàn)過(guò) RST# 上拉電阻沒(méi)貼導(dǎo)致 PHY 一直處于復(fù)位狀態(tài)的。這些問(wèn)題單純靠軟件是發(fā)現(xiàn)不了的。6.2 寄存器快照改動(dòng)前先留證據(jù)調(diào)試 PHY 問(wèn)題時(shí)建議在每次改動(dòng)寄存器前把當(dāng)前 BMCR、ANAR、狀態(tài)寄存器的值打印出來(lái)。后面如果改了配置發(fā)現(xiàn)鏈路異??梢詫?duì)比快照判斷是哪個(gè)配置被覆蓋了。軟復(fù)位失效場(chǎng)景下快照尤其有用能確認(rèn)復(fù)位前后寄存器變化是否符合預(yù)期。6.3 把軟復(fù)位做成單獨(dú)調(diào)試接口不要把軟復(fù)位邏輯藏在完整初始化函數(shù)深處。把它做成一個(gè)獨(dú)立接口比如調(diào)試串口命令phy_reset addr這樣無(wú)論什么時(shí)候懷疑軟復(fù)位有問(wèn)題都能單獨(dú)觸發(fā)觀察結(jié)果。完整的初始化流程里調(diào)用這個(gè)接口最小化腳本里也調(diào)用這個(gè)接口調(diào)試一致性和復(fù)現(xiàn)路徑都會(huì)清晰很多。在實(shí)際項(xiàng)目里我最后都是靠這種分離式設(shè)計(jì)快速定位問(wèn)題的。一旦懷疑軟復(fù)位不生效先通過(guò)調(diào)試接口單獨(dú)復(fù)位如果單獨(dú)復(fù)位正常再回頭查驅(qū)動(dòng)初始化的上下文邏輯。6.4 軟復(fù)位失效但讀 ID 正常優(yōu)先查邏輯如果軟復(fù)位失敗但 PHY ID 能正常讀到那可以確定 MDIO 總線基本通路是好的問(wèn)題大概率在邏輯層地址配錯(cuò)、時(shí)序不夠、配置覆蓋。如果 PHY ID 都讀不到先別急著查軟復(fù)位總線電氣和硬件復(fù)位才是重點(diǎn)。這是我調(diào)試以來(lái)總結(jié)得最實(shí)用的一條排查順序ID 能讀查邏輯ID 不能讀查硬件。