SONiC × CMIS:光模块固件工程师的开源参考实现#
一、SONiC 是什么#
SONiC(Software for Open Networking in the Cloud)是一个开源的网络操作系统(NOS),由微软于 2016 年在 OCP(Open Compute Project)峰会上首次发布,最初为 Azure 数据中心的白盒交换机设计。
其核心设计理念可以概括为三个词:解耦、开源、社区驱动。
- 解耦:SONiC 运行在标准 x86 白盒交换机上,通过 SAI(Switch Abstraction Interface)抽象层屏蔽底层 ASIC 差异(支持 Broadcom、Marvell、Nvidia Spectrum、Intel Tofino 等),上层网络功能与硬件完全分离。
- 开源:全部代码托管在 GitHub(
sonic-net组织下),采用 Apache 2.0 / BSD 许可证,任何人可以阅读、修改、分发。 - 社区驱动:由 Linux 基金会托管,微软、Meta、Google、阿里、腾讯、快手、Cisco、Juniper、Nvidia 等公司共同维护,每月有社区例会和版本发布。
架构概览#
SONiC 采用 容器化微服务架构,每个功能域运行在独立的 Docker 容器中:
┌────────────────────────────────────────────────────────────┐
│ SONiC NOS │
├──────────┬──────────┬──────────┬──────────┬────────────────┤
│ BGP │ LLDP │ SNMP │ PMON │ SWSS │
│ (FRR) │ │ │ 容器 │ (Switch │
│ │ │ │ │ State Service)│
│ │ │ │ xcvrd │ │
│ │ │ │ psud │ │
│ │ │ │ thermalctld│ │
│ │ │ │ ledd │ │
├──────────┴──────────┴──────────┴──────────┴────────────────┤
│ Redis Database (StateDB / ConfigDB / APPL_DB)│
├────────────────────────────────────────────────────────────┤
│ SAI (Switch Abstraction Interface) │
├────────────────────────────────────────────────────────────┤
│ ASIC Vendor SDK │
├────────────────────────────────────────────────────────────┤
│ 交换芯片 (Memory-Mapped / MDIO) │
└────────────────────────────────────────────────────────────┘plaintext其中与光模块直接相关的,是 PMON 容器 中的 xcvrd(Transceiver Daemon)。后文将重点展开。
二、SONiC 用在哪里#
SONiC 的部署场景已经远超最初的”微软 Azure 内部工具”:
| 场景 | 代表用户 | 规模 |
|---|---|---|
| 超大规模云数据中心 | Microsoft Azure、Meta、阿里云、腾讯云、快手 | 数十万端口 |
| 电信运营商 | AT&T、德国电信、中国电信(部分试点) | 城域网/骨干网 |
| 企业园区网 | 部分金融、高校网络 | 中小规模 |
| AI/GPU 集群互联 | 多家 AI 基础设施公司 | 高密度 400G/800G |
关键特征:SONiC 运行的交换机,几乎全部使用可插拔光模块(QSFP-DD、OSFP、QSFP56 等)作为端口介质。 这意味着每一台 SONiC 交换机,都需要与光模块进行完整的 CMIS 协议交互。
对于光模块厂商而言,“通过 SONiC 兼容性验证”正在成为进入云厂商供应链的基本门槛。
三、为什么光模块固件很重要#
一颗 400G/800G 光模块内部,除了光芯片(激光器、探测器、TIA/Driver)之外,还有一颗 MCU(通常是 Cortex-M0/M3/M4 级别),运行着模块固件。这颗固件的职责:
- 实现 CMIS 协议栈:响应主机的所有管理请求(读/写 EEPROM、状态机驱动、应用选择、告警上报);
- 控制光电器件:激光器偏置、APD/TIA 增益、CDR 锁定、均衡器调节;
- 执行 DSP 算法(针对 PAM4 模块):信号均衡、眼图优化;
- 实现固件升级(CDB):支持带内固件更新;
- 安全与认证:部分场景需要模块认证(Module Authentication)。
固件是光模块与主机之间唯一的”语言通道”。 一颗光模块,无论光学性能多好,如果固件的 CMIS 实现有 bug——状态机卡死、寄存器映射错误、中断标志未及时清除——在主机侧的表现就是”模块不识别”或”链路起不来”。
在封闭交换机时代,主机侧 CMIS 实现是黑盒,固件工程师只能被动适配。而 SONiC 的出现,彻底改变了这一局面。
四、SONiC 中 CMIS 相关代码的结构#
这是本文的核心部分。SONiC 中与光模块/CMIS 相关的代码分布在多个仓库中,以下逐一说明。
4.1 代码仓库地图#
sonic-net/
├── sonic-buildimage/ # 主仓库,集成构建
│ ├── platform/ # 各硬件平台适配
│ └── dockers/docker-platform-monitor/ # PMON容器定义
│
├── sonic-platform-daemons/ # 平台守护进程(核心)
│ └── sonic-xcvrd/
│ ├── xcvrd/
│ │ ├── xcvrd.py # 主守护进程:模块发现、状态机驱动
│ │ └── utilities/ # 辅助工具
│ └── tests/
│
├── sonic-platform-common/ # 平台公共库(核心)
│ └── sonic_platform_base/
│ └── sonic_xcvr/
│ ├── api/
│ │ └── public/
│ │ ├── cmis.py # ★ CMIS 协议实现(CmisApi)
│ │ ├── cmis_cdb.py # ★ CDB 命令实现
│ │ ├── sff8436.py # 旧协议(QSFP28)
│ │ └── sff8472.py # 旧协议(SFP+)
│ ├── mem_maps/
│ │ └── public/
│ │ ├── cmis.py # ★ CMIS 内存映射定义
│ │ └── cmis_cdb.py # CDB 内存映射
│ ├── codes/
│ │ └── public/
│ │ └── cmis.py # ★ CMIS 编码/解码(应用码等)
│ └── fields/
│ └── public/
│ └── cmis.py # ★ CMIS 字段定义
│
└── sonic-utilities/ # CLI 工具
└── show/interfaces/ # show interface transceiver 命令plaintext4.2 核心组件:sonic_platform_base/sonic_xcvr/api/public/cmis.py#
这是 SONiC 中 CMIS 协议的主机侧实现,对应固件工程师最关心的内容:
(1)内存映射(mem_maps/cmis.py)
定义了 CMIS 5.x 的全部 Page/Bank 结构:
# 简化示意
class CmisMemMap:
# Page 0x00 (Lower)
MODULE_IDENTIFIER = ...
MODULE_STATE = ... # 模块状态机当前状态
INTERRUPT_FLAGS = ... # 中断标志
# Page 0x00 (Upper)
MODULE_FLAGS = ...
MODULE_MONITORS = ... # 温度、电压
# Page 0x10
DP_STATE = ... # 数据通路状态
DP_INIT_CONTROL = ... # 数据通路初始化控制
# Page 0x11
APP_ADVERTISEMENT = ... # 应用码通告
# Page 0x12, 0x13
DP_CONFIG = ... # 数据通路配置
# Page 0x2F
CDB = ... # Command Data Blockpython对固件工程师的意义:这里定义了主机侧”认为”的寄存器地址。如果固件的 Page 偏移、Bank 选择与这里不一致,主机就读不到正确数据。
(2)CMIS 状态机驱动(api/public/cmis.py)
class CmisApi:
def enter_datapath_init(self, ...):
"""驱动模块进入 DPInit 状态"""
def get_datapath_state(self, ...):
"""读取数据通路状态"""
def configure_datapath(self, lane, tx_enable, rx_enable, ...):
"""配置单个 lane 的 Tx/Rx"""
def select_application(self, app_code):
"""选择应用码(400G-DR4, 800G-2xFR4 等)"""
def get_module_state(self):
"""读取模块状态:ModuleReady / ModuleLowPwr / ..."""python对固件工程师的意义:这里就是”主机怎么操作模块”的完整逻辑。每一步的顺序、超时、重试策略,全部可读。
(3)CDB 实现(api/public/cmis_cdb.py)
class CmisCdbApi:
def firmware_download(self, image):
"""通过 CDB 下载固件镜像到模块"""
def firmware_commit(self):
"""触发模块固件切换"""
def firmware_activate(self):
"""激活新固件"""
def get_firmware_version(self):
"""查询模块当前固件版本"""python对固件工程师的意义:CDB 升级流程中主机侧的每一步(分块大小、校验方式、超时、重试)都在这里。固件端必须严格匹配。
(4)应用码编解码(codes/public/cmis.py)
class CmisCodes:
APPLICATIONS = {
0x01: "400GBASE-DR4",
0x18: "800G-2xFR4",
# ...
}
HOST_INTERFACE = { ... }
MEDIA_INTERFACE = { ... }python对固件工程师的意义:模块通告(Application Advertisement)中写入的应用码编号,必须与主机侧的解码表一致,否则主机无法识别模块能力。
4.3 守护进程:sonic-xcvrd/xcvrd/xcvrd.py#
xcvrd 是实际运行时的守护进程,它调用上述 CMIS 库,完成:
| 功能 | 说明 |
|---|---|
| 模块热插拔检测 | 通过 GPIO 中断感知模块插入/拔出 |
| 模块初始化序列 | 按 CMIS 规范驱动 Module State Machine 和 DP State Machine |
| DOM 轮询 | 周期性读取温度、电压、Tx Power、Rx Power、Bias Current |
| 中断处理 | 读取模块中断标志,映射为系统告警 |
| 状态上报 | 将模块状态写入 Redis StateDB |
| 固件升级 | 调用 CDB API 执行模块固件 OTA |
关键参数(对固件工程师至关重要):
# xcvrd 中的典型配置(具体值因版本而异)
TRANSCEIVER_INIT_TIMEOUT = 60 # 模块初始化超时(秒)
TRANSCEIVER_RETRY_COUNT = 3 # I²C 读取重试次数
DOM_POLL_INTERVAL = 1 # DOM 轮询周期(秒)
DP_INIT_POLL_INTERVAL = 1 # DP 状态轮询周期(秒)python固件工程师可以从源码中确认:主机不会在 1 秒内连续发起多次 I²C 读取,不会在模块初始化完成前强制 Reset,超时窗口足够宽。 这些是设计固件状态机时序的直接依据。
4.4 CLI 与调试工具#
SONiC 提供了一组命令行工具,方便在交换机上直接观察模块状态:
# 查看所有光模块基本信息
show interfaces transceiver eeprom
# 查看 DOM 数据(温度、功率等)
show interfaces transceiver dom
# 查看模块详细信息(JSON 格式)
show interfaces transceiver info
# 查看模块固件版本
show interfaces transceiver firmware
# 触发 CDB 固件升级
sudo sfputil firmware upgrade <port> <image_path>
# 直接读取模块 EEPROM 原始数据(调试用)
sudo sfputil show eeprom <port> --page 0x11bash对固件工程师而言,sfputil show eeprom 可以直接 dump 模块任意 Page 的原始字节,是验证固件 EEPROM 写入是否正确的利器。
五、如何借助 SONiC 辅助光模块固件开发#
将上述信息整合,以下是光模块固件工程师利用 SONiC 的具体方法:
5.1 以 SONiC 代码作为”主机侧规范解读”#
CMIS 规范是英文文档,许多描述存在歧义(SHOULD / MAY / RECOMMENDED)。当遇到不确定的条款时:
- 打开
sonic-platform-common/sonic_xcvr/api/public/cmis.py - 搜索对应的寄存器/状态机节点
- 观察 SONiC 的实际读写顺序、位域操作、超时处理
- 以此为基准实现固件端
SONiC 的实现,代表了业界主流主机的行为。 因为 SONiC 被多家云厂商采用,兼容 SONiC 就等于兼容了最大的客户群。
5.2 搭建本地验证环境#
无需购买商用交换机,即可在实验室完成完整的 CMIS 交互验证:
方案 A:SONiC-VS(虚拟交换机)
- 在 x86 服务器或虚拟机上安装 SONiC-VS
- 通过 I²C-to-USB 转接板连接真实光模块
- 修改 platform 驱动,将物理 I²C 总线映射到 SONiC 的 port 抽象
- 运行 xcvrd,观察完整的模块初始化、DOM 读取、中断处理流程
方案 B:仅运行 xcvrd
- 不安装完整 SONiC,仅提取
sonic-platform-daemons和sonic-platform-common - 编写一个最小化的 I²C 驱动后端(对接转接板)
- 直接调用
CmisApi的方法,逐步验证固件的每个寄存器
方案 C:单元测试级验证
- 参考
sonic-xcvrd/tests/中的 mock 测试 - 用 Python 模拟 I²C 读写,验证固件输出的 EEPROM 内容是否符合 SONiC 的解析预期
5.3 定位兼容性问题的标准流程#
当模块在某台 SONiC 交换机上出现”不识别""链路不起来""DOM 数据异常”等问题时:
步骤 1:查看 xcvrd 日志
docker exec pmon cat /var/log/xcvrd.log
→ 确认 xcvrd 卡在哪一步(模块检测?DP Init?应用选择?)
步骤 2:用 sfputil 读取原始 EEPROM
sudo sfputil show eeprom Ethernet0 --page 0x00
→ 对比固件实际写入的值
步骤 3:对照 CMIS 内存映射
打开 sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py
→ 确认偏移地址、位域定义
步骤 4:用 I²C 逻辑分析仪抓波形
→ 确认是否存在地址错误、NACK、总线仲裁失败
步骤 5:修复固件 → 重新验证plaintext整个过程不再依赖交换机厂商的技术支持,完全自主可控。
5.4 跟踪 CMIS 新版本的实现#
CMIS 协议持续演进(5.0 → 5.1 → 5.2 → 5.3),新版本可能增加:
- 新的应用码(如 800G-2xLR4、1.6T 相关)
- 新的状态机节点(如 CPO 场景下的特殊低功耗模式)
- CDB 扩展命令
- 模块认证(Module Authentication)
SONiC 社区通常会在规范发布后数月到一年内完成主机侧实现。固件工程师可以:
- 关注
sonic-net/sonic-platform-common的 PR 和 Issue - 在 CMIS 新版本发布后,第一时间查看 SONiC 的适配进度
- 提前根据 SONiC 的实现方向调整固件开发计划
5.5 参与社区,影响主机行为#
如果发现 SONiC 的 xcvrd 对某个 CMIS 条款的实现不合理(例如:超时太短导致某些模块初始化失败),可以:
- 在
sonic-net/sonic-platform-daemons提交 Issue,附上模块侧的日志和时序分析 - 提交 PR 修复(例如增加超时、调整重试策略)
- 在 OCP 的 SONiC 社区会议上展示问题
光模块固件工程师不再是生态的被动接受者,而是可以主动影响主机行为的参与者。
六、总结#
| 问题 | 答案 |
|---|---|
| SONiC 是什么? | 开源网络操作系统,容器化微服务架构,运行在白盒交换机上 |
| 用在哪里? | 全球头部云厂商数据中心、部分电信和企业网络 |
| 为什么光模块固件重要? | 固件是模块与主机之间唯一的协议通道,决定互操作性 |
| SONiC 中 CMIS 库在哪? | sonic-platform-common/sonic_xcvr/(协议实现)+ sonic-xcvrd(运行时守护进程) |
| 怎么用 SONiC 帮助写固件? | 阅读主机侧代码、本地搭建验证环境、用 CLI 工具调试、跟踪新版本、参与社区 |
对于光模块固件工程师而言,SONiC 的最大价值在于:将曾经不可见的主机侧 CMIS 实现,变成了一份可阅读、可运行、可调试的开源代码。 它不是替代 CMIS 规范,而是为规范提供了最广泛采用的”参考实现”。
读懂 SONiC 的 CMIS 代码,就是读懂你的客户。
参考资源: