ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zynq裸机RTL8211FS网口不通?隐藏寄存器0xD08:0x11配置揭秘

Zynq裸机RTL8211FS网口不通?隐藏寄存器0xD08:0x11配置揭秘 1. 项目背景与问题定位1.1 一个让人抓狂的网口不通现象Zynq 平台上跑裸机程序PL 端逻辑已经跑通PS 端也正常启动但网口就是不通。具体表现是PHY 芯片 RTL8211FS 的链路状态寄存器读出来是 0自协商一直不完成ping 不通连最基本的 ARP 都发不出去。更让人头疼的是同样的硬件设计在 Linux 下跑得好好的一到裸机就翻车。这个现象在 Zynq 裸机开发里其实不算罕见尤其是用 RTL8211FS 这颗 PHY 的时候。RTL8211FS 是 Realtek 的一款千兆以太网 PHY支持 RGMII 和 GMII 接口在 Zynq 7020、7010 这些板子上用得很多。Linux 下有完整的驱动MDIO 读写、自协商、时钟配置都帮你处理好了但裸机环境下这些都得自己来。我这次遇到的问题最终定位到一个非常隐蔽的寄存器——0xD08 页的 0x11 寄存器。这个寄存器在 RTL8211FS 的公开手册里描述得并不显眼但它直接决定了 RGMII 接口的延时配置配错了网口就是不通。下面我把整个排查过程、原理分析和修复方法完整记录下来。1.2 为什么裸机下更容易踩坑Linux 内核的 stmmac 驱动里对 RTL8211FS 有一整套初始化流程包括页切换、延时配置、自协商启动等。这些操作在驱动源码里是写死的你不需要关心。但裸机环境下你得自己写 MDIO 读写函数自己配置 PHY自己启动自协商。任何一个环节漏了网口就不通。而且 RTL8211FS 的寄存器是分页的0x00 到 0x1F 是标准页0xD08 是扩展页。要访问扩展页的寄存器得先写 0x1F 寄存器切换到对应页再读写目标寄存器最后切回来。这个页切换机制如果没处理好读出来的值全是错的。注意RTL8211FS 的页切换是通过写寄存器 0x1F 实现的写 0xD08 表示切换到扩展页写 0x0000 表示回到标准页。切换后需要等待几个 MDIO 时钟周期再操作。2. RTL8211FS 的寄存器架构与 MDIO 通信基础2.1 MDIO 接口的基本工作原理MDIO 是管理数据输入输出接口用来读写 PHY 的寄存器。它只有两根线MDC 是时钟MDIO 是双向数据线。Zynq 的 GEM 控制器内置了 MDIO 主机可以直接通过寄存器操作来发起读写。MDIO 的帧格式是固定的前导码 32 个 1然后是起始码 01接着是操作码读 10写 01再是 PHY 地址 5 位寄存器地址 5 位最后是 16 位数据。Zynq 的 GEM 控制器会自动处理这些格式你只需要往GEM_MDIO_ADDR和GEM_MDIO_DATA寄存器里写值就行。裸机下写 MDIO 读写函数核心就是操作这两个寄存器。以 Xilinx 的 Standalone 库为例XEmacPs_PhyRead和XEmacPs_PhyWrite就是现成的接口。但如果你用的是自己写的驱动就得手动实现。// MDIO 读寄存器示例 u16 mdio_read(u32 phy_addr, u16 reg_addr) { u32 addr (phy_addr 5) | reg_addr; XEmacPs_WriteReg(GEM_BASE, XEMACPS_MDIOADDR_OFFSET, addr); // 等待完成 while (XEmacPs_ReadReg(GEM_BASE, XEMACPS_NWSR_OFFSET) XEMACPS_NWSR_MDIOIDLE_MASK); return XEmacPs_ReadReg(GEM_BASE, XEMACPS_MDIODATA_OFFSET) 0xFFFF; }这段代码看起来简单但实际调试时经常遇到读出来全是 0xFFFF 或者 0x0000 的情况。原因可能是 MDC 时钟分频不对、PHY 地址配错、或者 PHY 还没上电完成。2.2 RTL8211FS 的分页寄存器机制RTL8211FS 的寄存器空间是分页的。标准页Page 0包含 0x00 到 0x1F 的寄存器这些是 IEEE 802.3 定义的标准寄存器比如 0x00 是控制寄存器0x01 是状态寄存器0x04 是自协商通告寄存器等。扩展页通过寄存器 0x1F 来切换。写 0x1F 为 0xD08就进入了扩展页这时候访问 0x11 寄存器读到的就是扩展页 0x11 的内容。要回到标准页写 0x1F 为 0x0000。这里有个坑页切换后不能立刻读写目标寄存器需要等至少 2 个 MDIO 时钟周期。有些代码里没加延时读出来的值就是错的。我实测下来加 10 个空操作循环比较稳妥。// 切换到扩展页 0xD08 mdio_write(phy_addr, 0x1F, 0xD08); for (volatile int i 0; i 10; i); // 等待页切换完成 // 读扩展页 0x11 寄存器 u16 val mdio_read(phy_addr, 0x11); // 切回标准页 mdio_write(phy_addr, 0x1F, 0x0000);2.3 RGMII 接口的延时问题RGMII 接口在千兆模式下数据速率是 125MHz 双沿采样。发送和接收数据时时钟和数据之间需要一定的延时才能保证接收端在正确的时刻采样到数据。这个延时可以来自 PHY 内部也可以来自 PCB 走线还可以来自 Zynq 的 GEM 控制器。RTL8211FS 支持内部延时配置通过扩展页的寄存器来控制。如果延时配置不对数据采样就会出错表现为网口不通或者丢包严重。我这次遇到的问题就是延时配置寄存器被写成了错误的值。3. 隐藏寄存器 0xD08:0x11 的深入解析3.1 这个寄存器到底控制什么0xD08 页的 0x11 寄存器在 RTL8211FS 的手册里叫RGMII Delay Control Register。它的 bit 4 到 bit 7 控制 RGMII 的延时模式具体来说Bit名称功能4RGMII_TX_DLY发送延时使能5RGMII_RX_DLY接收延时使能6-7RGMII_DLY_SEL延时选择实际配置时常见的值有 0x00无延时、0x11TX 和 RX 都加延时、0x22另一种延时组合等。具体用哪个值取决于你的硬件设计——PHY 到 Zynq 的走线长度、是否用了外部延时芯片等。我这次的问题就是代码里把这个寄存器写成了 0x00但硬件设计需要 PHY 内部加延时。结果 RGMII 接收数据采样错误PHY 自协商虽然能完成但数据包就是收不到。3.2 为什么手册里不容易找到RTL8211FS 的公开手册有几百页寄存器描述分散在各个章节。0xD08 页的寄存器在手册里叫 Extension Page 0xD08但很多版本的手册里这个页的寄存器列表并不完整0x11 寄存器的描述只有一句话RGMII Delay Control。而且 Realtek 的 PHY 手册不像 TI 或 Microchip 的手册那么详细很多寄存器的默认值和推荐配置都没有明确给出。你得结合 Linux 驱动源码、硬件设计文档和实际测试来推断。提示Linux 内核的drivers/net/phy/realtek.c里有 RTL8211FS 的初始化代码里面会写 0xD08 页的 0x11 寄存器。你可以参考这个值来配置裸机代码。3.3 延时配置的计算逻辑RGMII 延时的需求取决于几个因素PCB 走线延时、PHY 内部延时、Zynq GEM 的采样时钟相位。一般来说如果 PHY 到 Zynq 的走线比较短小于 2 英寸可以用 PHY 内部延时如果走线较长可能需要外部延时或者调整 GEM 的采样相位。RTL8211FS 的内部延时大约是 1.5ns 到 2ns。在 125MHz 时钟下一个周期是 8ns延时 2ns 相当于 90 度相位。这个延时量对于大多数设计来说是够用的。我实测下来对于 Zynq 7020 RTL8211FS 的组合0xD08:0x11 写成 0x11 比较稳。但这不是万能值具体还得看你的板子。4. 完整调试过程与修复步骤4.1 第一步确认 MDIO 通信正常在怀疑延时配置之前先得确认 MDIO 读写是正常的。读 PHY 的 ID 寄存器0x02 和 0x03RTL8211FS 的 ID 应该是 0x001C 和 0xC916。如果读出来不对说明 MDIO 通信有问题先解决这个。u16 phy_id1 mdio_read(phy_addr, 0x02); u16 phy_id2 mdio_read(phy_addr, 0x03); // RTL8211FS: phy_id1 0x001C, phy_id2 0xC916如果读出来是 0xFFFF 或者 0x0000检查 MDC 时钟分频、PHY 地址、PHY 复位引脚是否释放。4.2 第二步检查自协商状态读标准页的 0x01 寄存器bit 5 是自协商完成标志bit 2 是链路状态。如果自协商一直不完成可能是 PHY 的 0x00 寄存器配置不对或者自协商被禁用了。u16 bmsr mdio_read(phy_addr, 0x01); if (bmsr (1 5)) { // 自协商完成 } if (bmsr (1 2)) { // 链路建立 }我这次的情况是自协商能完成链路也能建立但数据包收不到。这就指向了 RGMII 延时问题。4.3 第三步读写 0xD08:0x11 寄存器这是关键步骤。先切换到 0xD08 页读 0x11 的当前值然后写入正确的值再读回来确认。// 切换到扩展页 mdio_write(phy_addr, 0x1F, 0xD08); for (volatile int i 0; i 10; i); // 读当前值 u16 delay_val mdio_read(phy_addr, 0x11); xil_printf(RGMII Delay Reg: 0x%04X\r\n, delay_val); // 写入新值 mdio_write(phy_addr, 0x11, 0x11); for (volatile int i 0; i 10; i); // 读回确认 delay_val mdio_read(phy_addr, 0x11); xil_printf(RGMII Delay Reg after write: 0x%04X\r\n, delay_val); // 切回标准页 mdio_write(phy_addr, 0x1F, 0x0000);我读出来的原始值是 0x0000写入 0x11 后读回也是 0x11说明写入成功。然后重新启动自协商网口就通了。4.4 第四步验证网口通信写完之后重新启动自协商等链路建立然后发 ping 包测试。如果通了说明问题解决。如果不通可能还需要调整其他寄存器比如 0x10 寄存器RGMII 控制寄存器。// 重启自协商 u16 bmcr mdio_read(phy_addr, 0x00); bmcr | (1 9); // 重启自协商 mdio_write(phy_addr, 0x00, bmcr);5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因排查方法MDIO 读出来全是 0xFFFFMDC 时钟太快、PHY 未上电、地址错降低 MDC 分频检查 PHY 电源和复位自协商不完成0x00 寄存器配置错、自协商被禁用读 0x00 和 0x01确认自协商使能链路建立但 ping 不通RGMII 延时配置错、MAC 地址冲突读写 0xD08:0x11检查 MAC 地址丢包严重延时配置不匹配、时钟抖动调整延时值检查 PCB 走线千兆不通百兆通RGMII 延时问题、时钟频率不对重点检查 0xD08:0x11 和 GEM 时钟配置5.2 独家避坑技巧技巧一页切换后一定要加延时。我一开始没加延时读出来的值全是 0x0000以为是寄存器不存在。后来加了 10 个空操作循环才读到正确的值。这个延时不需要精确但必须有。技巧二写寄存器后要读回确认。有些 PHY 寄存器是只读的或者写入需要特定条件。写完后读回来确认能避免很多无效操作。技巧三参考 Linux 驱动源码。Linux 内核的realtek.c里有完整的 RTL8211FS 初始化流程包括页切换、延时配置、自协商启动。裸机代码可以照着这个流程来写。技巧四用示波器看 RGMII 信号。如果条件允许用示波器看 RGMII 的时钟和数据线能直观看到延时是否合适。数据眼图张开度好的话延时就是对的。技巧五不同批次的 PHY 可能默认值不同。我遇到过同一型号的 PHY不同批次 0xD08:0x11 的默认值不一样。所以不要依赖默认值一定要显式配置。5.3 一个容易被忽略的细节Zynq 的 GEM 控制器也有 RGMII 延时配置在GEM_NWCFG寄存器里。如果 PHY 内部加了延时GEM 这边就不需要再加。两边都加的话延时过大反而会导致采样错误。我这次的情况是 PHY 内部没加延时GEM 也没加结果采样时刻不对。后来在 PHY 内部加了延时问题解决。所以配置延时的时候要明确延时加在哪一端不要重复加。6. 裸机代码的完整实现参考6.1 PHY 初始化函数把上面的步骤整合成一个完整的 PHY 初始化函数放在裸机代码的网口初始化部分。void phy_init(u32 phy_addr) { u16 val; // 1. 软复位 PHY mdio_write(phy_addr, 0x00, 0x8000); for (volatile int i 0; i 100000; i); // 2. 等待复位完成 while (mdio_read(phy_addr, 0x00) 0x8000); // 3. 配置 RGMII 延时 mdio_write(phy_addr, 0x1F, 0xD08); for (volatile int i 0; i 10; i); mdio_write(phy_addr, 0x11, 0x11); for (volatile int i 0; i 10; i); mdio_write(phy_addr, 0x1F, 0x0000); for (volatile int i 0; i 10; i); // 4. 配置自协商 mdio_write(phy_addr, 0x04, 0x01E1); // 通告千兆全双工 mdio_write(phy_addr, 0x09, 0x0300); // 千兆控制 // 5. 启动自协商 mdio_write(phy_addr, 0x00, 0x1340); // 6. 等待自协商完成 while (!(mdio_read(phy_addr, 0x01) (1 5))); }6.2 调试打印的添加裸机环境下没有 printf但可以用xil_printf输出调试信息。在关键步骤加上打印能快速定位问题。xil_printf(PHY ID: 0x%04X 0x%04X\r\n, mdio_read(phy_addr, 0x02), mdio_read(phy_addr, 0x03)); xil_printf(BMSR: 0x%04X\r\n, mdio_read(phy_addr, 0x01)); xil_printf(RGMII Delay: 0x%04X\r\n, delay_val);6.3 不同硬件设计的适配如果你的板子用的是外部延时芯片或者 PCB 走线特别长0xD08:0x11 的值可能需要调整。常见的值有0x00无内部延时依赖外部延时或走线延时0x11TX 和 RX 都加内部延时0x22另一种延时组合具体看手册0x0B只加 RX 延时我建议先用 0x11 试不通再换其他值。每次改完都要重新自协商。7. 经验总结与扩展思考7.1 裸机网口调试的通用思路裸机下调网口我的经验是分三步走先通 MDIO再通自协商最后通数据。MDIO 不通后面都白搭自协商不通说明 PHY 基本配置有问题自协商通了但数据不通多半是 RGMII 延时或者 MAC 配置问题。每一步都要有明确的验证手段。MDIO 通不通读 PHY ID 就知道自协商通不通读 BMSR 就知道数据通不通ping 一下就知道。不要跳步不要猜。7.2 寄存器调试的通用方法遇到不熟悉的 PHY 寄存器我的方法是先读默认值再写测试值再读回确认。如果写不进去检查页切换和写保护如果读出来不对检查 MDIO 时序和地址。另外Linux 驱动源码是最好的参考。内核里支持的 PHY 型号很多初始化流程都是验证过的。裸机代码照着改能少走很多弯路。7.3 这个问题的扩展影响RTL8211FS 的 0xD08:0x11 寄存器问题不仅影响裸机也可能影响 U-Boot 和 Linux。如果 U-Boot 里没配置这个寄存器Linux 启动后可能也会遇到网口不通。不过 Linux 的 PHY 驱动通常会重新初始化所以问题不一定暴露。如果你在 U-Boot 里也遇到网口不通可以检查一下 U-Boot 的 PHY 驱动有没有配置这个寄存器。没有的话在board_init里加上。最后再分享一个小技巧调试 PHY 的时候准备一个 USB 转 MDIO 的工具或者用另一块板子通过 MDIO 读写目标 PHY能大大加快调试速度。不用每次都烧写 FPGA 和 ARM 程序直接在外部读写寄存器效率高很多。
返回列表