DPDK获取光模块信息
背景
最近在实现一个需求,在DPDK层面,获取到光模块光衰信息。方案上其实也就是和内核里面的网卡驱动获取DDM信息的手段一致,读取相关寄存器就行了。
DDM
DDM全称是Digital Diagnostic Monitoring,中文一般叫数字诊断监控。支持DDM的光模块会把自身运行状态暴露出来,主机侧可以通过I2C等管理接口读取这些诊断数据。
常见的DDM信息包括:
- 温度:光模块当前工作温度。
- 电压:光模块供电电压。
- Tx Bias:激光器偏置电流。
- Tx Power:发射光功率。
- Rx Power:接收光功率。
其中和光衰最直接相关的是Tx Power和Rx Power。Tx Power表示本端光模块发出去的光功率,Rx Power表示本端光模块收到对端的光功率。正常情况下,链路两端都可以读取各自模块的DDM信息,因此可以分别观察本端发光、收光以及对端发光、收光是否在合理范围内。
在Linux内核里,网卡驱动通常不会自己解释所有光模块诊断字段,而是通过ethtool相关接口把EEPROM或诊断页数据暴露给用户态。用户态工具再按照SFF-8472、SFF-8636等规范解析出温度、电压、发射功率、接收功率等信息。
Linux内核读取方式
以Intel i40e驱动为例,内核侧读取光模块信息的实现位于drivers/net/ethernet/intel/i40e/i40e_ethtool.c。驱动通过ethtool_ops把模块读取能力注册给内核ethtool框架:
1 | static const struct ethtool_ops i40e_ethtool_ops = { |
用户态执行类似下面的命令时:
1 | ethtool -m eth0 |
调用链大致是:
1 | ethtool用户态工具 |
i40e_get_module_info()主要做两件事:
- 检查固件是否支持读取模块EEPROM,例如是否具备
I40E_HW_CAP_AQ_PHY_ACCESS能力。 - 根据
hw->phy.link_info.module_type[0]判断模块类型,并设置modinfo->type和modinfo->eeprom_len。
对于SFP模块,驱动会读取SFF-8472相关字节,判断模块是否支持DDM。如果模块不支持SFF-8472或没有实现DDM,则按ETH_MODULE_SFF_8079处理;如果支持DDM,则设置为ETH_MODULE_SFF_8472。对于QSFP+和QSFP28,驱动会根据revision字段判断使用ETH_MODULE_SFF_8436还是ETH_MODULE_SFF_8636。
i40e_get_module_eeprom()负责真正读取EEPROM内容。它会根据模块类型选择不同的访问地址和页偏移:
- SFP模块通常访问I2C地址
0xA0,当offset超过基础页长度后切到0xA2读取诊断页。 - QSFP类模块按页组织读取,驱动根据offset计算页号和页内偏移。
最终每个字节通过i40e_aq_get_phy_register()从外部模块读出,填入ethtool框架传进来的buffer。这里驱动仍然只是把模块原始数据交给上层;温度、电压、Tx Power、Rx Power等字段的展示解析,主要由用户态ethtool按照模块规范完成。
i40e_aq_get_phy_register()这个接口名字里带aq,指的是Admin Queue。它不是CPU直接去bit-bang I2C,而是驱动构造一条网卡固件命令,通过Admin Send Queue交给网卡固件执行。源码里i40e_aq_get_phy_register()是一个简单宏,实际调用i40e_aq_get_phy_register_ext():
1 | i40e_aq_get_phy_register() |
i40e_aq_get_phy_register_ext()会把这些参数填进i40e_aqc_phy_register_access命令结构:
phy_interface:选择访问对象,例如I40E_AQ_PHY_REG_ACCESS_EXTERNAL_MODULE表示外部光模块。dev_address:设备地址。SFP场景下可以理解为I2C EEPROM地址,例如0xA0或0xA2。reg_address:模块页内偏移。cmd_flags:控制是否切换QSFP页等行为。
然后驱动通过i40e_asq_send_command()把descriptor放到Admin Send Queue,更新队列tail寄存器通知硬件。固件处理完成后会把结果写回descriptor,驱动再从cmd->reg_value取出读到的值。对于光模块EEPROM读取,i40e_get_module_eeprom()就是循环调用这个接口,每次读一个offset,最终拼出完整的EEPROM/DDM buffer。
i40e中QSFP Page的含义
40G QSFP/QSFP+模块通常按照SFF-8436或SFF-8636组织EEPROM数据。和SFP的0xA0、0xA2两个I2C地址不同,QSFP更多是一个低128字节区域加多个上半页,也就是lower page和upper page的结构。
在i40e驱动里,QSFP类模块的最大读取长度定义为:
1 |
这640字节可以按下面方式理解:
1 | offset 0 - 127 : lower page 00h |
i40e_get_module_eeprom()里对QSFP的offset处理也体现了这个映射:
1 | while (offset >= ETH_MODULE_SFF_8436_LEN) { |
这里ETH_MODULE_SFF_8436_LEN是256,所以超过第一个256字节后,每跨过128字节,addr就递增一次。对于QSFP场景,这里的addr可以理解为要访问的上半页编号:
1 | 原始offset 0 - 255 -> addr = 0, offset = 0 - 255 |
也就是说,i40e最终读到的QSFP buffer不是简单的连续物理地址,而是驱动把不同page拼接成一个线性buffer后交给上层。
各page的常见内容大致如下:
- lower page 00h:模块状态、告警标志、实时监控值,例如温度、电压、各lane的Tx Bias、Tx Power、Rx Power等。
- upper page 00h:模块基础信息,例如Identifier、Connector、速率能力、Vendor Name、Vendor PN、Vendor SN、Revision等。
- upper page 01h:扩展能力或应用相关信息,例如不同速率/编码模式的能力描述。具体内容和模块规范版本有关。
- upper page 02h:用户可写EEPROM或厂商自定义区域,很多模块里这页内容不一定有统一语义。
- upper page 03h:阈值、控制和mask类信息,常见包括温度、电压、光功率、电流等告警/告警恢复阈值,以及部分通道控制字段。
这也解释了排查时看到的现象:如果page1、page2、page3读出来完全一样,通常说明页切换或页访问没有真正生效。对于DDM来说,尤其要关注lower page 00h里的实时值,以及upper page 03h里的阈值。如果page 03h读错,解析出来的告警上限、下限就可能非常离谱。
DPDK调用方式
在DPDK里,应用层一般不直接调用i40e PMD内部的i40e_get_module_eeprom()。DPDK上层已经通过ethdev做了一层统一封装,应用侧调用rte_eth_*接口,具体由支持该能力的PMD去实现。
rte_eth_dev_get_module_info():获取光模块EEPROM类型和长度。rte_eth_dev_get_module_eeprom():读取光模块EEPROM数据。
DPDK的examples/ethtool里又基于ethdev封了一层更接近Linux ethtool语义的接口:
rte_ethtool_get_module_info()rte_ethtool_get_module_eeprom()
调用链大致是:
1 | APP |
对于i40e网卡,这两个API最终会走到drivers/net/i40e/i40e_ethdev.c里注册的dev_ops回调。ice、igb等驱动如果实现了同样的dev_ops,上层调用方式也是一致的;如果某个PMD或硬件不支持,则会返回-ENOTSUP。
这和Linux内核里的ethtool路径是对应关系:内核是netdev->ethtool_ops->get_module_info/get_module_eeprom,DPDK是rte_eth_dev_get_module_info/get_module_eeprom -> dev_ops。两者上层框架不同,但驱动内部要解决的问题类似,都是判断模块类型、读取对应EEPROM/DDM页,再把原始数据交给上层解析。
大致调用方式如下:
1 |
|
这里需要注意,ethdev这层API统一解决的是“向支持的PMD读取光模块信息”的问题。rte_eth_dev_get_module_eeprom()拿到的是模块EEPROM/DDM原始数据;如果上层工具或业务代码已经集成了解析逻辑,就可以直接展示温度、电压、Tx Power、Rx Power等DDM字段。光衰仍然不是驱动直接返回的单个字段,需要在拿到Tx Power和Rx Power之后再计算或对比,并且最好结合链路两端的数据一起判断。
DDM数据解析
DDM数据解析一般不在PMD驱动内部完成。PMD负责把模块EEPROM或诊断页读出来,上层再根据模块类型选择对应规范解析字段。
整体分层可以理解为:
1 | PMD驱动:读取原始字节 |
解析的第一步是判断模块类型。rte_eth_dev_get_module_info()返回的type和eeprom_len可以用来区分后续该按哪类规范处理:
- SFP/SFP+模块通常按SFF-8472解析。
- QSFP/QSFP+模块通常按SFF-8636解析。
- QSFP-DD、OSFP等新模块还可能涉及CMIS。
真正解析时,本质上就是从EEPROM buffer的固定偏移位置取出原始值,再按照规范定义的单位和比例因子换算成可读值。常见字段包括:
- 温度:通常是有符号定点数,需要换算成摄氏度。
- 电压:通常以固定步进表示,需要换算成V。
- Tx Bias:激光器偏置电流,通常换算成mA。
- Tx Power:发射光功率,通常先得到uW,再换算成dBm。
- Rx Power:接收光功率,通常先得到uW,再换算成dBm。
光功率字段常见的换算逻辑是先得到微瓦值,再转成dBm:
1 | dBm = 10 * log10(uW / 1000) |
例如解析出来的接收光功率是500 uW,那么对应功率大约是:
1 | 10 * log10(500 / 1000) = -3.01 dBm |
所以代码实现上通常会拆成两部分:
1 | read_module_eeprom() |
这样做的好处是驱动适配和数据解释解耦。i40e、ice、igb等PMD只要实现ethdev的模块读取回调,上层就可以复用同一套DDM解析逻辑。
光衰
光衰本质上是光信号在传输路径上的功率损耗。对于一条光纤链路来说,可以粗略理解为:
1 | 光衰 = 发射光功率 - 接收光功率 |
实际排查时不能只看单端的Rx Power,因为接收光功率低可能有多种原因:
- 对端光模块本身发光弱。
- 光纤距离过长或链路损耗过大。
- 光纤端面脏污、弯折、接头接触不良。
- 本端光模块接收能力异常。
- 模块类型、速率、波长或单多模光纤不匹配。
所以判断光衰时,一般需要结合两端DDM一起看。本端的Rx Power偏低,只能说明本端收到的光弱;还需要继续确认对端Tx Power是否正常,以及中间链路是否存在额外损耗。
问题
完成开发,进行自测的时候,发现其他模块都正常,但是40G的模块,读取出来的DDM信息存在问题
- DDM数据差异很大,告警上限阈值比下限阈值还低,并且数据很离谱
- 同一张网卡,同样的固件,存在能正常读取的模块,但是也存在读取的模块
- 不能正常读取的模块占多数,读取出来的都是异常的数据
排查
- 使用内核反接管网卡,用
ethool输出,和dpdk上输出的内容一致。 - 内核使用的内部定制的内核,为了确认排除是不是我们自己的定制内核有问题,安装了
ubuntu26(7.x的内核)还是一样的输出 - 将有问题的模块,插入交换机,用交换机的DDM查询接口,查询的信息有正常
- 用
ethtool按字节输出,发现page0之后的1,2,3三个下页输出的信息完全一样
结论
没有结论
- 光模块本身模块存在问题,没有page03的信息,查找了规范,确认不实现page03的数据,也是能符合规范的。同时也不能确实保证测试用的模块就是正规的光模块。
- 实现的协议有问题,对于特定厂商的模块,厂商不符合规范,对于DDM有自己的一套解析协议。
现在最大问题是,如果模块真没写数据,那么手上的大部分模块都不符合规范。如果符合规范,我们按照dpdk以及Linux内核那套去解析,似乎还是有问题。具体还要继续排查吧。






