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画这种图,清晰且可维护。
4.4 实际项目中的分层经验
我在做一款25G SmartNIC时,固件代码量大概有15万行。分层带来的好处很明显:
- 驱动层:只占2万行,但改动频率最低。换PHY芯片时,只需要改驱动层的PHY驱动模块,其他层完全不动。
- 协议栈层:占8万行,是核心。我们支持了VXLAN、NVGRE、GENEVE三种隧道,每种隧道都是一个独立的模块,通过统一的接口注册到协议栈层。
- 应用层:占5万行,变化最快。客户要支持P4可编程时,我们直接在应用层加了一个P4编译器后端,协议栈层和驱动层完全不用动。
总结一下:
- CPU和加速单元要明确分工:CPU管控制面,加速单元管数据面。
- 固件分三层:驱动层(搬数据)、协议栈层(懂协议)、应用层(做业务)。
- 层间接口要稳定,不要跨层调用。
- 应用层要插件化,方便快速迭代。
嗯,这一章就到这里。记住:好的架构是改出来的,不是设计出来的。先搭一个简单的分层框架,然后在迭代中不断优化。下一章我们会讲具体的开发环境搭建和调试技巧。
个人小贴士:如果你刚开始做SmartNIC固件,建议先从驱动层入手。把硬件调通了,协议栈层和应用层才有根基。我曾经见过一个团队,一上来就写应用层代码,结果硬件还没调通,白忙了两个月。
公众号:蓝海资料掘金营,微信deep3321