驅(qū)動調(diào)試:從MAC-PHY鏈路到工業(yè)協(xié)議優(yōu)化)
干過嵌入式驅(qū)動的人都有個共同體驗?zāi)玫揭粔K新板子最先想干的事就是讓網(wǎng)口亮起來。燈亮了串口能進shellping通上位機這塊板子才算活了。反過來說如果燈不亮、link up不上或者在end用戶態(tài)怎么都收不到包排查起來往往比點燈復(fù)雜得多——因為以太網(wǎng)驅(qū)動從來不是一個驅(qū)動而是一條鏈路控制器MAC、物理層芯片PHY、MDIO總線、設(shè)備樹/PCI枚舉、內(nèi)核協(xié)議棧任何一個環(huán)節(jié)掉鏈子表現(xiàn)就是網(wǎng)口有脾氣。這一期是嵌入式驅(qū)動開發(fā)經(jīng)驗系列的第10期主題是Ethernet以太網(wǎng)。我把這幾年在嵌入式Linux平臺上碰到的網(wǎng)口問題和處理思路理了一遍重點圍繞幾個高頻關(guān)鍵詞展開Intel I219-V這類板載千兆控制器的適配、VLAN配置、1G/2.5G PCS/PMA與SGMII這些MAC與PHY之間的鏈路細節(jié)以及面向EtherNet/IP等工業(yè)協(xié)議場景時底層驅(qū)動該做什么樣的取舍。內(nèi)容不一定高深但都是能直接拿去排查問題的思路。1. 一塊新板子來了先搞清楚網(wǎng)口這條鏈路上誰在干活1.1 MAC、PHY與MII總線驅(qū)動開發(fā)眼里的一條鏈很多新手拿到網(wǎng)口問題第一反應(yīng)是查驅(qū)動但驅(qū)動這兩個字在以太網(wǎng)場景里至少包含三層MAC控制器驅(qū)動、PHY芯片驅(qū)動、以及把兩者連起來的MII總線管理機制。這三個角色各管一段缺一不可。MAC是主控側(cè)的核心它負責(zé)把內(nèi)存里DMA ring上的數(shù)據(jù)包搬運到線路接口上同時處理幀的填充、CRC校驗、流控等底層邏輯。在嵌入式SoC里MAC通常已經(jīng)是片上資源比如Zynq、i.MX、RK系列都有自己的GEM/MAC外設(shè)。而PHY是芯片外部那個小顆粒負責(zé)把MAC送出來的數(shù)字比特流變成物理線纜上的電平信號同時承擔鏈路自協(xié)商、狀態(tài)檢測這些臟活。MIIMedia Independent Interface則是兩者間的語言規(guī)范常見的有RMII、RGMII、SGMII等不同接口決定了引腳數(shù)、時鐘方式和能跑的最高速率。驅(qū)動開發(fā)時要記住一個關(guān)鍵點MAC和PHY之間不是簡單的主從關(guān)系而是一條雙向協(xié)商通道。MAC側(cè)說我支持千兆PHY側(cè)說我可以千兆物理介質(zhì)上才能以千兆速率把鏈路拉起來。兩者任何一個撒謊結(jié)果就是表面上設(shè)備存在、驅(qū)動加載正常實際卻link down。1.2 從PCI/設(shè)備樹到net_device驅(qū)動掛載的順序理解驅(qū)動加載順序才能看懂dmesg里那些日志。以X86嵌入式平臺為例Intel I219-V這類網(wǎng)卡走PCIe總線內(nèi)核先在PCI枚舉階段發(fā)現(xiàn)設(shè)備然后由對應(yīng)驅(qū)動如e1000e的probe回調(diào)接管而在ARM/FPGA平臺上走的是設(shè)備樹描述MAC節(jié)點通過compatible屬性匹配到平臺驅(qū)動PHY掛在MDIO總線上由PHY驅(qū)動識別。無論哪種路徑最后都會走到同一個出口注冊net_device調(diào)用ndo_open此時MAC驅(qū)動會做三件事——配置MAC控制器的時鐘與復(fù)位、啟動DMA描述符環(huán)、然后觸發(fā)PHY的狀態(tài)機開始自協(xié)商。我習(xí)慣把這一步叫做三方握手PCI/設(shè)備樹只是把設(shè)備認出來了真正讓網(wǎng)口能用的是open時MAC和PHY之間的交互。1.3 第一步硬件摸底把設(shè)備挖出來拿到板子先別急著改代碼先把硬件身份確認了。X86平臺下最直接的是lspci -nn | grep -i ethernet lspci -vvv -s 00:1f.6第一行能告訴你設(shè)備編號第二行能看到LnkCap/LnkSta、中斷號、BAR空間以及EEPROM里固化的MAC地址。我遇到過不止一次批量板卡MAC地址全寫入同一個值的情況那就是工廠燒錄環(huán)節(jié)的問題跟驅(qū)動沒關(guān)系。如果是ARM平臺看設(shè)備樹里MAC節(jié)點的status狀態(tài)是不是okayPHY節(jié)點有沒有正確引用MDIO子節(jié)點。還有個小技巧解析設(shè)備樹時用dtc -I fs -O dts /proc/device-tree導(dǎo)出當前實際生效的設(shè)備樹比翻源碼里的dtsi靠譜得多——因為bootloader可能覆蓋了部分屬性。這一節(jié)的核心思路概括成一句話遇到網(wǎng)口問題先分清是哪一段在喊痛。PCI枚舉失敗是硬件識別問題net_device注冊了但link up不上是MAC-PHY協(xié)商問題鏈路速率正常卻丟包嚴重又是DMA和中斷配置的問題。方向錯了后面全是無用功。2. Intel I219-V 在嵌入式板卡上的適配不止是e1000e能用就行2.1 I219-V為什么出現(xiàn)在工業(yè)板卡Intel I219-V全稱Intel(R) Ethernet Connection (16) I219-V是一款集成在Intel芯片組里的千兆以太網(wǎng)控制器廣泛出現(xiàn)在各類x86工控板、嵌入式BOXPC和邊緣計算設(shè)備上。它沒有獨立PHY芯片而是通過板級線路直接把SerDes接到RJ45或內(nèi)部PHY驅(qū)動是內(nèi)核里經(jīng)典的e1000e。選它做工業(yè)板卡的理由很樸素生態(tài)成熟、CPU占用低、驅(qū)動常年穩(wěn)定。但穩(wěn)定不代表不需要適配。e1000e雖然主線上一直在維護具體到某個板卡廠商的定制設(shè)計仍然會出現(xiàn)EEPROM配置、LED極性、Wake-on-LAN等細節(jié)問題需要針對板卡做定制。2.2 加載e1000e驅(qū)動后第一步確認什么驅(qū)動加載成功后我一般依次看三樣?xùn)|西ethtool -i enp0s31f6 ethtool enp0s31f6 ethtool -k enp0s31f6ethtool -i確認driver名稱、固件版本和總線位置ethtool查看速率、自協(xié)商狀態(tài)、wake-on設(shè)置ethtool -k查offload特性重點看tx-checksumming、tcp-segmentation-offload、vlan-offload。I219-V對TSOTCP Segmentation Offload和VLAN offload支持都很好但有時板級設(shè)計有缺陷時需要臨時關(guān)掉這些特性后面會詳細說。還有一個容易被忽略的檢查項EEPROM里的MAC地址和PCI配置空間是否一致。通過ethtool -e enp0s31f6可以dump EEPROM內(nèi)容做備份。工業(yè)現(xiàn)場設(shè)備多運維經(jīng)常要按MAC地址管理資產(chǎn)如果出廠燒錄環(huán)節(jié)出了岔子驅(qū)動層面是無解的。2.3 VLAN配置的兩種路徑與offload陷阱給I219-V配VLAN大多數(shù)人第一反應(yīng)是用ip link命令ip link add link enp0s31f6 name enp0s31f6.100 type vlan id 100 ip link set enp0s31f6.100 up ip addr add 192.168.100.10/24 dev enp0s31f6.100這套命令本身沒問題但嵌入式工程師還要問一句VLAN tag的添加和剝離是在CPU里軟處理還是由網(wǎng)卡硬件完成如果e1000e啟用了VLAN offload默認開啟那收包時硬件已經(jīng)剝掉了tag內(nèi)核看到的skb不帶VLAN頭而是帶著VLAN_CFI/VLAN_VID元數(shù)據(jù)走專用路徑。這種情況下如果上層應(yīng)用直接抓包看raw socket會發(fā)現(xiàn)看不到VLAN標簽——這不是丟包而是硬件加速生效了。反過來如果遇到VLAN口收不到廣播報文、或者tcpdump看到重復(fù)的VLAN層多半是offload和軟件處理形成了矛盾。排查命令ethtool -k enp0s31f6 | grep vlan ip link show enp0s31f6.100實際項目里我會根據(jù)業(yè)務(wù)場景決定是否保留VLAN offload。單純跑ModbusTCP、EtherNet/IP這類普通業(yè)務(wù)流offload開不開影響不大但涉及工控報文深度解析、旁路抓包分析時建議先統(tǒng)一關(guān)掉再做測試避免排查時被硬件幫忙處理了這個隱藏因素干擾。2.4 性能調(diào)整隊列、中斷合并與功耗平衡I219-V雖然不像高端網(wǎng)卡那樣有幾十個隊列但它同樣支持多個DMA隊列和RSSReceive Side Scaling。在嵌入式Linux里可以通過ethtool -L調(diào)整隊列數(shù)量ethtool -L enp0s31f6 combined 1 ethtool -L enp0s31f6 combined 4隊列少好管理但多核平臺上高吞吐場景也會受限于單核中斷隊列多又可能讓緩存命中率下降。工業(yè)場景我通常先用combined 1保證邏輯簡單遇到吞吐瓶頸再逐步加大。中斷合并是另一個關(guān)鍵點。e1000e默認為了吞吐會盡量合并中斷但這會帶來不確定的延遲抖動。等會兒第5節(jié)講EtherNet/IP時會專門展開這里先記住一條命令ethtool -C enp0s31f6 rx-usecs 0 tx-usecs 0把合并關(guān)到最低延遲上來了但確定性變好。功耗方面ethtool --set-phy-tunable enp0s31f6 downshift 0這類參數(shù)在I219-V上不一定都支持更多是控制EEE節(jié)能以太網(wǎng)。工業(yè)現(xiàn)場我建議關(guān)掉EEE因為它會在鏈路空閑時進入低功耗模式有些交換機和遠端設(shè)備配合不好會導(dǎo)致鏈路偶發(fā)斷連。3. SGMII、PCS/PMA與1G/2.5G速率MAC和PHY之間沒說的線速秘密3.1 用快遞分揀理解SGMII的每秒1.25GRGMII用4對數(shù)據(jù)線并行傳千兆SGMII則把線減到1對差分線跑1.25Gbps的串行碼流。很多工程師第一次聽到千兆用1.25G串行線都覺得矛盾數(shù)據(jù)明明只有1G怎么線速是1.25G其實多出來的25%是8b/10b編碼開銷——每8bit數(shù)據(jù)編碼成10bit線上符號目的是保證直流平衡和時間同步。這就好比快遞分揀時為了確保包裹不丟不錯每個包裹都額外貼了一張帶校驗碼的面單面單本身也占體積。所以SGMII的本質(zhì)是把MAC數(shù)據(jù)流先做8b/10b編碼串行化后送出。這個編碼過程可以由MAC內(nèi)置完成也可以由外置的PCS/PMA芯片完成。在FPGA的以太網(wǎng)IP核里這就對應(yīng)最常見的配置選項1G/2.5G Ethernet PCS/PMA or SGMII。3.2 PCS/PMA在FPGA/SoC里的角色FPGA做以太網(wǎng)端口擴展時Xilinx現(xiàn)在叫AMD的1G/2.5G Ethernet PCS/PMA or SGMIIIP核是繞不開的。它承擔了PCS物理編碼子層和PMA物理介質(zhì)接入子層的功能內(nèi)部可以做SGMII接口、也可以直接做1000BASE-X/2500BASE-X光口模式。PCS負責(zé)編碼、自協(xié)商AN、鏈路狀態(tài)跟蹤PMA負責(zé)高速串行收發(fā)也就是說PMA直接對接板上的SerDes引腳。在Zynq平臺中常見的接法是PS端GEM通過SGMII接到片外PHY如Marvell 88E1512、Realtek RTL8211或者PL端以太網(wǎng)IP核通過SGMII/1000BASE-X接到PHY/光模塊。設(shè)備樹里對應(yīng)的描述是gem0 { phy-mode sgmii; phy-handle phy0; phy0: phy0 { reg 0; device_type ethernet-phy; }; };phy-mode驅(qū)動了驅(qū)動側(cè)對MAC接口模式的配置必須和實際硬件設(shè)計嚴格一致。這個屬性配錯了MAC側(cè)的數(shù)據(jù)收發(fā)時序就會完全錯位。3.3 速率協(xié)商失敗的經(jīng)典現(xiàn)場SGMII場景最典型的故障是PHY協(xié)商成100M甚至10M但MAC側(cè)還鎖在1000M。SGMII的自協(xié)商機制里MAC和PHY之間會傳遞速率信息可如果中間的PCS/PMA配置成了固定速率模式或者IP核把AN功能關(guān)閉了兩邊就不在同一個頻道上。我曾經(jīng)處理過一個板卡PHY用的是RTL8211MAC側(cè)是Zynq GEMphy-mode寫的是rgmii-id但硬件實際走的是SGMII結(jié)果Link Up后收包全是FCS錯誤。內(nèi)核日志里會反復(fù)刷macb f8008000.ethernet eth0: link up (1000Mbps/Full duplex) macb f8008000.ethernet eth0: link up (1000Mbps/Full duplex)看著是up了其實MAC在不停重傳。最后定位是設(shè)備樹接口模式和實際硬件不匹配。所以排查這類問題第一步永遠是檢查接口模式屬性與實際電路的對照關(guān)系比看寄存器快得多。3.4 2.5G速率模式別想當然2.5G Ethernet和SGMII之間很容易混淆。標準SGMII按協(xié)議只能跑10/100/1000M2.5G速率下接口形態(tài)雖然和SGMII很像但跑的是2500BASE-X沒有自協(xié)商機制鏈路兩端必須強制設(shè)定相同速率。很多工程師在FPGA里配置了2.5G PCS/PMA然后拿標準SGMII去對端對接結(jié)果駐留相位正確但雙方速率認知不一致鏈路始終無法起來。實際調(diào)試時要確認的清單很短但都很關(guān)鍵MAC側(cè)是否支持2.5G速率上報、PCS/PMA IP核是否選對了2.5G模式、對端交換端口是否強制2500M、線纜和連接器是否支持2.5G SerDes速率。漏掉任何一項就可能出現(xiàn)link指示燈亮但數(shù)據(jù)不通的假象。2.5G應(yīng)用我還有個建議先用iperf3做雙向打流確認實測速率不要只看ethtool顯示的協(xié)商速率——速率上報機制在非標模式下經(jīng)常是假的。4. 從dmesg和netlink日志挖出link up但ping不通的根因4.1 網(wǎng)口驅(qū)動初始化的時間線一個網(wǎng)口從驅(qū)動加載到能正常收發(fā)內(nèi)核日志里其實有條清晰的時間線。我處理這類問題時喜歡先把dmesg里所有相關(guān)日志粘出來標上序號[ 12.345] e1000e 0000:00:1f.6 eth0: (PCI Express:2.5GT/s:Width x1) [ 12.351] e1000e 0000:00:1f.6 eth0: MAC: 3, PHY: 7, PBA No: E5020-008 [ 12.357] e1000e 0000:00:1f.6 eth0: Intel(R) PRO/1000 Network Connection [ 12.363] e1000e 0000:00:1f.6 enp0s31f6: renamed from eth0 [ 12.789] IPv6: ADDRCONF(NETDEV_UP): enp0s31f6: link is not readylink is not ready出現(xiàn)在這里很正常因為此時IP層配置已經(jīng)完成但驅(qū)動還沒觸發(fā)PHY自協(xié)商。真正的link up日志要等ifconfig up之后才出現(xiàn)。很多誤報驅(qū)動掛了的板子其實只是日志看少了后面半段。4.2 PHY狀態(tài)機是怎么跑的內(nèi)核PHY子系統(tǒng)有一套標準狀態(tài)機背后對應(yīng)了一個workqueue周期調(diào)度。狀態(tài)機從PHY_DOWN開始open后進入PHY_UP然后向PHY_AN發(fā)起自協(xié)商協(xié)商完成進入PHY_RUNNING如果鏈路斷開會退回PHY_NOLINK并周期性重試。內(nèi)核里一旦PHY狀態(tài)變化會通過netlink發(fā)送RTM_NEWLINK最終反映到用戶態(tài)就是carrier的變化。調(diào)試的時候關(guān)注兩個位置/sys/class/net/enp0s31f6/carrier的值以及/sys/class/net/enp0s31f6/operstate。前者是物理鏈路是否通暢后者是協(xié)議棧對鏈路可用性的判斷。如果carrier1但operstatedown基本可以斷定是上層配置問題比如沒配IP、沒up如果carrier來回跳就是物理鏈路在閃斷。4.3 快速定位的四類現(xiàn)場根據(jù)經(jīng)驗link up但ping不通的問題可以歸成四類現(xiàn)象常見原因快速定位手段link反復(fù)up/downPHY復(fù)位設(shè)計不良、電源噪聲、交換端強制模式dmesg頻率統(tǒng)計換端口驗證link持續(xù)up但收不到包VLAN tag錯配、offload不匹配、MAC地址過濾tcpdump抓包看VLAN/源MAC收發(fā)都不通接口模式phy-mode與硬件不符檢查設(shè)備樹與原理圖只能小包通大包不通MTU不一致、TSO/GSO問題分別ping -s 1472和9000測試這里最想提醒的是MTU問題。嵌入式設(shè)備默認MTU是1500但如果對端交換機配置了巨型幀或VLAN透傳時MTU加了4字節(jié)就會出現(xiàn)小包通、大包卡的經(jīng)典故障。排查命令也很簡單ping -M do -s 1472和ping -M do -s 8000分別測一下再對比兩端MTU設(shè)置即可。4.4 rgmii-id與外部PHY的相位坑RGMII接口本身存在時鐘和數(shù)據(jù)線的相位對齊問題所以標準里定義了不同的延遲模式rgmii-id表示TX和RX都加內(nèi)部延遲rgmii-txid只加TX延遲rgmii-rxid只加RX延遲。很多人把這個屬性當成擺設(shè)實際在PHY芯片選型和PCB布線不同的情況下差的正是這一點延遲直接導(dǎo)致采到的數(shù)據(jù)全是錯位比特。處理建議是拿到一塊新板子如果網(wǎng)口link up但收包不正常先把設(shè)備樹里phy-mode從rgmii-id換成rgmii-rxid或rgmii-txid試試。有次我在一塊定制的AM335x板卡上排查默認rgmii-id下FCS錯誤率高達30%改成rgmii-txid后錯誤率直接歸零。這種問題光看寄存器很難看出名堂偏偏改一個設(shè)備樹字符串就能解決。5. 為EtherNet/IP這類工業(yè)協(xié)議服務(wù)的驅(qū)動特調(diào)5.1 工業(yè)協(xié)議對網(wǎng)卡驅(qū)動的三個隱性要求EtherNet/IP是基于標準以太網(wǎng)的工業(yè)協(xié)議CIP報文跑在TCP/UDP之上。很多人認為跑標準TCP/IP驅(qū)動不用特別改但真正部署產(chǎn)線時你會發(fā)現(xiàn)工業(yè)現(xiàn)場對底層驅(qū)動有超出能通之外的要求一是確定性延遲報文到達和發(fā)出的時間抖動要小二是穩(wěn)定優(yōu)先級排除中斷合并和驅(qū)動bug造成的偶發(fā)超時三是異常報文處理能力比如廣播風(fēng)暴下不能把CPU打滿。所以驅(qū)動層面的調(diào)優(yōu)思路和通用服務(wù)器不一樣。服務(wù)器追求高吞吐可以大開中斷合并工業(yè)實時場景則要壓低延遲抖動必要時犧牲一點吞吐。5.2 中斷合并關(guān)閉與NAPI先看中斷合并。e1000e驅(qū)動默認的rx-usecs不是0意味著收包后不會立刻觸發(fā)中斷而是攢一段時間或攢夠數(shù)量再上報。對EtherNet/IP這種要求循環(huán)周期穩(wěn)定的應(yīng)用這個攢的動作就是不確定性的根源。建議直接關(guān)ethtool -C enp0s31f6 rx-usecs 0 tx-usecs 0 rx-frames 1 tx-frames 1NAPI那邊不用特別關(guān)。內(nèi)核里NAPI的輪詢機制本來就是處理高吞吐的有效手段而且中斷NAPI的組合在工業(yè)場景下表現(xiàn)很好。重點是別在驅(qū)動里隨便加modprobe e1000e InterruptThrottleRate0這類全局參數(shù)I219-V的驅(qū)動參數(shù)雖然支持但全局禁用對性能和CPU占用影響很大不如針對單個接口用ethtool做精準控制。5.3 隊列、中斷親和性與CPU隔離當設(shè)備有多隊列可以把網(wǎng)卡中斷綁定到指定CPU核心避免中斷在各個核之間漂移。常見做法是查/proc/irq/下對應(yīng)中斷號寫到/proc/irq/N/smp_affinity。但注意嵌入式平臺的CPU核數(shù)少盲目綁定可能適得其反建議先壓測確認瓶頸確實在中斷漂移上再做綁定。如果是EtherNet/IP協(xié)議棧比如EtherNet/IP Scanner/Adapter部署在Linux用戶態(tài)還建議把網(wǎng)卡的協(xié)議處理CPU與實時控制CPU分開。Linux的isolcpus內(nèi)核參數(shù)可以隔離出專屬核配合sched_setaffinity把協(xié)議棧線程固定到非隔離核驅(qū)動中斷固定到另一個核這樣能有效降低協(xié)議棧線程被搶占的概率。5.4 驅(qū)動版本與PHY固件的別輕易動最后這條是經(jīng)驗教訓(xùn)。工業(yè)設(shè)備網(wǎng)卡驅(qū)動一旦穩(wěn)定不要因為內(nèi)核升級了順手更新一下就隨意換驅(qū)動版本。e1000e這類驅(qū)動在不同內(nèi)核版本上的行為差異不少尤其涉及PCIe電源管理、EEE、VLAN offload的部分。曾經(jīng)有客戶把內(nèi)核從4.19升到5.10I219-V網(wǎng)卡在低流量時出現(xiàn)偶發(fā)斷流最后定位是新的驅(qū)動默認開啟了EEE而板載PHY對端設(shè)備不支持正確的喚醒序列。解決方案不是打補丁而是關(guān)掉EEE參數(shù)。固件和EEPROM同理。ethtool -S里能看到tx_timeout、rx_errors等統(tǒng)計如果這些計數(shù)長期為0就不要去碰固件升級。網(wǎng)絡(luò)設(shè)備在產(chǎn)線上跑得好好的任何不必要的變更都是在引入風(fēng)險。給現(xiàn)場維護團隊的建議永遠是先備份當前驅(qū)動版本、固件版本和配置命令再考慮任何升級操作。這一期從驅(qū)動鏈路的基礎(chǔ)認知聊到具體芯片適配、接口速率、狀態(tài)機排錯再到工業(yè)協(xié)議場景下的驅(qū)動特調(diào)算是把嵌入式以太網(wǎng)驅(qū)動開發(fā)中容易出問題的幾塊硬骨頭都啃了一遍。如果讓我總結(jié)一條最實用的經(jīng)驗?zāi)蔷褪蔷W(wǎng)口故障別急著改代碼先沿著PCI/設(shè)備樹識別 → MAC-PHY協(xié)商 → 鏈路狀態(tài) → offload配置 → 協(xié)議棧行為這條鏈路逐層驗證絕大多數(shù)問題都能在半小時內(nèi)定位到具體環(huán)節(jié)。至于SGMII的速率陷阱、RGMII的相位細節(jié)、VLAN offload的隱性問題這些都是需要拿板子實測才能沉淀下來的經(jīng)驗光看芯片手冊遠遠不夠。