4、固件架构设计:主控CPU与数据面加速单元(NPU/FPGA)的协同、固件分层模型(驱动层、协议栈层、应用层)

好,咱们进入第四章。这一章讲的是固件架构设计的核心——说白了,就是怎么让主控CPU和加速单元(NPU或FPGA)好好配合,以及固件代码该怎么分层。

我刚开始做SmartNIC时,踩过一个坑:把CPU和FPGA当成两个独立模块来开发,结果联调时发现数据通路根本对不上。嗯,从那以后我明白了一个道理——协同设计必须在架构阶段就定好

4.1 主控CPU与数据面加速单元的协同

SmartNIC里,CPU负责控制面,NPU/FPGA负责数据面。这俩怎么分工?我个人的习惯是:CPU管“慢路径”,加速单元管“快路径”

  • CPU:处理协议协商、表项下发、异常上报、管理面交互。这些操作不频繁,但对灵活性要求高。
  • NPU/FPGA:处理报文解析、查表、转发、封装解封装。这些操作要求线速,必须硬件化。

举个例子,我在做RoCEv2卸载时,CPU负责建QP(队列对)和配置拥塞控制参数,FPGA负责处理数据包的CRC、序列号检查和重传。这样CPU的负载从每包处理降到了每连接处理,性能提升了一个数量级。

协同的关键接口

  • 控制通道:CPU通过PCIe或内部总线,把流表、ACL规则下发给加速单元。
  • 数据通道:加速单元处理完报文后,通过描述符队列把结果(比如“已转发”或“需上送CPU”)通知CPU。
  • 状态同步:加速单元定期把统计信息(如丢包计数、队列深度)同步给CPU,用于拥塞控制。

你想想看,如果CPU和加速单元之间没有清晰的握手协议,就会出现“CPU以为FPGA处理了,FPGA却还在等指令”的情况。我曾经在调试一个P4交换机时,就因为描述符队列的深度没配够,导致FPGA频繁丢包,查了三天才找到原因。

4.2 固件分层模型

固件架构不能一锅粥。我建议分成三层:驱动层、协议栈层、应用层。每一层只做自己的事,层与层之间通过标准接口通信。

4.2.1 驱动层

驱动层是固件的最底层,直接跟硬件寄存器打交道。它的职责包括:

  • 初始化硬件(PHY、MAC、DMA引擎)
  • 管理描述符环(Rx/Tx ring)
  • 处理中断(MSI-X、链路状态变化)
  • 提供硬件抽象接口(比如“发送一个包”、“读取统计计数器”)

我个人的经验是:驱动层一定要做“防御性编程”。因为硬件行为有时候会出乎意料——比如某个寄存器在特定条件下返回全0,或者中断被意外触发。我曾经在一个项目中,因为没检查DMA完成标志的合法性,导致固件在收到空描述符时直接崩溃。从那以后,我在驱动层加了严格的参数校验。

避坑指南:驱动层不要做任何协议解析。我曾经见过有人把ARP解析写在驱动里,结果换了一个PHY芯片后,整个驱动都得重写。驱动层只负责“搬数据”,不负责“懂数据”。

4.2.2 协议栈层

协议栈层是固件的“大脑”。它负责解析报文、维护协议状态机、处理控制面协议。常见的协议栈包括:

  • L2:MAC学习、STP、LLDP
  • L3:ARP、ICMP、路由查找
  • L4:TCP/UDP会话管理、NAT
  • Overlay:VXLAN、Geneve、NVGRE

这里要注意:协议栈层不能直接操作硬件。它只能调用驱动层提供的接口。比如,协议栈要发送一个ARP请求,它调用驱动层的“发送包”接口,而不是直接写发送描述符。

我记得在做一个VXLAN卸载项目时,协议栈层需要处理VTEP的MAC学习。一开始我把MAC表直接放在FPGA里,结果发现CPU查表太慢。后来改成“FPGA做快速查表,CPU做慢速学习”,性能才达标。说白了,协议栈层的设计要考虑到硬件加速的边界

4.2.3 应用层

应用层是固件的最上层,负责实现具体的业务逻辑。比如:

  • OVS卸载:把OpenFlow流表下发给协议栈层
  • 存储加速:NVMe over Fabrics的卸载
  • 安全卸载:IPsec、TLS的硬件加速
  • 遥测:INT(In-band Network Telemetry)数据收集

应用层的特点是:变化快。今天客户要支持VXLAN,明天要支持GENEVE,后天又要支持SRv6。所以应用层一定要设计成插件化的,方便动态加载。

注意:应用层不要直接调用驱动层接口。我曾经见过一个项目,应用层直接操作DMA描述符,结果协议栈层升级后,描述符格式变了,应用层代码全部报废。正确的做法是:应用层调用协议栈层提供的API,协议栈层再调用驱动层。

4.3 分层模型的结构图

下面这张图展示了固件分层模型以及CPU与加速单元的协同关系。我习惯用SVG画这种图,清晰且可维护。

SmartNIC固件分层架构与协同模型 应用层 OVS卸载 | 存储加速 | 安全卸载 | 遥测 调用协议栈层API,不直接操作硬件 协议栈层 L2 (MAC/STP) | L3 (ARP/路由) | L4 (TCP/UDP) | Overlay (VXLAN/Geneve) 维护协议状态机,调用驱动层接口 与加速单元协同:下发流表、同步状态 驱动层 硬件初始化 | 描述符管理 | 中断处理 | 硬件抽象接口 直接操作寄存器,不做协议解析 硬件层 主控CPU (控制面) ←→ NPU/FPGA (数据面) 控制通道:PCIe/内部总线 | 数据通道:描述符队列 状态同步:统计信息、拥塞控制 协同关键点 1. 控制通道:表项下发 2. 数据通道:描述符队列 3. 状态同步:统计/拥塞

4.4 实际项目中的分层经验

我在做一款25G SmartNIC时,固件代码量大概有15万行。分层带来的好处很明显:

  • 驱动层:只占2万行,但改动频率最低。换PHY芯片时,只需要改驱动层的PHY驱动模块,其他层完全不动。
  • 协议栈层:占8万行,是核心。我们支持了VXLAN、NVGRE、GENEVE三种隧道,每种隧道都是一个独立的模块,通过统一的接口注册到协议栈层。
  • 应用层:占5万行,变化最快。客户要支持P4可编程时,我们直接在应用层加了一个P4编译器后端,协议栈层和驱动层完全不用动。

总结一下

  • CPU和加速单元要明确分工:CPU管控制面,加速单元管数据面。
  • 固件分三层:驱动层(搬数据)、协议栈层(懂协议)、应用层(做业务)。
  • 层间接口要稳定,不要跨层调用。
  • 应用层要插件化,方便快速迭代。

嗯,这一章就到这里。记住:好的架构是改出来的,不是设计出来的。先搭一个简单的分层框架,然后在迭代中不断优化。下一章我们会讲具体的开发环境搭建和调试技巧。

个人小贴士:如果你刚开始做SmartNIC固件,建议先从驱动层入手。把硬件调通了,协议栈层和应用层才有根基。我曾经见过一个团队,一上来就写应用层代码,结果硬件还没调通,白忙了两个月。


公众号:蓝海资料掘金营,微信deep3321