以太网PHY调试:Clause 22与Clause 45管理接口帧格式详解 以太网调试现场最常听到的一句话就是我明明读到了PHY寄存器怎么值不对。十次里有八次问题出在Clause 22和Clause 45这两套管理接口帧格式被混用了。SMIStation Management Interface作为MAC与PHY之间的管理通道底层跑的是MDIOManagement Data Input/Output两线协议但上层帧格式分成了两代Clause 22管的是5位寄存器地址Clause 45扩展到16位并引入了设备类型Device Type/MMD的概念。很多PHY芯片同时支持两套访问方式寄存器地址空间却各算各的你拿Clause 22的偏移去读Clause 45的寄存器读回来的自然是一堆无意义的数。这篇内容就是把这层窗户纸捅破从帧结构、时序、寄存器映射到实际驱动代码把两种访问姿势彻底讲清楚适合正在调PHY驱动、写MDIO工具或者排查链路问题的嵌入式工程师。1. 先搞清楚SMI和MDIO到底谁管谁1.1 从MAC和PHY的物理连接说起一块以太网芯片MAC侧和PHY侧之间除了数据通道RGMII、RMII、SGMII这些还有一条独立的管理通道。这条管理通道就是SMI它由两根线组成MDCManagement Data Clock和MDIOManagement Data Input/Output。MDC是时钟由MAC侧或者叫STAStation Management Entity驱动频率通常在1MHz到12.5MHz之间标准里规定最高不超过25MHz。MDIO是双向数据线用来传输命令和读写的数据。很多人把SMI和MDIO当成一回事严格来说SMI是接口的整体名称MDIO是其中数据线的名字但在工程语境里大家经常混着用说MDIO读写其实就是指通过SMI接口访问PHY寄存器。这个不用太纠结理解成MDIO协议就行。关键点在于MDIO协议本身只定义了物理层的时序和帧的传输方式但帧里每个bit代表什么意思是由Clause 22和Clause 45分别规定的。这就好比同一条电话线你可以用老式拨号的方式打电话也可以用新的信令协议线没变但说的话的格式变了。1.2 为什么会有两套ClauseClause 22诞生于以太网早期那时候PHY的寄存器数量很少5个bit的地址0到31完全够用。标准寄存器就那么十几个BMCR0x00、BMSR0x01、PHYID10x02、PHYID20x03、ADVERTISE0x04等等。这套东西简单直接一个帧里塞进PHY地址5bit、寄存器地址5bit、操作码2bit和数据16bit一共32个bit干净利落。但后来以太网速率往上走1000BASE-T、10GBASE-T出来了PHY内部需要配置的东西暴增。光是各种均衡器参数、时钟校正、SerDes配置就几百个寄存器5位地址根本不够用。于是Clause 45被提出来把寄存器地址扩展到16位同时引入了Device Type也叫MMDMDIO Manageable Device的概念用5位Device Type来区分不同的功能块比如PMA/PMD、WIS、PCS、PHY XS等等。这样地址空间一下子从32个变成了32×65536个彻底解决了地址不够的问题。注意Clause 45并不是要取代Clause 22两者是并存的。很多支持Clause 45的PHY芯片前32个寄存器依然用Clause 22的方式访问因为BMCR、BMSR这些标准寄存器在Clause 45里也有对应的映射但习惯上还是用Clause 22读。1.3 两套帧格式的核心差异对比先把最核心的差异用一张表列出来后面再逐项拆解。特性Clause 22Clause 45寄存器地址位宽5 bit0-3116 bit0-65535Device Type/MMD无5 bit0-31帧总长度32 bit64 bit分两帧操作码2 bit10读/01写2 bit00地址/11读/01写是否需要地址帧否是先发地址帧再发数据帧典型用途标准寄存器、PHY ID扩展寄存器、SerDes、PCS配置这张表里最关键的一行是帧总长度和是否需要地址帧。Clause 22一帧搞定Clause 45要两帧先发一个地址帧把Device Type和16位寄存器地址写进去再发一个数据帧做读或写。这个两帧机制是很多驱动写错的根源。2. Clause 22帧结构逐bit拆解2.1 32个bit怎么分配Clause 22的一帧是32个bit从前往后依次是Preamble32bit实际上标准里Clause 22的帧前面有32个连续的1作为前导码但很多MAC控制器会自动处理软件层面不用管。有些资料把前导码算进去说帧是64bit这里我们只算有效载荷部分。STStart of Frame2bit固定为01标志帧开始。OPOperation Code2bit10表示读01表示写。PHYADPHY Address5bitPHY的地址由硬件引脚或者上拉下拉决定总线上最多挂32个PHY。REGADRegister Address5bit寄存器地址0到31。TATurnaround2bit读操作时前一个bit是Z高阻后一个bit是0给PHY时间切换数据线方向写操作时固定为10。DATA16bit读出来的或者要写进去的数据。算一下2255216 32刚好。2.2 读操作的时序细节读操作时MAC先发出ST、OP、PHYAD、REGAD然后进入TA阶段。TA的第一个bitMAC释放MDIO线变成高阻态PHY这边检测到之后在第二个bit把线拉低输出0然后紧接着输出16位数据。整个过程MDC时钟由MAC持续提供PHY在MDC的上升沿采样命令在下降沿输出数据。这里有个容易踩的坑TA阶段的方向切换。如果MAC没有正确释放MDIO线PHY就没法驱动数据线读回来的全是1或者全是0。有些MAC控制器硬件会自动处理TA但有些需要软件在发送完地址后把GPIO方向切过来。我在调一颗国产交换芯片的时候就遇到过它的MDIO引脚是复用的需要先写一个寄存器把引脚切到MDIO模式否则读出来永远是0xFFFF。2.3 写操作的时序细节写操作简单一些TA固定为10MAC发完TA之后直接发16位数据PHY在MDC上升沿采样。写操作不需要方向切换因为MDIO线始终由MAC驱动。写操作的一个常见问题是时序余量。MDC频率如果跑太高比如超过12.5MHz而PHY的MDIO建立/保持时间不够就会写失败。标准里MDIO在MDC上升沿之前要有10ns的建立时间之后要有10ns的保持时间。实际调试时如果发现写寄存器偶尔失败先把MDC降频到2.5MHz试试能稳定工作再往上加。2.4 一个完整的Clause 22读时序示例假设PHY地址是0x01要读寄存器0x02PHYID1操作码读是10那么帧的32个bit是ST: 01 OP: 10 PHYAD: 00001 REGAD: 00010 TA: Z0 DATA: 由PHY输出拼起来就是01 10 00001 00010 Z0 xxxxxxxxxxxxxxxx用逻辑分析仪抓的时候你会看到MDC有32个时钟周期MDIO线上前14个bit是MAC驱动的然后一个bit高阻一个bit被PHY拉低最后16个bit是PHY驱动的数据。如果PHY ID读出来是0x0141之类的值说明时序对了。3. Clause 45为什么要发两帧3.1 地址帧和数据帧的分工Clause 45的帧是64个bit但它不是一帧发完而是分成两个独立的32bit帧地址帧Address Frame和数据帧Data Frame。地址帧的作用是把Device Type和16位寄存器地址先寄存到PHY内部数据帧再根据这个地址做读或写。地址帧的结构是ST00注意Clause 45的ST是00和Clause 22的01不同OP00表示地址帧PHYAD5bitDEVADDevice Address5bit就是Device TypeTA10DATA16bit的寄存器地址数据帧的结构是ST00OP11表示读01表示写PHYAD5bitDEVAD5bitTA读时Z0写时10DATA16bit数据关键点地址帧和数据帧的PHYAD和DEVAD必须一致否则PHY会忽略。而且地址帧发完之后PHY内部会记住这个地址直到下一个地址帧到来。所以如果你要连续读同一个寄存器的不同位置理论上可以只发一次地址帧后面跟多个数据帧。但实际驱动里为了简单通常每次读写都重新发地址帧。3.2 Device Type到底有哪些Device Type是Clause 45引入的核心概念5个bit标准里定义了一部分Device Type值名称用途0x00PMA/PMD物理介质附加/相关0x01WISWAN接口子层0x02PCS物理编码子层0x03PHY XSXSBI相关0x04DTE XSDTE扩展0x05TC测试相关0x06Clause 22扩展映射Clause 22寄存器0x1E厂商自定义各厂商自己用注意0x06这个Device Type它把Clause 22的寄存器映射到了Clause 45的地址空间里。也就是说你可以用Clause 45的方式去读Clause 22的寄存器Device Type填0x06寄存器地址填0x00到0x1F。这个设计是为了兼容但实际用起来容易混淆因为同一个寄存器有两个地址。3.3 两帧之间的间隔要求Clause 45标准里规定地址帧和数据帧之间不能有太长的间隔否则PHY可能会把地址丢掉。具体来说两帧之间的空闲时间不能超过MDC的若干个周期不同PHY实现不一样。稳妥的做法是地址帧发完紧接着就发数据帧中间不要插入其他操作。我在调一颗Marvell的PHY时遇到过这个问题驱动里在地址帧和数据帧之间加了一个延时函数结果读出来的数据全是0。后来把延时去掉改成连续发送立刻就正常了。所以如果你发现Clause 45读出来的数据不对先检查两帧之间有没有多余的延时或者调度。4. 寄存器地址映射同一个功能两套地址4.1 标准寄存器在Clause 22和45里的对应关系前面提到Device Type 0x06可以把Clause 22寄存器映射到Clause 45空间但即使不用0x06很多寄存器在Clause 45的各个Device Type里也有独立的定义。比如BMCRBasic Mode Control Register在Clause 22里是寄存器0x00在Clause 45的PMA/PMDDevice Type 0x00里是寄存器0x0000在PCSDevice Type 0x02里也有对应的控制寄存器。这就导致一个现象同一个功能你可以通过不同的路径去配置。比如设置自协商你可以写Clause 22的0x00寄存器bit12也可以写Clause 45 PMA/PMD的0x0000寄存器bit12。两条路都能走通但如果你只改了一边另一边没同步读状态的时候就会迷惑。提示调试时先确认PHY的数据手册里某个功能到底映射在哪个Device Type的哪个寄存器。不要想当然地认为Clause 22的0x00就等于Clause 45的0x0000有些PHY的映射关系是错位的。4.2 厂商扩展寄存器的访问方式厂商扩展寄存器是Clause 45的主战场。比如一颗支持2.5G/5G/10G的PHY它的SerDes配置、均衡器系数、时钟校正参数都在厂商自定义的Device Type里地址动辄0x8000以上。这些寄存器用Clause 22根本访问不到必须用Clause 45。访问厂商扩展寄存器时Device Type通常填0x1E或者厂商指定的值。具体填多少看数据手册。有些PHY的Device Type是硬件固定的有些可以通过寄存器配置。我见过一颗PHY它的厂商扩展Device Type默认是0x1E但可以通过写某个Clause 22寄存器改成0x1F这种设计就是为了避免总线上多个PHY的Device Type冲突。4.3 双网口共用一个MDIO时的地址规划双网口设备很常见两个PHY挂同一条MDIO总线。这时候PHYAD就关键了两个PHY必须有不同的PHYAD否则地址冲突。PHYAD通常由硬件引脚决定比如PHY0的PHYAD引脚接地PHY1的PHYAD引脚上拉这样PHY0地址是0PHY1地址是1。但有些板子设计的时候没注意两个PHY的PHYAD引脚都接地了结果两个PHY地址都是0。这时候怎么办有两个办法一是改硬件把其中一个PHY的PHYAD引脚改掉二是用软件有些PHY支持通过写某个寄存器来改PHYAD但这个方法不通用而且改完之后如果掉电重启就丢了。还有一种情况是PHY芯片背靠背back-to-back连接两个PHY之间用SGMII直连这时候MDIO总线可能只连到其中一个PHY另一个PHY通过内部寄存器间接访问。这种场景下Clause 45的Device Type就派上用场了因为可以通过不同的Device Type来区分访问的是哪个内部模块。5. 驱动代码里怎么区分两种访问5.1 Linux内核的mdio接口Linux内核里访问PHY寄存器用的是mdiobus_read和mdiobus_write这两个函数它们最终会调用MDIO总线的读写。但这两个函数默认走的是Clause 22。如果要走Clause 45需要用mdiobus_read_nested或者直接调用总线驱动的read_c45/write_c45回调。在较新的内核里PHY驱动框架提供了phy_read_mmd和phy_write_mmd这两个函数专门用来访问Clause 45的MMD寄存器。函数原型大概是int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum); int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val);devad就是Device Typeregnum是16位寄存器地址。这两个函数内部会判断PHY是否支持Clause 45如果支持就走Clause 45帧格式不支持就尝试用Clause 22的间接访问方式有些PHY通过Clause 22的0x0D和0x0E寄存器来间接访问扩展寄存器。5.2 自己写MDIO工具时的注意事项如果你要自己写一个MDIO读写工具比如在FPGA或者单片机上模拟MDIO时序有几个点必须注意第一MDC频率不要超过PHY手册规定的最大值。大部分PHY支持到12.5MHz但有些只支持到2.5MHz。保险起见先用2.5MHz调通再往上加。第二Clause 22和Clause 45的ST和OP编码不同不要搞混。Clause 22的ST是01Clause 45的ST是00。Clause 22的读OP是10Clause 45的读OP是11。这些编码在标准里有明确定义写错一个bit就读不出来。第三TA阶段的处理。Clause 22读的时候TA是Z0Clause 45读的时候也是Z0但Clause 45的地址帧TA是10。写操作时Clause 22和Clause 45的TA都是10。第四Clause 45的两帧之间不要插入其他MDIO操作否则地址会丢。5.3 一个实际的调试案例之前调一颗10G PHY用Clause 22读PHYID能读到说明MDIO时序没问题。但读厂商扩展寄存器时用Clause 45怎么读都是0。排查过程是这样的先确认Device Type对不对查手册发现厂商扩展的Device Type是0x1E没错。然后确认寄存器地址手册上写的是0x8000也没错。接着用逻辑分析仪抓MDIO波形发现地址帧发完之后数据帧的OP是11读但PHY返回的数据全是0。后来仔细看波形发现地址帧和数据帧之间的间隔太长了有几十个MDC周期。原因是驱动里在发完地址帧后调用了一个打印函数打印函数耗时太长。把打印去掉改成连续发送立刻就读到了正确的值。这个案例说明Clause 45对两帧之间的时序很敏感调试时尽量不要在中间插入任何可能引入延时的操作。6. 常见误区与排查清单6.1 误区一以为Clause 45是Clause 22的超集很多人以为Clause 45兼容Clause 22所以直接用Clause 45的方式去读Clause 22的寄存器就行。实际上不是这样。Clause 45的Device Type 0x06确实映射了Clause 22的寄存器但并不是所有PHY都实现了这个映射。有些PHY只支持Clause 22你发Clause 45的帧它根本不认。所以驱动里必须先读PHYID判断PHY型号再决定用哪种方式。6.2 误区二寄存器地址直接照搬Clause 22的寄存器0x00和Clause 45 PMA/PMD的0x0000虽然功能相似但bit定义可能不同。比如BMCR的bit12在Clause 22里是自协商使能在Clause 45的PMA/PMD里可能对应的是别的功能。写之前一定要查手册不要照搬。6.3 误区三忽略TA阶段的方向切换前面说过读操作时TA阶段MAC要释放MDIO线。如果用的是GPIO模拟MDIO必须在TA的第一个bit把GPIO切为输入否则PHY驱动不了数据线。这个坑在单片机或者FPGA上很常见Linux内核的MDIO控制器一般硬件自动处理但GPIO模拟的bitbang驱动需要软件处理。6.4 排查清单遇到MDIO读写问题时按这个顺序排查确认MDC频率是否在PHY支持范围内先降到2.5MHz试试。确认PHYAD是否正确总线上有没有地址冲突。确认用的是Clause 22还是Clause 45ST和OP编码对不对。如果是Clause 45确认Device Type和寄存器地址是否正确。用逻辑分析仪抓波形看TA阶段方向切换是否正常。检查两帧之间有没有多余延时。确认PHY是否支持Clause 45有些低端PHY只支持Clause 22。7. 几个实战中总结的小技巧第一个技巧读PHYID是判断MDIO通不通的最快方法。PHYID1和PHYID2是Clause 22的寄存器0x02和0x03几乎所有PHY都支持。如果这两个读出来是0x0000或者0xFFFF说明MDIO时序有问题先别急着调Clause 45。第二个技巧用Clause 22的0x0D和0x0E寄存器间接访问扩展寄存器。有些PHY虽然不支持Clause 45但提供了间接访问机制先写0x0D寄存器设置地址再写0x0E寄存器设置数据然后读0x0E寄存器拿结果。这种方式速度慢但兼容性好。第三个技巧MDC频率不要一开始就跑最高。先用1MHz或者2.5MHz调通确认读写都正常了再逐步提高频率。很多时序问题在低频下不会暴露高频下才出现所以低频调通不代表高频没问题但低频都调不通肯定有问题。第四个技巧如果总线上挂了多个PHY读PHYID的时候要逐个PHYAD去读确认每个PHY都能正确响应。有时候两个PHY的PHYAD冲突了读出来的ID会不对这时候要检查硬件引脚。第五个技巧Clause 45的地址帧发完之后如果紧接着发数据帧中间不要有任何函数调用或者打印最好用汇编或者直接操作寄存器的方式连续发送。我在FPGA上实现MDIO控制器时地址帧和数据帧是在同一个状态机里连续发出的中间没有间隙这样最稳。8. 从寄存器访问到链路调试的延伸搞懂了Clause 22和Clause 45的区别只是PHY调试的第一步。实际链路调试中你还需要知道怎么读链路状态、怎么配置自协商、怎么查看误码率。这些功能分布在不同的寄存器里有些用Clause 22读有些用Clause 45读。比如链路状态Clause 22的BMSR0x01bit2是Link Status读这个bit就能知道链路通没通。但自协商完成状态在BMSR的bit5自协商能力在ADVERTISE寄存器0x04。如果要看更详细的链路参数比如均衡器状态、信噪比就得用Clause 45去读厂商扩展寄存器。还有一个常见的需求是配置PHY的工作模式比如强制1000M全双工或者自协商。这些配置在Clause 22的BMCR0x00里就能搞定。但如果要配置SerDes的预加重、均衡器系数就必须用Clause 45。所以实际调试时通常是两种方式混着用标准寄存器用Clause 22扩展寄存器用Clause 45。驱动里要封装好两套读写函数根据寄存器地址范围自动选择用哪种方式。Linux内核的phy_read和phy_write默认走Clause 22phy_read_mmd和phy_write_mmd走Clause 45用的时候注意区分就行。最后说一个我踩过的坑有些PHY的Clause 45寄存器读出来是16位的但实际有效数据只有低8位或者低12位高位是保留的。如果你直接把16位数据当成有效值用可能会得到错误的结果。读之前先看手册里寄存器的位定义确认哪些bit是有效的。这个坑在调SerDes参数时特别容易踩因为SerDes寄存器的位定义往往很复杂不同bit段代表不同的参数读出来之后需要做位域提取才能用。