简介:PEX8749-BA/CA 数据手册面向PCIe交换芯片选型、硬件设计与底层驱动开发人员,提供PCI Express Gen 3多根交换机的架构、寄存器与配置参考。文档围绕Multi-Root多主机共享、DMA直接内存访问、18端口拓扑与高通道带宽管理展开,并说明DMA降低CPU负担的方式,同时给出Gen 3 8GT/s速率、寄存器偏移、JTAG标识及修订历史等工程细节,可服务于数据中心、服务器、网络设备和存储系统的I/O扩展与调试。压缩包内含1个PDF文件,约18.06MB,适合本地检索、归档和随工程资料留存;目前已有1747人学习下载。读者可据此了解芯片特性、端口划分、DMA机制与寄存器定义,理清多主机访问、带宽分配和版本差异,辅助完成方案评估、原理图设计、链路带宽排查及多主机设备共享问题分析,也可作为驱动开发和硬件调试阶段的案头参考。
1. 一块 48 通道的 PCIe Switch 数据手册,什么时候你会真的翻开它
板子插上电,上游根端口认到了,lspci里却只看到一个孤零零的桥,下面挂着的加速卡、NVMe 一个都不出来;或者链路倒是起来了,协商结果停在 x4、2.5 GT/s,换卡换线换槽位都没用。这类问题查到最后,十有八九要回到那颗 PCI Express Gen 3 交换机(PCIe Switch)本身——PEX8749,48 条 Lane、18 个端口、支持多根(Multi-Root)和 DMA 的那一颗。
数据手册 v1.2 覆盖 BA 和 CA 两个硅版本,从 I/O 信号定义、复位时序、EEPROM 加载,一路写到后面十几章的寄存器偏移表。它适合三类人:画原理图、定 strap 的硬件工程师,写配置 EEPROM 和上电流程的固件工程师,以及要在几台主机之间做资源分区的系统软件。不适合想找一份“五分钟点亮教程”的人,通篇没有一个字的示例代码。
2. 48 条 Lane 怎么切成 18 个端口:Station、端口配置与虚拟交换机的读法
拿到手册先别翻寄存器表,先把第 4 章到第 5 章的架构图啃下来。同一颗片子,18 个端口不是唯一的切法,切法由 strap 引脚在上电那一刻决定,软件事后改不了。搞不清这一层,后面所有寄存器都读不懂——你会以为某个端口没枚举出来是驱动问题,实际是它压根没被切出来。
2.1 Lane、Port、Station 三层结构先对齐
Lane 是最小物理单位,一对差分线,Gen 3 下跑 8 GT/s,128b/130b 编码扣掉开销,单向实际可用约 985 MB/s。Port 是一条逻辑链路,宽度可以是 x1、x2、x4、x8、x16,宽度必须是 2 的幂,一条 Port 内部不能出现“x3 这种”宽度。
Station 是把若干 Lane 圈在一起、再往里切 Port 的单元。手册里带 STNx 前缀的 strap 和复位信号,都是按 Station 生效的。这一点非常关键:STNx_PERST#只复位某个 Station,不是整颗片子;STNx_PORTCFGx只决定这个 Station 内部的端口切法。一颗片子上多个 Station 可以配成完全不同的端口形态,这在做混合业务背板时很有用。
由此带来一个必然结果:48 条 Lane 分到各 Station 之后,每个 Station 内部切完 Port 剩下的零头 Lane 就只能空着。想凑满 18 个端口,意味着大量 Port 是 x1 或 x2 的宽度,手册的端口配置表就是干这个的——它告诉你哪一档配置能切出几个端口、每个多宽。
2.2 端口配置编号:0 到 7 里,v1.2 只剩 1 到 6
端口配置是用 strap 引脚电平编码的,选哪一档,芯片上电时就把 Lane 按预设的形态切好。手册的修订记录里有一行很容易被跳过:v1.2 删除了端口配置 0 和 7。这意味着如果手上的板子是从更早的设计改过来的,strap 电阻还按配置 0 接,芯片读到的就是一个已经作废的编码,行为会退回到默认档或者干脆不训练链路,现象是这条链路上什么都没有。
排查思路很朴素,但很多板子就栽在这:先翻原理图,找到STRAP_STNx_PORTCFGx那几个引脚,把上下拉电阻的取值抄出来,编码成 0/1 序列,再对照手册第 21 章的 strap 表。对不上就改电阻,别在软件里找补。
| 中文章节里要确认的事 | 在手册里查哪里 | 典型翻车点 |
|---|---|---|
| 这颗片子有几个 Station | 第 4 章架构图 | 以为整颗只有一个 Station,复位拉错引脚 |
| 每个 Station 能切几档配置 | 第 5 章端口配置表 | 用了已删除的配置 0 / 7 |
| 每个 Port 的宽度上限 | 端口配置表对应的行 | 规划了 x16,实际该档位只给到 x8 |
| strap 引脚的上电采样时刻 | 第 21 章 strap 时序 | 上拉太弱,采样到中间电平 |
2.3 多根与虚拟交换机:一根板子上的几台“主机”怎么分家
Multi-Root 的实现方式是虚拟交换机(Virtual Switch,手册里写作 VS)。一个 VS 有自己的上行端口、自己的配置空间视图,主机 A 挂在 VS0 的上行口上,就只看得到 VS0 下游的设备,看不见 VS1 下面的东西。这在多主机存储、双控设备里是标准玩法。
三个必须记住的边界条件。第一,VSx_PERST#是每个 VS 独立的复位信号,复位的粒度不是整颗片子。第二,端口可以在 VS 之间迁移(Port Migration),但手册明确要求迁移之前必须先把经过源 VS 的流量停掉,不停就迁,表现就是数据串到另一台主机上去。第三,NT 端口(Non-Transparent Port)是跨域通信的通道,NT0 和 NT1 这两个端口可以落在同一个 Station 里,也可以分在不同 Station 里,灵活度高但也意味着地址规划要自己兜底。
还有一个细节值得单独拎出来:扩展 ROM(Expansion ROM)只在 NT0 端口上可用。做固件的时候如果指望在 NT1 上挂一段 option ROM,会发现根本没这个空间。
2.4 用 lspci 把拓扑对到手册的图上
手册的框图是逻辑视图,板子上的真实拓扑得自己拉出来。先用树形输出看整体,再逐个端口核对链路状态。
# 1. 打印整棵 PCIe 拓扑树,交换机会呈现为一串桥节点 lspci -tv # 2. 上游端口的链路能力与实际协商结果,两者要分开看 lspci -vvv -s 01:00.0 | grep -E "LnkCap:|LnkSta:|DevSta:" # 3. 逐个下游端口确认有没有设备、速率宽度是否达到预期 for d in 02:00.0 03:00.0 04:00.0 05:00.0; do echo "== $d ==" lspci -vv -s "$d" | grep -E "LnkSta:" doneLnkCap只说明这颗芯片和这条链路“支持到哪一档”,LnkSta才是“现在跑在哪一档”。两者不一致时,先怀疑物理层:连接器、背板走线、参考时钟。两者一致但都低于预期,那就是端口配置本身只切了这么宽,回去查 strap。
DevSta里的 Correctable Error 计数也值得顺手看一眼,链路训练阶段如果有大量可纠正错误,说明信号质量已经在临界点上,跑压力测试迟早翻车。
3. 上电那几百毫秒:复位时序、strap 引脚与串行 EEPROM 加载
上电到链路训练完成这段时间,手册里散在第 5 章、第 6 章、第 21 章。三块内容必须一起看,因为它们互相咬合:复位放早了,EEPROM 加载不完整;strap 接错了,EEPROM 里的配置再好也没用;EEPROM 镜像格式对不上加载模式,前面全白做。
3.1 复位与参考时钟:谁先谁后有硬要求
顺序是参考时钟稳定、PERST#释放、内部初始化、串行 EEPROM 加载、链路训练。手册第 5 章有一节专门讲复位与时钟的初始化时序,里面给了参考时钟稳定到PERST#释放之间的最小时间。
这条要求在实际板子上极容易被压缩。电源芯片的 PG 信号一到就拉高PERST#,看起来响应快,实际上参考时钟的 PLL 还没锁住,链训在某些批次上成功、某些批次上失败,而且换台机器就复现不了,属于标准的玄学问题。稳妥做法是给PERST#加一段 RC 延迟,或者直接用电源的 PG 加一级延时电路,把余量留足。
另一个坑是PERST#的复位来源混用。有些设计把整板的复位和STNx_PERST#短接在一起,单 Station 出问题时一复位就把整颗片子连带其它 Station 全带走了,正在跑的业务直接断。既然手册把 Station 级复位单独做出来,就别省那几颗电阻。
3.2 strap 引脚:上电采样一次,之后软件改不回来
strap 相关的信号在手册第 21 章,v1.2 改过好几个的描述,几个高频的:
STRAP_I2C_SMBUS_CFG_EN#、STRAP_I2C_SMBUS_EN、STRAP_MGMT_PORT_EN:决定管理接口走 I2C 还是 SMBus,以及是否启用管理端口。STRAP_REFCLK_SSC_ENx:v1.2 把它的作用范围从 SerDes quad 改成了按 Port 关联。如果原理图还是照旧版把几个 quad 绑一起,可能给不该开 SSC 的端口开了扩频,链路能起但误码率上不去。STRAP_STNx_PORTCFGx:端口切法,前面已经说过。
这些引脚都是上电采样一次然后锁存。想换管理接口、想换端口切法,只能动板子。软件侧唯一能做的是读回来确认实际采样值,别假设自己配对了。
3.3 串行 EEPROM 加载:Base Mode 和 Virtual Switch Mode 的格式不一样
第 6 章把加载分成两个场景。Base Mode 是一套配置打天下,单主机场景用这个。Virtual Switch Mode 下,每个 VS 有自己的配置区,镜像里还要额外带NTx_PERST#的控制位。
两个 v1.2 新增的说明,做固件的人必须知道。其一,A30h[2]这一位在 NT 模式使能时应该置 1。其二,A30h[27]的默认值改过,而且当 Virtual Switch 模式和 NT 模式同时开启时,这个值可能会变。第二条的实际含义是:同一个 EEPROM 镜像不能无脑复制到另一种工作模式的板子上,加载完读回来可能和你写进去的不一样。
同理,BA 和 CA 两个硅版本的寄存器默认值有不少差异。修订记录里列了一串,比如74h[11:10]在 CA 上默认是10b(禁用 ASPM L0s 支持),BA 上是11b;204h[15:0]在 CA 上默认置 1,开启错误注入的推断模式;BDCh里 Port x 的信号检测电平默认值也改成了011b。拿 BA 的镜像刷 CA 的板子,最轻的结果是 ASPM 行为不符合预期,最重的是信号检测阈值不对,长链路上直接训练不起来。
3.4 镜像解析脚本:把 offset/value 对还原成能 include 的头文件
调试阶段经常要拿 EEPROM 里的实际内容和设计值做 diff,肉眼对十六进制不现实。下面这段把镜像里成对的(偏移, 值)记录抽出来,生成一张 C 表,方便和驱动里的默认值逐条比对。
#!/usr/bin/env python3 """把串行 EEPROM 镜像里的 (offset, value) 记录转成 C 头文件。 记录区的起始位置、每条记录的字节序、头部长度,都以手册第 6 章 "Serial EEPROM Load" 的描述为准。Base Mode 和 Virtual Switch Mode 的头部定义不同,这里只处理已经确认过格式的记录区。 """ import struct import sys REC_SIZE = 8 # 4 字节偏移 + 4 字节值;手册若定义为 3 字节字段需同步修改 def parse(path): with open(path, "rb") as f: blob = f.read() records = [] pos = 0 # 逐条向后读,剩余长度不足一条记录就停止,避免读到填充区 while pos + REC_SIZE <= len(blob): offset, value = struct.unpack_from("<II", blob, pos) records.append((offset, value)) pos += REC_SIZE return records def emit(records, out_path): with open(out_path, "w") as f: f.write("/* 由 EEPROM 镜像自动生成,勿手改 */\n") f.write("#ifndef PEX8749_EEPROM_CFG_H\n#define PEX8749_EEPROM_CFG_H\n\n") f.write("struct pex8749_cfg { unsigned int off; unsigned int val; };\n\n") f.write("static const struct pex8749_cfg pex8749_cfg_tbl[] = {\n") for off, val in records: f.write(" { 0x%08X, 0x%08X },\n" % (off, val)) f.write("};\n\n#endif /* PEX8749_EEPROM_CFG_H */\n") if __name__ == "__main__": recs = parse(sys.argv[1]) emit(recs, sys.argv[2]) print("records: %d" % len(recs))REC_SIZE是最容易出错的地方——不同加载模式下记录长度不一定都是 8 字节,写完先拿一份已知正确的镜像跑一遍,记录条数和总长度能对上再往下走。生成的头文件建议直接进版本管理,每次改 EEPROM 都重新生成一次、看 diff,比对着二进制文件猜要靠谱得多。
如果 EEPROM 在板子上能通过 SMBus 直接读,连拆焊都省了:
# 扫描 SMBus,配置 EEPROM 通常落在 0x50 段 i2cdetect -y 3 # dump 前 256 字节,和手里的镜像做 diff i2cdump -y 3 0x50 b | head -20注意i2cdump是读操作,安全;i2cset是写,动手前先i2cget把原值读出来存一份,EEPROM 的擦写寿命经不起反复试错。
4. DMA 与 NT 端口:数据路径怎么组织,带宽怎么测才算数
4.1 DMA 引擎省掉的是什么,没省掉的是什么
DMA 让数据在主机内存和交换机侧之间直接搬,不经过 CPU 的拷贝循环。省掉的是 CPU 占用和中断次数,不是总线带宽——总线带宽该多少还是多少,48 条 Lane 单向加起来大约 47 GB/s,这是物理上限,DMA 只是让你更接近它。
实际做的时候有三个参数绕不开。缓冲区对齐:按 1 MiB 对齐能避免描述符被拆分,拆分一次就多一次事务开销。缓冲区大小:要和链路上协商的 Max Payload Size、Max Read Request Size 配合,缓冲区远大于 MPS 时,硬件会拆成多个 TLP,看起来是“DMA 慢”,实际是参数没配平。描述符环深度:太浅会在高并发下频繁等中断,太深则首包延迟变差。
4.2 NT 端口与 A-LUT:地址转换靠什么匹配
跨域访问的路径是:主机 A 发起访问,地址落在 NT 端口上,NT 端口查地址查找表(A-LUT,Address Lookup Table),把地址翻译成目标域里的地址再转发出去。v1.2 对 A-LUT 的改动不小,明确扩展了入方向(ingress)的支持,第 16 章专门加了一节讲入方向的地址查找。
A-LUT 是有限条目资源,规划地址段时得算清楚:每个条目覆盖多大的地址窗口、总共需要几条、留多少余量给后续扩容。表项配少了,超过窗口的访问直接落空,现象是“一部分地址能访问、另一部分不行”,很容易被误判成驱动 bug。
还有一个边界:扩展 ROM 只在 NT0 端口上可用,NT1 没有。做双 NT 端口的对称设计时,这一条会让两边能力不对等,得在软件侧补齐。
4.3 带宽与延迟的打点程序
手册给的是理论值,板子上能跑出多少得自己测。别用dd读裸设备那种方式,测出来的是文件系统缓存的速度。写一个最小化的打点程序,只测 DMA 搬运这一段:
/* 最小 DMA 吞吐打点:反复搬同一块缓冲区,只统计搬运耗时 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define BUF_BYTES (64UL << 20) /* 64 MiB,远大于任何一级缓存 */ #define ROUNDS 32 static double now_s(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } int main(void) { void *host = NULL; /* 1 MiB 对齐,避免描述符跨界被硬件拆分 */ if (posix_memalign(&host, 1 << 20, BUF_BYTES) != 0) { return 1; } memset(host, 0x5a, BUF_BYTES); double t0 = now_s(); for (int i = 0; i < ROUNDS; i++) { /* 换成实际的 DMA 提交入口,等待完成后再进下一轮 */ /* device_dma_xfer(host, BUF_BYTES, DMA_TO_HOST); */ } double t1 = now_s(); double gbps = (double)BUF_BYTES * ROUNDS * 8 / (t1 - t0) / 1e9; printf("%.2f Gbps\n", gbps); free(host); return 0; }跑之前先把链路状态确认一遍,LnkSta不是 8 GT/s 的话,测出来的数字没有参考意义。跑的时候用perf stat看 CPU 占用,如果 CPU 在满负荷跑,说明 DMA 根本没生效,数据还是走 memcpy 路径。延迟的话,把BUF_BYTES降到 4 KiB 再跑一轮,单次往返时间就是延迟的粗测值——注意这只是粗测,精确值要靠硬件时间戳。
5. 上电枚举不出来的排查清单:五个现象、五个根因
5.1 现象:上游端口能认到,下游一个设备都没有
原因通常是端口配置没有生效,或者下游端口根本没被切出来。v1.2 删掉了端口配置 0 和 7,板子如果还是按旧设计接 strap,芯片读到的编码无效,该 Station 的端口全部不出现,上游自然只能看到一颗光杆交换机。
解决:先用lspci -tv确认桥下面确实没有子节点,再翻原理图核对STRAP_STNx_PORTCFGx的电阻编码,对照手册第 21 章的 strap 表重算一遍。改完电阻重新上电,别指望软复位能生效——strap 只在上电采样。
5.2 现象:链路起来了,但降级到 x4 或者 2.5 GT/s
原因分两类。物理侧是连接器、背板走线、参考时钟质量不达标;配置侧是这个端口本来就只切了这么宽。区分方法很简单:看LnkCap和LnkSta。两个都低,是配置问题;LnkCap高、LnkSta低,是物理问题。
物理侧先查参考时钟的 SSC 配置。v1.2 把STRAP_REFCLK_SSC_ENx从按 quad 关联改成了按 Port 关联,老原理图如果还按 quad 绑,可能给不该开 SSC 的端口开了扩频,或者反过来。解决是把 strap 改成按端口独立控制,逐端口验证。
5.3 现象:EEPROM 有时候能加载,有时候加载不了
原因多半在 I2C 的上拉电阻上。v1.2 明确改过I2C_SCL0、I2C_SDA0、I2C_SCL1、I2C_SDA1的上拉要求和外围电路说明。按旧版手册选的阻值,在温度和批次波动下会落到临界区,表现就是“十次里能成七八次”,非常折磨人。
解决:按 v1.2 的要求重算上拉阻值,同时用示波器看上升沿。另外确认STRAP_I2C_SMBUS_EN的状态和实际上挂的器件类型匹配——管理接口走 I2C 还是 SMBus,时序要求不一样。
5.4 现象:虚拟交换机之间的流量串了
原因通常有两个。一是端口迁移(Port Migration)的时候没有把源 VS 的流量停干净,手册第 14 章明确要求迁移前先停流量,不停就迁,正在飞的 TLP 会落到错误的域里。二是 A-LUT 的地址窗口规划有重叠,跨域访问落到了不该落的条目上。
解决:迁移流程里加一步显式的流量静默,等所有 outstanding 事务回完再动配置。地址窗口做一张表,落盘,重启后比对,别靠记忆维护。
5.5 现象:DMA 带宽只有理论值的三成
先排除测量误差:缓冲区是不是太小、有没有被缓存吸收、CPU 是不是在忙等。排除之后,查三个参数:缓冲区是否 1 MiB 对齐、Max Payload Size 和缓冲区大小是否匹配、描述符环深度是否够。
还有一个容易被忽略的点:链路上如果有可纠正错误在持续增长,硬件会反复重传,带宽看着就是上不去。用lspci -vv看DevSta里的 Correctable Error 计数,跑一轮压力测试前后各读一次,涨得快就是信号完整性问题,回去查物理层,别在软件里调参数。
6. 把数据手册当寄存器字典用:改一个 strap 的完整走法
手册十几章的寄存器偏移表,正确的用法不是通读,是当字典查。我一般按这个顺序走一遍,能把大部分配置错误挡在前面。
第一步,定位。先在目录里找到功能对应的章节,再在该章的寄存器映射表里定位偏移,最后确认位域。比如要改 ASPM L0s 的支持行为,落到74h[11:10]。
第二步,查默认值和硅版本差异。同一个偏移在 BA 和 CA 上默认值可能不同,74h[11:10]就是典型:BA 默认11b,CA 默认10b。不查这一条,写进去的值和预期对不上,会怀疑是自己写错了。
| 偏移 | 位域 | v1.2 中的关键变化 | 什么时候要动它 |
|---|---|---|---|
74h | [11:10] | CA 默认改为10b,禁用 ASPM L0s | 排查链路功耗与唤醒异常 |
108h | 整体 | 随硅版本变化,用于识别 BA / CA | 上电第一件事,确认片子版本 |
204h | [15:0] | CA 默认置 1,开启错误注入推断 | 做错误注入测试前先确认 |
A30h | [2] | NT 模式使能时应置 1 | 启用 NT 端口时 |
A30h | [27] | VS 与 NT 同时开启时值可能变化 | 双模式共存场景 |
760h | [16] | CA 上重新定义为 ECAM 访问使能 | 需要 ECAM 访问全部寄存器时 |
第三步,分清 strap 和寄存器。strap 是上电采样一次就锁死的,寄存器是随时可写的。想在运行时改端口切法,改寄存器没用,只能改板子。这一条踩过一次之后就不会再踩了。
第四步,留回滚路径。改之前把原值读出来存文件,改完立刻读回确认。EEPROM 里的内容尤其要备份,写坏了还能用编程器烧回去,没有备份就只能对着手册一行行推。
从那以后,我每次碰这类交换芯片,上电第一件事都是读108h确认硅版本,再读一遍 strap 相关引脚的实际采样状态,然后才动任何配置。这个习惯帮我省掉了至少两次“明明按手册配的却不生效”的深夜排查。
希望帮到你。
本文还有配套的精品资源,点击获取