把 SP/FPGA 接口改成 FIFO 之后 AFBR-89BDDZ QSFP28 的 vendor 字段读出 0905000000000000
我们负责自研硬件上交换机的管理固件,自从 SP/FPGA 接口从内存映射缓冲区改成 FIFO 之后,部分端口读回的光模块清单信息就开始变成垃圾数据。
- QSFP28 光模块,全部是同一批的 AFBR-89BDDZ
- 读取要经过 service processor 和 FPGA 这条路径到模块 EEPROM
- 主机侧的辅助函数是 get_i2c_status_and_read_buffer:检查状态、读整个缓冲区、再检查一次状态
本该拿到 vendor 数据的地方,我们得到的是:
one port, vendor field: 0905000000000000
the next port, vendor field: all ones
on another machine: vendor field all zeros, elsewhere repeated digit characters
目前已经尝试过:
- 对同一个端口连续重读多次,垃圾数据在每个端口上是稳定的,不是随机噪声
- 给模块断电重插,没有区别
- 确认机箱里每个模块都是同一个料号,所以不是我们解码错了某种特殊的厂商数据块
在我们开始从生产环境里往外拔光模块之前,这更可能是模块本身的问题、I2C 路径的问题,还是我们自己读取辅助函数的问题?
Comments 7
这明显指向读取路径,而 FIFO 改动正是原因所在。内存映射缓冲区不管你问多少次都返回同样的内容;FIFO 每个字节只交出一次,交出去就没了。get_i2c_status_and_read_buffer 沿用的那套流程——检查状态、读整个缓冲区、再检查一次状态——在背后是内存的时候是合理的,换成 FIFO 就不成立了:它会在模块 EEPROM 的读取还在总线上进行时就把队列掏空。你拿到的是那一瞬间恰好在队列里的东西,而姗姗来迟的字节留在了后面,出现在下一次读取里。
这正好符合你描述的特征:每个端口上的垃圾数据是稳定的,损坏会随着读取顺序一起走,一台机器上是全零,另一台是重复的数字模式,具体取决于时序恰好落在哪里。这些现象都不需要模块本身有问题,而且你换模块的结果也已经把光学部分排除掉了。
修复办法是不要再让调用方负责完成检查。把这个责任挪到光模块驱动的读取例程内部:让它等到 I2C 事务报告完成之后才去碰缓冲区,这样从结构上就不可能有调用方提前把它掏空。只补丁某一个调用点,只会把这个竞争条件挪到别的地方去。
先说清楚,这只是修复方案的一个雏形,还没经过多年验证,所以在你重新信任这份清单之前,先在自己的平台上验证一遍。不过这里有个成本很低的教训可以带走:厂商字符串乱掉时,应该先做模块和端口的交叉测试,因为读取顺序才是罪魁祸首的次数,远比光模块本身要多。
有一个测量能把这件事一分为二:损坏是跟着模块走,还是跟着端口走?把返回
0905000000000000那个端口里的模块拔出来,和一个读取正常的端口上的模块对调,再重新读取两边。如果错误的字符串跟着物理模块走,那就该去查光学部分。如果它一直停留在同一个端口号上,或者更糟,跟着下一个被读取的模块一起移动,那模块就是无辜的,问题出在主机的读取环节。顺带把原始 EEPROM 字节和解码后的字段一起 dump 出来。如果底层字节是正常的,只是解码出的 vendor 字符串是垃圾,那和字节本身就是垃圾,是完全不同的两个 bug。
在一对端口上做了这个对调测试。坏数据没有跟着模块走:返回
0905000000000000的那个端口,换上另一个模块之后依然返回这个值,而我们拔出来的那个模块插到新的槽位上读取完全正常。更关键的是,我们改变了端口的读取顺序之后,损坏也跟着这个顺序移动了。垃圾数据总是落在出问题的那个模块之后被读取的那个模块身上。也就是说它跟的是读取顺序,不是物理部件。原始字节也是错的,所以不是我们这边的解码问题。
根源不一样,但是同一类坑,来自驱动这一侧。在一块用了树外 ice 1.15.4 驱动的 Intel E810-C 上,我在 QSFP28 光模块上用
ethtool -m读到了错误且不完整的数据页:page 1 和 page 3 的数据,包括阈值和各通道监控值,和模块实际存的内容对不上。我最终也没得到一个明确的根因结论,帖子标记已解决就关闭了,没给太多细节,所以这只能当作一个个案,别当真理。我最后的处理方式是升级 ice 驱动和 E810 的 NVM(用
nvmupdate64e),拿另一台主机上内核自带的 ice 驱动读出来的数据做交叉核对,然后用配合明确的偏移量和长度去读取指定页,而不是直接相信解码后的输出。如果你的平台能做原始 dump,先把原始数据和解码结果对比一下,再决定信哪个。
值得把分层关系讲清楚,这样排查起来能快很多。在 Linux 上,
ethtool -m负责解码模块 EEPROM(厂商名称、OUI、型号、序列号、生产日期,以及模块带 DDM 时的各项数值),ethtool -e用来 dump 原始字节,如果 I2C 总线是暴露出来的,i2cdump -y 1 0x50读的是 A0h,i2cdump -y 1 0x51读的是 A2h。在 A0h 里,厂商名称位于字节 20-35,型号、版本和序列号在 40-59。所以如果 vendor 字段是乱的,而往后二十几个字节的型号却是完好的,这本身就说明问题出在读取的时序上,而不是 EEPROM 坏了,这和你看到的情况是吻合的。
有一种情况不要和这个混淆:
ethtool -m返回 Input/output error,通常只是说明这个模块没有 DDM。A0h 字节 92 的 bit 6 就是标记 A2h 是否存在的标志位,内核自带的 ixgbe 和 bnx2x 驱动很早就加了这个检查,免得去读取根本不存在的另外 256 字节。从 SONiC 那边过来的人可以参考一下,这是同一类问题。
sfputil show eeprom对某些模块会报 Cannot get Module EEPROM data: Invalid argument,或者悄悄地和同一端口上show interfaces transceiver eeprom的结果对不上。据我遇到的情况,这大多是平台和驱动的缺口,而不是光模块的问题:这两个命令在 202012 分支里用的键名不一致,到 202205 才修好;有些平台在断电重插之后会从 sfputil 里丢失 QSFP 模块,直到有驱动修复才恢复;还有一些平台干脆没实现 get_transceiver_info。当我需要一个能放心用在脚本里的方式时,我会直接用 optoe 内核驱动自己去原始读取 SFP、QSFP 或 CMIS 的 EEPROM。不过还是要在你自己的平台上验证一下,各平台之间的表现差异很大。
我们这边的进展。按建议把完成检查挪到了驱动的读取函数里,之后几百次清单扫描中每个端口的厂商字符串都是正确的,包括之前互相串数据的那两个端口。原始字节现在也和解码字段一致了。
目前我先把这算作部分解决,而不是彻底解决:这个补丁还没合并进我们的代码树,另外还有一款用了不同 FPGA 构建的平台需要再验证一遍,之后才能在所有地方都放心信任它。但光模块自始至终都没问题,这一点是我自己一开始最容易判断错的地方。