最近买了一块 Silicom 的 PE31625G24DIRA,一块基于 Intel EZFM10840 交换芯片的 100G 六端口交换卡,或者说是一张 switch on NIC。可能有大船二手价格很便宜,但折腾起来挺麻烦的。这篇文章就是把这段时间的发现都记下来,想到哪写到哪。
先说说这块卡是什么
PE31625G24DIRA 不是普通的网卡,它更像是一块完整的交换机。核心是 Intel EZFM10840 FocalPoint 交换芯片,支持 100G/40G/25G/10G/1G 多速率,有 6 个外部端口(两个 MPO24),算上 PCIE 金手指,一共八个可用端口。
最有意思的地方是:板子上集成了一块 Atom E3826作为控制平面。这个 SoM 运行完整的 Linux 系统,FM10K 通过 PCIe 连接到它。所有的 SDK 操作、配置下发、状态查询都在这个 SoM 上完成,外部主机只是通过网口或管理口与它通信。
所以这块卡的架构大概是:
外部主机
│ (网络流量)
▼
6 x 100G 外部端口 + 2 x PCIE3.0x8
│
▼
fm10k 交换芯片 ←──PCIe──→ Atom E3826 SoM (控制平面, Linux)
SSH 进 SoM 之后,lspci 能看到 FM10K 的设备 ID 0x15A4(Intel 厂商 ID 0x8086),内核已经加载了 fm10k 驱动。
IES SDK:控制这块芯片的软件栈
要真正控制 FM10K 的交换功能,光有内核驱动不够,需要 Intel 的 IES SDK(Intel Ethernet Switch SDK)。在 SoM 的文件系统里找到了 IES SDK 4.3.2,及其源码,预编译二进制里面有两个核心库:
libFocalpointSDK.so.4.3.2:SDK 主体,导出了整整 3338 个函数libLTStdPlatform.so.4.3.2:Liberty Trail 平台库,负责 I2C、SMBUS、IPMI 等板载外设,导出 34 个函数
3338 个函数这个数字挺庞大的。我后来做了个交叉引用,把这些导出符号和头文件声明对应起来,大概分布是:
| 分类 | 数量 |
|---|---|
内部 API(internal-api) |
1752 |
公开 API(public-api) |
663 |
平台相关(platform) |
339 |
调试接口(debug) |
155 |
ALOS 抽象层(alos) |
113 |
| 未匹配声明 | 203 |
公开 API 只有 663 个,但整个库导出了 3338 个符号——大量内部函数也被导出了,这在闭源 SDK 里挺常见的,方便调试工具直接调用内部函数。
构建系统:比想象中复杂
IES SDK 的构建系统是基于 GNU Make 的,入口在 ies/src/Makefile,需要在 ies/src/ 目录下执行 make。
构建系统支持多个平台:
| 平台 | 说明 |
|---|---|
libertyTrail |
主参考平台,x86_64/i386,FM10K |
atwoodChannel |
基于 libertyTrail,单交换机 |
boulderRapids |
基于 libertyTrail,单交换机 |
cathedralGlen |
基于 libertyTrail,支持多交换机 SWAG |
cliftonGorge |
基于 libertyTrail,2~3 交换机 |
rubyRapids |
基于 libertyTrail,单交换机 |
PE31625G24DIRA 对应的是 libertyTrail 平台。
有一个坑值得记录:make install 的依赖链里不包含 basic 示例程序和 rdifd 守护进程。rdifd 被定义为 EXAMPLES_SIL(Silicom 专用示例,而且 rdif 的其他工具也没有包含),默认 make 会构建它,但 make install 和 make install-examples 都不会安装它,需要手动复制:
cp -f ../examples/rdifd/rdifd ${INSTALL_DIR}/examples/
另外,构建前必须确认内核支持 UIO:
grep CONFIG_UIO /boot/config-$(uname -r)
# CONFIG_UIO=y 或 CONFIG_UIO=m 都行
# 如果是 m,需要 modprobe uio && modprobe fm10k
配置文件:SDK 的"地图"
SDK 启动时会读取一个平台属性配置文件(fm_platform_attributes.cfg),这个文件定义了端口映射、SerDes 参数、I2C 外设等所有硬件相关的配置。
配置文件里有几个关键参数:
numSwitches = 1 # 系统中只有 1 个 FM10K
platformName = libertyTrail
uioDevName = /dev/uio0 # UIO 设备节点,用于 PCIe BAR4 mmap
msiEnabled = true # 启用 MSI 中断
netDevName = enp1s0 # Raw socket 收发包用的网卡名
cpuPort = 9 # CPU 逻辑端口号
端口拓扑映射也在这里定义。FM10K 内部有 EPL(以太网端口逻辑)、PCIE(PCIe Endpoint)、TE(隧道引擎)三类接口,配置文件把逻辑端口号映射到物理接口:
逻辑端口 物理接口 用途
LOG=0 PCIE=8 CPU 端口 (PEP#8)
LOG=1 EPL=0 LANE=0-3 QSFP0 (100G)
LOG=2 EPL=1 LANE=0-3 QSFP1
...
LOG=7 PCIE=0 PCIe 端口 0
注意 EPL 编号不连续(0,1,2,5,6,7),跳过了 EPL 3 和 4,说明板卡物理布线跳过了这两个 SerDes 组。PCIE 4 应该是 SOM 所连接的那个 PCIE。
硬件访问:UIO + mmap,不是 ioctl
这是我觉得最有意思的发现。
通常想到"用户态程序控制硬件",第一反应是通过内核驱动的 ioctl 接口。但 IES SDK 的主要路径完全不是这样——它通过 UIO(Userspace I/O)驱动,把 FM10K 的 PCIe BAR4 寄存器空间直接 mmap 到用户态进程的地址空间,然后像读写普通内存一样操作硬件寄存器,整个 SDK 完全运行在用户态。
整条链路:
SDK API 层(fmSetPortAttribute 等)
│
▼
寄存器抽象层(ReadUINT32 / WriteUINT32)
│
▼
UIO 内存映射(/dev/uio0 → mmap BAR4 64MB)
│
▼
FM10K 硬件寄存器(PCIe BAR4)
mmap 完成后,switchMem 指针就指向了 FM10K 的寄存器空间,之后的读写是这样的:
// 读寄存器——直接内存读,没有系统调用
*value = GET_PLAT_MEMMAP_CSR(sw)[addr];
// 写寄存器——直接内存写,没有系统调用
GET_PLAT_MEMMAP_CSR(sw)[addr] = value;
GET_PLAT_MEMMAP_CSR(sw) 展开后就是 (volatile fm_uint32 *)switchMem,对 mmap 地址的直接 32 位读写。
映射地址被固定在 0x08000000,这是为了保证多进程场景下指针的一致性。
中断处理也走 UIO:对 UIO fd 调用 select() 等待中断,read() 读取中断计数(同时重新使能中断),write(1/0) 使能或禁止中断。SDK 里有专门的中断监听线程处理这个。
RDIF 工具链:Silicom 的控制平面实现(并不是很有用,代码还存在 fall through bug)
在 SDK 的示例程序里,有一个叫 rdifd 的守护进程,版本号 6.0.14i,4339 行 C 代码。这不是一个简单的示例,而是 Silicom 实际用于生产的控制平面守护进程。
rdifd 的架构是这样的:
外部控制程序 (rdifctl / 网管软件)
│
│ Unix socket: /var/run/rdifctl.sock
│ 协议: if_rdi_t 结构体序列化
▼
rdifd (用户空间守护进程)
│
│ IES SDK API
│ fmAddACLRule / fmSetPortAttribute / fmGetPortCounters ...
▼
libFocalpointSDK.so
│
│ UIO mmap → /dev/uio0 → PCIe BAR4
▼
FM10K 硬件
任何需要控制 FM10K 的程序,只需要包含 rdi_lib.h,连接到 /var/run/rdifctl.sock,发送 if_rdi_t 结构体就能下发命令。
支持的命令覆盖面很广:
- ACL 规则:下发 DROP/PERMIT/REDIRECT/MIRROR 规则,支持五元组、VLAN、MPLS、UDF 匹配
- 端口管理:设置端口转发掩码、查询链路状态、设置环回
- 负载均衡组(LBG):创建/删除 LBG,管理成员端口
- 镜像组:创建/删除镜像,管理镜像端口
- 哈希配置:配置 L2/L3 哈希键,影响 LAG/ECMP 负载均衡
- 统计查询:读取端口 80+ 计数器、16 个优先级队列包计数
- SerDes 诊断:动态调整预游标/主游标/后游标,设置 PRBS 测试
- 硬件诊断:PHY 寄存器读写、CPLD 读写、GPIO 操作
rdifd 支持几种工作模式,通过 RDI_SET_CFG 命令切换:
| 模式 | 说明 |
|---|---|
RDI_CFG_BYPASS_ALL |
旁路模式:端口两两直连,流量直通 |
RDI_CFG_INLINE |
内联模式:端口 1-4 流量通过 ACL 重定向到端口 5 |
RDI_CFG_MON2 |
监控模式:所有端口隔离 |
RDI_CFG_INIT |
初始化模式:启动时默认,同 MON2 |
ACL 规则下发有一个细节值得记录:所有规则都会自动附加 FM_ACL_ACTIONEXT_COUNT,支持后续查询命中次数。规则更新使用 FM_ACL_COMPILE_FLAG_NON_DISRUPTIVE,不中断现有流量。
光模块访问:I2C + PCA MUX
FM10K 的两个 FCI 光模块通过 I2C 访问,但因为端口数量多,需要 PCA9548 I2C 开关(MUX)来扩展 I2C 总线。板子上并不存在 PCA MUX,所有功能是由板载的 Lattice 的 FPGA 实现的。
访问流程大概是:
SDK → fmPlatformI2cWriteRead()
→ 选择 PCA9545 通道(对应目标光模块槽位)
→ 访问模块的 I2C 地址(0x50 页 0 / 0x51 页 1)
→ 读取光功率、温度、电压等 DOM 信息
光功率读取需要从模块的 DOM(Digital Optical Monitoring)寄存器里读原始 ADC 值,再按厂商规格换算成 dBm。这部分逻辑在 SDK 的平台代码里有实现,但需要配置文件里正确配置 PCA MUX 的地址和通道映射。
SAI 适配:让 SONiC 能控制这块卡
研究到后期,开始考虑一个更大的问题:能不能让这块卡跑 SONiC?
SONiC 通过 OCP SAI(Switch Abstraction Interface)标准接口控制交换芯片,而 IES SDK 有自己的一套 API。要让两者对接,需要写一个 SAI 适配层,把 SAI 的调用翻译成 IES SDK 的调用。
整体架构是:
SONiC (syncd)
│ SAI API 调用
▼
IES-SAI 适配层(libsai.so)
│ IES SDK API 调用
▼
libFocalpointSDK.so
│
▼
FM10K 硬件
IES SDK 的 API 覆盖面很广,大部分 SAI 模块都能找到对应的实现:
| SAI 模块 | 支持程度 |
|---|---|
| Switch / Port / VLAN / FDB / LAG / STP | ✅ 完全支持 |
| Virtual Router / Route / NextHop / Neighbor | ✅ 完全支持 |
| ACL / Mirror / QoS / Policer | ✅ 完全支持 |
| Tunnel (VxLAN/NVGRE) / Multicast / sFlow | ✅ 完全支持 |
| MACsec / MPLS | ❌ 硬件不支持 |
适配层有几个设计要点:
OID 管理:SAI 用 64-bit sai_object_id_t 标识对象,IES SDK 用整数 ID。适配层需要维护双向映射哈希表,OID 编码格式为 [对象类型 8bit][交换机索引 8bit][内部索引 48bit]。
SerDes Lane 分配:SAI 的 create_port 通过 SAI_PORT_ATTR_HW_LANE_LIST 指定物理 lane 列表,适配层需要把 lane 数量和速率组合转换成 IES SDK 的 FM_PORT_ETHERNET_INTERFACE_MODE 枚举值(比如 4 lanes + 100G → FM_ETH_MODE_100GBASE_SR4)。
事件通知:IES SDK 的事件(MAC 学习、端口 link 变化、CPU 收包)通过事件线程异步通知,适配层需要把这些转换成 SAI notification callback。
这个适配层目前还在规划阶段,但 API 映射关系已经梳理清楚了,实现路径是明确的。
一些零散发现
关于 TestPoint:SDK 里有一个基于 Perl 的测试框架叫 TestPoint,通过 SWIG 生成的 Perl 绑定(SDK.so、FM10000.so 等)直接调用 SDK 函数。这个框架主要用于芯片验证,但也可以用来做快速原型验证。可以快速确定你的交换机是不是完好的。
关于 SWAG:SDK 支持多交换机聚合(Switch Aggregation),通过 SUPPORT_SWAG=1 编译选项启用。支持 Fat-Tree 和 Mesh 拓扑,最多可以聚合多个 FM10K 芯片。PE31625G24DIRA 是单芯片,用不上这个特性,但了解一下也好。
关于端口速率协商:FM10K 的端口速率协商(Auto-Negotiation)实现在 SDK 内部,有一套完整的状态机。rdifd 里有 RDI_SET_PRECURSOR/RDI_SET_CURSOR/RDI_SET_POSTCURSOR 命令可以动态调整 SerDes 均衡参数,这在调试信号完整性问题时很有用。
总结
我也是第一次折腾类似的东西,折腾这块卡的收获比预期多很多。从硬件架构到 SDK 内部实现,从 UIO 的 mmap 机制到 RDIF 的控制协议,每一层都有值得深挖的细节。
后续打算继续研究 SAI 适配层的实现,以及看看能不能在这块卡上跑起来一个简化版的 SONiC。
硬件:Silicom PE31625G24DIRA(Intel FM10840 + Atom E3826 SoM)
软件:IES SDK 4.3.2,Linux,fm10k 内核驱动