第4章 PCIe接口连接故障排查(硬件识别/中断冲突)

PCIe接口,说白了就是个高速串行总线。但真出故障的时候,它可一点都不「高速」——你经常会被卡在设备不识别、驱动加载失败、或者莫名其妙的中断风暴里。

我做了这么多年HSM开发,PCIe相关的问题占了将近四成。硬件识别和中断冲突是两个最常见的坑。咱们一个一个讲。

4.1 硬件识别:设备为什么没出现?

先说硬件识别。PCIe设备插上去以后,系统怎么发现它?流程其实很清晰:

  1. 复位与链路训练——上下电后,Root Complex和Endpoint之间会自动做链路训练(LTSSM)。
  2. 配置空间枚举——BIOS或OS通过配置空间读写,给每个设备分配Bus/Device/Function号。
  3. 资源分配——BAR空间、MSI中断号等资源的分配与映射。

我记得有个项目,PCIe SSD盘怎么插都不认。lspci看不到,dmesg也没报错。物理链路看起来好好的,灯也亮。后来我用示波器卡了一下差分信号——发现接收端焊盘虚焊。所以我的习惯是:硬件识别出问题,先别急着查驱动,先确认链路训练是否成功。

检查链路状态的小技巧

在Linux下直接用 lspci -vvv 查看 LnkSta 字段。如果显示 Speed 2.5GT/s, Width x1 而不是你期望的 8GT/s, x4,那说明链路训练没跑到最高性能——可能信号完整性有问题。

实战命令清单

# 查看所有PCIe设备
lspci -nn

# 查看指定设备(例如00:01.0)的详细链路状态
lspci -s 00:01.0 -vvv | grep -i "lnk"

# 触发重新扫描(热插拔场景)
echo 1 > /sys/bus/pci/rescan

还有一个经典问题:设备端口挂在了错误的PCIe switch下面。你想想看,如果上游Switch的端口没有正确配置Downstream Port,甚至没上电,下游设备当然找不到。我会在系统起来前先对照原理图,确认物理slot对应的BDF号。

4.2 中断冲突:性能杀手与神秘崩溃

硬件识别过了,设备也枚举出来了,但一跑吞吐量测试就丢包,或者内核疯狂打印中断相关的错误——这一般就是中断冲突。

PCIe支持三种中断机制:INTx(传统边带信号)、MSI(消息信号中断)、MSI-X(扩展版)。现在基本都用MSI或MSI-X,因为它可以不共享中断线,每个设备有自己的中断向量。但如果你内核配置或者BIOS有问题,退回到INTx模式,那就可能和其他设备抢中断号。

我曾经遇到过一台服务器,插了两张HSM加速卡,结果只有一张能正常工作。 dmesg看到大量“irq xx: nobody cared”和“spurious interrupt”。查了一下午才发现,两张卡都要求分配同样的MSI-X向量,但BIOS没有给它们分配到不同的CPU核心组。解决办法是在kernel cmdline里加上pci=noacpi或者手动指定irq affinity。

中断类型对比表
特性 INTx MSI MSI-X
中断线数量 每个设备最多4条(共享) 最多32个向量 最多2048个向量
是否共享 是(容易冲突) 否(每个向量独立) 否(每个向量独立)
性能开销 高(需轮询ISR) 低 极低(支持每个向量单独Mask)
硬件支持 所有PCIe设备 PCI 2.2+ PCI 3.0+

注意:INTx共享中断的坑

如果你的设备工作在INTx模式(或者你手动降级了),而其他设备也用了同一条中断线,那么中断处理程序必须判断中断是否属于自己。如果两个设备同时触发中断,且优先级低的ISR返回IRQ_NONE,很容易产生“spurious interrupt”导致系统panic。

排查中断冲突的一般步骤:

  1. 查看 /proc/interrupts 确认每个中断号绑定了哪些设备。如果两个设备共享同一个IRQ号且都在使用INTx,就要小心了。
  2. 用 cat /proc/irq/<IRQ>/smp_affinity 看亲和性设置。建议把关键HSM设备的中断绑定到隔离的CPU核上。
  3. 如果设备支持MSI-X,检查驱动是否有参数强制启用(比如 module_param(msix_enable, int, 0))。

亲测有效的快速诊断命令

# 查看中断分布
watch -n 1 cat /proc/interrupts

# 查看PCIe设备当前使用的中断模式
lspci -s 03:00.0 -vvv | grep -i "msi"

# 手动更改中断亲和性
echo 2 > /proc/irq/92/smp_affinity

4.3 一张图看懂PCIe故障排查流程

我把硬件识别和中断冲突两部分的核心逻辑画成了流程图。遇到问题时,照着这个顺序走,基本能快速定位。

PCIe接口故障排查流程图 PCIe设备不工作 lspci能查到设备? (硬件识别检查) 否 链路训练失败 检查差分信号 供电/复位/焊盘 是 设备已枚举 检查BAR空间 确认驱动绑定 检查中断配置 /proc/interrupts + lspci -vvv 存在INTx共享中断? 或IRQ亲和性冲突 是 中断冲突 关闭INTx,强制启用MSI 或调整亲和性 否 驱动/性能优化 检查DMA地址映射 固件与驱动版本 每个步骤都要用示波器或逻辑分析仪交叉验证 不要只看软件日志

我个人用这张图处理过不下二十次PCIe故障。基本上,顺着左边走是硬件链路问题,右边是枚举和资源分配问题,中间的分支专门针对中断。你记住一个原则:先硬件再软件,先识别再中断。

避坑指南:我曾经被BIOS的PCIe ASPM坑过一次

那是在一个低功耗设计的板子上,BIOS默认开启了ASPM L1。结果HSM卡在休眠状态下,链路直接断开。驱动rescan根本没用,必须把设备下电再上电。后来我在kernel参数里加了 pcie_aspm=off 才解决。如果你发现设备间歇性丢失,先关掉ASPM试试。

4.4 实战案例:HSM加速卡中断风暴

最后讲一个真实案例。有一款HSM卡,插在PCIe x16槽位上,驱动加载后CPU软锁。我查了下/proc/interrupts,发现中断号95被两个设备共享:一个是板载网卡,另一个就是HSM卡。而且两者都工作在INTx模式。

为什么?因为该HSM卡的固件bug——它没有正确报告MSI能力。BIOS于是退回到INTx。又因为PCIe槽位与网卡同属一个PCI桥,中断线被路由到了同一根线上。

解决办法很简单:给HSM卡打一个驱动补丁,强制告知固件支持MSI。然后通过setpci把中断线重新分配。具体步骤:

# 查看设备当前中断线
lspci -s 05:00.0 -vvv | grep "Interrupt"

# 强制写中断线寄存器(需要先确认硬件设计)
setpci -s 05:00.0 INTERRUPT_LINE=0x0b

# 重启后检查中断共享情况
cat /proc/interrupts | grep "pci-msi"

嗯,这不算标准做法,但有时候应急只能用这种手段。当然,最终还是要联系固件厂商修复。

总之,PCIe故障排查说难也不难——链路训练、设备枚举、中断配置,这三层一层层剥开,大多数问题都能定位。养成看 lspci -vvv 和 /proc/interrupts 的习惯,能省下你大量抓头发的时间。