88E1512 SGMII链路不稳定排查与调稳实战 1. 先弄清“网口不稳定”到底指什么88E1512 在 SGMII 模式下工作不稳定几乎是每个做嵌入式网络硬件的人都会碰到的问题。这块 PHY 本身不算复杂但 SGMII 这条链路上有一堆容易被忽略的细节参考时钟的方向、带内管理有没有使能、自协商状态机停在哪个中间态、MAC 侧和 PHY 侧是不是各自认为链路已经好了。任何一个环节出了问题表现都是同一句话——“网口不稳定”。而“不稳定”这三个字太模糊直接开始改寄存器基本等于盲目尝试。我见过太多人一上来就 dump 一堆寄存器然后对着手册逐个 bit 比对耗掉两三天也定位不到根因。正确的顺序是先给现象分类不同症状背后的根因集合差异很大分类之后你至少能把排查范围压缩到三分之一。这一节讲的就是怎么分以及手边必须准备哪些工具。先说清楚这个内容解决什么问题它针对 MAC 侧走 SGMII、媒体侧接铜缆或光模块的千兆以太网设计覆盖从硬件上电、PHY 初始化、链路自协商到实际吞吐验证的完整链路。适合正在调试类似方案的人、被 link 抖动折磨过的驱动工程师、以及需要在板子回板后快速判断问题归属的硬件工程师。零基础也能看但至少得知道 PHY 和 MAC 是两个东西。1.1 五种典型症状对应五套不同的排查路径我把实际遇到过的现象归成五类每一类的第一怀疑对象都不一样按这个表往下走能省很多时间。症状现场表现第一怀疑对象链路抖动dmesg 反复出现 Link Up / Link Down间隔几秒到几分钟参考时钟、自协商配置不一致链路稳定但丢包ping 丢包率 1%~30%大包几乎不通速率/双工不匹配、SerDes 信号质量吞吐上不去能通但 iperf 只跑到 300~600 MbpsSGMII 速率字段错误、CRC 错误累积协商降速强制千兆实际协商成百兆自协商能力寄存器、对端能力不匹配冷启动不通热重启正常断电重启后无 link软重启后恢复复位时序、上电时序、时钟未稳第一类最容易被误判成软件问题。link 抖动很多时候确实跟软件有关比如 MAC 侧的 in-band 自协商没打开PHY 反复上报状态但 MAC 不认两边的状态机就在那里来回摆。但也可能是 25MHz 晶振的负载电容选错起振裕量不够温度一变化频率就飘出 ±50ppm 的窗口。这两类原因在外行看来完全是同一个现象只有把寄存器状态抓下来看才能区分。第二类和第三类经常一起出现。链路能起来说明 SerDes 基本锁上了但数据通路有问题。这时候要第一时间去看 MAC 的统计计数器rx_crc_errors、rx_align_errors、rx_over_errors 这几个字段会告诉你问题严重程度。如果 CRC 错误随流量线性增长那基本上是物理层信号完整性问题如果零 CRC 错误但吞吐就是上不去那更可能是协商参数不对比如速率字段写的是千兆但实际 SerDes 只跑在百兆的重复模式上。1.2 开工前手边必须有这几样东西硬件层面示波器是刚需而且带宽要够。SGMII 是 1.25Gbps 的差分信号测眼图至少需要 2GHz 以上带宽的探头普通 500MHz 的示波器只能看个大概判断不了抖动。如果实在没有高速示波器退而求其次用频率计测 25MHz 参考时钟的精度用万用表测各路的供电电压和上电顺序也能查到不少问题。我手上常备的是一个 25MHz 的有源晶振模块用来做对照测试——怀疑板载晶振有问题时直接飞线换一个已知良好的时钟源一分钟就能确认。软件层面需要能直接读写 PHY 寄存器的通道。Linux 下有几种做法内核自带的 mdio 工具、自己写一个字符设备驱动、或者用 ethtool 的--register-dump。最灵活的还是自己写一套基于 MDIO 总线的最小读写函数能精确控制每次读写的时序排查问题时不受上层框架干扰。另外要准备 iperf3、ping 的变长包测试脚本、以及一个能持续记录 dmesg 的后台进程把 link 状态变化的时间戳记下来事后跟其他日志对齐。还有一个容易被忽略的东西对端设备的配置权限。如果对端是商用交换机你至少要能看到它的端口状态和错误计数。有些问题根本不是本地板子的问题是对端端口被配置成了半双工或者开了某种节能特性本地怎么调都没用。这种时候如果看不到对端的计数器就会陷入无限自我怀疑。提示别在没抓到任何寄存器快照的情况下就开始改代码。改完你也不知道是改对了还是碰巧好了下次出问题还得从头来一遍。2. SGMII 这条链路到底在传什么很多人调不通 SGMII 的根源是对这个协议的理解停留在“它是一根差分线跑 1.25G”这个层面。实际上 SGMII 在物理层之上做了三件很关键的事把千兆数据流塞进 1.25Gbps 的通道、用带内字段传递链路状态、以及用一套速率适配机制兼容百兆和十兆。这三件事每一件出问题都会表现为“网口不稳定”而且症状极其相似。这一节不打算照着手册念而是从“为什么这么设计”的角度讲。理解了设计意图你在看寄存器的时候就知道每个 bit 是在解决什么问题排查时能直接往对应的方向去想而不是逐个 bit 死记。2.1 1.25Gbps 装 1000Mbps 数据的带宽计算先算一笔最基础的账。SGMII 的线路速率是 1.25Gbps采用 8b/10b 编码也就是每 8 个 bit 的有效数据要占用 10 个 bit 的线路带宽编码开销固定是 20%。1.25Gbps 乘以 0.8得到 1000Mbps 的有效载荷速率。这个数字刚好等于千兆以太网的线速一个 bit 都不多。这笔账的意义在于SGMII 的带宽是刚好够用的没有任何余量。这条链路上不存在“留点余量更稳”这种事任何导致有效带宽下降的因素都会直接吃掉吞吐。比如 8b/10b 编码在遇到长连 0 或长连 1 时会插入特殊的控制字符来做直流平衡如果你的数据流里恰好有大量的重复模式插入的字符会更多实际可用带宽就会略低于理论值。这也是为什么有些场景下 iperf 只能测到 940~950Mbps 而不是满 1000Mbps属于正常损耗不用纠结。真正需要警惕的是明显低于 900Mbps 的情况。如果测出来只有 600Mbps 左右比较常见的原因是 SerDes 的均衡参数没调好接收端眼图张开度不够导致误码率上升链路层不断重传。另一个可能的原因是 SGMII 链路上跑在了百兆模式但速率字段没同步数据被重复发送占掉了带宽。这两种情况用示波器看眼图、用寄存器回读速率字段都能很快区分。2.2 带内管理为什么自协商必须两端都开SGMII 最反直觉的设计是“带内管理”。千兆以太网在正常工作时每一帧数据前面都有 7 个字节的 0x55 前导码和一个 0xD5 的帧起始符。SGMII 把这个前导码的位置“征用”了改成发送一个 16 bit 的控制字里面编码了当前链路的速率、双工、link 状态和应答位。这个控制字不占用额外带宽是嵌在数据流里的所以叫带内。问题在于这个控制字只有在链路稳定之后才能传。链路还没建立的时候两端靠什么知道对方支持什么答案就是自协商。PHY 侧通过 SGMII 的配置字告诉 MAC “我现在是千兆全双工link 已建立”MAC 侧收到了才把对应的 MAC 控制器配置成同样的参数。如果一边开了自协商、另一边没开或者两边对配置字格式的理解不一致就会出现这种经典场面PHY 侧显示铜缆 link 已经 up但 MAC 侧始终认为链路是 down或者链路起来了但速率是错的。我遇到最多的一种情况是 MAC 侧把 SGMII 配置成了“固定千兆、禁用 in-band”而 PHY 侧仍在跑自协商。这时候 PHY 会按照自协商结果去驱动 SerDes但对端 MAC 根本不看那个控制字直接按固定千兆收数据。如果 PHY 协商出来的正好是千兆全双工那碰巧能通如果协商成百兆数据就被按错误的方式解析表现为大量 CRC 错误或者干脆不通。这类问题在实验室里可能一直不复现因为对端交换机一直协商成千兆换一台老交换机或者换根网线就炸了。注意判断 in-band 有没有生效别只看 MAC 驱动报的 link 状态那个状态可能是驱动自己根据别的信号猜的。最可靠的方法是回读 PHY 里那个专门记录“当前实际速率和双工”的状态寄存器跟 MAC 侧的实际配置做比对两边一致才算真的一致。2.3 参考时钟整条链路的时间基准SGMII 需要一个参考时钟来驱动内部的 PLL再由 PLL 倍频出 SerDes 需要的 1.25GHz 时钟。这个参考时钟的选择有两种常见方案一种是 PHY 自己外挂一个 25MHz 晶振内部倍频另一种是由 MAC 侧提供 125MHz 的时钟给 PHY 用。两种方案各有各的坑。外挂晶振的方案坑在晶振本身和负载电容。25MHz 无源晶振的精度通常标 ±30ppm 到 ±50ppm听起来很宽松但 SGMII 对时钟抖动的要求其实挺严。晶振的负载电容如果选错起振频率会偏极端情况下根本不起振或者起振后不稳定。我见过一块板子负载电容按典型值选了 12pF但实际晶振要求的负载是 8pF结果常温下能工作一到冬天就频繁掉 link。换成 8pF 之后问题消失。这种问题在实验室室温环境下几乎查不出来。由 MAC 提供时钟的方案坑在时序。MAC 侧的 125MHz 输出通常来自内部 PLL这个 PLL 在 MAC 完成初始化之前是不稳定的甚至可能是关闭的。如果 PHY 的复位在这之前就释放了PHY 会锁到一个不存在或者不稳定的时钟上内部状态机直接卡死。必须保证时钟稳定输出之后再释放 PHY 复位这个顺序在很多参考设计里都没说清楚得自己看两边的上电时序图确认。还有第三种情况就是时钟方向搞反了。SGMII 的时钟方向由一个专门的配置引脚决定这个引脚在上电复位时被锁存。如果硬件上把这个脚接错了PHY 会期望从 MAC 那边收时钟而 MAC 那边又期望 PHY 提供时钟两边都在等对方结果就是谁也不工作。这种硬件级的错误软件完全救不回来只能改板。3. 88E1512 的工作模式与寄存器操作这颗 PHY 支持多种工作模式模式选择在硬件上是靠一组配置引脚在上电复位瞬间锁存的软件上可以读取寄存器确认当前处于哪种模式部分型号还允许通过扩展寄存器覆盖。模式选错是最容易被忽略的问题之一因为现象看起来跟别的问题一模一样但你无论怎么调参数都不会有效果。这一节把模式选择、关键寄存器的用途、以及一个正确的初始化顺序讲清楚。寄存器地址和位定义我按常见约定给出但不同后缀的型号比如带不同封装或者不同特性的版本映射可能有差异具体一定要对照手上那份手册别拿别人的配置表直接抄。3.1 几种工作模式先确认自己在哪一个从 MAC 侧接口和媒体侧接口两个维度组合常见的有这么几种MAC 侧 RGMII、媒体侧铜缆MAC 侧 SGMII、媒体侧铜缆MAC 侧 RGMII、媒体侧接光模块以及 MAC 侧 SGMII、媒体侧也接 SGMII 对端。这四种模式的寄存器配置、时钟方向、自协商行为都不一样。我们这次排查的是第二种和第四种的混合场景也就是 MAC 走 SGMII媒体侧可能接铜缆也可能接光模块。这种情况下有个特别容易混淆的点PHY 内部其实有两条独立的链路一条是 SGMII 侧的 SerDes 链路另一条是媒体侧的铜缆或者光纤链路。这两条链路各自有各自的 link 状态各自跑各自的自协商。很多人调试时只看媒体侧的 link 状态寄存器看到它 up 了就以为整条链路通了结果 MAC 侧没通。反过来也有人只看 MAC 侧的 link 状态媒体侧根本没接对端也没发现。正确的做法是两个都看而且要建立因果关系——媒体侧先 upPHY 才会通过带内字段告诉 MAC 侧 up。如果媒体侧 up 了但 MAC 侧一直 down问题就在 SGMII 这一段如果媒体侧一直 down那 SGMII 配得再对也没用。确认当前模式最省事的办法是看硬件配置引脚的接法或者直接读一个包含模式信息的扩展寄存器。我一般会写一个小小的自检函数上电后把这几个寄存器的原始值打出来存到日志里。这块板子以后出现任何网络问题第一件事就是翻这份上电快照跟正常板子比对能省掉大量猜测。3.2 关键寄存器速查哪些必须看哪些可以跳下面这张表是我调试时会主动去读的寄存器集合不是全部但覆盖了 90% 的排查场景。地址是 IEEE 标准寄存器编号0x00~0x1F其中 0x00~0x0F 是标准定义的0x10 以上是厂商扩展页。地址名称关键位与用途0x00控制寄存器bit15 软复位bit12 使能自协商bit9 重启自协商0x01状态寄存器bit2 链路状态自锁存bit5 自协商完成0x04自协商通告bit8/7 百兆能力bit6/5 双工bit15 下一页0x09千兆控制bit9 通告千兆全双工能力0x11模式控制光纤/铜缆模式、时钟方向相关0x12扩展配置接口模式、SerDes 相关参数0x14PHY 特定控制SGMII 速率与双工的强制/自协商选择0x15页地址扩展页切换的前置操作0x17PHY 特定状态当前实际链路速率、双工、是否同步0x1C扩展控制各种补充配置含 EEE 相关看这张表的时候注意两点。第一0x01 的 bit2 是自锁存位读一次就清零如果你用脚本连续读第二次读到的是 0会误判成链路断开。要么读的时候先读两次取第二次要么干脆改读 0x17 里那个非锁存的实时状态位。第二0x15 是页地址寄存器写扩展寄存器之前必须先往这里写入页号写完之后记得切回来否则后续所有标准寄存器的操作都会跑到别的页上去读出来的全是垃圾数据。还有一个坑是软复位的等待时间。往 0x00 的 bit15 写 1 之后PHY 内部要重新加载所有配置这个时间手册上通常写的是“至少 100ms”但实际测试下来有些批次要等到 300ms 才真正就绪。如果你复位后立刻去读寄存器读回来的可能是 0xFFFF 或者 0x0000然后你的初始化代码基于这个错误的值继续往下配整条链路就废了。稳妥的做法是复位后轮询 0x00 的 bit15直到它自己变回 0再额外延时 50ms 才开始后续操作。3.3 配置顺序顺序错了后面全白搭正确的顺序是硬件复位释放 → 等待 PHY 内部就绪 → 确认模式 → 配置自协商能力 → 重启自协商 → 轮询链路状态 → 读取实际协商结果 → 按结果配置 MAC 侧。这里面最容易被跳过的是“确认模式”。很多初始化代码是照着某个参考设计抄的那套配置对应的是 RGMII 模式直接搬到 SGMII 的板子上寄存器写进去不报错但 PHY 内部走的完全是另一条路径链路永远起不来。加一行代码把模式相关的寄存器读出来打个日志成本几乎为零收益巨大。另一个容易出错的地方是自协商能力的配置。千兆以太网的自协商涉及三个寄存器0x04 管十兆和百兆的能力0x09 管千兆的能力0x00 的 bit12/bit9 管使能和重启。如果你只在 0x09 里通告了千兆没在 0x04 里配置百兆能力对端如果是个只支持百兆的老设备协商过程会走完但结果是不通。更隐蔽的情况是 0x04 的默认值带着一些你不需要的能力比如半双工协商时对端优先选了半双工然后你就得到一个能 ping 通但吞吐极低的链路。提示如果你明确知道对端只支持千兆全双工可以考虑关掉自协商走强制模式。但强制模式要求两端都强制只要有一端在自协商就会出现一端强制一端自协商的经典不匹配问题link 起来但大量丢包。这个组合坑了无数人。4. 从硬件到软件的完整调稳流程前面讲的是原理和寄存器这一节是能直接照着做的流程。我的习惯是从最底层往上走先确认供电和时钟这些物理量没问题再动软件配置。反过来的顺序会让你在软件层反复折腾最后发现是硬件问题白费一天。整个流程大概需要半天到一天取决于你手头的工具齐全程度。如果板子上有测试点把时钟和差分信号引出来了会快很多。如果没有至少要在关键节点飞线别指望盲调。4.1 硬件自检清单先排除物理层的硬伤第一步测供电。这颗 PHY 通常需要多路电源常见的是 1.2V 内核、2.5V 或 3.3V 的 IO。每一路都要测两个值静态电压和纹波。静态电压偏离超过 5% 就直接不合格纹波峰峰值超过 50mV 也要警惕。纹波大的话SerDes 的抖动会明显恶化眼图闭合表现为随机误码。测纹波要用示波器的 AC 耦合模式带宽限制到 20MHz探头用弹簧地针别用长地线否则测到的全是空间耦合的噪声。第二步测上电顺序。多路电源如果有明确的先后要求手册里一般会写要示波器多通道同时抓。顺序错了可能导致内部 ESD 保护二极管导通虽然不一定立刻烧但会让器件工作在异常状态。顺便看一下复位信号的释放时刻复位必须在所有电源稳定并且参考时钟稳定之后才释放提前释放会让内部状态机锁死。第三步测参考时钟。用频率计量频率常温下应该在标称值 ±50ppm 以内。然后用示波器看波形注意峰峰值是否达到要求一般是 CMOS 电平接近 IO 电压以及上升沿是否有过冲或者台阶。有些板子为了省成本时钟走线没有做阻抗控制走到 PHY 引脚上已经是严重畸变的方波了这种情况 PLL 会锁得不稳。如果条件允许直接飞线接一个外部的信号发生器输出标准 25MHz 方波看看问题是否消失这是最快的判定方法。第四步检查 SGMII 差分对。AC 耦合电容有没有装、容值对不对常见 0.1µF、差分阻抗是否接近 100Ω、长度是否匹配。这些在原理图上都能看出来回板后先用万用表量一下通断别等到调软件调两天才发现耦合电容没贴。4.2 MAC 侧与设备树的配置要点软件这边第一件事是把接口模式配对。以常见的 Linux 平台为例设备树里要明确写出 PHY 的连接方式、地址、以及速率限制。gmac0 { status okay; phy-mode sgmii; phy-handle phy0; max-speed 1000; /* 时钟由外部晶振提供时不需要以下两行 */ clocks clk_125m; clock-names stmmaceth; }; mdio0 { phy0: ethernet-phy0 { reg 0; /* 若硬件上复位引脚有接可在此声明 */ reset-gpios gpio 12 GPIO_ACTIVE_LOW; reset-assert-us 10000; reset-deassert-us 300000; }; };这里有两个参数值得单独说。phy-mode写错是最常见的低级错误写成rgmii但硬件是 SGMII驱动会按 RGMII 的时序去配置 MAC 控制器结果就是完全不通或者大量错误。reset-deassert-us这个值我一般会设成 300ms 而不是手册上写的最小值前面说过复位后内部就绪的时间有批次差异多等一点不会有害等少了可能出诡异问题。另外注意max-speed。有些参考设计把它设成 100 来“求稳”这个做法在调试阶段会掩盖问题——你以为是链路稳了其实是根本没跑到千兆。调试期间一定设成 1000等问题解决了再考虑要不要限制。4.3 寄存器初始化代码一段能直接用的骨架下面这段是我常用的初始化骨架C 语言基于一套最简的 MDIO 读写接口。实际移植时你只需要实现mdio_read和mdio_write两个函数其余逻辑可以直接用。#define PHY_ADDR 0x00 #define REG_BMCR 0x00 #define REG_BMSR 0x01 #define REG_PHYSTS 0x17 #define REG_SPEC_CTRL 0x14 #define REG_PAGE 0x15 static int phy_wait_ready(int timeout_ms) { int i; u16 v; for (i 0; i timeout_ms; i) { if (mdio_read(PHY_ADDR, REG_BMCR, v) 0 !(v 0x8000)) return 0; mdelay(1); } return -1; } int phy_init_sgmii(void) { u16 v; /* 1. 软复位然后等它自己清掉 */ mdio_write(PHY_ADDR, REG_BMCR, 0x8000); if (phy_wait_ready(500) ! 0) return -1; mdelay(50); /* 2. 确认当前速率/双工状态可读顺手打印出来 */ mdio_read(PHY_ADDR, REG_PHYSTS, v); pr_info(phy status after reset: 0x%04x\n, v); /* 3. 配置自协商能力十兆/百兆 千兆全双工 */ mdio_write(PHY_ADDR, 0x04, 0x01E1); /* 全双工半双工 10/100 */ mdio_write(PHY_ADDR, 0x09, 0x0200); /* 千兆全双工 */ /* 4. 使能并重启自协商 */ mdio_read(PHY_ADDR, REG_BMCR, v); v | (0x1000 | 0x0200); mdio_write(PHY_ADDR, REG_BMCR, v); /* 5. 轮询自协商完成 */ for (int i 0; i 3000; i) { mdio_read(PHY_ADDR, REG_BMSR, v); if (v 0x0020) break; mdelay(1); } /* 6. 读实际协商结果交给 MAC 侧使用 */ mdio_read(PHY_ADDR, REG_PHYSTS, v); pr_info(negotiated: speed%d duplex%d link%d\n, (v 10) 0x3, (v 13) 0x1, (v 11) 0x1); return 0; }这段代码里有几处刻意为之的设计。第 2 步在读状态之前不做任何配置目的是拿到一个干净的复位后快照跟正常板子对比时用得上。第 5 步的轮询上限设成 3000ms比标准要求的超时时间长很多因为实际遇到过对端设备自协商很慢的情况按标准超时判失败会导致误报。第 6 步打印出来的信息一定要留着这是判断“PHY 认为链路是什么状态”的唯一可靠来源。如果你的平台支持 SGMII 的 in-band 自协商还需要额外确认 MAC 侧有没有使能这个功能。有些 MAC 控制器默认关闭需要单独写一个配置寄存器打开具体看主控的手册。打开之后MAC 会自动从收到的 SGMII 配置字里解析速率和双工不需要软件再手动配置能省掉一整类参数不一致的问题。4.4 验证别只看 link up 就收工链路起来只是第一步真正的验证要看三层。第一层看协商结果是否如预期。用ethtool确认速率和双工Speed: 1000Mb/s、Duplex: Full、Link detected: yes三个都对才算过关。如果显示Unknown!说明驱动没拿到有效的协商信息多半是 in-band 没通。第二层看错误计数。连续跑几分钟流量之后读一下接口统计watch -n 2 ethtool -S eth0 | egrep err|drop|crc|align|carrier正常情况下这些计数应该全程为 0 或者只在最开始有个位数。如果 CRC 错误持续增长直接回到 4.1 检查信号完整性。如果 carrier 变化次数在涨说明链路在反复断连回到 3.3 检查配置顺序。第三层看实际吞吐。用 iperf3 双向跑时长至少 60 秒多线程并发这样才能压满带宽。# 服务端 iperf3 -s -p 5201 # 客户端上行 iperf3 -c 192.168.1.100 -p 5201 -t 60 -P 4 # 客户端下行反向 iperf3 -c 192.168.1.100 -p 5201 -t 60 -P 4 -R千兆链路的合理预期是 930~950Mbps低于 900 就值得查一查。另外建议同时跑一个长时间的 ping包大小设成 1472加上 28 字节头刚好 1500间隔设成 0.01 秒跑十分钟观察有没有偶发丢包。有些问题只有在长时间高负载下才会暴露短时间的 iperf 测不出来。5. 问题速查与实战踩坑记录前面四节讲的是“应该怎么做”这一节讲“做的时候会出什么岔子”。我把这几年在这颗 PHY 上踩过的坑整理成一张速查表后面再展开讲三个印象最深的案例。案例里的具体场景做了脱敏处理但技术细节是真实的。5.1 症状与处置速查表现象最可能原因处置方法复位后读寄存器全 0xFF复位未释放或 MDIO 不通查复位引脚电平查 MDIO 上拉电阻媒体侧 link up 但 MAC 侧 downSGMII in-band 未使能使能 MAC 侧带内自协商link 反复 up/down周期数秒参考时钟不稳或频率偏移换外部时钟源验证量晶振频率能 ping 通但大包不通双工不匹配或 MTU 配置不一致检查两端双工设置确认 MTU吞吐只有 300~600MbpsSerDes 参数不当或 CRC 错误重传看错误计数必要时调均衡参数协商成千兆但实际百兆速率速率字段与 SerDes 实际速率不同步回读 PHY 状态寄存器比对冷启动不通热重启正常上电时序或时钟就绪顺序问题延后复位释放时刻多口板子只有部分口工作PHY 地址冲突或 MDIO 广播写检查地址引脚确认无广播操作5.2 案例一等了 100ms 但 PHY 还没准备好的教训这是最早踩的一个坑也是后来所有初始化代码都加长延时习惯的由来。当时的现象是板子冷启动后网络完全不通重新加载驱动就好了。第一反应是驱动初始化顺序有问题查了半天没找到原因。后来在初始化函数里加了详细的日志发现软复位之后立刻读 0x00 寄存器读回来是 0x8000也就是复位位还挂着说明内部根本没复位完。按手册上写的“复位完成后位自动清零最长 100ms”我们的代码是延时 100ms 之后直接往下走没有做轮询确认。把那块板子的延时改成 500ms 之后问题消失。后来陆续测了几批不同的板子复位完成时间从 80ms 到 400ms 不等跟供电爬升速度和温度都有关系。所以现在的做法是固定用轮询而不是固定延时轮询上限给到 800ms正常情况下几十毫秒就出来了极端情况也能兜住。这个经验推广开来凡是手册上写“典型值 XX”的地方都当成“最小值”代码里按 3 到 5 倍去留余量。嵌入式开发里因为卡着典型值写延时导致的诡异问题比因为参数配错导致的还多。5.3 案例二in-band 没开两个状态机各说各话第二个坑更隐蔽。现象是链路在大部分情况下正常但每隔十几分钟就断一次断开时间大约 2 秒然后又自己恢复。这种周期性抖动最难查因为它看起来像外部干扰。后来在 dmesg 里抓到一条规律每次断连之前MAC 侧都会打一条“link partner not responding”而 PHY 侧的铜缆 link 状态始终是 up。这就说明 PHY 认为链路好好的但 MAC 认为对端没响应。查 MAC 的配置发现带内自协商那个使能位是关着的MAC 是按固定千兆全双工在跑。两边在绝大多数时间能对上因为交换机也是千兆全双工但交换机在做某种周期性的端口状态刷新时会短暂地在控制字里改一些位MAC 因为没有解析控制字的能力直接把这当成链路异常处理了。打开 MAC 侧的 in-band 之后问题彻底消失。这个案例说明一件事SGMII 链路的两端必须使用同一套协商机制。一端强制一端自协商的配置在实验室里跑一小时可能都没事但放到实际环境里就是一颗定时炸弹。5.4 案例三节能特性与 SGMII 的兼容性第三个坑跟链路稳定性本身没关系但表现完全一样。有一批设备在接入某些交换机之后会出现不定期的丢包丢包率不高大约十万分之一但 TCP 重传会让长连接的性能明显下降。抓包看是随机的 CRC 错误但几十次之后又恢复正常。排查了很久最后是在交换机的端口配置里找到了原因那台交换机默认开启了节能特性EEE它会在链路空闲时让 PHY 进入低功耗状态。SGMII 这条链路不支持这种低功耗握手链路进入低功耗状态之后恢复的过程中会有短暂的不稳定就产生了随机的 CRC 错误。处理办法有两个。一是本地 PHY 侧把 EEE 能力关掉让自协商时不通告这个特性对端就不会尝试进入低功耗状态。二是在交换机侧把对应端口的节能特性关掉如果是自研设备就直接改配置。前者更可控因为是我们在自己的代码里做不依赖对端。具体做法是在扩展寄存器里找到 EEE 相关的通告位清零之后重启自协商然后用寄存器回读确认那个位确实变成了 0。这个坑给我最大的启示是排查链路问题时除了本地配置一定要把对端的能力集合也考虑进来。越是“默认开启”的特性越要警惕因为它们在双方都支持时才会激活而你的实验室里可能压根就没有支持它的对端设备。提示遇到“偶尔丢一点点包”这种问题先别怀疑信号完整性。看看两端的自协商结果里都通告了哪些高级特性把非必需的特性全关掉很多时候问题就没了。功能越少出问题的概率越低。5.5 几条不写在手册里的经验第一条是关于测试覆盖的。任何一次配置改动之后不要只测正常路径。至少要覆盖对端强制千兆、对端强制百兆、对端自协商三种组合。我遇到过配置只在自协商场景下正常一旦对端强制百兆就彻底不通原因是 PHY 侧的速率字段没有正确跟随自协商结果更新。这种问题在全是新设备的实验室里永远发现不了。第二条是关于日志的。所有跟 PHY 相关的寄存器读写我都在驱动里加了一级可开关的调试日志把地址、寄存器号、读写的值、时间戳全打出来。平时关着不影响性能出问题的时候打开能完整回放整个初始化过程。这块代码只花半小时写但每次救我都是几小时起步。第三条是关于版本管理的。PHY 的初始化参数在新版硬件上可能需要微调所以配置文件要跟硬件版本绑定。我见过有人用同一套参数烧到不同批次的板子上一半正常一半有问题查了两个星期才发现是批次差异。让硬件同事在板子上留一个版本标识软件启动时读出来选择对应的参数集这个投入产出比极高。5.6 关于扩展寄存器和页切换的补充最后补充一个操作细节这个细节在文档里通常一句话带过但实际很容易翻车。这颗 PHY 的很多关键配置放在扩展页里访问方式是先往页地址寄存器写页号再访问目标寄存器完了之后要切回标准页。问题在于如果中间有任何一次访问失败比如 MDIO 时序出错或者总线被别的设备抢了页指针就会停在错误的页上后续所有操作全部错乱。我的做法是封装成一对函数phy_ext_write(page, reg, val)和phy_ext_read(page, reg)内部强制遵守“切页 → 操作 → 切回”的流程而且切回不管成功失败都执行。另外在多口设备上页指针是每个 PHY 独立的还是全局共享的这个要看具体型号如果是共享的还要考虑并发访问的加锁问题。多核系统上两个核同时操作不同的 PHY很容易因为页指针打架出现随机错误这种错误极难复现加锁成本又极低别省这一步。static DEFINE_MUTEX(phy_page_lock); int phy_ext_write(u8 addr, u16 page, u8 reg, u16 val) { int ret; mutex_lock(phy_page_lock); ret mdio_write(addr, REG_PAGE, page); ret | mdio_write(addr, reg, val); ret | mdio_write(addr, REG_PAGE, 0x0000); /* 无论成败都切回 */ mutex_unlock(phy_page_lock); return ret; }写完这段再回看前面那些坑你会发现大部分“不稳定”其实都能归结成两类一类是时序没等够一类是两端的认知没对齐。把这两类问题的检查点固化进初始化流程剩下真正需要靠经验和仪器解决的其实不多。我在实际项目里形成的习惯是每块新板子第一次上电先不跑业务花二十分钟把上面这些检查点全部走一遍把正常状态下的寄存器快照存下来。这份快照的价值在半年后会体现出来当同一批次的某块板子出现问题时把它和快照一比对差异点基本就是根因所在。