1. 从一次"网口死活不通"的调试现场说起
Zynq 裸机环境下调以太网,最让人抓狂的不是代码写不出来,而是代码看起来全对、编译零警告、PHY 也能读到 ID,但网口就是不通。我最近就碰到这么一档子事:板子用的是 RTL8211FS 这颗千兆 PHY,Zynq 侧 GEM 控制器初始化、DMA 描述符环、MDIO 读写时序全都按手册来,ping出去连 ARP 都发不出去,示波器打 TX_CLK 有波形,PHY 的 link 灯也亮着,但数据就是出不去。
这类问题最坑的地方在于:它不在你写的代码里,而在一个你根本没注意到的隐藏寄存器里。具体来说,是 RTL8211FS 扩展寄存器空间0xD08页的0x11寄存器。这个寄存器在标准 PHY 手册里往往一笔带过,甚至有些版本的 datasheet 压根没提,但它直接决定了 RGMII 接口的时序延迟模式,配错了就是"链路 up、数据不通"的经典症状。
这篇内容适合两类人看:一类是正在用 Zynq + RTL8211FS 做裸机以太网调试、被网口不通折磨的嵌入式工程师;另一类是想搞清楚"为什么 PHY 能识别、链路能 up,但数据包发不出去"这类问题排查思路的从业者。我会把整个踩坑过程、寄存器原理、排查链路和最终修复方案完整还原出来,包括我试过的错误方向,让你少走几天弯路。
需要先说明的是,下面涉及的寄存器地址、位定义都基于 RTL8211FS 的公开手册和实测验证,不同批次或封装可能有细微差异,实际操作时请以你手上芯片的 datasheet 为准。另外,Zynq 侧的 GEM 配置细节我会讲,但重点放在 PHY 这一侧,因为坑就在那儿。
2. RTL8211FS 的寄存器分页机制:为什么你读到的不是你以为的寄存器
2.1 标准寄存器空间只有 0x00~0x1F,扩展寄存器得先"换页"
RTL8211FS 遵循 IEEE 802.3 的 MDIO 管理接口规范,标准寄存器空间是 5 位地址,也就是0x00到0x1F,共 32 个寄存器。这里面放的是 BMCR、BMSR、PHYID1/2、ANAR、ANLPAR 这些通用寄存器,任何一颗合规 PHY 都能在这段空间里被识别和基本配置。
但千兆 PHY 的功能远不止这 32 个寄存器能装下,所以厂商会把扩展功能塞进"分页寄存器"机制里。RTL8211FS 的做法是:用0x1F寄存器作为页选择寄存器(Page Select Register),往里面写一个页号,然后再访问0x00~0x1F,实际读写的就变成了那个扩展页里的寄存器。
这里有个非常容易踩的坑:页选择是"粘滞"的。你写完0x1F切到某个扩展页之后,如果不切回来,后续所有对0x00~0x1F的访问都会落在扩展页上。很多人在初始化流程里切了页去配 LED 或者 RGMII 延迟,配完忘了切回第 0 页,结果后面读 BMSR 读到的是一堆莫名其妙的位,还以为是 PHY 坏了。
我当时的初始化代码里就有这么一段:先切到0xD08页配 RGMII 相关寄存器,然后切回0x00页读链路状态。看起来没问题,但问题出在——我压根没意识到0xD08页里有个0x11寄存器需要配,因为手册里那一段写得太隐蔽了。
2.2 0xD08 页是什么:RGMII 时序配置的"暗门"
0xD08这个页在 RTL8211FS 里主要放的是 RGMII 接口相关的配置,包括 TX/RX 延迟、时钟 skew、驱动能力等。千兆 RGMII 接口有个绕不开的问题:数据和时钟之间需要满足建立/保持时间,而不同 PCB 走线长度、不同 MAC 侧设计,需要的延迟补偿量不一样。
解决方式通常有两种:一种是在 MAC 侧(也就是 Zynq GEM)加延迟,另一种是在 PHY 侧加延迟。RTL8211FS 支持在 PHY 侧对 TX 和 RX 分别加约 2ns 的内部延迟,配置入口就在0xD08页的0x11寄存器。
关键点来了:这颗 PHY 上电默认的 RGMII 延迟配置,不一定和你的板子匹配。如果你的 PCB 走线已经保证了时序,PHY 再加延迟就会过头;如果走线偏短需要 PHY 补延迟,默认没开就会采样错误。两种情况的表象都是"链路 up 但数据不通"。
2.3 为什么手册里这个寄存器这么难找
我翻了三版 RTL8211FS 的公开资料,0xD08:0x11的描述深度差异很大。有的版本只在寄存器映射表里列了一行,位定义要另外对照应用笔记;有的版本干脆把它归在"RGMII Timing"章节的脚注里。对于习惯了"照着寄存器表逐个配"的工程师来说,这种藏在脚注里的寄存器极容易被漏掉。
更麻烦的是,很多参考代码(包括一些开发板例程)用的是 RTL8211E 或者别的 PHY,它们的延迟配置寄存器地址和位定义跟 FS 版本不完全一样。你直接抄过来,可能配了个寂寞,甚至配错位导致更奇怪的现象。
3. 网口不通的完整排查链路:我是怎么一步步缩小范围的
3.1 第一步:确认 MDIO 通信本身是通的
排查任何 PHY 问题,第一件事都是确认 MDIO 读写正常。我的做法是读0x02和0x03(PHYID1/PHYID2),RTL8211FS 的 PHY ID 应该是0x001C和0xC916这个组合(具体以手册为准)。如果这两个值读出来是对的,说明 MDIO 时钟、数据线、PHY 地址都配对了,MDIO 层没问题。
这一步我当时是过的,读出来 ID 正确,心里还踏实了一下。但后来证明,MDIO 通只能说明管理通道正常,跟数据通道能不能工作完全是两码事。MDIO 走的是独立的两根线(MDC/MDIO),RGMII 数据走的是另外一堆线,两者互不验证。
3.2 第二步:确认链路状态和自协商结果
接着读 BMSR(0x01)看 link 状态和自协商完成位,再读0x0A(PHY Specific Status)看速度和双工模式。我当时读到的是:link up、自协商完成、1000M 全双工。到这一步,从 PHY 的角度看,物理链路是完全正常的。
这里有个经验点:如果 BMSR 的 link 位是 1,但 PHY Specific Status 里的速度和双工跟你预期不符,优先怀疑自协商或者对端设备。我这边速度和双工都对,所以排除了协商问题。
3.3 第三步:抓 RGMII 信号,看数据到底有没有出去
链路正常但 ping 不通,下一步就是看数据通道。我用示波器打了 TX_CLK、TXD0~TXD3、TX_CTL 这几根线。现象是:TX_CLK 有 125MHz 波形(千兆模式),但 TXD 线上几乎看不到有效的数据翻转,偶尔有一点毛刺。
这个现象很关键:TX_CLK 有波形说明 GEM 在往外发时钟,但 TXD 没数据说明要么 GEM 没真正发出数据,要么数据在 PHY 侧被"吃掉"了。为了区分这两种可能,我在 GEM 侧加了统计,看 DMA 发送描述符有没有被消费、发送字节计数有没有增加。结果是:GEM 确实在发,描述符被正常回收,发送计数器在涨。
也就是说,数据从 GEM 出去了,但 PHY 没把它正确发到网线上。问题范围一下子缩小到了 PHY 的 RGMII 接收侧时序。
3.4 第四步:怀疑 RGMII 延迟配置
到这一步,基本可以锁定是 RGMII 时序问题。RGMII 在千兆模式下,时钟是 125MHz,数据在时钟的上下沿都采样(DDR),对时序非常敏感。如果 PHY 侧的采样时钟和数据之间的相位关系不对,PHY 就会采到错误的数据,表现为"发出去的数据全是错的",对端自然收不到正确的包。
RTL8211FS 的 RGMII 延迟配置就在0xD08页的0x11寄存器。我当时的默认配置是 TX 延迟关、RX 延迟关,而我的板子 PCB 走线偏短,理论上需要 PHY 补一点延迟。改成 TX 延迟开后,网口立刻通了。
4. 0xD08:0x11 寄存器到底怎么配:位定义与实测验证
4.1 寄存器位定义拆解
根据 RTL8211FS 的公开资料和实测,0xD08页0x11寄存器的关键位大致是这样的(不同版本可能有差异,务必对照你手上的手册):
| 位 | 名称 | 含义 | 常用取值 |
|---|---|---|---|
| bit 4 | RGMII_TX_DLY | TX 侧内部延迟使能 | 0=关闭,1=开启约 2ns |
| bit 3 | RGMII_RX_DLY | RX 侧内部延迟使能 | 0=关闭,1=开启约 2ns |
| bit 2 | RGMII_TX_DLY_SEL | TX 延迟档位选择 | 配合 bit4 使用 |
| bit 1 | RGMII_RX_DLY_SEL | RX 延迟档位选择 | 配合 bit3 使用 |
| bit 0 | 保留或测试位 | 一般保持默认 | 0 |
实际配置时,最常见的组合是:
- TX 延迟开、RX 延迟关:适合 MAC 侧不加延迟、PHY 侧补 TX 延迟的场景。
- TX 延迟关、RX 延迟开:适合 MAC 侧补 TX 延迟、PHY 侧补 RX 延迟的场景。
- 两个都开或都关:需要根据 PCB 走线和 MAC 配置具体分析。
我这边最终用的是 TX 延迟开、RX 延迟关,因为 Zynq GEM 侧我配置了 RX 方向的延迟,PHY 侧只需要补 TX。
4.2 配置代码怎么写
在裸机环境下,配置这个寄存器的流程是:先写0x1F切到0xD08页,再写0x11设置延迟位,最后切回0x00页。下面是我实际用的代码片段(基于 Xilinx 的 xemacps 驱动风格,MDIO 读写函数需要你自己实现):
#define PHY_PAGE_SEL_REG 0x1F #define PHY_RGMII_DLY_REG 0x11 #define PHY_PAGE_RGMII 0xD08 /* 切到 RGMII 配置页 */ XEmacPs_PhyWrite(EmacPsInstancePtr, PhyAddr, PHY_PAGE_SEL_REG, PHY_PAGE_RGMII); /* 读当前值,避免覆盖其他位 */ u16 dly_val; XEmacPs_PhyRead(EmacPsInstancePtr, PhyAddr, PHY_RGMII_DLY_REG, &dly_val); /* 开启 TX 延迟,关闭 RX 延迟,保留其他位 */ dly_val |= (1 << 4); /* RGMII_TX_DLY = 1 */ dly_val &= ~(1 << 3); /* RGMII_RX_DLY = 0 */ XEmacPs_PhyWrite(EmacPsInstancePtr, PhyAddr, PHY_RGMII_DLY_REG, dly_val); /* 切回标准页,这一步千万别忘 */ XEmacPs_PhyWrite(EmacPsInstancePtr, PhyAddr, PHY_PAGE_SEL_REG, 0x0000);注意:切回第 0 页这一步是必须的。我见过有人配完扩展寄存器不切回来,后面读 BMSR 读到的全是扩展页的数据,然后对着错误的状态位排查了半天。
4.3 怎么验证配置生效了
配完之后,最直接的验证就是重新读一遍0xD08:0x11,确认写入的值和读回的值一致。然后重新触发一次自协商或者软复位 PHY,让新配置生效。接着再 ping 一次,如果通了,基本就确认是这个问题。
如果配了 TX 延迟还是不通,可以试试 RX 延迟也打开,或者两个都关掉,做一组对照实验。RGMII 延迟这东西没有"万能配置",必须结合你的 PCB 和 MAC 侧设置来调。我建议把这几种组合都试一遍,用 ping 通不通作为判据,最快。
5. 那些我试过的错误方向:帮你省下几天时间
5.1 错误方向一:反复检查 GEM 的 DMA 描述符环
在怀疑 PHY 之前,我花了整整一天在 GEM 的 DMA 描述符上。因为现象是"数据发不出去",第一反应就是 DMA 没配好。我检查了描述符环的对齐(要求 64 字节对齐)、owner 位的翻转、buffer 地址的有效性、TX 和 RX 环的分离,甚至把 Xilinx 的 lwIP 例程里的 GEM 初始化代码逐行对照了一遍。
结论是:GEM 侧完全正常。描述符被正常消费,发送计数器在涨,说明数据确实从 GEM 出去了。这一天的价值在于,它帮我彻底排除了 MAC 侧的问题,把范围锁定到了 PHY。但如果你一开始就知道要查 PHY 的 RGMII 延迟,这一天可以省下来。
5.2 错误方向二:怀疑 MDIO 地址和 PHY 地址冲突
因为板子上可能有多颗 PHY 或者别的 MDIO 从设备,我一度怀疑 PHY 地址配错了。RTL8211FS 的 PHY 地址由硬件引脚决定,我对着原理图确认了 strapping 电阻,又用 MDIO 扫描的方式遍历了0x00~0x1F所有地址,确认只有目标地址能读到正确的 PHY ID。
这一步其实很快,但如果你没做,可能会在"为什么读不到 PHY"上卡很久。MDIO 扫描是个好习惯,尤其是板子上有多个 MDIO 设备的时候。
5.3 错误方向三:怀疑时钟和复位
我还检查了 PHY 的 25MHz 参考时钟(RTL8211FS 需要外部 25MHz 晶振或时钟输入)、复位引脚时序、电源电压。这些都没问题。这里提醒一句:RTL8211FS 的复位低电平持续时间有最小值要求,如果复位脉冲太短,PHY 可能处于半初始化状态,表现也会很奇怪。我这边复位是硬件 RC 电路做的,时间足够。
5.4 错误方向四:照抄了 RTL8211E 的延迟配置
这是最坑的一个。我一开始找的参考代码是 RTL8211E 的,它的 RGMII 延迟配置寄存器地址和位定义跟 FS 版本不一样。我照着配了一通,结果网口还是不通,还一度以为是芯片坏了。后来换成 FS 版本的正确地址0xD08:0x11,才解决问题。
教训:PHY 型号后缀不同,寄存器映射可能完全不同,参考代码一定要确认型号匹配。
6. 裸机环境下调试 PHY 的几个实用心得
6.1 先读 ID,再读状态,最后动配置
调试任何 PHY,建议按这个顺序来:先读 PHYID 确认通信正常,再读 BMSR 和 PHY Specific Status 确认链路状态,最后才去动扩展寄存器配置。这个顺序能帮你快速定位问题在哪一层。如果 ID 都读不到,别往下查了,先解决 MDIO 通信。
6.2 扩展寄存器操作要"有借有还"
每次切到扩展页配完东西,一定要切回第 0 页。我建议把"切页-配置-切回"封装成一个函数,避免漏掉切回这一步。裸机代码里这种低级错误一旦发生,排查起来非常费劲,因为现象往往很诡异。
6.3 用 ping 做判据,但别只靠 ping
ping 通不通是最直观的判据,但它只能告诉你"通"或"不通",不能告诉你"为什么不通"。配合示波器看 RGMII 信号、配合 GEM 的统计寄存器看收发计数,能帮你快速区分是 MAC 侧问题还是 PHY 侧问题。我这次就是靠"GEM 发送计数在涨但 TXD 没数据"这个现象,把范围锁定到 PHY 的。
6.4 保留一份"能通"的配置备份
网口调通之后,我做的第一件事是把所有 PHY 寄存器的值 dump 出来存了一份。下次再遇到类似问题,直接对比"能通"和"不通"两套寄存器值,差异一目了然。这个习惯在调试任何外设时都值得养成。
6.5 注意 RGMII 延迟和 MAC 侧配置的配合
RGMII 延迟不是 PHY 单方面的事。Zynq GEM 侧也有 TX/RX 延迟配置(在 GEM 的 network control 寄存器里)。PHY 侧和 MAC 侧的延迟配置要配合着来,不能各配各的。我这次是 MAC 侧配了 RX 延迟,PHY 侧配了 TX 延迟,两边各补一个方向,刚好匹配。如果你两边都开或者都关,可能就过头或者不够。
7. 关于这个坑,我最后想说的
RTL8211FS 这颗 PHY 本身很稳,Zynq 的 GEM 控制器也很成熟,但两者凑在一起,RGMII 时序这个"中间地带"就成了最容易出问题的地方。0xD08:0x11这个寄存器之所以坑,不是因为它难配,而是因为它藏得深、手册写得含糊、参考代码又容易拿错型号。
我现在调新板子的网口,流程已经固定下来了:MDIO 读 ID、读链路状态、dump 全部扩展寄存器、配 RGMII 延迟、ping 验证。这套流程走下来,大部分网口不通的问题都能在半天内定位。如果你也在调 Zynq + RTL8211FS,建议先把0xD08:0x11这个寄存器记在心上,它大概率能帮你省下不少时间。
另外提一句,不同批次的 RTL8211FS 在扩展寄存器的默认值上可能有差异,我遇到过一块板子默认 TX 延迟就是开的,另一块是关的。所以不要假设默认值,每次都读出来确认,这是最稳妥的做法。