做嵌入式Linux的,总会在某个项目里跟PHY芯片杠上。我最近连续在两个平台间来回切换——一边是飞腾D2000的板卡,一边是RK3568的板卡,PHY芯片则固定用YT8521和AR8035。一开始我以为换个CPU而已,设备树抄过来改改引脚就能跑,结果整整折腾了三天,最后发现问题全在PHY寄存器调试这个环节上。
这篇文章不是芯片手册的翻译,也不是内核驱动源码解析,而是我实际在两个平台之间移植、调试PHY的记录。核心会围绕YT8521和AR8035这两颗芯片,讲清楚怎么用MDIO读寄存器、怎么判断link/协商/CRC问题、设备树里各种delay配置到底怎么配对、以及从飞腾平台换到RK3568平台时最容易踩的几个坑。适合正在做BSP适配、SCM平台切换、国产化替换的嵌入式工程师参考,尤其适合被“百兆正常千兆CRC错误”折磨的朋友。
1. 从飞腾D2000到RK3568:换了CPU,PHY为什么先翻车
1.1 背景:两块板卡、两颗PHY,一次交叉替换
先说项目背景。上一块板子是飞腾D2000平台,跑Linux 4.19内核,GMAC接口外接一颗AR8035千兆PHY,设备树里配的是rgmii-id模式,链路稳如老狗。新板子是RK3568平台,用的还是那颗AR8035,我当时很自信地照着飞腾的设备树改了一版,结果插上网线link是有的,但千兆模式下PING丢包,ethtool -S里rx_crc_errors蹭蹭往上涨。
后来另一块RK3568板卡用了国产YT8521,问题更隐蔽:百兆一切正常,切到千兆后接收方向大量硬件CRC错误,协商却显示1000M成功。这两次经历让我意识到,平台换了,PHY寄存器调试思路必须跟着换,不能只搬设备树。
如果你也遇到类似情况,先别怀疑PHY芯片坏了。绝大多数情况下,问题出在MAC和PHY之间的RGMII接口——特别是时钟延迟(delay)配置。RGMII不像MII那样有独立的RXDV采样窗口,它靠边沿对齐和2ns延迟来保证数据正确性,这个2ns到底是MAC补还是PHY补,不同的平台、不同的PHY默认情况完全不一样。
1.2 平台差异点:不只是CPU指令集变了,MAC和MDIO实现也不同
飞腾D2000和RK3568都是ARM架构,但外设控制器设计差别很大。飞腾的GMAC和瑞芯微的GMAC虽然都可能挂到Linux的stmmac框架下,但硬件寄存器、时钟树、复位逻辑、DMA描述符布局都有差异。也就是说,哪怕设备树语法差不多,内核驱动实际操作的是两套完全不同的硬件。
最直接的体感是MDIO总线。飞腾平台在默认内核配置下,MDIO时钟分频、上拉强度、PHY地址映射都比较宽松;RK3568这边如果管脚复用没配好,或者MDIO上拉电阻没贴,会出现“PHY地址扫不到”“读ID读一半”的现象。而且不同平台自带的内核配置差别很大,比如飞腾内核可能默认开启了CONFIG_PHYLIB和CONFIG_AT803X_PHY,RK3568则默认启用的是瑞芯微自己的GMAC驱动,PHY驱动不一定匹配。
另外,时钟源差异也容易被忽略。飞腾板卡上GMAC参考时钟一般固定由外部晶振提供,而RK3568开发板设计里,GMAC的125MHz时钟经常是由CPU内部PLL输出,经过SoC的clk_gmac1_ethernet引脚送给PHY的。设备树里少了assigned-clock-rates配置,或者clock_in_out字段不对,会导致PHY根本没有参考时钟,线上完全没信号。
所以,跨平台调试PHY的第一步不是翻PHY的数据手册,而是先把两个平台的MAC、MDIO、时钟、复位这四件事对齐。很多寄存器问题本质上是物理链路时钟不对,不是寄存器值不对。
1.3 调试思路:先别动硬件,先拿寄存器说话
我常用的调试顺序是这样的:
- 确认PHY能被MDIO访问到。如果PHY ID都读不出来,后面所有操作都是空谈。
- 确认PHY的link状态。用基本状态寄存器看link up没有,协商到多少速率,是全双工还是半双工。
- 确认收发路径。通过
ethtool -S看错误计数,比如CRC错误、alignment error、missed packets。 - 最后才去动delay、EEE、流控这些高级寄存器。
这个顺序看起来简单,但很多人容易跳步。我在调试YT8521的时候,一开始直接看设备树里的delay配置,改来改去也没找到问题,后来老老实实把PHY寄存器从头到尾dump了一遍,才发现是PHY内部的接收延迟没有使能,导致RK3568 GMAC采样窗口不对。
再补充一句,调试PHY寄存器的时候,建议手边放一个万用表,先把PHY供电、复位引脚、参考时钟这些物理电平等量出来。尤其是RK3568平台,有些公板设计GPIO默认上下拉会自动把PHY的某个strap引脚拉到非预期电平,这个用寄存器看不出来,只能靠硬件排查。
2. 熟悉两颗PHY:YT8521与AR8035的寄存器基础
2.1 怎么用MDIO把PHY内裤翻出来
PHY寄存器调试的核心工具是MDIO总线。不管是标准Clause 22还是扩展的Clause 45,调试第一步都建议用mdio命令行工具,而不是直接在驱动里打日志。Linux下的mdio-tools装好之后,用法很简单:
# 扫描当前系统里的PHY地址 mdio list # dump一个PHY的所有标准寄存器 mdio dump phy0 # 读某个寄存器的值 mdio read phy0 0x02mdio dump phy0输出0x00到0x1F共32个寄存器,这是诊断PHY问题最直接的“体检报告”。如果你发现某一项值明显不对,再去查数据手册对应寄存器位,基本能定位方向。
对PHY来说,最重要的几个寄存器如下:
| 寄存器地址 | 寄存器名 | 关键信息 |
|---|---|---|
| 0x00 | BMCR | 速度、双工、自协商、回环、Power Down |
| 0x01 | BMSR | Link状态、自协商能力、支持的速度 |
| 0x02/0x03 | PHY ID Reg | 读取PHY芯片型号和版本号 |
| 0x04 | ANAR | 本端自协商通告能力 |
| 0x05 | ANLPAR | 对端自协商能力接收结果 |
| 0x0A | 扩展状态寄存器 | 1000BASE-T半双工/全双工支持 |
通过BMSR的bit2可以确认link状态。注意这个位是锁存的,如果曾经link up过然后再断掉,必须重新读才会更新。所以排查时经常要连续读两次,第一次读把锁存状态清掉,第二次读才是当前真实状态。
2.2 YT8521和AR8035的常见寄存器差异
YT8521和AR8035虽然都是千兆PHY,寄存器兼容IEEE 802.3通用部分,但在扩展寄存器上差异非常大。AR8035属于高通的QCA8033系列,PHY ID在设备树里常见写法是ethernet-phy-id004d.d072,它的扩展寄存器有一部分叫Debug Register,需要通过两个寄存器间接访问。
YT8521则是国产裕太微的芯片,同样支持千兆RGMII/SGMII,但扩展寄存器访问方式更接近“Page寄存器”模式,经常要先往某个寄存器写入页选择值,再去另一个寄存器读写具体数据。具体页号和寄存器地址不同型号版本会有差异,但这个访问套路一定要知道,否则你看到的寄存器值全是默认值,改了半天等于白改。
另外,YT8521支持以太网供电检测、wake-on-lan、节能以太网(EEE)这些功能。EEE在普通网络下问题不大,但在EtherCAT这类实时工业以太网场景下,默认打开EEE或者MDI节能功能会引入额外的延迟和丢包,调试时最好先在设备树或者寄存器层面把它关掉。
AR8035这边还有个典型特征是“Smart Speed”,在某些百兆/千兆切换场景下会自动调整信号补偿。这个功能本意是好的,但如果在板级匹配不好的时候,反而会导致千兆接收误码。我遇到过AR8035在低温环境下出现大量CRC错误,最后不是寄存器配置问题,而是接收端走线串扰,但一开始也怀疑过Smart Speed相关寄存器。
2.3 用寄存器判断link、speed、duplex和CRC问题
当你怀疑“协商到了1000M,但收发不正常”时,不要只信ethtool eth0的输出。ethtool显示的Speed/Duplex是MAC驱动从PHY那边拉过来的状态,它只代表PHY认为链路协商好了,并不代表物理信号质量没问题。
标准做法是同时看这几个寄存器:
- BMSR (0x01):确认link up,并且当前PHY有能力工作在千兆模式。
- ANLPAR (0x05):对端是否通告了1000BASE-T全双工。如果对端只通告百兆,那协商下来自然是百兆。
- 扩展状态寄存器 (0x0A):确认PHY确实支持1000BASE-T,并且协商结果为1000M全双工。
- 厂商状态寄存器:例如某些PHY在特定寄存器里统计接收Symbol误差、CRC错误次数,这个比MAC侧统计更靠近物理层。
CRC错误多,通常说明PHY接收到的信号没问题但是时钟采样错位了。在RGMII模式下,最典型的两个原因:一是TX/RX delay配置错误,二是参考时钟质量不佳。前者靠寄存器能查,后者就需要用示波器看时钟边沿和数据边沿的相位关系了。
我在遇到“百兆正常千兆CRC错误”时,先用寄存器确认了协商结果确实是1000M全双工,然后看MAC收包错误计数。错误集中在rx_crc_errors,基本排除软件栈问题,直接定位到RGMII时钟采样窗口。接下来就是反复修改delay配置做二选一实验。
3. 从飞腾设备树到RK3568设备树:PHY部分改哪里
3.1 管脚复用和MDIO总线先查清楚
从飞腾平台移植到RK3568,最不能偷懒的就是管脚复用。飞腾D2000的GMAC管脚不一定和RK3568的管脚号、复用功能一一对应,直接复制设备树会导致MDIO读写异常。
在RK3568设备树里,需要确认gmac1_miim、gmac1_rgmii_clk、gmac1_rgmii_bus这些pinctrl节点是否被正确引用了。以gmac1为例,经常是这样定义的:
&gmac1 { phy-mode = "rgmii-id"; clock_in_out = "output"; assigned-clocks = <&cru CLK_GMAC1_TX_RX>, <&cru CLK_GMAC1_ETHERNET>; assigned-clock-rates = <0>, <125000000>; pinctrl-names = "default"; pinctrl-0 = <&gmac1_miim &gmac1_rgmii_clk &gmac1_rgmii_bus>; phy-handle = <&rgmii_phy1>; status = "okay"; };注意clock_in_out这个字段,它告诉驱动MAC的参考时钟是输出给PHY还是从外部输入。如果设成output,就意味着RGMII参考时钟由RK3568产生并以125MHz送出;如果板上有独立晶振给PHY,可能应该设成input。这个字段配错,PHY寄存器是能读到的,但链路根本起不来。
另外,MDIO总线上如果挂了两颗PHY,不要假设它们在同一个地址。YT8521和AR8035的默认PHY地址都由strap引脚决定,可能是0x00、0x01、0x04等,必须通过mdio list确认真实地址,再去改设备树reg字段。
3.2 phy-mode与tx/rx delay一定要和PHY内部delay配对
这是整篇最核心的问题。RGMII协议要求数据在时钟上升沿/下降沿附近采样,为了满足时序,需要为参考时钟增加大约2ns的延迟。这个延迟可以加在TX方向,也可以加在RX方向,可以由MAC加,也可以由PHY加,关键是不能两边都加,也不能两边都不加。
在Linux设备树里通常这么分类:
| phy-mode | 含义 |
|---|---|
rgmii | 双方都不加内部delay,所有延迟由外部PCB走线或MAC内部delay配置保证 |
rgmii-id | 由PHY内部同时提供TX和RX delay |
rgmii-txid | 只由PHY提供TX delay |
rgmii-rxid | 只由PHY提供RX delay |
飞腾D2000平台那套板卡,AR8035的strap电阻已经选择让PHY内部delay全开,所以设备树里写rgmii-id是没问题的。但RK3568平台如果照抄rgmii-id,而板上YT8521的默认strap并没有把delay打开,那就会出现link能起来但数据采样不对的现象。相反,如果RK3568内部delay也打开,PHY内部也打开,延迟叠到4ns,同样会出错。
调试时可以借助一个简单实验:先把phy-mode分别改成rgmii、rgmii-id、rgmii-txid、rgmii-rxid,配合ethtool -S观察CRC错误的变化。哪个模式的错误明显下降,就说明这个方向上的delay来源匹配对了。
3.3 reset GPIO时序、phy地址和compatible字段别想当然
RK3568设备树里PHY节点的reset-gpios不是摆设。很多开发板默认模板会给一个GPIO做PHY复位,但飞腾平台的老设备树可能没有这个字段。移植时必须确认复位GPIO是否存在、极性是否正确,以及复位时间是否足够长。
常见写法:
&mdio1 { rgmii_phy1: ethernet-phy@0 { reg = <0>; compatible = "ethernet-phy-id004d.d072", "ethernet-phy-ieee802.3-c22"; reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <50000>; }; };reset-assert-us表示复位拉低保持10ms,reset-deassert-us表示释放复位后等待50ms再访问PHY。不同PHY需要的复位时间不一样,AR8035和YT8521我都遇到过释放太快导致PHY ID读不对的情况。特别是YT8521,上电初始化比较慢,50ms未必够,如果发现第一次MDIO访问不到,可以把这个时间加长到100ms试试。
compatible字段里的ethernet-phy-idxxxx.xxxx是PHY驱动绑定用的。很多人直接抄AR8035的ID到YT8521节点里,结果内核用AR8035驱动去访问YT8521的扩展寄存器,越调越乱。建议先不加compatible,让内核自动探测PHY ID,正常识别后再把正确的ID写回设备树。
3.4 RK3568特殊点:gmac没有内部delay补偿时的处理
RK3568的GMAC在设计上支持内部延迟线调整,但不同内核版本的驱动对设备树delay字段的支持程度不一样。有些RK3568 SDK设备树里会有tx_delay和rx_delay:
&gmac1 { tx_delay = <0x2f>; rx_delay = <0x1b>; phy-mode = "rgmii"; };这里的值不是随便写的,它们代表MAC内部delay line的档位。具体换算关系每个SoC不同,需要参考RK3568 TRM。如果phy-mode设置成rgmii-id,驱动一般会认为delay由PHY负责,从而忽略tx_delay/rx_delay;如果设置成rgmii,则MAC会去配置这些内部延迟值。
但这里有个坑:rgmii模式下如果驱动没有写入delay寄存器,或者默认值落在采样窗口边缘,就会复现“CRC错误很多”的现象。我建议调试时优先走PHY内部delay这条路,也就是把phy-mode设成rgmii-id,然后在PHY端通过寄存器确认delay确实打开了。这样MAC和PHY职责清晰,排查起来更快。
4. 实操:RK3568平台YT8521/AR8035寄存器调试记录
4.1 环境准备与工具
我调试用的环境是RK3568跑Linux 5.10内核,根文件系统里安装了mdio-tools和ethtool。系统起来后,先确认网络接口名和PHY地址:
ifconfig eth0 up mdio list默认情况下,mdio list会显示类似mdio-bus:01这样的名字,或者phy0。如果没有任何PHY输出,检查MDIO管脚是否被复用、PHY复位GPIO是否正确、供电是否正常。排除物理问题后,再去查驱动。
如果系统里没有mdio命令,也可以用devmem直接读写GMAC的MDIO寄存器,但那样效率太低,不推荐。另外强烈建议打开内核的PHY debugfs:
# 确认内核挂载了debugfs后,可以看phy状态 cat /sys/kernel/debug/mdio_bus/*/*这些节点能看到PHY寄存器快照,比反复敲命令更方便分析。内核需要打开CONFIG_MDIO_DEVID相关选项,不同内核实现略有差异。
4.2 复现问题:百兆正常、千兆CRC错误一屏
我的第一块RK3568板子用的是AR8035,第二块用的是YT8521。两块板子都出现了“百兆正常,千兆大量接收CRC错误”的现象,但详细迹象不同:
AR8035那块的ethtool eth0显示:
Speed: 1000Mb/s Duplex: Fullethtool -S eth0里:
rx_crc_errors: 1048573YT8521那块更典型,一开始强制千兆:
ethtool -s eth0 speed 1000 duplex fulllink是起来了,但是PING包不稳定,rx_crc_errors每秒钟涨几十万个。切回百兆:
ethtool -s eth0 speed 100 duplex full立刻全部正常,0错误。
这里有个重要现象:手段强制到千兆后,PHY不协商,直接在物理层建链。CRC错误多基本上不是协商问题,而是RGMII数据线上的时钟/数据对齐问题。也就是说,问题大概率在MAC与PHY之间的digital接口,而不在线缆和变压器端。
4.3 定位与修复:delay配置导致RX时钟采样窗口不对
我先把YT8521的PHY ID读出来,确认驱动绑定的正确性:
mdio read phy0 0x02 mdio read phhy0 0x03读出的ID和Linux驱动匹配无误。再读BMSR和扩展状态寄存器,确认千兆能力没问题。接下来把重点放在RGMII delay上。
我在不同phy-mode下做了几组对比实验:
| 设备树phy-mode | AR8035结果 | YT8521结果 |
|---|---|---|
rgmii | CRC错误很多 | CRC错误很多 |
rgmii-id | 正常 | 仍然有CRC错误 |
rgmii-txid | CRC错误很多 | CRC错误很多 |
rgmii-rxid | 基本正常 | 仍然有CRC错误 |
这个结果说明AR8035的PHY内部delay配合rgmii-id是没问题的,但YT8521即使设了rgmii-id,PHY内部的RX delay可能没有真正打开。我后来在YT8521数据手册里找到扩展寄存器,通过MDIO手动开启内部delay后,再配合rgmii-id,千兆CRC错误立刻清零。
这里要强调的是,不要以为设备树写rgmii-id就万事大吉。rgmii-id只是告诉MAC驱动“delay由PHY提供”,但PHY到底有没有默认打开delay,由芯片的strap引脚和寄存器默认值决定。如果PHY默认关闭,必须额外配置。
4.4 用寄存器确认最终配置并固化到设备树
修复之后,我用以下命令确认最终状态:
# 读PHY basic control register,确认自协商开启 mdio read phy0 0x00 # 读PHY link状态,确认千兆全双工 mdio read phhy0 0x01 # 读扩展状态寄存器,确认1000BASE-T全双工能力 mdio read phy0 0x0A确认无误后,将PHY内部delay的初始化配置写成设备树或内核PHY驱动补丁。有些方案是用udev/systemd在系统启动后执行mdio write,但那只是临时方案,重启就丢。我建议要么改PHY驱动,在config_init阶段调用phy_write,要么在板级设备树的PHY节点里挂载一个小的PHY fixup,这两条路都比启动脚本可靠。
如果做EtherCAT主站,还需要额外把PHY的EEE功能关掉,并且把自协商关闭,强制工频100M全双工,避免PHY在实时通信过程中重新协商。用MDIO命令就是:
# 0x2100代表100M全双工,关闭自协商 mdio write phy0 0x00 0x2100这个配置同样建议写到驱动初始化代码里,而不是依赖开机脚本。
5. 常见问题与排查技巧实录
5.1 问题速查表
调试过程中积累了一些典型问题,我把它们整理成速查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| MDIO读不到PHY ID | 复位未释放、时钟没给、管脚复用错误、供电异常 | 示波器量复位和时钟;检查设备树reset-gpios、pinctrl |
| PHY ID能读,但link一直down | 变压器/网口焊接问题、对端未协商、PHY被Power Down | 读BMSR和BMCR,确认没置power down,换网线/对端设备试 |
| link up但ping不通 | MAC的RX/TX delay配置错误、PHY mode不匹配 | 切换phy-mode,观察CRC错误;用ethtool看packets no管用 |
| 百兆正常,千兆CRC很多 | RGMII采样窗口不对、delay叠加或缺失、信号质量差 | 分别测rgmii/rgmii-id/rgmii-txid/rgmii-rxid;检查PCB走线 |
| 千兆协商成功但吞吐很低 | EEE、流控、对端不支持千兆、MDI-X问题 | 读ANLPAR确认对端能力;关闭EEE和流控重测 |
| 重启后PHY配置丢失 | PHY strap引脚默认值改变、复位时序不对 | 量strap引脚电平;延长复位时间;将配置固化到驱动 |
这张表不是万能的,但覆盖了绝大多数常规调试卡点。很多问题最终排查发现都是“寄存器设置没固化”导致的——调试时手动写寄存器好了,一重启又恢复原样。
5.2 排查步骤:不看文档只看寄存器怎么判断
如果你手头没有完整的数据手册,依靠通用寄存器也可以做一轮有效的判断。我建议按下面几步走:
- 读PHY ID寄存器,确认PHY型号。如果ID全是0xFFFF或0x0000,先解决物理访问问题。
- 读BMSR,确认link状态。如果link为0,检查媒体接口和PHY供电。
- 读ANAR和ANLPAR,对比两端协商能力。如果对端只通告百兆,PHY自然协商到百兆,不是配置问题。
- 强制千兆或者百兆,看错误计数变化。如果强制百兆正常、千兆异常,优先级指向RGMII接口delay。
- 用示波器抓RGMII时钟和数据信号的相位关系。理想的TCLK边沿应该落在数据有效窗口中间,如果边沿和数据跳变对齐,就是delay缺失或过大。
这套流程我在多个平台验证过,基本能排除九成以上的PHY问题。而且它不依赖某个具体PHY厂商驱动,通用性很强。
5.3 实操心得与避坑清单
最后分享几条我个人很深的体会:
第一,PHY调试最忌“经验主义”。AR8035那套在飞腾D2000上跑得很顺的配置,直接搬到RK3568和YT8521上就是不行。不同PHY的默认delay策略不一样,不同CPU平台的GMAC delay策略也不一样,必须逐板实测。
第二,多准备几个phy-mode测试。我遇到CRC错误后,会把四种rgmii模式都跑一遍,然后看错误计数。这个实验成本很低,但信息量极大,能快速区分是谁的责任。
第三,不要轻信ethtool的link状态。协商成功不代表数据通路正常,ethtool -S里的CRC、alignment error、rx_fifo_errors这些计数才是数据链路的真实体检结果。特别是“link up但没有流量”的时候,一定要去翻这些计数。
第四,EtherCAT时间敏感网络场景下,PHY的初始化不能只依赖自动协商。建议强制100M全双工并关闭EEE,同时把PHY的所有状态中断、回环、Power Save功能关掉,减少实时通信中的不确定因素。
第五,所有调试用的寄存器修改,最终都要固化到代码里。用命令行敲寄存器只能验证想法,线上设备一重启就会回到原点。我踩过好几次“调试好了,忘了固化,换一台设备又出问题”的坑,后来直接在驱动初始化代码里加了一个板级PHY fixup函数,统一处理YT8521和AR8035的delay、EEE、自协商配置。
所以说,从飞腾D2000到RK3568,换的不只是SoC,还有整套硬件调试思维方式。PHY寄存器调试这件事,说白了就是跟信号时序和芯片默认行为打交道,平台再变,核心思路不变:先物理通,再寄存器通,最后才谈性能优化。