嵌入式开发里有一个很经典的场景:主控芯片通过 RMII 接口连接一颗 RTL8306MB 交换芯片,用来扩展多路百兆以太网口,常见于智能网关、工业控制板、企业级 AP 这类产品。你想的是“驱动写一写,网络就能通”,实际调起来却发现 Link 起不来、丢包、甚至寄存器都读不通。这篇文章我就把 RTL8306MB 驱动开发中 RMII 接口调试会踩到的典型陷阱,按我自己的排查经验完整拆一遍。无论你是刚接触 Linux 驱动开发,还是已经在做交换芯片适配,这几个坑多少都能给你提个醒。
1. 方案选型与设计思路拆解
1.1 为什么采用 RTL8306MB + RMII 这个组合
RTL8306MB 是一颗非常经典的 5+1 口百兆交换芯片,自带 5 个 10/100M PHY,同时预留一个管理口用于连接 CPU 或主控 SoC。在成本和集成度上,这种方案比“CPU 自带 N 个 MAC + 外挂 N 颗 PHY”要划算很多,尤其适合端口数量多、单端口速率不需要上 GbE 的场景。
RMII 接口在这个组合里的角色,就是 CPU 与交换芯片之间的数据管道。相比传统 MII 接口,RMII 把数据线从 16 根压缩到了 9 根左右,而且把工作时钟统一提高到 50MHz。对于 PCB 面积受限、引脚紧张的板卡来说,这种“少引脚、高频率”的设计非常讨喜。代价就是,RMII 的时序余量更小,调试难度比 MII 高一个级别。
很多第一次做交换芯片适配的工程师会低估这个部分。大家习惯性地以为“既然是标准接口,接上去就应该通”,但 RMII 恰恰是最不“标准”的地方。它有两种时钟方向模式、三种常见的电平域,再加上不同厂商的 MAC 控制器实现细节不同,很容易出现硬件看起来全对、驱动却死活调不通的情况。
1.2 RMII 接口是什么,为什么它是调试重灾区
拿城市交通来类比吧:MII 是双向八车道,车多但每条道都是低速;RMII 是双向两车道,但把限速提到了 50MHz,用高频换取通道数量的缩减。RMII 的收发数据各用 2 根线,配合 TX_EN、CRS_DV、REF_CLK 这几个控制信号,完成以太网帧的搬移。
整个 RMII 接口的信号组大致如下:
- TXD[1:0]:发送数据,2 bit 并行
- RXD[1:0]:接收数据,2 bit 并行
- TX_EN:发送使能,高电平表示 TXD 上的数据有效
- CRS_DV:载波侦听与数据有效指示,接收数据时拉高
- REF_CLK:50MHz 参考时钟,整个接口的时序基准
- MDC / MDIO:管理通道,用来读写 PHY/交换芯片寄存器
正因为数据线只有 2 bit,所有数据都是在 50MHz 时钟的上升沿或下降沿被采样,对建立时间、保持时间、时钟抖动的要求非常严格。只要 PCB 上有一根线走得太长、时钟源不干净、或者两边时钟方向没配对,表现出来就是通信随机失败。
我在实际项目里见过太多人把时间浪费在反复改驱动代码上,最后发现是 REF_CLK 的方向配置错了。所以后面我会重点把时钟方向、信号质量、管理通道这三个层面拆开讲。
1.3 调试前必须建立的认知框架
RMII 链路可以分成三层来看:
- 物理信号层:引脚定义、电平域、时钟频率与方向、信号完整性
- 数据链路层:RMII 协议的时序、TX_EN/CRS_DV 的配合、数据采样点
- 管理通道层:MDC/MDIO 是否可读写,寄存器值是否符合预期
调试顺序应该是“由底向上、先硬后软”。第一优先确认物理信号:用示波器看 50MHz 时钟是否正常,用万用表量电平域是否匹配。第二步确认管理通道:MDIO 能读写寄存器,Link 状态能读到。第三步才去看数据收发和驱动代码。最忌讳的就是一上来就怀疑 Linux 驱动,反复改 dts、改 phy driver,浪费大量时间。
2. 核心细节解析与实操要点
2.1 50MHz REF_CLK:方向与质量决定成败
RMII 的 REF_CLK 是整个接口的灵魂。它必须是一个精确的 50MHz 时钟,且由谁产生、往哪个方向送,必须在硬件设计阶段就定死。RMII 支持两种时钟模式:
- MAC 提供 REF_CLK:主控的 RMII 模块主动输出 50MHz,交换芯片作为从端同步
- PHY 提供 REF_CLK:交换芯片输出 50MHz,主控的 RMII 模块输入该时钟
RTL8306MB 和大多数主控 SoC 都支持这两种模式,但默认状态不一定一致。比如你的主控 RMII 默认是 MAC 输出时钟,而 RTL8306MB 默认也输出时钟,两边同时驱动同一根线,轻则时序错乱,重则损伤 IO。我在一块板子上就遇到过这种情况,原理图上 REF_CLK 直接连在一起,但两边都把它配成了输出,结果 Ethernet 完全无法协商。
排查方法其实很简单:先在原理图或芯片手册里确认 REF_CLK 是从哪个方向来的,然后查主控 RMII 控制寄存器的时钟方向位,再确认 RTL8306MB 的 RMII 时钟输出使能位。两边必须保持“一主一从”。时钟频率也要实测,用示波器量 REF_CLK 引脚,频率偏差不能超过 50ppm,否则交换机可能偶发丢包。
还有一个容易被忽略的细节:REF_CLK 必须保持干净。如果这个时钟由 SoC 的 PLL 分频出来,要确认 PLL 的抖动是否超标;如果由独立有源晶振提供,要注意晶振到芯片引脚的走线不要跨越分割的地平面。我自己踩过最深的坑就是 REF_CLK 走线太靠近一串 DC-DC 电感,导致时基不稳,速率一高就 CRC 错误。
2.2 引脚电平域与板级设计上的细节
RTL8306MB 的 IO 电平一般是 3.3V,但很多主控 SoC 的 RMII 引脚可能是 1.8V 或者 2.5V。直接对接会导致两种后果:要么高电平识别不了,要么长期运行后 IO 损伤。所以在调试之前,先查主控芯片手册里的 RMII IO 电压域,确认与 RTL8306MB 的 VDDIO 是否一致,不一致就必须加电平转换芯片。
PCB 布线方面,RMII 数据线 TXD[1:0]、RXD[1:0]、TX_EN、CRS_DV、REF_CLK 最好做等长处理,长度差控制在 100mil 以内。因为 50MHz 时钟的周期只有 20ns,微小的走线长度差就会产生相移,导致采样点偏移。尤其是 TX_EN 和 TXD 这对信号,如果它们之间的走线长度差过大,MAC 发出的数据会使能无效,另一端就会丢弃帧。
另外,RMII 信号线尽量避免使用过孔换层,尤其 REF_CLK 不要穿过分割的电源平面。RXD 和 CRS_DV 上可以加 22Ω 到 33Ω 的串联电阻做阻抗匹配,这个不是必须,但实测下来能明显改善信号边沿过冲。很多“时好时坏”的 RMII 问题,都是这些硬件细节造成的。
2.3 MDIO/MDC 管理通道:读得通才是第一步
RMII 数据通道不通,你至少还能靠管理通道去查 PHY 状态;如果 MDIO/MDC 都不通,那整个调试就完全抓瞎了。管理通道的优先级,我认为甚至要高于数据通道。
MDC 是管理时钟,MDIO 是双向数据线。注意 MDIO 是开漏结构,外部必须接上拉电阻到相应的电平域。上拉电阻接错的话,MDIO 读出来的数据会全是 0xFF。还要注意 MDC 的频率,标准规定不能超过 2.5MHz,很多调试工具默认用 2.5MHz 以上就可能导致读写不稳定。
RTL8306MB 的 PHY 地址一般由芯片的 PHY_AD[2:0] 引脚上的上下拉电阻决定。这组地址非常重要,驱动里访问的 MDIO PHY 地址必须和硬件一致。我见过一个项目,硬件按默认把 PHY_AD 全部拉低,得到地址 0,但驱动里写死了地址 1,结果始终读不到寄存器。
调试管理通道可以用 mdio-tools 或者内核里的 mii-tool。先用 mii info 看能不能枚举到 PHY,再用 mii dump 读寄存器 0 和寄存器 1,检查基本 ID 和 Link 状态。如果这一步通过,说明物理层和管理层已经通了,接下来才值得去折腾数据通路。
2.4 交换芯片初始化与 RMII 模式配置
RTL8306MB 上电后有一套默认寄存器配置,但 RMII 接口路径不一定直接就能用。常见的初始化动作包括:
- 复位芯片,等待复位完成
- 配置 RMII/MII 模式选择寄存器,确保管理口工作于 RMII
- 配置 CPU 管理口的速率、双工模式(通常是 100M 全双工)
- 设置 VLAN 和端口转发规则,保证 CPU 口和 LAN 口之间能转发
- 配置 LED 模式、流控等可选功能
这些寄存器往往分散在 PHY 寄存器空间和芯片扩展寄存器空间里。RTL8306MB 支持通过 MDIO 访问 PHY 寄存器,扩展寄存器则需要先通过特定寄存器做窗口切换。这里有一个实践中很管用的方法:不要一上来就自己写所有寄存器,先用官方工具或参考驱动把默认配置序列抓出来,对比芯片手册逐个看含义,然后再按项目需求裁剪。
另外,一定要在初始化代码里加一个“读回校验”。写完寄存器后再读一遍,确认写入成功。RTL8306MB 有时会因为时序问题导致寄存器写入丢失,如果发现写读不一致,先查 MDC 频率和 MDIO 上拉,不要急着改初始化值。
3. 实操过程与核心环节实现
3.1 拿到板子后的第一步:硬件检查清单
把板子焊好拿到手,先别急着编译内核。花十分钟过一遍硬件检查清单,很多问题在这个阶段就能暴露。
| 检查项 | 检查方法 | 容易踩的坑 |
|---|---|---|
| REF_CLK 频率 | 示波器测频率,确认 50MHz 且稳定 | 晶振贴错、PLL 配置错 |
| REF_CLK 方向 | 查手册确认是 MAC 出还是 PHY 出 | 两端都配置成输出 |
| PHY 地址 | 量 PHY_AD[2:0] 引脚电平 | 上下拉电阻虚焊 |
| MDIO 上拉 | 确认 MDC/MDIO 上拉到正确电平域 | 上拉电阻漏贴或错值 |
| 电源去耦 | 检查 AVDD/DVDD 去耦电容 | 电源纹波过大导致 PHY 异常 |
| 复位引脚 | 确认复位时序和释放电平 | 复位信号被拉死 |
| 信号线等长 | 查看 PCB 布线报告 | TX_EN 与 TXD 长度差过大 |
我个人习惯用示波器先把每个关键引脚的直流电平和时钟量一遍,确认没有虚焊、短路、电平异常,再进入软件环节。硬件问题如果带到软件里去排查,效率极低。
3.2 从 uboot 到 Linux:一套可复用的调试流程
软件调试不要一上来就跑完整系统,推荐按下面这个流程走:
第一层:在 uboot 阶段验证 PHY。如果 uboot 里已经带了 mii 命令,先用 mii info 查看指定 PHY 地址是否有回应。如果 uboot 没有网络命令,可以在初始化代码里加一段临时 MDIO 读操作,把 PHY 寄存器 0x00 的值打印出来。0x00 是控制寄存器,上电后应有可读值;如果读出 0xFFFF,说明 MDIO 链路有问题。
第二层:进入 Linux 后,先用 ethtool 查看网卡 link 状态。执行 ethtool eth0,如果 Speed/Duplex 有值且 Link detected: yes,说明物理层和管理层已经打通。这时候如果 ping 不通,问题在交换芯片的转发配置或 VLAN 设置。
第三层:验证 CPU 端口与 LAN 端口的双向通信。可以在交换芯片里把 CPU 口镜像到某个 LAN 口,或者在 LAN 口接一个 PC,直接抓包。确认 CPU 发出来的报文能到 PC,PC 发出的报文能到 CPU。
这套流程非常有效,因为它把“能不能通”和“驱动写得对不对”分成两个阶段。绝大多数 RMII 调试陷阱,都会在第一、二层就暴露,根本不至于拖到 Linux 网络协议栈里去猜。
3.3 一版可直接参考的 RMII 初始化配置示例
以 Linux 环境下通过 mdio 工具直接操作寄存器为例,下面是一段常见的 RTL8306MB RMII 初始化思路,供参考:
# 先复位交换芯片,假设复位引脚由某个 GPIO 控制 gpioset gpiochip0 7=1 sleep 0.05 gpioset gpiochip0 7=0 sleep 0.05 gpioset gpiochip0 7=1 sleep 0.2 # 确认 MDIO 能读到 PHY,假设 PHY 地址为 0 mii-tool --phyaddr=0 eth0 # 读 PHY 控制寄存器 0x00,正常应读到 0x1000(自动协商开启等配置) mdio-tools mdio mdio_bus:0 read 0 0x00 # 写 PHY 控制寄存器,强制 100M 全双工 mdio-tools mdio mdio_bus:0 write 0 0x00 0x2100 # 如果需要配置扩展寄存器,先通过窗口寄存器切换到扩展页 mdio-tools mdio mdio_bus:0 write 0 0x1F 0x0001 mdio-tools mdio mdio_bus:0 write 0 0x10 0x0005 mdio-tools mdio mdio_bus:0 write 0 0x1F 0x0000上面这段命令里的扩展寄存器地址,必须按你手头芯片的具体手册来填。RTL8306MB 在不同封装、不同版本下,扩展寄存器的地址映射可能有差异。我的建议是先在参考代码里确定你用的芯片版本,再逐个寄存器确认含义。
初始化完成后,一定读回关键寄存器做校验。有一次我写完扩展寄存器之后,没有读回,结果实际配置没进去,浪费了整整一个下午排查数据通路。这个习惯养成后,后面对账会轻松很多。
3.4 用示波器验证 RMII 时序的关键测量点
如果寄存器读到 Link up,但数据依然异常,就需要示波器上场。测量点主要有四个:
- REF_CLK:确认 50MHz,占空比尽量接近 50%,边沿要干净
- TX_EN 和 TXD[1:0]:发送数据时,TX_EN 拉高期间 TXD 必须有稳定数据,且相对 REF_CLK 满足建立/保持时间
- CRS_DV 和 RXD[1:0]:接收数据时,CRS_DV 拉高,RXD 在时钟沿附近不能被采样到跳变沿
- MDC 和 MDIO:管理通道通信时,MDIO 数据要在 MDC 上升沿附近保持稳定
常见的波形异常包括:
- TX_EN 和 TXD 之间的时序偏移过大,导致远端采样错位
- RXD 数据线上的毛刺触发了 CRS_DV 的误判
- REF_CLK 抖动过大,数据采样点不稳
实际调试时,我习惯把示波器时基调到 20ns/div 左右,同时抓 REF_CLK、TX_EN、TXD[0] 三路信号,对比手册里的时序图。如果波形和手册差别太大,大概率是硬件或初始化配置问题,而不是数据包内容问题。
4. 常见问题与排查技巧实录
4.1 问题:RMII 接口完全无 Link,协商失败
表现:ethtool 显示 Link detected: no,PHY 状态寄存器里 Link 状态位始终为 0。
排查步骤:
- 先测 REF_CLK,确认频率是否为 50MHz。如果频率不对,查晶振或 PLL 配置。
- 确认 REF_CLK 方向。把主控 RMII 时钟方向寄存器和 RTL8306MB 的时钟输出使能寄存器对比,必须一主一从。
- 检查 VDDIO 电平域。主控和交换芯片的 RMII IO 电平不匹配时,信号可能无法识别。
- 用 mii-tool 或 mdio-tools 读 PHY 寄存器 0x00 和 0x01,确认基本 ID 是否可读,Link 状态寄存器里有没有显示远端协商成功。
这个问题的根因,我碰到过多次都是 REF_CLK 方向冲突或者电平不匹配。修改方向配置后,Link 通常几秒内就起来了。
4.2 问题:Link 已 up,但 PING 不通或丢包严重
表现:ethtool 能看到 100M full duplex,但 ping 网关延迟极高,或者大量 timeout,抓包发现 CRC 错误。
排查步骤:
- 检查 CRS_DV 信号。RXD 和 CRS_DV 是接收路径上最敏感的信号,如果 CRS_DV 有毛刺,交换芯片可能错误判断接收状态。2. 检查全双工/半双工一致性。RMII 两端必须同时配置为全双工或半双工,不一致会导致大量冲突。
- 用示波器抓 RXD 数据,观察数据线上是否存在边沿过冲或振铃。如果数据在采样点附近跳变,就需要调节采样延迟或优化走线。
- 检查交换芯片的 VLAN 配置。CPU 端口和 LAN 端口必须划分在同一个可互通的 VLAN 里,否则即使物理层正常,报文也会被丢弃。
这类问题最容易让人往协议栈方向去查,实际大多是硬件信号完整性和交换芯片配置问题。
4.3 问题:MDIO/MDC 管理通道不通,寄存器读回全 F
表现:mii-tool 无法枚举到 PHY,mdio read 返回 0xFFFF,管理通道完全瘫痪。
排查步骤:
- 测量 MDIO 引脚电平,确认有上拉电阻,且上拉到了正确的电平域。
- 检查 MDC 频率,确保不超过 2.5MHz。某些工具默认频率过高。
- 确认 PHY 地址与硬件一致。量 PHY_AD[2:0] 引脚电平,换算成二进制,和驱动里配置的地址对比。
- 确认 MDIO 引脚没有被其他外设复用。有些 SoC 的 MDIO 和 GPIO 共用引脚,如果引脚被其他驱动占用,管理通道当然不通。
管理通道是 RMII 调试的“眼睛”,这一步不通,后面的所有操作都像盲人摸象。我建议在硬件设计阶段就预留一组调试用的 MDIO 测试点,焊盘上用跳线引出,方便量测。
4.4 问题:复位或重启后 RMII 偶发性异常
表现:每次上电后,有时能通,有时不能通;或者复位后大概率不通,必须断电重启才恢复。
排查步骤:
- 检查复位时序。交换芯片的复位释放时间必须足够长,要等电源稳定后再释放复位。如果复位信号与主控初始化时序冲突,交换芯片可能进入异常状态。
- 检查 REF_CLK 稳定时间。如果主控在 REF_CLK 还没稳定时就开始初始化 RMII,接收路径容易锁错状态。
- 在驱动初始化代码里加延时。复位释放后至少等待 100ms,再开始 MDIO 访问;如果 MDIO 访问失败,额外重试几次。
- 考虑交换芯片的复位引脚是否需要 RC 延时电路。RTL8306MB 的数据手册一般有推荐的复位电路,照抄即可。
这类“偶发问题”最磨人,因为它不是必现,复现路径不清晰。对付它的办法就是增加初始化鲁棒性:复位后多等、多读、多校验,把时序不确定性吸收掉。
4.5 几条通用的避坑建议
第一个建议:硬件评审阶段就拉上驱动工程师。很多 RMII 的问题,等到软件调试时才发现,已经是 PCB 定版之后,改都改不了。驱动工程师提前介入,至少能把 REF_CLK 方向、PHY 地址、电平域这几个关键点审一遍。
第二个建议:初始化代码里多留 debug 信息。MDIO 读到的寄存器值、Link 状态、每次写操作的返回值,都打印出来。有了这些日志,现场问题基本能缩小到一个很小的范围。
第三个建议:保留一个可调试的 Linux 环境。不要阉割 mii-tool、ethtool、mdio-tools 这些工具,它们在底层调试时能省掉大半时间。生产固件再做裁剪也不迟。
我自己做了这么多年嵌入式网络设备,越来越觉得 RMII 本身不难,难的是那些没人写进文档的“隐性假设”:时钟方向谁来定、电平域是否匹配、PHY 地址怎么分配、上拉电阻是否漏贴。只要把这份检查意识刻进流程里,RTL8306MB 这种芯片的驱动调试,真的可以做到一次点亮。最后再分享一个小技巧:如果你手头有逻辑分析仪,最好在 uboot 阶段就抓一次 CPU 发往 RTL8306MB 的复位和 MDIO 波形,留作“黄金样本”,后面任何一次初始化异常,都能拿它做对比。数据链路的问题往往藏在时序里,而时序问题,只有波形不会说谎。