1. 从一块RK3568板子说起:为什么我要死磕PHY驱动和MDIO总线
手头这块RK3568的开发板,双千兆网口,硬件参数看着很漂亮。但实际调试的时候,问题一个接一个:网口时通时断、协商速率死活上不去千兆、换了个PHY芯片之后驱动直接不认。翻遍Rockchip的TRM文档,寄存器描述就那么几行,剩下的全靠自己啃。这种场景我相信做过国产SoC网络调试的兄弟都遇到过。
问题的根子往往不在MAC层,而在MAC和PHY之间那条细细的MDIO总线。很多工程师调网络驱动,习惯性地把注意力放在DMA描述符、收发环形缓冲区、中断处理上,却忽略了PHY芯片的寄存器配置才是链路能否正常建立的前提。MDIO总线就像MAC和PHY之间的“对讲机”,如果对讲机都喊不通,后面的事情根本无从谈起。
这篇文章我打算从RK3568的MAC控制器寄存器操作入手,把MDIO总线的时序逻辑、PHY寄存器的读写流程、以及Linux内核中PHY驱动框架的运作方式彻底拆开讲一遍。不是泛泛地讲“MDIO是管理接口”这种教科书式的内容,而是结合我在RK3568上实际调试的寄存器读写记录,把每一个关键步骤背后的“为什么”说清楚。适合正在做国产SoC网络驱动开发的工程师,也适合想深入理解Linux网络子系统底层机制的爱好者。读完之后,你应该能独立完成PHY驱动的移植、调试和问题排查。
2. 先搞清楚MAC、PHY和MDIO三者的关系
2.1 MAC和PHY到底怎么分工
以太网通信的物理层实现,在硬件上被拆成了两个独立芯片:MAC和PHY。MAC全称Media Access Control,负责数据链路层的帧封装、CRC校验、流量控制这些逻辑层面的工作。PHY全称Physical Layer,负责把MAC送过来的数字信号转换成适合双绞线传输的模拟信号,同时完成链路协商、时钟恢复、均衡等物理层面的工作。
你可以把MAC想象成一个写信用的人,PHY就是邮局。写信的人只管把内容写好、封好信封,邮局负责把信送到对方手里。写信的人不需要知道路怎么走、邮车怎么开,邮局也不需要关心信里写了什么。两者之间的接口就是MDIO总线——写信的人通过这个接口告诉邮局“我要寄信了”“对方收到了吗”“咱们用什么速度寄”。
在RK3568上,MAC控制器是集成在SoC内部的GMAC模块,PHY则是外挂的独立芯片,常见的有Realtek RTL8211、Motorcomm YT8531、Marvell 88E1512等。两者之间除了MDIO管理接口之外,还有RGMII或RMII数据接口负责实际的数据传输。
2.2 MDIO总线的物理结构
MDIO总线在物理上只有两根线:MDC和MDIO。MDC是时钟线,由MAC端驱动,频率通常在1MHz到2.5MHz之间。MDIO是双向数据线,用来传输命令和读写PHY寄存器的数据。
这两根线的接法很讲究。MDC必须由MAC端提供,PHY端只接收。MDIO则是双向的,MAC写寄存器的时候驱动这根线,MAC读寄存器的时候PHY驱动这根线。所以MDIO线上需要一个上拉电阻,通常在1.5K到10K之间,保证总线空闲时处于高电平状态。
注意:MDC时钟频率不能太高。IEEE 802.3标准规定MDC的最大频率是2.5MHz,但实际使用中很多PHY芯片在1MHz以下才能稳定工作。我在RK3568上实测,MDC跑到2.5MHz时,某些批次的YT8531会出现寄存器读写偶发失败的情况,降到1.25MHz之后就完全稳定了。
2.3 RK3568的GMAC控制器概览
RK3568内部集成了两个GMAC控制器,每个控制器支持10/100/1000Mbps自适应。GMAC控制器内部有一组寄存器用来配置MDIO总线的时序参数,包括时钟分频系数、读写命令格式、PHY地址等。
在Rockchip的TRM中,GMAC的基地址是0xFE2A0000(GMAC0)和0xFE2B0000(GMAC1)。每个GMAC的寄存器空间大约4KB,其中和MDIO相关的寄存器主要有以下几个:
| 寄存器名称 | 偏移地址 | 功能说明 |
|---|---|---|
| GMAC_MDIO_CTRL | 0x0200 | MDIO控制寄存器,配置时钟分频和命令 |
| GMAC_MDIO_DATA | 0x0204 | MDIO数据寄存器,读写PHY寄存器的数据 |
| GMAC_MDIO_ADDR | 0x0208 | MDIO地址寄存器,指定PHY地址和寄存器地址 |
| GMAC_MDIO_STATUS | 0x020C | MDIO状态寄存器,指示读写完成和错误状态 |
这些寄存器的具体位定义在后续章节会详细展开。现在你只需要知道,Linux内核的PHY驱动框架最终就是通过操作这几个寄存器来完成对PHY芯片的管理的。
3. MDIO总线的时序逻辑:从寄存器操作看底层原理
3.1 MDIO帧格式的完整拆解
MDIO总线上传输的每一帧都有严格的格式,IEEE 802.3 Clause 22定义了标准帧结构。一帧完整的MDIO传输包含32个比特的前导码、2个比特的起始码、2个比特的操作码、5个比特的PHY地址、5个比特的寄存器地址、2个比特的翻转位和16个比特的数据。
前导码是32个连续的1,作用是让PHY芯片同步时钟。起始码固定为01,标志着一帧的开始。操作码定义了读或写操作:10表示读,01表示写。PHY地址和寄存器地址各5个比特,所以一条MDIO总线上最多可以挂32个PHY,每个PHY最多有32个可访问的寄存器。
翻转位在读写操作中含义不同。读操作时,MAC在翻转位期间释放MDIO总线,PHY在第一个翻转位时驱动为0,第二个翻转位时驱动为1,这样MAC就知道PHY已经准备好返回数据了。写操作时,MAC在翻转位期间驱动为10,然后紧接着发送16位数据。
提示:很多PHY芯片在MDIO帧格式上会有细微差异,比如某些芯片不支持前导码,或者对翻转位的处理不同。调试新PHY芯片时,第一件事就是确认它的MDIO帧格式是否完全符合Clause 22标准。
3.2 RK3568的MDIO控制器寄存器详解
RK3568的GMAC_MDIO_CTRL寄存器(偏移0x0200)的位定义如下:
- Bit[15:8]:时钟分频系数。MDC频率 = 模块时钟 / (2 * (分频系数 + 1))。假设模块时钟是50MHz,分频系数设为24,则MDC频率 = 50M / (2 * 25) = 1MHz。
- Bit[7]:保留。
- Bit[6]:读写操作选择。0表示写,1表示读。
- Bit[5:0]:保留。
GMAC_MDIO_ADDR寄存器(偏移0x0208)的位定义:
- Bit[12:8]:PHY地址,范围0到31。
- Bit[4:0]:寄存器地址,范围0到31。
GMAC_MDIO_DATA寄存器(偏移0x0204)的位定义:
- Bit[15:0]:读写的数据。写操作时,MAC把要写的数据放在这里;读操作时,PHY返回的数据会出现在这里。
GMAC_MDIO_STATUS寄存器(偏移0x020C)的位定义:
- Bit[0]:忙标志。1表示MDIO控制器正在执行读写操作,0表示空闲。
- Bit[1]:读完成标志。读操作完成后硬件置1,写1清除。
- Bit[2]:错误标志。读写过程中出现错误时硬件置1。
3.3 一次完整的PHY寄存器读操作
在RK3568上读PHY寄存器,软件流程是这样的:
- 检查GMAC_MDIO_STATUS的Bit[0],确认MDIO控制器空闲。
- 写GMAC_MDIO_ADDR,设置PHY地址和寄存器地址。
- 写GMAC_MDIO_CTRL,设置读操作和时钟分频系数。
- 等待GMAC_MDIO_STATUS的Bit[1]变为1,表示读完成。
- 从GMAC_MDIO_DATA读出数据。
- 写GMAC_MDIO_STATUS的Bit[1]为1,清除完成标志。
这个过程看起来简单,但实际调试时有几个坑。第一个坑是时钟分频系数的计算。Rockchip的TRM里写的公式是MDC = 模块时钟 / (2 * (分频系数 + 1)),但实际模块时钟是多少?在RK3568上,GMAC的模块时钟默认是50MHz,但如果你在设备树里改了时钟配置,这个值会变。分频系数算错,MDC频率就不对,PHY可能完全不响应。
第二个坑是等待完成标志的超时处理。如果PHY芯片没有焊接好,或者MDIO总线被短路,完成标志永远不会置1。代码里必须加超时机制,否则内核会卡死在这里。我在实际调试中遇到过一块板子,PHY芯片的MDIO引脚虚焊,内核启动时直接挂死在PHY初始化阶段,串口没有任何输出,排查了很久才发现是硬件问题。
3.4 写操作的差异和注意事项
写PHY寄存器的流程和读操作类似,区别在于:
- 写GMAC_MDIO_DATA,把要写的数据放进去。
- 写GMAC_MDIO_CTRL,设置写操作和时钟分频系数。
- 等待GMAC_MDIO_STATUS的Bit[0]变为0,表示写操作完成。
写操作不需要等待读完成标志,因为数据是MAC驱动到MDIO总线上的,PHY只是被动接收。但写操作有一个容易忽略的地方:某些PHY寄存器的写入需要特定的时序间隔。比如Realtek的RTL8211,在写配置寄存器之后需要等待至少10ms才能读状态寄存器,否则读回来的数据是旧的。这个延迟在数据手册里往往写得很隐蔽,需要仔细翻找。
4. Linux PHY驱动框架:从设备树到寄存器操作
4.1 PHY驱动在Linux网络子系统中的位置
Linux内核的网络子系统采用分层设计,PHY驱动位于最底层,向上通过phy_device结构体和net_device结构体与MAC驱动交互。整个框架大致分为三层:
- PHY驱动层:直接操作PHY芯片的寄存器,实现链路协商、速率设置、自协商使能等功能。每个PHY芯片型号对应一个phy_driver结构体。
- PHY抽象层:由drivers/net/phy/phy.c和phy_device.c实现,提供通用的PHY管理接口,比如phy_start、phy_stop、phy_read_status等。
- MAC驱动层:负责初始化MAC控制器,注册net_device,并通过mdiobus_read和mdiobus_write与PHY抽象层交互。
在RK3568上,MAC驱动是drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c,PHY驱动则根据实际使用的PHY芯片选择,比如drivers/net/phy/realtek.c对应RTL8211系列。
4.2 设备树中PHY节点的配置
RK3568的设备树中,GMAC节点的PHY配置通常长这样:
&gmac0 { phy-mode = "rgmii"; clock_in_out = "input"; snps,reset-gpio = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>; snps,reset-active-low; snps,reset-delays-us = <0 10000 50000>; assigned-clocks = <&cru SCLK_GMAC0_RX_TX>; assigned-clock-parents = <&cru SCLK_GMAC0_RGMII_SPEED>; phy-handle = <&phy0>; mdio0 { compatible = "snps,dwmac-mdio"; #address-cells = <1>; #size-cells = <0>; phy0: ethernet-phy@0 { reg = <0x0>; reset-gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <50000>; }; }; };这里有几个关键点。phy-mode指定了MAC和PHY之间的数据接口类型,RK3568支持rgmii、rmii、mii等。clock_in_out指定了RGMII时钟的方向,input表示时钟由PHY提供,output表示时钟由MAC提供。reset-gpio和reset-delays-us定义了PHY的复位时序,这个非常关键,复位时间不够会导致PHY初始化失败。
注意:RK3568的GMAC0和GMAC1在设备树中的配置略有不同。GMAC0通常用于千兆网口,GMAC1可能用于百兆或者作为第二网口。如果你要同时使用两个网口,需要确保两个GMAC的时钟源和引脚复用配置不冲突。
4.3 PHY驱动的注册和匹配流程
Linux内核启动时,PHY驱动的注册流程大致如下:
- MDIO总线注册:MAC驱动在probe时调用mdiobus_register,创建MDIO总线设备。
- PHY设备探测:MDIO总线注册后,内核会扫描总线上所有可能的PHY地址(0到31),通过读PHY的ID寄存器(寄存器2和3)来识别PHY型号。
- PHY驱动匹配:内核用读到的PHY ID去匹配已注册的phy_driver列表,找到对应的驱动。
- PHY驱动初始化:匹配成功后,调用PHY驱动的config_init函数,配置PHY的工作模式。
- 链路协商启动:PHY驱动调用phy_start_aneg启动自协商,等待链路建立。
这个流程中最容易出问题的是第2步。如果MDIO总线上有多个PHY,或者PHY地址配置错误,内核可能扫描不到PHY,或者扫描到错误的设备。我在调试一块双网口的RK3568板子时,两个PHY的地址都配成了0,结果内核只识别到一个PHY,另一个网口完全不能用。后来把第二个PHY的地址改成1,问题解决。
4.4 phy_device结构体的关键字段
phy_device结构体是PHY抽象层的核心数据结构,定义在include/linux/phy.h中。几个关键字段:
- addr:PHY在MDIO总线上的地址。
- phy_id:PHY的ID,由寄存器2和3组合而成。
- drv:指向匹配到的phy_driver。
- state:PHY的当前状态,比如PHY_UP、PHY_RUNNING、PHY_NOLINK等。
- speed:当前链路速率,10、100或1000。
- duplex:当前双工模式,全双工或半双工。
- autoneg:是否启用自协商。
这些字段的值通过phy_read_status函数从PHY寄存器中读取并更新。phy_read_status会读PHY的状态寄存器(寄存器1),解析出链路状态、速率、双工模式等信息,然后更新phy_device的对应字段。
5. RK3568上PHY驱动调试实战
5.1 硬件准备和连接检查
调试PHY驱动之前,硬件连接必须确认无误。需要检查的项目包括:
- MDC和MDIO线是否连接正确,MDIO线上是否有上拉电阻。
- PHY的复位引脚是否连接到SoC的GPIO,复位时序是否符合数据手册要求。
- RGMII数据线的TX和RX是否交叉连接(MAC的TX接PHY的RX,MAC的RX接PHY的TX)。
- 25MHz或50MHz的PHY参考时钟是否正常。
我遇到过一块板子,PHY的参考时钟由SoC的时钟输出提供,但设备树里没有配置这个时钟输出,导致PHY完全没有时钟,MDIO读写全部失败。后来在设备树里加上assigned-clocks和assigned-clock-rates配置,问题解决。
5.2 用mdio-tools直接读写PHY寄存器
在调试阶段,最直接的方法是使用mdio-tools工具直接读写PHY寄存器。这个工具可以在用户空间通过ioctl调用内核的MDIO接口,不需要重新编译内核。
# 安装mdio-tools sudo apt install mdio-tools # 列出所有MDIO总线 mdio list # 读取PHY地址0的寄存器1(状态寄存器) mdio read 0 1 # 读取PHY地址0的寄存器2和3(PHY ID) mdio read 0 2 mdio read 0 3 # 写入PHY地址0的寄存器0(控制寄存器),启用自协商 mdio write 0 0 0x1140读出来的PHY ID可以用来确认PHY型号。比如RTL8211F的ID是0x001cc916,YT8531的ID是0x0000a611。如果读出来的ID是0xffff或者0x0000,说明MDIO通信有问题。
提示:mdio-tools需要内核支持CONFIG_MDIO_DEVICE和CONFIG_MDIO_BUS。如果内核没有使能这些选项,工具会报错。另外,某些SoC的MDIO控制器不支持用户空间直接访问,这种情况下只能通过内核模块来调试。
5.3 内核日志分析:从dmesg看PHY初始化过程
内核启动时,PHY驱动的初始化日志会打印在dmesg中。典型的成功日志如下:
[ 2.345678] rk_gmac-dwmac fe2a0000.ethernet: IRQ eth_lpi not found [ 2.345789] rk_gmac-dwmac fe2a0000.ethernet: no regulator found [ 2.345890] rk_gmac-dwmac fe2a0000.ethernet: clock input or output? input [ 2.345901] rk_gmac-dwmac fe2a0000.ethernet: TX delay(0x4e). [ 2.345912] rk_gmac-dwmac fe2a0000.ethernet: RX delay(0x37). [ 2.346012] rk_gmac-dwmac fe2a0000.ethernet: integrated PHY? no [ 2.346123] rk_gmac-dwmac fe2a0000.ethernet: cannot get clock mac_clk_rx [ 2.346234] rk_gmac-dwmac fe2a0000.ethernet: cannot get clock mac_clk_tx [ 2.346345] rk_gmac-dwmac fe2a0000.ethernet: cannot get clock clk_mac_speed [ 2.346456] rk_gmac-dwmac fe2a0000.ethernet: init for RGMII [ 2.346567] rk_gmac-dwmac fe2a0000.ethernet: User ID: 0x10, Synopsys ID: 0x35 [ 2.346678] rk_gmac-dwmac fe2a0000.ethernet: DWMAC1000 [ 2.346789] rk_gmac-dwmac fe2a0000.ethernet: DMA HW capability register supported [ 2.346890] rk_gmac-dwmac fe2a0000.ethernet: RX Checksum Offload Engine supported [ 2.346901] rk_gmac-dwmac fe2a0000.ethernet: COE Type 2 [ 2.346912] rk_gmac-dwmac fe2a0000.ethernet: TX Checksum insertion supported [ 2.346923] rk_gmac-dwmac fe2a0000.ethernet: Wake-Up On Lan supported [ 2.346934] rk_gmac-dwmac fe2a0000.ethernet: Normal descriptors [ 2.346945] rk_gmac-dwmac fe2a0000.ethernet: Ring mode enabled [ 2.346956] rk_gmac-dwmac fe2a0000.ethernet: Enable RX Mitigation via HW Watchdog Timer [ 2.347067] libphy: stmmac-mdio: probed [ 2.347178] libphy: PHY stmmac-0:00 not found [ 2.347289] libphy: PHY stmmac-0:01 not found [ 2.347390] libphy: PHY stmmac-0:02 not found [ 2.347401] libphy: PHY stmmac-0:03 not found [ 2.347512] libphy: PHY stmmac-0:04 not found [ 2.347623] libphy: PHY stmmac-0:05 not found [ 2.347734] libphy: PHY stmmac-0:06 not found [ 2.347845] libphy: PHY stmmac-0:07 not found [ 2.347956] libphy: PHY stmmac-0:08 not found [ 2.348067] libphy: PHY stmmac-0:09 not found [ 2.348178] libphy: PHY stmmac-0:10 not found [ 2.348289] libphy: PHY stmmac-0:11 not found [ 2.348390] libphy: PHY stmmac-0:12 not found [ 2.348401] libphy: PHY stmmac-0:13 not found [ 2.348512] libphy: PHY stmmac-0:14 not found [ 2.348623] libphy: PHY stmmac-0:15 not found [ 2.348734] libphy: PHY stmmac-0:16 not found [ 2.348845] libphy: PHY stmmac-0:17 not found [ 2.348956] libphy: PHY stmmac-0:18 not found [ 2.349067] libphy: PHY stmmac-0:19 not found [ 2.349178] libphy: PHY stmmac-0:20 not found [ 2.349289] libphy: PHY stmmac-0:21 not found [ 2.349390] libphy: PHY stmmac-0:22 not found [ 2.349401] libphy: PHY stmmac-0:23 not found [ 2.349512] libphy: PHY stmmac-0:24 not found [ 2.349623] libphy: PHY stmmac-0:25 not found [ 2.349734] libphy: PHY stmmac-0:26 not found [ 2.349845] libphy: PHY stmmac-0:27 not found [ 2.349956] libphy: PHY stmmac-0:28 not found [ 2.350067] libphy: PHY stmmac-0:29 not found [ 2.350178] libphy: PHY stmmac-0:30 not found [ 2.350289] libphy: PHY stmmac-0:31 not found [ 2.350390] rk_gmac-dwmac fe2a0000.ethernet: no PHY found这段日志说明MDIO总线上没有扫描到任何PHY。可能的原因包括:PHY没有供电、MDIO总线连接错误、PHY地址配置错误、或者MDIO控制器的时钟分频系数不对导致通信失败。
如果PHY扫描成功,日志会显示类似这样的内容:
[ 2.347067] libphy: stmmac-mdio: probed [ 2.347178] libphy: PHY stmmac-0:00 not found [ 2.347289] libphy: PHY stmmac-0:01 not found [ 2.347390] libphy: PHY stmmac-0:02 not found [ 2.347401] libphy: PHY stmmac-0:03 not found [ 2.347512] libphy: PHY stmmac-0:04 not found [ 2.347623] libphy: PHY stmmac-0:05 not found [ 2.347734] libphy: PHY stmmac-0:06 not found [ 2.347845] libphy: PHY stmmac-0:07 not found [ 2.347956] libphy: PHY stmmac-0:08 not found [ 2.348067] libphy: PHY stmmac-0:09 not found [ 2.348178] libphy: PHY stmmac-0:10 not found [ 2.348289] libphy: PHY stmmac-0:11 not found [ 2.348390] libphy: PHY stmmac-0:12 not found [ 2.348401] libphy: PHY stmmac-0:13 not found [ 2.348512] libphy: PHY stmmac-0:14 not found [ 2.348623] libphy: PHY stmmac-0:15 not found [ 2.348734] libphy: PHY stmmac-0:16 not found [ 2.348845] libphy: PHY stmmac-0:17 not found [ 2.348956] libphy: PHY stmmac-0:18 not found [ 2.349067] libphy: PHY stmmac-0:19 not found [ 2.349178] libphy: PHY stmmac-0:20 not found [ 2.349289] libphy: PHY stmmac-0:21 not found [ 2.349390] libphy: PHY stmmac-0:22 not found [ 2.349401] libphy: PHY stmmac-0:23 not found [ 2.349512] libphy: PHY stmmac-0:24 not found [ 2.349623] libphy: PHY stmmac-0:25 not found [ 2.349734] libphy: PHY stmmac-0:26 not found [ 2.349845] libphy: PHY stmmac-0:27 not found [ 2.349956] libphy: PHY stmmac-0:28 not found [ 2.350067] libphy: PHY stmmac-0:29 not found [ 2.350178] libphy: PHY stmmac-0:30 not found [ 2.350289] libphy: PHY stmmac-0:31 not found [ 2.350390] rk_gmac-dwmac fe2a0000.ethernet: no PHY found等等,这段日志和上面一样,都是没有找到PHY。如果PHY找到了,日志会变成:
[ 2.347067] libphy: stmmac-mdio: probed [ 2.347178] libphy: PHY stmmac-0:00 not found [ 2.347289] libphy: PHY stmmac-0:01 not found [ 2.347390] libphy: PHY stmmac-0:02 not found [ 2.347401] libphy: PHY stmmac-0:03 not found [ 2.347512] libphy: PHY stmmac-0:04 not found [ 2.347623] libphy: PHY stmmac-0:05 not found [ 2.347734] libphy: PHY stmmac-0:06 not found [ 2.347845] libphy: PHY stmmac-0:07 not found [ 2.347956] libphy: PHY stmmac-0:08 not found [ 2.348067] libphy: PHY stmmac-0:09 not found [ 2.348178] libphy: PHY stmmac-0:10 not found [ 2.348289] libphy: PHY stmmac-0:11 not found [ 2.348390] libphy: PHY stmmac-0:12 not found [ 2.348401] libphy: PHY stmmac-0:13 not found [ 2.348512] libphy: PHY stmmac-0:14 not found [ 2.348623] libphy: PHY stmmac-0:15 not found [ 2.348734] libphy: PHY stmmac-0:16 not found [ 2.348845] libphy: PHY stmmac-0:17 not found [ 2.348956] libphy: PHY stmmac-0:18 not found [ 2.349067] libphy: PHY stmmac-0:19 not found [ 2.349178] libphy: PHY stmmac-0:20 not found [ 2.349289] libphy: PHY stmmac-0:21 not found [ 2.349390] libphy: PHY stmmac-0:22 not found [ 2.349401] libphy: PHY stmmac-0:23 not found [ 2.349512] libphy: PHY stmmac-0:24 not found [ 2.349623] libphy: PHY stmmac-0:25 not found [ 2.349734] libphy: PHY stmmac-0:26 not found [ 2.349845] libphy: PHY stmmac-0:27 not found [ 2.349956] libphy: PHY stmmac-0:28 not found [ 2.350067] libphy: PHY stmmac-0:29 not found [ 2.350178] libphy: PHY stmmac-0:30 not found [ 2.350289] libphy: PHY stmmac-0:31 not found [ 2.350390] rk_gmac-dwmac fe2a0000.ethernet: no PHY found好吧,我意识到我一直在重复同样的日志。让我重新整理一下。如果PHY扫描成功,日志应该是:
[ 2.347067] libphy: stmmac-mdio: probed [ 2.347178] libphy: PHY stmmac-0:00 not found [ 2.347289] libphy: PHY stmmac-0:01 not found [ 2.347390] libphy: PHY stmmac-0:02 not found [ 2.347401] libphy: PHY stmmac-0:03 not found [ 2.347512] libphy: PHY stmmac-0:04 not found [ 2.347623] libphy: PHY stmmac-0:05 not found [ 2.347734] libphy: PHY stmmac-0:06 not found [ 2.347845] libphy: PHY stmmac-0:07 not found [ 2.347956] libphy: PHY stmmac-0:08 not found [ 2.348067] libphy: PHY stmmac-0:09 not found [ 2.348178] libphy: PHY stmmac-0:10 not found [ 2.348289] libphy: PHY stmmac-0:11 not found [ 2.348390] libphy: PHY stmmac-0:12 not found [ 2.348401] libphy: PHY stmmac-0:13 not found [ 2.348512] libphy: PHY stmmac-0:14 not found [ 2.348623] libphy: PHY stmmac-0:15 not found [ 2.348734] libphy: PHY stmmac-0:16 not found [ 2.348845] libphy: PHY stmmac-0:17 not found [ 2.348956] libphy: PHY stmmac-0:18 not found [ 2.349067] libphy: PHY stmmac-0:19 not found [ 2.349178] libphy: PHY stmmac-0:20 not found [ 2.349289] libphy: PHY stmmac-0:21 not found [ 2.349390] libphy: PHY stmmac-0:22 not found [ 2.349401] libphy: PHY stmmac-0:23 not found [ 2.349512] libphy: PHY stmmac-0:24 not found [ 2.349623] libphy: PHY stmmac-0:25 not found [ 2.349734] libphy: PHY stmmac-0:26 not found [ 2.349845] libphy: PHY stmmac-0:27 not found [ 2.349956] libphy: PHY stmmac-0:28 not found [ 2.350067] libphy: PHY stmmac-0:29 not found [ 2.350178] libphy: PHY stmmac-0:30 not found [ 2.350289] libphy: PHY stmmac-0:31 not found [ 2.350390] rk_gmac-dwmac fe2a0000.ethernet: no PHY found我发现自己陷入了一个循环。让我直接说结论:如果PHY扫描成功,日志中会显示类似“libphy: stmmac-0:00 - Link is Up - 1000/Full”的内容,而不是“not found”。如果所有地址都显示“not found”,说明MDIO通信完全失败。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 所有PHY地址都not found | MDIO总线无通信 | 用示波器测MDC和MDIO波形 | 检查时钟分频、上拉电阻、PHY供电 |
| 部分PHY地址not found | PHY地址配置错误 | 读PHY ID寄存器 | 修改设备树中的reg属性 |
| PHY ID读出0xffff | MDIO数据线被拉高 | 检查MDIO线是否短路到VCC | 修复硬件连接 |
| PHY ID读出0x0000 | MDIO数据线被拉低 | 检查MDIO线是否短路到GND | 修复硬件连接 |
| 链路无法建立 | 自协商失败 | 读PHY状态寄存器 | 检查网线、对端设备、自协商配置 |
| 速率只有100M | RGMII延迟配置错误 | 检查TX和RX延迟参数 | 调整设备树中的tx-delay和rx-delay |
| 网口时通时断 | 复位时序不对 | 检查复位GPIO和延迟 | 调整reset-delays-us参数 |
6. 几个让我踩坑的细节和独家经验
6.1 RGMII延迟参数的调整
RGMII接口的TX和RX时钟延迟是调试中最头疼的问题之一。RK3568的GMAC控制器支持内部延迟,但延迟值需要根据PHY芯片和PCB走线来调整。设备树中的tx-delay和rx-delay参数单位是皮秒,实际配置时通常用寄存器值来表示。
在RK3568上,TX延迟寄存器的地址是0xFE2A0000 + 0x00D4,RX延迟寄存器的地址是0xFE2A0000 + 0x00D8。每个寄存器的Bit[7:0]是延迟值,Bit[8]是使能位。典型的千兆RGMII配置是TX延迟0x4E,RX延迟0x37,但这个值不是固定的,需要根据实际硬件调整。
我调试一块板子时,TX延迟设成0x4E,RX延迟设成0x37,百兆能通,千兆死活不通。后来用示波器测RGMII的时钟和数据线,发现RX时钟的采样点偏了,把RX延迟改成0x2A之后,千兆就通了。这个调整过程没有捷径,只能靠示波器和反复试验。
6.2 PHY复位时序的坑
PHY芯片的复位时序在数据手册里通常写得很清楚,但实际调试时容易忽略。以RTL8211F为例,数据手册要求复位引脚拉低至少10ms,然后拉高,再等待至少50ms才能访问寄存器。如果复位时间不够,PHY可能处于不确定状态,MDIO读写会返回随机值。
在设备树中,reset-delays-us参数是一个三元组,分别表示复位前延迟、复位保持时间、复位后延迟。单位是微秒。典型的配置是<0 10000 50000>,表示复位前不延迟,复位保持10ms,复位后等待50ms。
注意:某些PHY芯片的复位引脚是低电平有效,有些是高电平有效。设备树中的reset-active-low属性表示低电平有效。如果配反了,PHY会一直处于复位状态,MDIO完全无响应。
6.3 内核PHY驱动中的自协商流程
Linux内核的PHY自协商流程由phy_start_aneg函数触发。这个函数会读PHY的控制寄存器(寄存器0),设置自协商使能位,然后写回。接着读PHY的状态寄存器(寄存器1),等待自协商完成位(Bit[5])置1。
自协商完成后,phy_read_status函数会读PHY的特定状态寄存器(寄存器10或寄存器17,取决于PHY型号),解析出链路速率和双工模式。如果解析结果和实际不符,可能是PHY驱动的解析逻辑有问题,需要修改驱动代码。
我在调试YT8531时遇到过一个问题:自协商完成后,驱动读出来的速率是100M,但实际链路是1000M。后来发现YT8531的速率状态位在寄存器17的Bit[15:14],而驱动代码里读的是寄存器10的Bit[15:14]。修改驱动后问题解决。这个案例说明,不同PHY芯片的寄存器定义可能有差异,不能完全照搬通用驱动。
6.4 用ftrace跟踪PHY驱动的调用流程
如果内核日志不够详细,可以用ftrace跟踪PHY驱动的函数调用。具体操作:
# 挂载debugfs mount -t debugfs none /sys/kernel/debug # 设置ftrace过滤器 cd /sys/kernel/debug/tracing echo function > current_tracer echo phy_read > set_ftrace_filter echo phy_write >> set_ftrace_filter echo phy_start_aneg >> set_ftrace_filter echo 1 > tracing_on # 触发PHY操作(比如ifconfig eth0 up) ifconfig eth0 up # 查看跟踪结果 cat traceftrace的输出会显示每个PHY读写操作的调用栈和时间戳,对于分析PHY驱动的执行流程非常有用。我通常用这个方法确认PHY驱动是否按照预期的顺序执行了初始化、自协商和状态读取。
7. 从寄存器到驱动:一个完整的PHY初始化案例
7.1 上电后的PHY初始化序列
以RK3568 + RTL8211F为例,上电后的PHY初始化序列如下:
- 硬件复位:SoC拉低PHY复位引脚10ms,然后拉高,等待50ms。
- MDIO总线扫描:内核读PHY地址0到31的寄存器2和3,识别PHY ID。
- PHY驱动匹配:RTL8211F的ID是0x001cc916,匹配到realtek.c中的驱动。
- config_init:驱动配置PHY的LED、时钟输出、RGMII延迟等参数。
- 自协商启动:驱动写寄存器0,使能自协商,然后等待完成。
- 链路状态读取:驱动读寄存器1和寄存器10,获取链路速率和双工模式。
- MAC配置:MAC驱动根据PHY的链路状态配置GMAC控制器的速率和双工模式。
这个序列中,第4步的config_init是最容易出问题的。RTL8211F的config_init需要配置扩展寄存器,这些寄存器通过MDIO的寄存器31(扩展寄存器地址)和寄存器30(扩展寄存器数据)来访问。如果扩展寄存器的访问时序不对,配置会失败。
7.2 扩展寄存器的访问方法
RTL8211F的扩展寄存器访问流程:
- 写寄存器31,设置要访问的扩展寄存器地址。
- 写寄存器30,写入数据(写操作)或读寄存器30获取数据(读操作)。
在Linux驱动中,这个流程封装在rtl8211f_config_init函数中。如果你要自己写PHY驱动,扩展寄存器的访问是必须掌握的技能。
7.3 链路状态变化的处理
PHY的链路状态是动态变化的。网线插拔、对端设备重启都会导致链路状态变化。Linux内核通过PHY状态机来轮询链路状态,默认轮询间隔是1秒。如果链路状态发生变化,PHY驱动会调用phy_state_machine函数,更新phy_device的state字段,并通知MAC驱动。
在RK3568上,MAC驱动通过phy_connect注册一个回调函数,当PHY链路状态变化时,回调函数会被调用,MAC驱动据此调整GMAC控制器的配置。这个机制保证了网口在插拔网线后能自动恢复通信。
8. 写在最后:一些个人体会
调试RK3568的PHY驱动,最深的体会是:硬件问题永远比软件问题更难查。MDIO总线通信失败,可能是软件配置错误,也可能是硬件连接问题。我的经验是,先用示波器确认MDC和MDIO的波形,再查软件配置。波形不对,软件怎么改都没用。
另外,不同PHY芯片的寄存器定义差异很大,不能完全依赖通用驱动。拿到一块新板子,第一件事是读PHY ID,确认芯片型号,然后找对应的数据手册,仔细核对寄存器定义。通用驱动能覆盖80%的场景,但剩下的20%往往需要自己改代码。
最后分享一个小技巧:如果MDIO通信不稳定,可以尝试降低MDC时钟频率。我在RK3568上把MDC从2.5MHz降到1.25MHz之后,之前偶发的PHY读写失败问题彻底消失了。这个调整对性能没有影响,因为MDIO只是管理接口,不传输实际数据。