1. 项目概述与整体思路拆解
1.1 为什么选RK3568做百兆以太网
先直接说结论:RK3568这颗芯片本身集成的GMAC控制器是支持千兆的,但很多实际项目里,硬件设计为了省成本、降功耗或者适配特定的交换机芯片,最终走的是RMII接口,把速率锁定在百兆。这时候如果直接沿用默认的千兆设备树配置,网口要么起不来,要么速率协商异常,甚至会出现“link up但ping不通”这种让人抓狂的毛病。
我最早接触这块板子的时候,也被这个坑绊过。板子用的是国产PHY芯片,RMII模式,时钟由外部有源晶振提供,结果内核启动后ifconfig -a死活看不到eth0。后来一步步排查设备树、PHY驱动、时钟树,才把整套逻辑理顺。这篇文章就是把那次实战的完整过程拆开揉碎,从设备树节点的每一个字段讲起,到编译、烧录、验证,再到常见坑的排查思路,全部记录下来。
如果你手头正好有RK3568的开发板,而且硬件上用的是RMII百兆PHY,那这篇文章可以直接照着操作。就算你用的是其他瑞芯微芯片(比如RV1126、RV1109、RK3588),核心思路也是一样的,只是reg基址和时钟名略有差异。
1.2 整体方案选型背后的逻辑
在动手改设备树之前,先要把整条数据链路理清楚。RK3568的以太网控制器叫GMAC,它对外提供两种接口模式:RGMII(千兆)和RMII(百兆)。芯片内部其实只有一个MAC控制器,通过复用引脚来切换不同模式。设备树里要做的,就是告诉内核“我现在用哪种模式、PHY接在哪个MDIO地址上、时钟怎么给”。
这里有个关键点:RMII模式对时钟的要求比RGMII苛刻。RGMII的TX_CLK和RX_CLK是独立的125MHz,而RMII是共用一个50MHz参考时钟。这个50MHz时钟可以由MAC侧提供(也就是主控输出),也可以由外部晶振直接给PHY,同时PHY再回传给MAC。两种方式在设备树里的配置完全不同,很多教程只讲了其中一种,导致换一块板子就翻车。
我这块板的硬件设计是:PHY的50MHz时钟由外部有源晶振产生,REF_CLK引脚直接连到PHY,MAC侧的GPIO2_A4复用为RMII_CLK,作为输入接收PHY回传的50MHz时钟。这种方案的好处是MAC不需要额外输出时钟,省了一个引脚;坏处是设备树里必须明确配置成“时钟由PHY提供”的模式,否则MAC侧会一直等时钟,链路永远起不来。
1.3 设备树在瑞芯微平台中的骨架结构
RK3568的设备树文件组织方式是多层叠加的。根文件是rk3568.dtsi,里面定义了SoC所有外设控制器的默认状态,包括GMAC。然后板级文件(比如rk3568-evb.dts)通过&gmac这种引用语法,覆盖或者追加特定配置。
实际操作中,你一般只需要关注两个文件:一个是SoC级rk3568.dtsi,另一个是板级dts。GMAC节点在rk3568.dtsi里已经有完整的基础定义,包括寄存器地址、中断号、时钟名、DMA配置等。我们板级要做的,主要是设置接口模式、PHY地址、时钟来源、复位引脚、以及实际使用的引脚复用。
还有个容易忽略的地方:RK3568的GMAC驱动依赖一个叫stmmac的内核框架,这个框架本身支持RGMII和RMII,但需要在平台驱动里注册对应的plat_data。好在瑞芯微SDK里已经把这些都封装好了,设备树里只需要设置pinctrl和phy-mode,驱动会自动匹配。
2. 核心细节解析与实操要点
2.1 RMII接口的物理层信号与时序要求
先说物理层。RMII接口总共用到以下几个信号:TX_EN(发送使能)、TXD[1:0](两位发送数据)、RX_EN(接收使能)、RXD[1:0](两位接收数据)、REF_CLK(50MHz参考时钟),再加上MDIO和MDC用于管理PHY寄存器。相比RGMII的12根线,RMII少了将近一半的引脚,这也是它适合低引脚数MCU和低成本硬件的原因。
但RMII有个性能上的特点:数据位宽只有2位,频率是50MHz,所以实际吞吐量上限就是100Mbps。有的人会问:“既然数据位宽减半,频率不是应该翻倍到100MHz吗?”这里容易误解。RMII确实用了50MHz时钟,但通过双沿采样(DDR)的方式,在时钟的上升沿和下降沿都采样数据,等效频率是100MHz,配合2位位宽正好达到100Mbps。
时序上,REF_CLK必须稳定且满足占空比要求(一般控制在45%~55%)。如果外部晶振质量不好或者走线过长,会出现偶发性的丢包,但链路状态又是正常的。这种问题很难查,我建议硬件上把PHY的REF_CLK走线尽量短,远离电源和高速信号。
2.2 设备树中gmac节点的关键字段解析
在板级dts文件里,&gmac节点的配置是整个任务的核心。我先把我实际验证过的完整配置贴出来,再逐字段解释:
&gmac { assigned-clocks = <&cru SCLK_GMAC1>; assigned-clock-rates = <50000000>; assigned-clocks-parents = <&cru SCLK_GMAC1_RMII_SPEED>; status = "okay"; clock_in_out = "input"; phy-supply = <&vcc_phy>; phy-mode = "rmii"; pinctrl-names = "default"; pinctrl-0 = <&rmii1_pins &gmac1_clkin>; rx_delay = <0>; tx_delay = <0>; phy-handle = <&phy0>; }; &mdio1 { phy0: ethernet-phy@1 { reg = <1>; status = "okay"; }; };这里最容易被忽略的是assigned-clocks和assigned-clocks-parents这两组属性。assigned-clocks指定要配置的时钟,assigned-clock-rates指定目标频率,assigned-clocks-parents指定父时钟。RMII模式下,GMAC内部的时钟分频器必须选择正确的父时钟源,否则即使PHY有时钟,MAC也可能无法同步。
clock_in_out = "input"表示MAC侧是时钟输入方向,也就是50MHz REF_CLK由PHY提供。如果改成"output",那么MAC就要自己输出50MHz时钟给PHY,此时assigned-clocks-parents要选择SCLK_GMAC1_RMII_SPEED作为父时钟,并且gmac1_clkin引脚要改为gmac1_clkout。很多人在这个点上搞反,导致PHY没时钟或者MAC没时钟。
2.3 引脚复用pinctrl的配置要点
pinctrl配置决定了硬件引脚如何被复用为GMAC功能。RK3568的RMII1引脚组定义在rk3568.dtsi的pinctrl节点中,通常已经预先定义好,板级直接引用即可。但要注意:不同板卡的引脚编号可能不同,尤其是RMII的TXD、RXD等数据引脚,有可能被其他功能占用。
我的板子上配置是:
pinctrl-0 = <&rmii1_pins &gmac1_clkin>;其中rmii1_pins包含了MDIO、MDC、TXD[1:0]、RXD[1:0]、TX_EN、RX_EN这几组引脚。gmac1_clkin则是把GPIO2_A4复用为RMII的CLK输入。如果硬件设计使用的是CLK输出模式,这里就要换成&gmac1_clkout。
有一点需要特别提醒:如果板子上同时启用了其他外设,比如SPI、UART或者PWM,而这些外设恰好复用了同样的引脚,就会导致GMAC的引脚被抢占,内核启动时大概率会报pinctrl-0 pin X busy之类的错误。排查的时候先看dmesg | grep pinctrl,再确认硬件原理图,避免改了半天设备树结果发现是引脚冲突。
2.4 PHY地址与MDIO总线映射
MDIO总线是用来读写PHY寄存器的管理接口。RK3568有多个MDIO控制器,mdio0对应GMAC0,mdio1对应GMAC1。我的板子用的是GMAC1,所以配置在&mdio1下面。
PHY地址通常是硬件通过PHY芯片的ADDR引脚上的上下拉电阻决定的,常见取值是0~31。我遇到最多的情况是:板子上PHY地址是1,但设备树里写的是0,导致驱动一直找不到PHY。这里有个土办法:先用mdio工具扫描总线,或者在内核启动日志里看PHY ID,确认实际地址再填。
phy-handle必须指向mdio1下的一个PHY子节点,这里的phy0只是一个标签名,可以随意取,但reg一定要和硬件地址一致。另外,phy-supply是PHY芯片的电源控制,如果PHY的供电引脚不是常供电,而是由GPIO控制的,这里需要正确配置,否则PHY上电时序不对也会导致探测失败。
3. 实操过程与核心环节实现
3.1 环境准备:编译内核与设备树
开始改之前,先把编译环境准备好。瑞芯微官方SDK一般基于Ubuntu 18.04或20.04,我用的就是SDK自带的内核源码。如果你只有单独的内核仓库,也可以,只是编译步骤略有不同。
先确认内核配置里STMMAC驱动已经打开:
make ARCH=arm64 menuconfig进入Device Drivers -> Network device support -> Ethernet driver support,找到STMicroelectronics Multi-Gigabit Ethernet driver,确保是<*>或<M>。在SDK默认配置中通常是<*>,但有的精简配置会关掉,这就导致设备树配得再完美,网口也不会上来。
确认无误后编译:
make ARCH=arm64 rk3568-evb.dtb这里的rk3568-evb.dtb只是一个示例,实际你要编译的是自己板卡对应的dtb。如果用的是SDK的完整编译流程,可以执行:
./build.sh kernel编译完成后,在kernel/boot.img或者out目录下能找到新的dtb和内核镜像。
3.2 修改设备树的具体操作
打开板级dts文件,找到&gmac1节点。注意有的SDK里默认用&gmac1,有的可能叫&gmac(如果只有一个GMAC被启用)。我的板子文件里有两个gmac节点,我只用GMAC1,所以直接修改GMAC1。
具体修改步骤:
- 将
phy-mode从"rgmii"改为"rmii",注意是字符串,不要拼错。 - 删除或者注释掉
rgmii相关的tx_delay和rx_delay。RMII模式下不需要delay,如果保留了反而可能导致时序问题。 - 设置
clock_in_out为"input"或者"output",取决于硬件方案。 - 配置
assigned-clocks、assigned-clock-rates、assigned-clocks-parents,确保时钟频率为50MHz。 - 修改
pinctrl-0,引用正确的RMII引脚组。 - 检查
mdio1子节点,修改PHY地址。
这是我实际修改过的一份配置(在SDK默认配置基础上改动的地方用注释标出):
&gmac1 { status = "okay"; phy-mode = "rmii"; // 从 rgmii 改为 rmii clock_in_out = "input"; // 时钟由外部PHY提供 assigned-clocks = <&cru SCLK_GMAC1>; assigned-clock-parents = <&cru SCLK_GMAC1_RMII_SPEED>; assigned-clock-rates = <50000000>; pinctrl-names = "default"; pinctrl-0 = <&rmii1_pins &gmac1_clkin>; phy-supply = <&vcc_phy>; tx_delay = <0>; // RMII下建议为0 rx_delay = <0>; // RMII下建议为0 phy-handle = <&phy0>; }; &mdio1 { phy0: ethernet-phy@1 { reg = <1>; status = "okay"; }; };改完以后,重新编译dtb并烧录。烧录的时候只需要更新dtb分区即可,不用整个固件重烧,省时间。
3.3 烧录与启动验证:从u-boot到内核
烧录方式取决于你用的烧录工具。瑞芯微的板子一般用RKDevTool,在烧录界面选择对应的分区,把新编译的dtb文件替换到resource分区或dtb分区。如果你的SDK是A/B分区架构,可能还需要注意当前活跃的槽位。
烧录完成后启动,先看u-boot阶段是否识别到PHY。有些板子在u-boot阶段会做PHY初始化,如果PHY配置不对,启动过程会卡在Net: eth0那里。不过大部分情况下u-boot不依赖Linux设备树,所以更关键的还是内核阶段。
内核启动后,执行:
dmesg | grep -i stmmac dmesg | grep -i eth正常情况会看到类似这样的日志:
stmmaceth 0001c30000.ethernet: IRQ eth_wake_irq not found stmmaceth 0001c30000.ethernet: IRQ eth_lpi_irq not found stmmaceth 0001c30000.ethernet: PTP uses main clock stmmaceth 0001c30000.ethernet: User ID: 0x10, Synopsys ID: 0x35 stmmaceth 0001c30000.ethernet: Ring mode enabled stmmaceth 0001c30000.ethernet: DMA HW capability register supported stmmaceth 0001c30000.ethernet: RX Checksum Offload Engine supported stmmaceth 0001c30000.ethernet: COE Type 2 stmmaceth 0001c30000.ethernet: TX Checksum insertion supported stmmaceth 0001c30000.ethernet: Enable RX Mitigation via HW Watchdog Timer ...接着用ifconfig -a查看网卡是否存在。如果出现eth0,再执行:
ip link set eth0 up注意:如果PHY没有正确link,ip link会显示NO-CARRIER状态。正常情况应该显示LOWER_UP。
3.4 网络连通性测试与吞吐验证
网口起来以后,先做基础连通性测试:
ping -I eth0 192.168.1.1这里-I指定网卡,避免多网卡时走错接口。如果ping通,再测试带宽:
iperf3 -c 192.168.1.1 -t 10百兆环境下,iperf3的TCP速率大概在94~98Mbps之间属于正常。如果你测出来只有70Mbps甚至更低,先别急着怀疑设备树,可能是网线质量、对端设备性能或者CPU负载问题。
还有一个很实用的技巧:用ethtool eth0查看PHY协商状态和当前速率。RMII模式下正常应该看到:
Speed: 100Mb/s Duplex: Full如果看到Speed: 10Mb/s,说明协商过程有问题,大概率是时钟或者引脚配置不对,而不是物理链路问题。
4. 常见问题与排查技巧实录
4.1 网口驱动加载失败:找不到PHY
这个是最常见的问题。现象是内核日志里只有stmmaceth的初始化信息,但没有mdio bus: ...或者PHY ...相关的日志,ifconfig -a也看不到eth0。
排查顺序:
- 确认
mdio节点中的PHY地址是否正确,用硬件原理图对照PHY芯片的ADDR引脚。 - 检查
phy-handle指向的标签是否和mdio1子节点的标签一致。 - 检查MDIO引脚是否被其他功能占用,用
cat /sys/kernel/debug/gpio看一下。 - 检查PHY供电是否正常,
phy-supply对应的GPIO电平是否正确。
我碰到过一次是厂商的硬件改版后,PHY地址从1变成了2,但设备树没同步更新,导致驱动一直枚举不到。这种问题如果硬件工程师和软件工程师没有对齐,很容易浪费半天时间。
4.2 时钟方向配置错误导致链路无法建立
现象是:dmesg里PHY已经探测到了,ethtool eth0也能看到PHY寄存器,但ip link set eth0 up之后,link始终是NO-CARRIER。
这种情况十有八九是clock_in_out配置错了。如果硬件是外部晶振给PHY,PHY回传时钟给MAC,那clock_in_out必须是"input"。如果硬件的REF_CLK是MAC输出的,那要设为"output",并且pinctrl-0里要引用gmac1_clkout而不是gmac1_clkin。
怎么确认你的硬件是哪种设计?就看原理图里PHY的REF_CLK引脚是直接连到晶振,还是连到SoC的某个引脚。另外,部分PHY芯片支持通过寄存器选择REF_CLK方向,比如RTL8201F有CLK_DIR相关的配置,这时候可能还需要在PHY驱动里额外处理。
4.3 RX CRC错误或者丢包
如果链路通但ping丢包严重,或者ifconfig显示的RX errors持续增长,先看ethtool -S eth0里的具体错误计数:
ethtool -S eth0重点关注rx_crc_errors、rx_missed_errors、rx_frame_errors这几项。
如果CRC错误很多,一般都是时钟质量或者信号完整性问题。RMII的50MHz时钟占空比如果偏差太大,数据采样就会出错。可以尝试调整PHY的时钟驱动能力,或者修改设备树里的rx_delay值。虽然RMII不像RGMII那样需要delay,但有些PHY在高频下还是需要一点延迟来对齐采样点,这个时候可以尝试把rx_delay从0改成2或3(单位是ns),不过这个值是SoC内部可调的,具体范围要看芯片手册。
我个人的经验是:优先检查地线和电源质量,RMII的REF_CLK走线过长或者参考地不连续,最容易引发这种问题,改设备树只能是治标不治本。
4.4 常见问题排查速查表
| 现象 | 可能原因 | 排查建议 |
|---|---|---|
| 内核无eth0 | stmmac驱动未编译 | 检查内核配置,确认驱动已启用 |
| 内核有eth0但link NO-CARRIER | PHY未探测到 | 检查MDIO地址、PHY供电、PHY_id |
| link正常但ping不通 | MAC时钟不同步 | 检查clock_in_out和pinctrl配置 |
| 速率协商成10M | 时钟方向错误 | 确认REF_CLK方向,检查硬件接线 |
| RX CRC错误多 | 时钟质量差或走线干扰 | 检查晶振和PCB布局,可微调rx_delay |
| 大量TX丢包 | 发送FIFO溢出 | 确认DMA配置和内存带宽是否足够 |
| 启动时引脚冲突 | 引脚复用抢占 | dmesg查pinctrl报错,修改pinctrl配置 |
| 网速只有50Mbps | RMII双沿采样异常 | 确认REF_CLK占空比,尝试调整驱动能力 |
这张表基本覆盖了我这段时间遇到的所有问题。实际排查时,最有效的工具就是dmesg和ethtool,再配合硬件原理图,不要凭感觉乱改参数。
5. 实用技巧与经验补充
5.1 如何快速定位硬件还是软件问题
当你面对“网口挂了”这种情况,第一步永远不要急着改设备树。先用万用表测PHY的供电、复位引脚电平、以及REF_CLK是否实际有波形(示波器看最好)。如果REF_CLK没有50MHz输出,那硬件问题占主要因素,软件再怎么调也没用。如果有波形,再进系统看寄存器状态。
第二步,确认PHY的复位时序。很多NMOS/GPIO控制复位的电路,如果软件初始化时没有释放复位引脚,PHY就一直处于复位状态,此时MDIO扫描是采不到任何设备的。设备树里可以通过reset-gpios属性指定复位引脚,确保驱动在初始化PHY之前释放复位。你可以加上:
phy0: ethernet-phy@1 { reg = <1>; reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <20000>; };这里的延时参数要根据PHY芯片手册要求来。太短可能导致PHY没有完全初始化,太长则会拖慢启动时间。一般10ms~20ms比较稳妥。
5.2 查看PHY寄存器的实用方法
内核提供了mdio-tools或者phytool,在嵌入式环境中,如果没装这些工具,可以直接通过sysfs访问。先找到PHY对应的驱动目录:
ls /sys/bus/mdio_bus/devices/例如输出stmmac-0:01,那么对应PHY寄存器可以通过:
cat /sys/bus/mdio_bus/devices/stmmac-0:01/phy_registers不过更直观的方式还是使用ethtool:
ethtool eth0 ethtool -d eth0ethtool -d会dump PHY寄存器,里面能直接看到链路状态、速度、双工模式、协商结果等关键信息。这个在排查时钟方向问题时特别有用,因为可以清楚看到PHY是否识别到对端。
5.3 设备树语法检查与编译优化
设备树写错字段名是最低级的错误,但也是最常见的。好在编译的时候会报warning或者error,但要确保编译的是修改后的文件。很多SDK编译的时候会先生成*.dtb.tmp,真正烧录的是合并后的镜像,所以改完dts后最好先单独编译dtb,再整体打包。
建议在修改dts文件后立即执行:
make ARCH=arm64 dtbs看到编译通过再继续后续步骤。如果添加了不存在的属性,编译器会得到undefined label或者类似错误。如果属性值类型不对(比如应该用<&label>却写了字符串),编译器也会报错。训练自己每次改动后都编译检查,能省下不少烧录后才发现问题的时间。
5.4 基于经验总结的完整配置模板
最后,我把一个适用于大多数RK3568 RMII方案的设备树模板放在这里,你可以根据自己的硬件细节调整:
&gmac1 { status = "okay"; phy-mode = "rmii"; clock_in_out = "input"; // input: PHY提供时钟, output: MAC提供时钟 assigned-clocks = <&cru SCLK_GMAC1>; assigned-clock-parents = <&cru SCLK_GMAC1_RMII_SPEED>; assigned-clock-rates = <50000000>; pinctrl-names = "default"; pinctrl-0 = <&rmii1_pins &gmac1_clkin>; // output模式换为 &gmac1_clkout phy-supply = <&vcc_phy>; tx_delay = <0>; rx_delay = <0>; phy-handle = <&phy0>; }; &mdio1 { phy0: ethernet-phy@1 { reg = <1>; reset-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <20000>; status = "okay"; }; };这里重点提醒几点:
assigned-clock-parents一定不要写成父时钟的rate,这里填的是父时钟的phandle。- 如果PHY芯片是内部集成了时钟源,且不需要外部50MHz,那更简单,
clock_in_out可以不配置,但需要确认PHY的工作模式。 - 如果你用的是GMAC0而不是GMAC1,把对应的
gmac0、mdio0、rmii0_pins、gmac0_clkin替换掉即可。
这个模板我在两个不同的板卡上验证过,一个是GPIO控制PHY复位,一个是常供电不控制复位。效果都很稳定,内核每次都能正常识别PHY并建立链路。
6. 从百兆到扩展:后续还能怎么玩
6.1 多网口配置的注意事项
RK3568有两个GMAC控制器,如果你想同时使用GMAC0和GMAC1,比如做路由或者工业网关,那就需要配置两个独立的设备树节点。这时候要注意的是:两个GMAC的MDIO总线是独立的,PHY地址可以相同也可以不同,但要注意引脚复用是否冲突。
我曾经在一个项目里同时启用双千兆,结果发现两个GMAC共用了一组中断引脚,导致第二个网卡的中断一直触发不了。后来去查芯片手册,才发现两个GMAC的中断是独立分配的,但SDK里的设备树模板有bug,没有分配第二个中断。这种问题只能通过读芯片手册来解决,设备树本身看不出毛病。
6.2 与EtherCAT、TSN等实时方案的关系
如果你在热搜词里看到“正点原子rk3568 ethercat”,那说明有人尝试在RK3568上跑EtherCAT主站。EtherCAT对以太网的实时性要求很高,但RMII百兆接口在工业现场非常常见。RMII接口本身不影响EtherCAT协议栈工作,但实时性瓶颈主要在MAC和DMA的中断延迟上。
如果要在RK3568上做EtherCAT,建议打开STMMAC的ptp和timestamp功能,并且要配置好优先级中断。设备树里一般需要追加:
assigned-clocks = <&cru SCLK_GMAC1_PTP_REF>; assigned-clock-rates = <250000000>;这样才能保证时间戳的精度。这块内容比较复杂,改天可以单独写一篇。
6.3 RMII换成RGMII的差异化配置
另一个常用场景是:同一块PCB上做了两种设计,一种走RMII,一种走RGMII,需要软件支持两种模式切换。这种情况下,建议把模式做成dts overlay或者用uboot环境变量选择不同dtb,而不是在同一个dtb里动态切换。因为RMII和RGMII的引脚复用差别太大,动态修改pinctrl很容易出现驱动状态不一致。
如果你需要手动验证RGMII模式,只需把phy-mode改成"rgmii",并把tx_delay和rx_delay改成适合你硬件的值(常见是2ns或者根据PHY的delay配置),同时把pinctrl改为rgmii1_pins。不过这需要硬件上确实有RGMII的走线,否则只会增加调试难度。
6.4 远程调试的小技巧
最后分享一个很实用的小技巧:设备树配置网口还没调通的时候,串口是唯一的救命稻草。建议在kernel cmdline里加上earlycon和console=ttyS0,1500000,这样可以尽早看到驱动日志。另外,在stmmac驱动调试时,可以打开动态调试:
echo 'file stmmac_main.c +p' > /sys/kernel/debug/dynamic_debug/control这样能打印更多stmmac内部的初始化信息,包括PHY扫描过程、DMA配置、时钟状态等。对于定位“PHY明明在,但驱动就是没找到”这种玄学问题,帮助极大。
我在实际调试RMII网口的时候,这类动态调试日志帮了我大忙。有些问题看代码根本看不出来,打印出来的实际数值能直接暴露问题,比如PHY ID读取出来全是0xffffffff,那基本就是MDIO时序不对或者PHY没上电,思路一下就清晰了。