48 lane PCIe Switch多根虚拟化与NT地址翻译排障
简介:《PEX8749.pdf》是 PLX Technology 推出的 PEX8749-BA/CA 系列 PCIe Gen 3 多根交换机数据手册,面向硬件设计、驱动开发与系统架构人员,用于解决高速 I/O 扩展、多主机独立访问及 DMA 卸载等设计问题。压缩包内仅含 1 个 PDF 文件,大小约 18.06MB,便于直接检索查阅芯片规格、寄存器与配置信息。目前已有 1747 人学习下载,可见其在 PCIe 交换芯片选型与开发中具有一定参考价值。手册围绕 18 端口多根交换架构展开,涵盖 Gen 3 链路特性、DMA 机制、端口与 lane 划分、寄存器偏移及修订历史等内容,可帮助读者理解多主机共享外设时的地址映射与访问隔离,并为板级设计、驱动适配和调试排错提供原始依据,适合作为 PCIe Gen 3 交换机相关项目的案头参考文档。
1. 一块标称 18 个端口的 PCIe Switch,出厂时其实一个端口都没有
翻开这块芯片的手册,第一眼看到的结论是反直觉的:48 条 lane、18 个端口、PCI Express Gen 3,但这些端口并不是在芯片上画好的 18 个固定盒子。芯片真正有的是 48 条 SerDes 通道、两个 Station(StN0 与 StN1),以及一张由 strap 引脚和串行 EEPROM 共同决定的配置表。端口是配置出来的,不是焊出来的。
这个前提决定了后面所有行为的解释方式。为什么 Virtual Switch 能在一颗芯片里跑出多套独立的 PCIe 层级、为什么 NT 端口要做地址翻译、为什么 DMA 引擎必须知道自己在哪个虚拟交换机里工作——全都源于“端口、lane、Station 三者是可重组的逻辑映射”这一设计。把 PCIe Switch 当成一个大号 hub 来理解,在 Gen3 速率和多根场景下会一路踩坑到底。
2. 端口切分:18 个端口怎么从 48 条 lane 里长出来
2.1 Station、Port、Lane 的三层映射与物理约束
从下往上数,物理层是 48 条 lane;往上一层是 Station,48 条 lane 被平分成两个 24 条 lane 的集合,也就是手册里反复出现的 StN0、StN1;再往上才是 Port,一个 Port 占用 1 到 16 条连续 lane。手册里那句容易被跳过的说明——“ECC 错误通过包含该 Station 的 Port 0 的虚拟交换机所关联的 VSx_FATAL_ERR# 和/或 VSx_PEX_INTA# 上报”——其实暴露了 Station 的结构:每个 Station 内部有一个编号为 0 的端口,它在错误归属和虚拟交换机绑定上有特殊地位。
关键在于这套映射不是任意的。手册 v1.2 的修订记录里明确写了“Removed Port configurations 0 and 7”,意思是端口配置是靠一组预定义编码来选择的,而不是让软件自由地把 24 条 lane 切成任意组合。原因不难推:crossbar 的走线是固定的,SerDes 与端口的复用关系在硅片阶段就锁死了,能做的只是从有限的几套方案里挑一套。
常见的实现思路是把每个 Station 的 24 条 lane 切成几种典型形态,下面这张表是这类多端口交换机最常见的切法,落到具体型号时以 EEPROM 或 strap 支持的配置编码为准:
| 切法 | 占用 lane | 典型用途 |
|---|---|---|
| x16 + x8 | 24 | 一个上行宽链路 + 一个下行设备 |
| x8 + x8 + x8 | 24 | 三个中带宽端点 |
| x4 × 6 | 24 | 多设备扇出,适合网卡/加速卡阵列 |
| x4 × 4 + x8 | 24 | 混合:多低速设备 + 一条宽链路 |
这张表的意义在于:端口数量不是配置目标,带宽分配才是。18 个端口的上限只在你愿意把 lane 切到 x1、x2 时才可能接近,而 x1 的 Gen3 链路在多数存储和网络场景里根本喂不饱设备。
2.2 端口宽度之外:Gen3 的编码账本与带宽推算
很多人第一次算这块芯片的带宽会算错,因为习惯用“速率 × lane 数”直接乘。Gen1 和 Gen2 用 8b/10b 编码,每 10 个 bit 里有 2 个是开销,效率 80%;Gen3 换成了 128b/130b,每 130 个 bit 里只有 2 个是同步头,效率 89.2% 左右。这个变化让 Gen3 相对 Gen2 的实际提升接近 1.97 倍,而不是账面速率的 1.6 倍(5 GT/s 到 8 GT/s)。
把账算清楚:单条 Gen3 lane 的理论有效带宽是 8 GT/s × 128/130 ÷ 8 ≈ 0.985 GB/s。48 条 lane 单向约 47.3 GB/s,双向约 94.5 GB/s。作为对照,同样 48 条 lane 的 Gen2 单向只有 24 GB/s。所以“48 lane Gen3”这个规格真正的价值不在端口多,而在于它把每条链路的单位带宽抬到了接近 1 GB/s,让 x4 端口就能跑到接近 4 GB/s——这才是它能用少量 lane 支撑多设备的物理基础。
编码方式的切换也解释了一个常见现象:链路训练总是从 Gen1 开始。128b/130b 需要收发双方在协议层完成协商之后才能启用,链路先以 8b/10b 的 Gen1 建立,再通过训练序列逐步提速到 Gen2、Gen3。这也意味着任何一次链路降级都会先在低速档上稳定下来,而不是直接断链——排查时看到“能通但只有 Gen1”,说明物理层通了、协商没谈成。
2.3 链路降级与 Signal Detect:为什么端口宽度会自动缩水
链路训练失败的默认行为不是报错停机,而是降级重试:x8 谈不成就试 x4,Gen3 谈不成就退回 Gen2。这是 PCIe 规范层面的容错设计,但对调试很不友好——系统跑起来了,性能只有预期的四分之一,日志里什么都没有。
这块芯片里有一个直接相关的寄存器:Port x Signal Detect Level(BDCh 内按端口划分的字段)。手册 v1.2 针对 Revision CA 把这个字段的默认值改成了 011b,也就是调整了信号检测阈值。这个改动值得留意:同一个板子换一颗不同 revision 的芯片,链路稳定性可能就不一样,因为判定“对面有没有信号”的门槛变了。差分链路的信号检测阈值直接决定训练起点,阈值偏严会导致长走线或经过连接器的链路被判为无信号。
另一个与降级强相关的配置项是参考时钟的扩频(SSC)。手册把 STRAP_REFCLK_SSC_ENx 的描述“significantly rewrote”,并把它的作用域从 SerDes quad 改成了按 Port 关联。这是一个语义变更:老版本按 quad 配 SSC,新版本按 port 配。如果有人拿旧版 EEPROM 配置去配新 revision 的芯片,SSC 的开关会落在错误的端口上,表现出来就是某几个端口反复训练失败、其余端口正常——这种局部故障最容易被误判成硬件焊接问题。
3. 虚拟交换机:一颗硅片里同时跑多套 PCIe 层级
3.1 枚举边界:每个 Virtual Switch 为什么必须有自己的上行端口
PCIe 的枚举是一棵树:一个 Root Complex 从总线 0 开始,逐级扫描桥设备,给每个下游桥分配总线号范围。如果两个主机共享同一个交换芯片,各自都想枚举下面挂的设备,冲突是必然的——同一个 endpoint 会被分配两次 BAR,两组总线号会互相覆盖。
Virtual Switch 的解法是把芯片内部的 crossbar 切成若干个互不可见的逻辑分区,每个分区有自己的上行端口、自己的配置空间视图、自己的总线号范围。对主机固件来说,它看到的是一颗标准 PCIe 桥,桥下面是这个分区里的下行端口和设备;分区之外的东西它根本看不见。手册里“第二个上行端口仅在 Virtual Switch 模式下可用”这句修订说明,点出了物理上的限制:非虚拟交换机模式下,芯片只有一条通往单一主机的路径。
这里还有一个容易被忽略的细节:手册 v1.2 把寄存器 760h[16] 在 Revision CA 上重新定义为“使能对全部 PEX 8749 寄存器的 ECAM 访问”。这句话的含义是,芯片自身的寄存器空间对主机是否可见、可见多少,是可配置的。默认情况下主机只能看到标准 PCIe 配置空间,看不到交换芯片内部的私有寄存器;打开这一位之后,主机可以通过 ECAM 直接读写全部内部寄存器。这个开关的存在本身就说明设计者把“隔离”当成了默认值,“全可见”是需要显式打开的调试或管理通道。
3.2 复位域:VSx_PERST# 与 Station 内 Port 0 的绑定关系
每个虚拟交换机有独立的 PERST# 信号(VSx_PERST#),这是分区独立性的物理体现。手册 v1.2 重写了这个信号的说明段落,并新增了“Initialization – Virtual Switch Mode”一节,说明这套初始化流程和单主机模式差别不小,直接照搬单主机模式的上电顺序会出问题。
上电时序大致是:参考时钟稳定 → 复位释放 → 串行 EEPROM 加载 → 端口配置生效 → 链路训练 → 枚举。EEPROM 加载这一步在虚拟交换机模式下要额外处理 NTx_PERST# 控制位,因为 NT 端口的复位需要和它所属的虚拟交换机对齐。手册专门加了一条注记:A30h[27] 的取值在 Virtual Switch 模式和 NT 模式同时使能时可能发生变化。这类“两个特性叠加时默认值会变”的说明,是数据手册里最值钱也最容易被略过的部分——它意味着你不能把两个特性的配置当成正交的独立项。
错误上报路径同样按虚拟交换机切分:VSx_FATAL_ERR# 和 VSx_PEX_INTA# 是 per-VS 的输出,错误被路由到“包含发生错误的那个 Station 的 Port 0 的虚拟交换机”。这条规则解释了为什么 Station 内的 Port 0 不是普通端口:它决定了这个 Station 的硬件事件归哪个虚拟交换机管。
3.3 Port Migration:把端口从一个虚拟交换机搬到另一个
这是虚拟交换机最考验理解深度的一个操作。手册 v1.2 里明确补充了一句:在把一个端口从 VSx 迁移到 VSy 之前,必须停止通过 VSx 的流量。
为什么必须停流量,而不是“最好停”?因为 PCIe 的 TLP 路由依赖总线号和地址域,而这两者在迁移瞬间会变。已经在链路里的请求如果带着旧 VS 的总线号,迁移完成后它到达上游时,路由逻辑已经按新 VS 的配置在查表了——completion 找不到对应的请求者,请求者等不到 completion,最后表现为超时或者更隐蔽的数据错位。手册加这句话,大概率是因为有人踩过。
迁移的完整步骤大致是:先停掉 VSx 上的业务流量并等待在途 TLP 排空,再解除端口与 VSx 的绑定,改写归属配置,然后对该端口单独复位,最后让目标主机重新枚举这个端口。每一步都不能省,尤其是第一步——排空没有硬件层面的确认信号,只能靠软件等待足够长的时间或者依靠上层协议确认静默。我一般的理解是,这个操作应该放在可控的维护窗口里做,而不是当成运行时热切换。
4. NT 端口与地址翻译:两台主机共享设备为什么不打架
4.1 Non-Transparent 端口的存在理由:BAR 冲突从哪来
虚拟交换机解决的是“两台主机各看各的设备”,但有一类场景它解决不了:两台主机要访问同一块设备。比如两块主板都要读写同一块存储卡,或者一个计算节点要把数据推给另一个节点的内存。这种情况下两边都需要看到这个设备,而 PCIe 的枚举机制不允许两个 Root Complex 给同一个 endpoint 分配不同的 BAR。
Non-Transparent 端口的做法是在两个地址域之间立一道翻译层。从 A 主机这一侧看过去,NT 端口是一个普通的 endpoint,有自己的配置空间、自己的 BAR;从 B 主机那一侧看,同一个 NT 端口是另一个 endpoint。两边的配置空间是独立的,TLP 跨过这道边界时地址按表翻译。这样 A 主机给这个“虚拟设备”分配的 BAR 落在 A 的地址空间里,B 主机分配的落在 B 的地址空间里,物理上不冲突。
手册里两处细节印证了这套设计:其一,NT0 和 NT1 的 Virtual and Link Interface Device ID 与 Subdevice ID(偏移 00h 和 2Ch)在 v1.2 里被改成了各自独立的默认值——两个 NT 端口如果在配置空间里长得一模一样,驱动会认错设备;其二,Expansion ROM 只在 NT0 端口上可用,说明 NT0 承担了更完整的端点职责,NT1 是简化版。
4.2 A-LUT 与地址重映射:查表发生在两个方向上
地址翻译的核心是一张 Address Lookup Table。手册 v1.2 大幅扩充了这一节,并新增了“Ingress Support for Address Lookup Table”及其子节——这是个重要变化:早期只在出方向(egress)查表,新版本增加了入口方向(ingress)的查表支持。双向查表意味着两个地址域可以互相翻译,而不只是一侧单向映射到另一侧。
表项的结构在所有这类实现里都高度相似,核心是基址、掩码、目标地址三件套。下面这段结构体是按这类硬件的通用形态写的,用来解释匹配逻辑,字段命名以手册寄存器定义为准:
/* 地址查找表项的概念结构:字段语义取自这类 NT 端口的通用实现 */ struct alut_entry { uint64_t base; /* 基地址:被匹配地址空间里的起始地址 */ uint64_t mask; /* 掩码:决定匹配粒度和地址对齐要求 */ uint64_t target; /* 目标地址:命中后要翻译成的对侧地址 */ uint32_t seg_size; /* 段大小:本次映射覆盖的地址窗口大小 */ uint8_t enable; /* 表项使能,未使能的表项不参与匹配 */ uint8_t ingress; /* 是否对入口方向生效(v1.2 新增支持) */ };匹配逻辑是拿请求地址和 base/mask 做按位比较:(addr & mask) == (base & mask)即命中,命中后用addr - base + target得到对侧地址。掩码决定了对齐粒度——掩码越宽,能匹配的地址范围越大但要求越对齐;段大小则约束了单条表项能覆盖的窗口上限。手册 v1.2 特别更新了 Address Re-Map 一节关于段大小的表述,说明这个参数有硬约束,配大了会被截断或者直接不生效。
这里有个很实际的坑:表项是要按地址顺序、从具体到宽泛排列的。如果先用一条大窗口表项兜底,后面的精细表项永远匹配不到,所有流量都走兜底翻译——功能上“能用”,性能和正确性上都可能出问题。
4.3 NTx 的部署位置与 Doorbell 通信
NT0 和 NT1 可以存在于同一个 Station,也可以分处不同 Station。手册 v1.2 专门澄清了这一点,因为两者的行为不完全等价:同一 Station 内的 NT 端口之间流量走 Station 内部的 crossbar,跨 Station 的流量要走两级。修订记录里还提到图 4-6 被改成标注“cross-station traffic”,并再次强调第二个上行端口只在虚拟交换机模式下可用——这两件事放在一起看,说明跨 Station 的流量路径是这块芯片的一个设计重点。
两个主机之间的通知机制靠 Doorbell:一侧写 Doorbell 寄存器,另一侧的 NT 端口触发中断。这套机制看着简单,但它绕过了 PCIe 标准的 MSI/MSI-X 路径,因为跨地址域的中断没法直接送达。调试时看到“数据写过去了但对方没反应”,八成是 Doorbell 的中断没有正确路由到目标主机。
4.4 DMA 引擎:为什么它必须理解虚拟交换机的边界
DMA 在这类芯片里的角色是“替主机搬数据”。它作为一个 PCIe 请求发起者,读源地址、写目标地址,把 CPU 从 memcpy 里解放出来。手册把 DMA 写进了产品名,说明它是主要卖点之一,相关控制分布在寄存器 70h、90h 等位置。
DMA 和虚拟交换机的耦合点是地址域。如果 DMA 不知道自己在哪个虚拟交换机里工作,它发起的请求会带着错误的地址语义穿到对侧,结果要么被翻译表拦下丢弃,要么被翻译到一个完全无关的地址上——后者更可怕,属于静默的数据损坏。手册里关于 760h[29] 的修订值得注意:Revision CA 把这个位默认设为 1,含义是“禁用对设置了 Relaxed Ordering 位的 Completion 的 Relaxed Ordering 支持”。也就是说,新一代硅片默认收紧了 completion 的乱序容忍度。DMA 场景下 completion 乱序是常态,这个默认值变化会直接影响 DMA 描述符环的处理逻辑。
5. 排查:调这块多根交换机时最容易翻车的五件事
5.1 现象与成因:从链路降级到设备认错
踩坑一:链路能通,但速率停在 Gen1、宽度缩到 x1。
现象是系统正常启动、设备能枚举,吞吐只有预期的十几分之一。原因通常是信号完整性或者参考时钟配置不匹配,其中最容易忽略的是 SSC 配置落在错误的端口上——手册 v1.2 把 STRAP_REFCLK_SSC_ENx 的作用域从 SerDes quad 改成了按 Port 关联,沿用旧版按 quad 配置的 EEPROM 会导致部分端口的 SSC 开关错位。排查方法是先读链路状态寄存器确认协商结果,再逐端口核对 SSC 配置和 Signal Detect 阈值,后者在 BDCh 里按端口划分,Revision CA 的默认值被改成了 011b。
踩坑二:NT 端口枚举不出来,或者枚举成两个一模一样的设备。
现象是主机侧看不到对端设备,或者看到两个 Device ID 相同的 NT 端口导致驱动加载错乱。原因有两个方向:一是 EEPROM 里 A30h[2] 没置位,手册明确写了“如果 NT 模式使能,这一位应当置位”,漏了它 NT 端口的行为不会按预期走;二是 NT0 和 NT1 的 Device/Subdevice ID 默认值在 v1.2 才被改成各自独立,用旧版配置时两个端口在配置空间里长得一样。解决方式是核对 EEPROM 映像里 A30h 的实际值,并显式给两个 NT 端口写入不同的 ID。
踩坑三:端口从一个虚拟交换机迁到另一个之后,业务偶发超时。
现象是迁移操作“看起来成功了”,但新归属下跑一段时间就出现 completion 超时或者数据错位。根本原因是在途 TLP 没排空——手册写明迁移前必须停止通过源虚拟交换机的流量。软件层面没有排空的确认信号,只能靠上层协议确认静默或者等待足够长的窗口。另外要顺手检查 760h[29]:Revision CA 默认禁用 Relaxed Ordering,如果软件逻辑依赖 completion 乱序返回,行为会和 Revision BA 不一致。
踩坑四:ECC 报错出来了,但定位不到是哪个虚拟交换机。
现象是 FATAL_ERR# 或者 PEX_INTA# 拉低,但软件不知道去查哪一侧。规则是错误被上报到“包含错误发生的那个 Station 的 Port 0 的虚拟交换机”所对应的 VSx_FATAL_ERR# 和 VSx_PEX_INTA#。所以定位第一步是先确定错误发生在哪个 Station,再查该 Station 的 Port 0 归属哪个 VS。同时注意术语变化:手册 v1.2 把原来的 1-Bit Soft errors 改称 1-Bit ECC、2-Bit errors 改称 2-Bit errors,两者在中断和状态位上的处理路径不同。
踩坑五:写了寄存器但值不生效,或者读回来是别的数。
现象是配置流程看着走完了,实际行为没变。原因是 v1.2 把一批寄存器位改成了 Factory Test Only:700h[0]、704h[0]、760h[19:17]、22Ch、764h[30]、77Ch、BD0h、F5Ch。写这些位不会有你期望的效果,读回来的值也不代表有效状态。解决方式是拿手册的寄存器表逐位核对你操作的字段,遇到标记为 Factory Test Only 的直接跳过,不要试图用它们做调试开关。
5.2 寄存器级的定位顺序
上面五条踩坑可以收敛成一套固定的排查顺序,从物理层往上爬:
- 先读链路状态,确认每条的速率和宽度,和预期配置比对;
- 再核对 EEPROM 加载是否成功,重点看 A30h[2]、A30h[27] 这类受多特性叠加影响的位;
- 然后确认虚拟交换机的划分和每个上行端口的归属,注意第二个上行端口只在 VS 模式下存在;
- 接着查 NT 端口的 Device/Subdevice ID 和 A-LUT 表项的排列顺序;
- 最后才看错误寄存器和中断输出,因为前面几层配错会制造大量假错误。
这个顺序的合理性在于:PCIe 的错误会向上传播,物理层的问题最终都会以协议层错误的形式暴露出来,先查上层会被噪声带偏。我见过最费时的一次排查,是在 AER 寄存器里翻了两天,最后发现是一个端口的 SSC 配置没开——链路以 Gen2 跑着,功能全部正常,只在满负载时偶发重传。
6. 设计取舍:哪些能力必须做进硅片
回到一个根本问题:多根、地址翻译、DMA 这些能力,为什么不能全部交给软件或者外部器件?
拿虚拟交换机来说,如果不用硬件分区,而是让两颗交换芯片各自接一台主机,成本和多出来的板级走线是次要的,真正的问题是两颗芯片之间没有共享的 crossbar——一台主机访问另一台主机侧挂的设备,流量必须绕出去再回来,延迟和带宽都翻倍。把分区做进同一颗芯片的 crossbar,端口之间的转发就是一次内部交换,路径最短。代价是端口切分被限制在一组预定义配置里,你不能任意地把 24 条 lane 切成你想要的形状。这是一个明确的取舍:灵活性换延迟。
地址翻译同样如此。软件可以做地址映射,但每一次跨域访问都要经过 CPU 中转,DMA 的意义就没了。把 A-LUT 做进硬件,翻译在 TLP 通过的瞬间完成,主机完全无感。代价是表项数量有限、匹配规则固定,映射关系需要在初始化阶段规划好,运行时改表要停流量。
| 能力 | 硬件实现 | 软件替代 | 主要代价 |
|---|---|---|---|
| 虚拟交换机分区 | crossbar 内部隔离 | 多颗芯片 | 端口切分受限 |
| 地址翻译 | A-LUT 线速查表 | CPU 中转 | 表项数与段大小受限 |
| DMA | 硬件描述符引擎 | 主机 memcpy | 需理解 VS 地址域 |
| 错误隔离 | per-VS 中断输出 | 全局中断 + 软件分发 | 需按 Station Port 0 归属定位 |
验证这些设计是否按预期工作,我的做法是分层验证而不是端到端跑通就算完:先在单主机模式下确认每条链路都能跑到 Gen3 全宽,再开虚拟交换机确认每个分区的枚举边界干净,然后加 NT 端口验证双向翻译,最后才上 DMA 压满带宽。每一层单独验证的价值在于,出问题时能立刻定位到是哪一层的配置引入了偏差,而不是在一片“能跑但慢”的模糊状态里瞎猜。
手册这类东西的价值也在这里:它不会告诉你“应该怎么设计系统”,但它把每一个可配置位的默认值、每一个特性的交互约束都写在了修订记录里。那些看起来枯燥的“Changed many register bits from RO to ROS”“Removed PRBS support”“Changed to Factory Test Only”,每一条背后都是一次硬件行为的实际变更。从那以后我每次拿到新 revision 的芯片,都会先把修订记录从头到尾读一遍,再去碰任何一个寄存器——这比在板子上试错省太多时间。希望帮到你。
本文还有配套的精品资源,点击获取