第4章 Linux下的两把利器:lspci 与 setpci

讲到配置空间读取,Linux 上最常用的就是 lspci 和 setpci。这两个命令我几乎每天都在用。一个负责看,一个负责改。配合起来,调试 PCIe 链路状态特别顺手。

一句话记住:lspci 是只读侦查兵,setpci 是外科手术刀。

4.1 lspci —— 读出配置空间的“眼睛”

lspci 默认会扫描所有 PCI/PCIe 设备,然后把配置空间头部信息吐出来。但默认输出太简略,想看到 Link Status,得加参数。

我个人习惯这样用:

# 列出所有设备,带上详细配置空间
lspci -vvv

# 只看某个 BDF 的设备
lspci -s 01:00.0 -vvv

# 用十六进制直接 dump 配置空间
lspci -s 01:00.0 -xxx

-vvv 会把 Capabilities 和 Extended Capabilities 都展开。你想想看,Link Status 藏在 PCIe Capability 的寄存器里,用这个参数一目了然。

我在项目中遇到过一块 NVMe 盘老是降速。直接跑 lspci -s 03:00.0 -vvv,在 "LnkSta:" 那一行清晰写着 "Speed 5GT/s, Width x2"。可这盘明明支持 8GT/s x4 啊!问题一下就锁定了——链路协商出了问题。

关键输出字段(LnkSta 行):
Speed 8GT/s —— 当前链路速率
Width x4 —— 当前链路宽度
(括号里是 Capable 值,比如 8GT/s, x4)

4.2 setpci —— 直接读写配置空间的“手”

lspci 只能看,不能改。setpci 就不一样了,它可以读写指定设备的配置空间寄存器。注意,修改配置空间可能让系统宕机,尤其是修改那些控制寄存器。我建议先在测试机上试。

基本用法:

# 读寄存器(十六进制)
setpci -s 01:00.0 CAP_ID.BYTE

# 写寄存器
setpci -s 01:00.0 CAP_ID.BYTE=0x01

这里的 CAP_ID 是 Capability 的偏移量或名称(比如 PCIe Capability 的 ID 是 0x10)。.BYTE 表示你要读/写几个字节(.B 一个字节,.W 两个字节,.L 四个字节)。

举个例子,想读 PCIe Capability 里的 Link Status 寄存器(通常偏移 0x12,2 字节):

setpci -s 01:00.0 CAP_EXP+0x12.W

不过呢,CAP_EXP 这个别名在不同发行版里可能不统一。我更喜欢直接用十六进制偏移:

# 先查 PCIe Capability 的指针位置(在配置空间 0x34 字节处)
setpci -s 01:00.0 0x34.B

# 假设得到 0x40,那么 Link Status 就在 0x40 + 0x12 = 0x52
setpci -s 01:00.0 0x52.W
危险操作:写 Link Control 寄存器(比如发链路复位)可能导致设备断开。我曾经在测试机上误操作,把 RAID 卡的链路复位了,结果系统直接 panic。生产环境慎用 setpci 写操作!

4.3 实战:抓出链路降速的根因

好,我们走一遍完整流程。假设服务器上有一张 GPU,感觉带宽不对。

  1. 定位设备:lspci | grep -i nvidia,拿到 BDF,比如 02:00.0。
  2. 看链路状态:lspci -s 02:00.0 -vvv | grep -i lnk。看到 “LnkSta: Speed 2.5GT/s, Width x1”。
  3. 对比能力:同一行括号里写着 “Capable: 8GT/s, Width x16”。说明链路协商只谈到了最低档。
  4. 检查链路训练:查看 Link Status 寄存器的第 11 位(Link Training),如果是 1 表示正在训练。使用 setpci -s 02:00.0 0x52.W,把结果换算成二进制,看 bit 11。
  5. 尝试复位:如果怀疑链路卡死,可以写 Link Control 寄存器(偏移 0x10)的 bit 5(Retrain Link)让链路重新训练。但先读出来,只改一个 bit:
    setpci -s 02:00.0 0x50.W # 读 Link Control
    setpci -s 02:00.0 0x50.W=0x0020  # 只置 bit 5,其他位不变
注意:很多服务器 BIOS 或 BMC 会监控链路状态,这样操作后链路重新训练,可能几秒后恢复正常速度。如果没恢复,多半是物理问题——金手指氧化、线缆松了、ReDriver 故障。

4.4 两个工具对比

特性lspcisetpci
默认读取范围配置空间头部 + 常用 Capability任意偏移(需手动指定)
写入能力无可写,有风险
数据格式解析好的文本(速度/宽度/状态)原始十六进制数字
最佳用途快速诊断、日常巡检深度调试、修改控制寄存器

说白了,日常排查先用 lspci 看,发现问题但想进一步操作,再上 setpci。我很少直接用 setpci 读 Link Status,因为 lspci -vvv 已经解析得很清楚。倒是写操作,比如“强制链路复位”或者“模拟热插拔事件”,setpci 才是唯一途径。

4.5 知识结构一览

下面这张图总结了本章的核心逻辑:

lspci 与 setpci 工具使用逻辑 Linux 配置空间读取工具流程 PCIe 设备 BDF lspci -vvv -s BDF setpci -s BDF 偏移.W 解析结果 Speed 8GT/s, Width x4 LnkSta 状态一目了然 原始十六进制 0x0052 → 0x0020 手动计算位域 定位链路状态/故障根因 先 lspci 侦查,再 setpci 手术,别搞反顺序
我的习惯:写脚本巡检时,先用 lspci 抓出所有设备的 LnkSta,记录到日志。如果某次巡检发现速度宽度变了,再用 setpci 读具体寄存器做深挖。这样既快又安全。

4.6 避坑指南

  • 大小端问题? PCIe 配置空间是小端字节序。setpci 读出来的数字,比如 0x1201,高位是偏移+1,低位是偏移+0。要看 Link Width 在 bit 20-23,其实对应寄存器的第 2 个字节。我刚开始搞混过,以为宽度是 1,结果是 4,因为字节颠倒了。
  • 权限不够:普通用户跑 lspci 没问题,但 setpci 需要 root。我曾经在调试脚本里忘了加 sudo,结果写操作静默失败,排查了半天。
  • 虚拟化环境:如果是 VM 直通设备,lspci 和 setpci 看到的配置空间是虚拟化软件(KVM/Xen)模拟的,不一定反映物理链路真实状态。得在宿主机上跑才准。
切记:setpci 写错一个 bit,轻则设备丢失,重则整个 PCIe 总线挂死。特别是写 Bridge Control 或 Device Control 寄存器时,先读后写,只改目标位,不要直接写死值。