这块主控板上只有一个原生千兆口,产品却要求至少四个千兆LAN口加一个上联口,光靠板载网口根本不够用。我最后交出来的方案是“RK3576 + RTL8367RB(SPI管理模式)”,核心思路很简单:数据面走RGMII,管理面走SPI,把一颗五口千兆交换芯片做成外挂模块,由Linux侧统一控制。这篇文章就把整个项目的选型逻辑、硬件接法、软件调试到最终跑通的过程完整写出来,适合正在做多口网关、NAS、软路由或者边缘计算设备的人参考。
1. 需求分析与选型:为什么是“RK3576+交换芯片”
1.1 核心需求拆解:多口从哪里来
我刚接到这个项目时,需求表格里写得很明确:4个千兆LAN口,1个千兆上联口,每个端口都要能做独立的VLAN隔离,同时设备要留有足够的CPU余量去跑应用层业务。单看主控性能,RK3576绰绰有余,四核A72加四核A53的大小核架构,NPU也够用,但问题是板子拿过来只有一路RGMII接口,直接接一颗千兆PHY只能出一个口,距离五个口差的不是一星半点。
这种情况下有三条路线可以选择。第一条是找带双路GMAC的主控,但RK3576在外面做产品很少见到原生多网口的版本,这条路等于换平台,代价太大。第二条是外接多颗千兆PHY,每个PHY接一个RJ45,再用Linux的bridge把几个接口桥接起来,逻辑上能通,但所有二层转发都压在CPU协议栈上,流量一大CPU占用直接起飞,根本不满足“留足CPU余量”这条要求。第三条就是外接交换芯片,让芯片内部的硬件交换引擎处理端口之间的数据转发,只有需要上送协议栈的报文才会走到CPU。
对比之后目标就很清晰了:选一颗带管理功能的千兆交换芯片,做成硬件转发。最终选定了RTL8367RB。这颗芯片给我的第一印象是“省事”,五个端口全部内置PHY,不用再额外买PHY芯片,一颗片子就把物理层和链路层都包了;管理接口支持SPI和I2C,又带基本的VLAN、QoS、端口镜像功能,正好覆盖项目需求。
1.2 三条方案路线的横向对比
| 扩展方案 | 成本投入 | 转发性能 | 管理复杂度 | 适用场景 |
|---|---|---|---|---|
| 更换多MAC主控 | 高,需改主控 | 强 | 中 | 原生多网口需求的定制板 |
| 多PHY+Linux Bridge | 低 | 弱,CPU转发 | 低 | 仅需要少数口,流量不高 |
| 主控+交换芯片 | 中,需写驱动 | 强,硬件转发 | 较高,需弄懂寄存器 | 多口网关、NAS、软路由 |
如果你只是做一个双口的小盒子,多PHY方案足够,成本还低。但如果像我这次一样既要多口又要隔离,还要处理器有充裕的余量跑应用,交换芯片方案是绕不开的,这也是绝大多数商业路由器、工业交换机内部的实际做法。
1.3 一颗五口芯片凭什么“够用”
很多人第一次看到RTL8367RB,会觉得它只是一个交换芯片,和普通路由器里的交换芯片没什么区别。但真正用起来才发现,这颗芯片的寄存器体系非常完整,端口速率、双工模式、流控、VLAN表、端口镜像、IGMP snooping这些二层功能全都能配。配合SPI管理通道,主控可以随时读取每个端口的链路状态、统计计数,甚至可以把某个端口彻底关掉,这比纯硬线连接的交换芯片要灵活太多。
另外还要提一个重要认知:SPI模式并不是拿来跑业务流量的,它只是芯片的“管理通道”,真正的数据面是另外一组并行的RGMII/MII接口。很多第一次做这类方案的人会误以为数据走SPI,结果拿到的数据吞吐惨不忍睹。这块板子的实际接法是:RTL8367RB的CPU上联口通过RGMII接到RK3576的GMAC,外部的五个RJ45口接终端设备,SPI只负责读写寄存器、查询状态、配置VLAN。搞清楚这条主线,后面所有调试才不会被带偏。
2. 硬件连接与上电时序:SPI管理面+RGMII数据面
2.1 两条通路的分工与接法
我把这部分的硬件关系整理成了两张大表,调试的时候经常翻。第一张是管理面,也就是SPI连接:RTL8367RB的SCK接RK3576的SPI0_CLK,MOSI接SPI0_TX,MISO接SPI0_RX,另外还需要一根片选信号CS和一根复位信号RST。第二张是数据面,RTL8367RB的CPU口RGMII信号依次接到RK3576的GMAC对应引脚,包括TXD[3:0]、RXD[3:0]、TXCLK、RXCLK、TX_CTL、RX_CTL。两边信号都是3.3V电平,不需要额外的电平转换,但布局时要注意两组信号尽量分开走,避免并行信号之间串扰。
| 信号类别 | RK3576侧引脚 | RTL8367RB侧引脚 | 说明 |
|---|---|---|---|
| 管理通道 | SPI0_CLK | SCK/CLK | SPI时钟 |
| 管理通道 | SPI0_TX | MOSI/SDI | 主出从入 |
| 管理通道 | SPI0_RX | MISO/SDO | 主入从出 |
| 管理通道 | GPIO片选 | CS/CSB | 低有效 |
| 复位控制 | GPIO | RST/RSTB | 低有效 |
| 数据通道 | GMAC1_TXD[3:0] | CPU口TXD[3:0] | 发送数据 |
| 数据通道 | GMAC1_RXD[3:0] | CPU口RXD[3:0] | 接收数据 |
| 数据通道 | GMAC1_TXCLK | TXCLK | 发送时钟 |
| 数据通道 | GMAC1_RXCLK | RXCLK | 接收时钟 |
2.2 模式选择引脚:上电前就必须定死
这块板子第一个坑就出现在RTL8367RB的工作模式选择上。芯片的SPI、I2C、MDIO三种管理接口是由几个专用引脚的电平组合决定的,而且这些引脚在上电复位时被采样,之后不会再变化。也就是说,你不能在软件里“切换”到SPI模式,必须保证硬件上已经把模式选择引脚拉到了SPI对应的电平组合。
我拿到的核心板上,这些模式引脚默认是I2C方向,第一次上电时SPI怎么读都是死寂一片。后来拿万用表逐个量模式引脚的电压,才发现有电阻焊错位置,把本该下拉的引脚拉高了。改完重新上电,SPI接口立刻有了响应。所以如果你调这类芯片没有反应,第一件事不是怀疑驱动,而是去量引脚电平,确认芯片真的处在SPI模式。
2.3 复位与时序:不要忽视那几百毫秒
RTL8367RB的复位时序值得单独提。上电后电源轨稳定,然后释放复位信号,芯片内部做初始化,这个过程通常需要几十毫秒到几百毫秒不等。如果主控在复位释放后立刻发起SPI访问,大概率会读到异常值,或者读回全0x00。
我当时的处理方式是用GPIO控制复位引脚,驱动加载时先拉低复位,延时200ms,再拉高,继续延时200ms,然后才开始SPI读写。这套流程看着简单,却能避开很多诡异问题。后来我又把这个逻辑封装到驱动里,每次系统重启都会自动执行,避免人工干预。
3. 让SPI管理通道先跑通:设备树配置与总线自检
3.1 RK3576 SPI控制器的设备树配置
Linux侧要让SPI工作起来,第一步是确认RK3576的SPI控制器在设备树里使能,并且挂上对应的子设备。RK3576的SPI控制器设备树写法比较固定,我贴一个精简版本作为参考:
&spi0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi0m0_clk &spi0m0_cs0 &spi0m0_tx &spi0m0_rx>; cs-gpios = <&gpio1 14 GPIO_ACTIVE_LOW>; #address-cells = <1>; #size-cells = <0>; rtl8367rb: rtl8367rb@0 { compatible = "realtek,rtl8367rb"; reg = <0>; spi-max-frequency = <10000000>; spi-cpha; /* 具体极性和相位看芯片手册时序图 */ spi-cpol; reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>; }; };设备树里重要的是spi-max-frequency和spi-cpha/spi-cpol这两个配置。时钟频率不是越高越好,我最终稳定跑在10MHz,再往上偶尔会出现读回数据跳变。如果你只是读寄存器,10MHz已经足够,没必要追求极限速率。极性和相位的配置则必须和RTL8367RB手册上的时序波形图严格对应,这个下面单独展开。
3.2 CPOL和CPHA:先回环,再谈读写
很多人容易忽略,SPI通信失败最常见的原因不是接线错误,而是时钟极性和相位不匹配。CPOL决定空闲时时钟电平是高还是低,CPHA决定数据在哪个边沿采样。两者组合起来共有四种模式,从模式0到模式3。RTL8367RB这类芯片通常对模式有明确要求,但手册里的波形图看起来总比实际操作抽象,所以我的建议是不要急着猜,先用回环测试确认主机配置。
具体做法:找一根杜邦线短接SPI_MOSI和SPI_MISO,然后在用户态通过spidev写一串已知数据再读回来,如果读回数据和写入的一致,说明主控SPI工作正常,可以接外设;如果不一致,就逐个尝试四种模式。做完这一步,再接到RTL8367RB上读芯片ID,排查范围就大大缩小。
3.3 用户态读写工具:先于驱动把链路验证
正式编写内核驱动之前,先用用户态工具把SPI通道摸透,效率会高很多。Linux的spidev接口暴露了/dev/spidev0.0,用ioctl就能完成读写。下面是一个经典的回环测试代码骨架:
#include <stdio.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> int main(void) { int fd = open("/dev/spidev0.0", O_RDWR); if (fd < 0) { perror("open spidev"); return -1; } uint8_t mode = SPI_MODE_0; ioctl(fd, SPI_IOC_WR_MODE, &mode); uint32_t speed = 10000000; ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed); uint8_t tx[] = {0xA5, 0x5A, 0x00, 0xFF}; uint8_t rx[4] = {0}; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = sizeof(tx), .speed_hz = speed, .bits_per_word = 8, }; ioctl(fd, SPI_IOC_MESSAGE(1), &tr); printf("tx: %02X %02X %02X %02X\n", tx[0], tx[1], tx[2], tx[3]); printf("rx: %02X %02X %02X %02X\n", rx[0], rx[1], rx[2], rx[3]); close(fd); return 0; }回环测试通过之后,再把MOSI和MISO接回到RTL8367RB上,把回环程序改成发寄存器访问命令,看芯片返回的数据。整个过程不进内核,出了错也容易改,等一切稳定之后再考虑把逻辑移进驱动,这样能省掉大量反复编译内核模块的时间。
4. 寄存器访问手记:从芯片ID到PHY链路状态
4.1 寄存器访问协议的基本框架
RTL8367RB的寄存器访问通过SPI收发指定格式的命令帧完成。不同批次或封装版本,命令字段的划分细节会有些差异,所以最权威的依据始终是对应版本的数据手册。我这边根据手册整理出通用框架:一次访问通常由命令字节、地址字节和数据字节组成,命令字节中包含读写方向标志,地址字节指向寄存器编号,数据字节则根据寄存器位宽有一个或两个字节。
写代码的时候,我把这个逻辑封装成两个底层函数:read_reg和write_reg。函数内部只做一件事,拼好命令帧、调用spidev收发、拆帧取数据。实际的寄存器地址不在底层编码,由上层业务调用时传入。这样即使遇到版本不一致,只需要核对地址表,不需要改动通信层。
4.2 读芯片ID:建立信任的第一步
整个方案里第一个要读的寄存器应该是芯片ID寄存器。这颗芯片的ID寄存器位置和期望值,我建议直接查手册确认,因为不同版本差异很大。我当时在用户态小工具里加上读ID功能,上电后执行了几次,看到读回值和手册标注一致,才算真正确认SPI链路、模式选择、复位时序全部正确。
如果读ID这一步不过关,不要急着往下调PHY或VLAN。我处理问题的顺序一向是:先确保能读到芯片ID,再读PHY寄存器,最后才做配置写操作。跳级调试只会让问题面变大,不知道是链路问题还是配置问题。
4.3 读PHY链路状态:插拔网线立刻可见
芯片ID正常之后,下一步就是读PHY寄存器。标准PHY寄存器0到寄存器15有统一的定义,其中寄存器1是状态寄存器,最低位就是链路状态位。RTL8367RB内部有多个PHY,访问某个PHY的寄存器通常需要先选择端口,再做间接读取,这点和直接挂在MDIO总线上的独立PHY芯片不同。
我写了一个小循环脚本,每秒轮流读五个端口PHY的状态,然后打印link状态。插上网线再拔掉,屏幕上会立刻看到对应端口状态从up变down。这个循环脚本在整个调试过程中帮了大忙,很多问题通过状态位变化就能快速定位,比如某个口一直down,先查硬件接线,再确认PHY寄存器选择是否正确。
5. 二层功能配置实操:VLAN隔离、端口控制与内核网桥
5.1 芯片内部需要动哪些“表”
搞定链路检测之后,才进入真正体现“管理型交换芯片”价值的部分:二层转发配置。RTL8367RB内部要理清三张表:VLAN表、端口PVID表、端口规则表。VLAN表决定每个VLAN里包含哪些端口,是Tagged还是Untagged;PVID表决定端口收到不带标签的报文时默认归类到哪个VLAN;端口规则表决定入口方向的过滤规则和出口方向的标签行为。
这三张表对应配置时是各自独立的,但最终效果又要协同起来。只配VLAN表不配PVID,接入的普通电脑(不带VLAN标签)可能无法通信;只配PVID不配VLAN表,隔离又不生效。我的经验是画一张拓扑图,标清每个端口属于哪个VLAN、是否打标签,然后一条条对照配置,避免思维混乱。
5.2 一个实际配置例子:四个LAN口隔离,一个上联口
这个项目的实际拓扑可以简化成:Port0到Port3是四个LAN口,分别属于VLAN10到VLAN40,互相隔离;Port4作为上联口连接主控GMAC,四个VLAN都要能到达。实现隔离最直观的方式就是端口隔离的寄存器配置,让每个端口只能和上联口通信,LAN口之间不转发。VLAN表则可以按业务需要再细分,比如某些口要临时互通,就允许它们加入同一个VLAN。
配置完成后,我把一个笔记本电脑接到Port0,另一个接到Port1,用iperf互打流量,发现两个口完全不通,验证了隔离生效;再从笔记本去ping主控侧网关地址,又能通。这就是典型的需求结果:终端之间隔离,但每个终端都能访问主控。
5.3 内核网桥与交换芯片的关系
配置完交换芯片,RK3576这边怎么让业务流量打通,很多人会卡在这一步。交换芯片负责的是“外网口之间的硬件转发”,而数据一旦从RTL8367RB的CPU口通过RGMII进入主控,就要靠Linux网络协议栈继续处理。如果你的Linux侧没有把GMAC接口和其他接口桥接起来,那主控自己还是访问不了外部终端。
我在系统里创建了一个bridge,把GMAC接口加进去,并分配了管理IP。这样交换芯片把数据转到CPU口后,Linux侧就像收到普通网口数据一样处理。有一点需要留意:如果交换芯片侧做了VLAN隔离,Linux bridge默认是不懂这些的,通常要么配置成Trunk口只放行带标签的报文,要么在Linux侧用VLAN子接口进一步隔离。这块要做通,需要两边思路一致,否则会出现“芯片配置看起来没问题,就是不通”的尴尬局面。
6. 实测中的四个隐蔽的坑与完整排查链路
6.1 坑一:复位释放后立刻读寄存器,读到一堆垃圾值
第一次把整套驱动跑起来时,我看到SPI工具能正常回环,但一接交换芯片读ID就异常,而且不是稳定的异常值,是每次都不一样。排查过程没有直接扎进驱动代码,而是先在硬件层面查。用示波器抓了复位引脚和SPI时钟,发现复位释放后不到1ms,主控就已经在发SPI命令了。而RTL8367RB内部初始化还没完成,芯片对命令的响应自然乱七八糟。
解决方式就是我在2.3节提到的复位流程:复位拉低保持200ms,拉高后再等200ms,然后才允许访问寄存器。加了这个延时,芯片ID稳定读出。这个坑给的教训是:SPI主机自检没问题,并不代表从设备已经准备好,上电时序是写在芯片手册里的,想省这一步就要承担莫名其妙的问题。
6.2 坑二:SPI读回数据全是0xFF,时钟极性换了才恢复
第二个坑出现在同事调试自己那块板子时。SPI回环测试正常,接线检查也正确,但一读RTL8367RB寄存器,MISO上回的全是0xFF。我让他把SPI模式逐一换了一遍,换到模式3后数据立刻正常。原因很简单,RTL8367RB手册中的时序图要求数据在时钟下降沿采样,而我们一开始套用的模式0是在上升沿采样,导致从设备输出的数据全部错位。
这事的排查链路是:先确认回环(排除主机问题),再确认接线(排除物理链路问题),再确认模式(排除通信协议问题),最后确认寄存器地址和期望值(排除逻辑问题)。如果你也遇到读回全0xFF或者全0x00,优先怀疑CPOL和CPHA的组合,不要先怀疑芯片坏了。
6.3 坑三:共享SPI总线上另一个器件被误片选
板上不只有一颗SPI从设备,还有一颗Flash挂在同一条SPI总线上。问题表现为RTL8367RB的读写偶尔失败,而且失败时机不固定。排查过程中,我抓了片选信号才发现:RTL8367RB的片选在通信结束后没有立刻拉高,或者拉高时机比Flash的片选要求晚,导致Flash误以为自己在被寻址,两个器件抢总线。
最后处理方式是将两个器件放到不同片选,并重新检查了GPIO片选的控制逻辑,确保每次SPI事务结束后片选状态干净。这个问题提醒我:共用SPI总线时,除了看从设备时序,还要考虑总线上其他设备会不会被“误唤醒”,尤其是片选信号极性不一致的设备混用时。
6.4 坑四:PHY链路up了,但灌流量丢包严重,问题不在SPI而在数据面
第四个坑最隐蔽。PHY状态一切正常,SPI读寄存器也正常,但两个LAN口之间打流丢包严重。当时沿着SPI配置查了很久,VLAN、PVID、端口规则全检查了一遍,没有发现问题。后来用芯片内部的统计寄存器查丢包计数,发现RGMII数据面存在大量CRC错误,问题定位到数据通路,而不是管理通路。
进一步查是RGMII接口的时钟延迟参数没有配置对。RK3576的GMAC和RTL8367RB的RGMII之间需要调整发送和接收时钟的相位关系,这个值在设备树里可以配置。反复试了几组参数后,丢包归零。这件事给我的经验是:一套方案里管理面和数据面是两条独立通路,SPI配置再完美,数据面的电气参数不对,照样跑不起来。遇到丢包,先分清是配置问题还是物理信号问题,别吊死在SPI一棵树上。
6.5 实际问题排查简表
| 现象 | 可能原因 | 优先排查手段 |
|---|---|---|
| SPI读写完全无响应 | 模式选择引脚电平不对 | 万用表量模式引脚电压 |
| 读回数据全是0xFF | 时钟极性/相位不匹配 | 切换CPOL/CPHA组合 |
| 读回数据随机跳变 | 时钟频率过高或供电不稳 | 降低SPI频率,检查电源纹波 |
| 偶发读写失败 | 片选控制不干净,总线竞争 | 抓片选波形,调整片选时序 |
| PHY up但丢包 | RGMII时钟相位不匹配 | 调整设备树时钟延迟参数 |
7. 跑通千兆后的一点复盘:哪些设计我会重来
这块板子从硬件调试到二层功能跑通,前后花了一周多的时间。真正稳定之后,用iperf打满千兆吞吐,CPU占用低得可以忽略,和最初用Linux Bridge的方案完全是两个体验。但如果再让我重新做一次,有几件事我会在一开始就做掉:首先,模式选择引脚和复位引脚在原理图上要额外标注清楚,避免贴片时焊错;其次,SPI时钟频率一开始就按10MHz设计,不追求极限速率;最后,把读PHY状态的工具从一开始就写好,贯穿整个调试过程,而不是等出了问题再补。
如果你也在做类似的方案,我的建议是先把管理面和数据面这两条通路在脑子里分清楚,然后按“SPI回环—读芯片ID—读PHY链路—配置VLAN—打流验证”的顺序一步步推进。这套流程看着慢,实际最省时间。RTL8367RB这颗芯片本身不复杂,真正复杂的往往是那些看起来不起眼的上电时序和时钟极性细节。