戰(zhàn):PCIe拓?fù)浣馕雠c鏈路協(xié)商排查指南)
搞嵌入式或者服務(wù)器運(yùn)維的兄弟一定遇到過這種場景新買的PCIe固態(tài)硬盤插上去了系統(tǒng)沒識別GPU明明亮了燈但lspci里死活找不到或者更詭異的是設(shè)備在系統(tǒng)里能看到但跑著跑著就消失了。這種時候大部分人第一反應(yīng)是查驅(qū)動、看dmesg但往往忽略了最基礎(chǔ)也最有力的工具——lspci。這篇文章我就帶你從lspci出發(fā)把PCIe設(shè)備的拓?fù)浣Y(jié)構(gòu)一層層扒干凈搞清楚設(shè)備到底掛在哪條總線上、鏈路寬度和速率協(xié)商到什么狀態(tài)、以及當(dāng)設(shè)備“消失”時系統(tǒng)內(nèi)部到底發(fā)生了什么。這篇文章適合三類人看剛接觸Linux下PCIe設(shè)備調(diào)試的嵌入式工程師、做服務(wù)器硬件維護(hù)的運(yùn)維老哥、以及搞GPU/NVMe高性能計(jì)算平臺搭建的同學(xué)。我會從lspci最常見的幾個用法講起配合實(shí)際截圖場景把BDF號、總線編號、鏈路能力、Switch拓?fù)溥@些概念全部串起來??赐曛竽阍儆龅皆O(shè)備識別異常的問題至少能快速定位是“鏈路沒協(xié)商上”還是“配置空間讀不到”這兩個方向的排查路徑是完全不同的。1. 從一列輸出看透整棵PCIe設(shè)備樹1.1 先搞懂BDF號和PCIe總線編號規(guī)則lspci最基本的一條命令就是不帶任何參數(shù)直接跑輸出長這樣00:00.0 Host bridge: Intel Corporation Device 2020 00:01.0 PCI bridge: Intel Corporation Device 2021 01:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd Device a809每一行的最前面就是PCIe設(shè)備的“門牌號”格式是BB:DD.F也就是Bus:Device.Function。Bus總線編號范圍0-255由系統(tǒng)在枚舉時分配。Device設(shè)備號范圍0-31是設(shè)備在某個總線上的槽位編號。Function功能號范圍0-7同一個物理設(shè)備下最多可以拆出8個獨(dú)立功能。這里最有意思的點(diǎn)是PCIe總線編號不是物理上刻死的而是系統(tǒng)啟動時由固件或內(nèi)核動態(tài)分配的。你可能注意到CPU直連的PCIe控制器Root Complex分配的總線號是0然后它下面每掛一個PCIe Bridge或者Switch就會分到一段新的總線編號空間。比如上面那個例子00:01.0是一個PCIe橋PCI bridge它把總線往下一級擴(kuò)展分配到了bus 1然后是01:00.0這個NVMe盤掛在bus 1上。這個機(jī)制可以類比成快遞分撥中心Root Complex就是總倉它下面每個PCIe Switch或Bridge就是分撥點(diǎn)分撥點(diǎn)再往下就是一個個末端網(wǎng)點(diǎn)。lspci的職責(zé)就是把整個分撥網(wǎng)絡(luò)的分層關(guān)系完整打印出來讓你一眼看出哪些設(shè)備掛在哪個分撥點(diǎn)下。這套編號規(guī)則理解透了你就能回答一個高頻問題——“為什么我的GPU是03:00.0而固態(tài)硬盤是01:00.0中間隔著的02:00.0去哪了”答案往往很簡單02:00.0可能是一個沒有掛任何設(shè)備的PCIe Switch下游端口系統(tǒng)給這一段總線分配了編號但實(shí)際是空槽所以lspci默認(rèn)不打印它。1.2 -tv參數(shù)是把“樹形”拓?fù)浯虺鰜淼年P(guān)鍵單純的列表模式只能告訴你有哪些設(shè)備看不出它們之間的父子關(guān)系。這時候要上lspci最核心的拓?fù)鋮?shù)lspci -tv輸出結(jié)構(gòu)大致如下-[0000:00]--00.0 Intel Corporation Device 2020 -01.0-[01-02]----00.0 Samsung Electronics Co Ltd Device a809 -02.0-[03]----00.0 NVIDIA Corporation Device 2230 \-03.0-[04-05]----00.0 Intel Corporation Device 3702看到那個[01-02]了嗎這是lspci -tv最有價值的信息之一。中括號里表示的是這一段PCIe橋管理下的總線編號范圍。比如01.0這個PCIe橋它管理bus 1到bus 2bus 2下面掛的是NVMe盤02.0這個橋管理bus 3bus 3下面掛GPU。這個輸出的縮進(jìn)層級就是設(shè)備在物理拓?fù)渲械恼鎸?shí)掛載關(guān)系??s進(jìn)越深離CPU越遠(yuǎn)。搞GPU服務(wù)器的人常用這個命令看一眼就知道這張GPU是直連CPU還是掛在PCIe Switch下面。如果是掛在Switch下面那還涉及Switch上下行端口的帶寬共享問題后面我會展開講。還有一個參數(shù)是-PP可以顯示橋的二級總線號secondary bus和從屬總線號subordinate bus配合-tv用能更清晰地看總線空間分配lspci -tvPP建議習(xí)慣性寫成lspci -tvPP因?yàn)槟J(rèn)的-t只畫了拓?fù)溥壿?PP會把每個橋的總線編號范圍標(biāo)得更清楚對理解總線段落劃分非常有幫助。2. 用lspci識別設(shè)備類型和Link狀態(tài)2.1 從class code看懂設(shè)備是干什么的lspci輸出中間那段字符串比如“Non-Volatile memory controller”、“VGA compatible controller”就是設(shè)備的類別名。這個類別由配置空間里的Class Code字段決定內(nèi)核會把它翻譯成人能讀懂的字符串。這個字段在排查時極其有用。比如你在一個嵌入式板卡上插了個PCIe設(shè)備但設(shè)備本身沒有固件、沒有驅(qū)動lspci很可能打出來一串mmo的“Device 1234”因?yàn)閺S商名和型號在數(shù)據(jù)庫里查不到。但只要類別能識別出來比如“Ethernet controller”或者“SATA controller”基本就能確認(rèn)設(shè)備默認(rèn)工作狀態(tài)是正常的只是缺少驅(qū)動或ID數(shù)據(jù)庫。如果你看到“PCI bridge”這個類別意味著這個設(shè)備是一個橋或者Switch它可以繼續(xù)擴(kuò)展下級總線。而“Host bridge”則說明這是CPU內(nèi)部的Root Complex組件一般不是獨(dú)立物理設(shè)備而是代表CPU內(nèi)部的總線入口。查看更詳細(xì)的類別信息可以用lspci -nn輸出會帶上PCI vendor ID和device ID的十六進(jìn)制編碼比如01:00.0 Non-Volatile memory controller [0108]: Samsung Electronics Co Ltd Device [144d:a809]方括號里前4位是廠商IDvendor ID后4位是設(shè)備IDdevice ID。這兩個ID是設(shè)備驅(qū)動匹配的核心依據(jù)驅(qū)動通過ID table來決定是否綁定這個設(shè)備。嵌入式開發(fā)中如果你的PCIe設(shè)備在Linux下不識別第一步就是確認(rèn)lspci -nn讀到的ID和硬件設(shè)計(jì)文檔里的注冊ID是否一致。ID對不上說明固件側(cè)的配置空間初始化有問題驅(qū)動再怎么寫都白搭。2.2 用-vvv抓取鏈路協(xié)商狀態(tài)lspci最強(qiáng)大的一個隱藏技能是-vvv它會把每個PCIe設(shè)備配置空間的關(guān)鍵寄存器全部dump出來。真正排查鏈路問題時我最常用的是看這幾段lspci -vvv -s 01:00.0重點(diǎn)關(guān)注LnkCapLink Capability和LnkStaLink Status。一段典型輸出如下LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM not supported LnkSta: Speed 8GT/s, Width x4這里的Speed就是當(dāng)前協(xié)商到的PCIe速率代際常見的對應(yīng)關(guān)系是2.5GT/s PCIe Gen15GT/s PCIe Gen28GT/s PCIe Gen316GT/s PCIe Gen432GT/s PCIe Gen5Width是協(xié)商到的通道數(shù)Lane數(shù)常見x1、x4、x8、x16。LnkCap顯示的是設(shè)備端和端口本身能支持的最大能力LnkSta顯示的是上電或復(fù)位后實(shí)際協(xié)商出來的工作狀態(tài)。這兩個值一旦對不上就能發(fā)現(xiàn)很多隱性問題。舉個例子我遇到過一塊PCIe Gen3 x4的NVMe盤LnkCap里寫的是“Speed 8GT/s, Width x4”但LnkSta卻是“Speed 5GT/s, Width x1”。這說明鏈路協(xié)商失敗了實(shí)際只跑在Gen2 x1性能損失慘重。導(dǎo)致這個現(xiàn)象的原因包括金手指接觸不良、插槽物理磨損、PCB布線過長導(dǎo)致信號質(zhì)量差甚至是CPU的PCIe控制器本身端口損壞。當(dāng)你看到LnkSta比LnkCap低一個檔次的時候第一個動作就是把設(shè)備拔下來重新插一次很多時候金手指接觸問題導(dǎo)致降速的概率非常高。還要重點(diǎn)看這段LnkSta2: Current De-emphasis Level: -6dB, Equalization Complete, Equalization Phase 3PCIe Gen3以上的鏈路協(xié)商依賴均衡Equalization機(jī)制。如果看到“Equalization Complete”說明訓(xùn)練成功如果停留在Phase 1或者Phase 2說明鏈路訓(xùn)練卡在了信號調(diào)優(yōu)階段大概率是鏈路信號質(zhì)量差或?qū)Χ嗽O(shè)備兼容性不佳。2.3 用-s定位特定設(shè)備并查鏈路上下游-s參數(shù)可以按BDF號過濾只查看某一個設(shè)備的詳細(xì)信息。但要注意一個細(xì)節(jié)-s支持的格式很靈活可以只填總線號、只填設(shè)備號也可以填完整BDF。常見的寫法lspci -s 00:01.0 -vvv # 查看橋設(shè)備 lspci -s 01:00.0 -vvv # 查看末端設(shè)備排查某個設(shè)備鏈路異常時習(xí)慣做法是把上游橋和下游設(shè)備都打一遍。為什么要這樣做因?yàn)镻CIe鏈路是由兩端端口共同組成的上游端口比如CPU Root Port或Switch上游端口在拓?fù)渲械慕巧珱Q定了它是否支持帶寬擴(kuò)展、方向反轉(zhuǎn)等特性。如果上游端口自身的能力不足下游設(shè)備再強(qiáng)也只能遷就低版本。舉個典型例子一塊PCIe Gen4的GPU插在主板上但主板的物理插槽實(shí)際由某顆PCIe Gen3的Switch擴(kuò)展出來。這時lspci -s 03:00.0 -vvv看GPU本身LnkCap能顯示Gen4能力但LnkSta會顯示協(xié)商到Gen3。這類問題不是因?yàn)樵O(shè)備壞了而是拓?fù)浔旧硐拗屏随溌匪俾蕦儆凇安邋e位置”導(dǎo)致的性能瓶頸。用lspci把每個橋的LnkCap打出來拓?fù)渲械亩贪逡荒苛巳弧?. 實(shí)戰(zhàn)從lspci輸出構(gòu)建完整拓?fù)鋱D的步驟3.1 用sysfs配合lspci確認(rèn)設(shè)備父子關(guān)系雖然lspci -tv已經(jīng)給了我們樹形圖但它本質(zhì)上是從內(nèi)核的PCI總線結(jié)構(gòu)里讀取的并不是讀取了硬件上的物理連線。為了完全確認(rèn)設(shè)備之間的父子關(guān)系可以結(jié)合sysfs來驗(yàn)證ls /sys/bus/pci/devices/0000:01:00.0/sysfs里會暴露幾個關(guān)鍵目錄和文件parent指向該設(shè)備的父級設(shè)備通常是PCIe Bridge端口subordinate僅橋設(shè)備才有表示它管理下的最大總線號driver設(shè)備當(dāng)前綁定的驅(qū)動config配置空間的二進(jìn)制鏡像相當(dāng)于lspci -xxx的數(shù)據(jù)源current_link_speed、current_link_width當(dāng)前鏈路協(xié)商的速率和寬度和lspci -vvv里L(fēng)nkSta一致最實(shí)用的一個驗(yàn)證方法是cat /sys/bus/pci/devices/0000:01:00.0/current_link_speed cat /sys/bus/pci/devices/0000:01:00.0/current_link_width這兩個文件是實(shí)時反映鏈路狀態(tài)的比lspci重新加載配置空間更直接。在排查鏈路降速問題時我習(xí)慣先用這兩個文件做快速判斷再用lspci -vvv對比兩端的LnkCap和LnkSta。另外一個sysfs里的隱藏幫手是/sys/kernel/debug/pci下的調(diào)試信息需要掛載debugfsmount -t debugfs none /sys/kernel/debug cat /sys/kernel/debug/pci/0000:01:00.0/device這里能看到lspci不直接輸出的更底層信息包括ASPM狀態(tài)、TR檢錯機(jī)制、可選的Completion Timeout設(shè)置等。嵌入式平臺調(diào)試早期這個目錄的價值非常明顯。3.2 手把手一張帶鏈路速率的完整拓?fù)浔韺?shí)際交付或者寫排查報(bào)告時我不會只給一張lspci -tv截圖因?yàn)檫@不夠直觀。我建議你按這個步驟做一張“帶鏈路狀態(tài)的拓?fù)浔怼钡谝徊较茸ピ纪負(fù)鋖spci -tvPP第二步逐個抓關(guān)鍵設(shè)備鏈路狀態(tài)for d in $(lspci | awk {print $1}); do echo $d lspci -s $d -vv | grep -E LnkCap|LnkSta done在服務(wù)器終端上跑這個循環(huán)不要加-vvv用-vv就夠了加上-grep過濾輸出速度會快很多幾百個設(shè)備也不會太卡。第三步把每個設(shè)備的Speed和Width填進(jìn)表格。以一臺典型雙路服務(wù)器為例整理出來的表格大概長這樣設(shè)備BDF設(shè)備類型上游橋當(dāng)前速率當(dāng)前通道數(shù)最大能力00:01.0PCIe Bridge (CPU Root Port)-8GT/sx168GT/s x1601:00.0NVMe SSD00:01.08GT/sx48GT/s x401:02.0PCIe Switch上游端口00:01.08GT/sx168GT/s x1602:00.0下游端口101:02.08GT/sx88GT/s x802:01.0下游端口201:02.08GT/sx88GT/s x8第三步的關(guān)鍵點(diǎn)在于判斷瓶頸。如果上游橋是x16的通道數(shù)下游設(shè)備全是x4的盤那確實(shí)沒有瓶頸問題但如果下游掛的是兩塊GPU每一塊都要x16而Switch下游端口只提供x8那么每塊GPU只能獲得x8的帶寬。這就是拓?fù)鋵用鎸?dǎo)致的帶寬損失任何驅(qū)動優(yōu)化都改變不了。這類情況在帶PCIe Switch的高密度GPU服務(wù)器上非常常見。設(shè)計(jì)者為了讓更多設(shè)備插入同一個CPU犧牲了單設(shè)備的通道數(shù)換來的是更多設(shè)備數(shù)量。用lspci的輸出去反推物理拓?fù)湓O(shè)計(jì)意圖是硬件工程師和系統(tǒng)運(yùn)維對齊信息的高效手段。3.3 用setpci讀配置空間看更多l(xiāng)spci不給的信息lspci已經(jīng)做了很多解析工作但有些原始寄存器值還是要靠setpci直接讀配置空間才能看到。比如想確認(rèn)某個設(shè)備的廠商ID是不是真的setpci -s 01:00.0 0x00.w讀出來的四位十六進(jìn)制就是Vendor ID。0x00.w表示從配置空間偏移0x00開始讀一個word2字節(jié)。同理setpci -s 01:00.0 0x04.w這個讀的是Device ID。可能你會問lspci -nn不是已經(jīng)顯示了嗎為什么還要setpci原因有兩個第一lspci依賴內(nèi)核的PCI子系統(tǒng)初始化完成如果設(shè)備在枚舉階段就出問題lspci輸出里可能完全不顯示但setpci只要總線號、設(shè)備號、功能號存在就能強(qiáng)行去讀配置空間不受驅(qū)動綁定影響。這在設(shè)備“枚舉失敗”但“物理鏈路存在”的排查場景下極為有用。第二lspci -xxx雖然能dump整個配置空間頭部但我想快速確認(rèn)某一位寄存器比如Link Control里的ASPM開啟位時setpci更精準(zhǔn)、更容易腳本化。舉個例子LnkCap在配置空間里的偏移地址一般是0x0cPCIe Capability結(jié)構(gòu)里但不同設(shè)備Capability的起始偏移不同不能盲讀。好在lspci -vvv已經(jīng)把這些信息解析成了可讀文本日常debug優(yōu)先用lspci。只有遇到lspci也解析不出、或者需要下修寄存器測試某種狀態(tài)時setpci才該出場。4. 場景實(shí)戰(zhàn)從拓?fù)涞焦收夏切﹍spci能幫你定位的事4.1 設(shè)備“掉卡”時怎么看拓?fù)浜腿罩竞芏喔鉇I服務(wù)器的人對“掉卡”這個詞不陌生。GPU跑著跑著從系統(tǒng)里消失lspci一開始能看到后來看不到了或者nvtop、nvidia-smi都找不到卡然后必須重啟才能恢復(fù)。這個問題在PCIe層面往往表現(xiàn)為鏈路不穩(wěn)定導(dǎo)致Surprise Down或Link Down事件。遇到這種時候第一步永遠(yuǎn)是dmesg | grep -i pcie典型錯誤碼包括PCIe Bus Error: severityCorrectedPCIe Bus Error: severityUncorrectedAER: Corrected error received: id01:00.0nvme ... link is down然后lspci就該派上用場了。如果設(shè)備BDF還在但鏈路狀態(tài)是downlspci -vvv的LnkSta區(qū)域會顯示Link Status為down。如果BDF整個消失說明上游橋已經(jīng)把這個設(shè)備從總線枚舉空間里移除了這是比鏈路down更嚴(yán)重的事件通常代表物理鏈路已經(jīng)高過閾值進(jìn)入了“Link Down”不可恢復(fù)狀態(tài)。定位是哪個橋出的問題看lspci -tv就夠了消失的設(shè)備和它的上游橋的從屬總線范圍一對比就能知道是哪一段物理鏈路斷掉了。比如GPU掛在03:00.0它上游是00:01.0這條Root Port管理bus 3。如果lspci里00:01.0還在但03:00.0沒了那問題就發(fā)生在00:01.0 - GPU之間范圍縮小到一根物理插槽或RISER板。這類問題的物理排查手段通常是重新插拔、換RISER槽位、清潔金手指、檢查主板/轉(zhuǎn)接線的信號完整性。lspci幫你劃定了排查范圍剩下的電工活兒就得手動干了。4.2 用lspci輔助判斷熱插拔問題熱插拔這個詞這兩年越來越熱PCIe熱插拔Hot-Plug在服務(wù)器里是標(biāo)準(zhǔn)能力。但嵌入式設(shè)備上要啟用熱插拔功能主控側(cè)需要Root Port支持Hot-Plug能力下游設(shè)備還需要有對應(yīng)的Presence Detect機(jī)制。當(dāng)熱插拔表現(xiàn)異常時lspci同樣能扮演關(guān)鍵角色。熱插拔的設(shè)備在拔出后該設(shè)備BDF應(yīng)該從lspci列表里消失同時上游橋的總線范圍會收縮重新插入后又會重新枚舉、重新分配BDF。如果拔掉設(shè)備后lspci里還能看到這個BDF大概率是Slot的Presence Detect管腳信號異常或Hot-Plug控制器驅(qū)動沒正確響應(yīng)。另外熱插拔場景下最容易遇到的是設(shè)備重新枚舉后總線號變了。比如第一次插入是04:00.0拔掉再插變成06:00.0。這本身是正常的不表示硬件出錯但如果你的軟件配置通過BDF號硬編碼綁定了設(shè)備就會出現(xiàn)找不到設(shè)備的情況。正確做法是用設(shè)備的廠商ID設(shè)備IDsocket物理位置聯(lián)合定位而不是固定寫死BDF。用lspci驗(yàn)證熱插拔是否恢復(fù)正常只要看鏈路協(xié)商結(jié)果lspci -vvv -s 06:00.0 | grep LnkSta如果LnkSta速度和寬度都恢復(fù)到了預(yù)期值熱插拔就算成功了。4.3 帶寬和穩(wěn)定性兼容性問題的lspci觀察法熱搜詞里反復(fù)出現(xiàn)“PCIe穩(wěn)定性/兼容性問題”這類問題的共性是設(shè)備能被識別但高負(fù)載下出錯率飆升或者性能遠(yuǎn)低于標(biāo)稱。lspci查LnkSta是一種靜態(tài)判斷還有一種動態(tài)判斷方法就是跑負(fù)載的同時反復(fù)讀取current_link_speed和current_link_width。在腳本里可以這樣while true; do cat /sys/bus/pci/devices/0000:03:00.0/current_link_speed cat /sys/bus/pci/devices/0000:03:00.0/current_link_width sleep 1 done配合FIO壓測或GPU算力壓測如果鏈路速率在壓力下掉到Gen1或者寬度減半說明鏈路的信號裕量不足觸發(fā)了PCIe的降速/降寬度機(jī)制。這種情況下lspci成為穩(wěn)定性監(jiān)視器比你抓AER日志要直觀得多。另一種兼容性問題典型現(xiàn)象是板卡插到A機(jī)器能識別插到B機(jī)器就枚舉失敗。這種時候把兩臺機(jī)器各自的lspci -tv和LnkCap打出來對比重點(diǎn)看鏈路兩端共同支持的速率代際有沒有交集。如果一方只支持到Gen4另一方最高只支持Gen3雙方協(xié)商的公共交集就是Gen3理論上不該出現(xiàn)枚舉失敗。一旦枚舉失敗多數(shù)不是速率協(xié)商的問題而是Initialization Pattern、參考時鐘Refclk格式、或者邊帶信號PERST、CLKREQ的時序不匹配。lspci能告訴你結(jié)果不對但要靠硬件設(shè)計(jì)文檔去推斷為什么不對。5. lspci排錯中容易忽略的細(xì)節(jié)與腳本技巧5.1 設(shè)備列表里的bridge設(shè)備別當(dāng)成無關(guān)信息很多朋友看lspci列表注意力都在GPU和NVMe盤上橋設(shè)備直接跳過。這其實(shí)是個壞習(xí)慣。橋設(shè)備的LnkSta和LnkCap決定了它下游所有設(shè)備的帶寬上限甚至能反映整條鏈路的熱狀態(tài)。比如CPU Root Port的LnkCap如果是x16但某個下游Switch上游端口的LnkSta協(xié)商成了x8這塊Switch下面所有設(shè)備的總帶寬直接少了一半。橋設(shè)備的問題還會成片影響——一個Switch帶8個NVMe盤如果它的協(xié)議轉(zhuǎn)換或內(nèi)部仲裁邏輯出故障8個盤都跑不動單看某一個盤的lspci完全找不到原因。先看上游橋狀態(tài)往往能幫你少走很多彎路。5.2 寫個一鍵腳本一鍵打印全鏈路拓?fù)浜蛥f(xié)商狀態(tài)配合嵌入式產(chǎn)品量產(chǎn)時的產(chǎn)測需求我通常會把lspci的檢查流程固化成腳本便于產(chǎn)線工位快速校驗(yàn)#!/bin/bash # 打印所有PCIe設(shè)備的BDF、類型、速率、寬度 for d in $(lspci -D | awk {print $1}); do speed$(cat /sys/bus/pci/devices/$d/current_link_speed 2/dev/null || echo N/A) width$(cat /sys/bus/pci/devices/$d/current_link_width 2/dev/null || echo N/A) desc$(lspci -s $d | cut -d -f2-) echo $d | $desc | $speed | $width done這個腳本在產(chǎn)測里的思路是固定型號的主板和CPU只要lspci輸出的設(shè)備數(shù)量和每個BDF的協(xié)商速率都在預(yù)期范圍內(nèi)就說明PCIe樹建立是健康的。比單純看設(shè)備能不能起來要多一層驗(yàn)證。5.3 別忽視ACPI和電源狀態(tài)對lspci輸出結(jié)果的影響最后一個容易被忽略的點(diǎn)是PCIe設(shè)備電源狀態(tài)。設(shè)備處于D3冷狀態(tài)時lspci -vvv里的LnkSta往往顯示的是協(xié)商前的狀態(tài)甚至可能讀不到完整的LnkCap。有些設(shè)備在睡眠后重新喚醒鏈路協(xié)商會重新進(jìn)行速率可能和冷啟動時的結(jié)果不同。所以對比兩套系統(tǒng)之間的lspci輸出時要確保雙方設(shè)備都處于同一電源狀態(tài)最好都在D0狀態(tài)、且沒有ASPM在中間搗亂。否則你查到的差異可能不是硬件性能差異而是電源狀態(tài)差異帶來的假象。調(diào)試中我經(jīng)常用這個命令強(qiáng)制設(shè)備回到D0echo 0 /sys/bus/pci/devices/0000:03:00.0/power/control然后重新讀lspci。如果鏈路狀態(tài)恢復(fù)正常說明整套電源管理和鏈路訓(xùn)練配合良好如果還是不行再去查硬件信號也不遲。這一步成本幾乎為零但能過濾掉大量“因?yàn)殡娫礌顟B(tài)沒到位導(dǎo)致的誤報(bào)”。文章寫到這里核心的lspci拓?fù)浣馕龇椒ㄒ菜阒v得比較全了。我自己在實(shí)際調(diào)試中最大的體會是遇到PCIe疑難問題時一定要先問自己兩個問題——這個設(shè)備在總線上到底存不存在枚舉問題以及它當(dāng)前鏈路協(xié)商到了什么狀態(tài)鏈路問題。前者看lspci -tv的樹形結(jié)構(gòu)后者看lspci -vvv的LnkCap/LnkSta。把這兩個問題搞清楚了排錯范圍就縮小了一大半。最后再分享一個小經(jīng)驗(yàn)做嵌入式板卡調(diào)試時建議在開機(jī)階段就用lspci -vvv把每塊板卡的鏈路信息保存一份歸檔一旦后續(xù)現(xiàn)場出了問題和歸檔數(shù)據(jù)一對比是硬件老化還是批次性物料問題基本就能看出方向了。