强制MT7621网口使用千兆模式 (ETH MAC PHY踩坑记)

家里一直使用Newifi D2做各个房间的AP覆盖和管理型交换机,一共有四个。这矿渣路由器胜在便宜,性能过的去,又有OpenWRT支持。作为开源强迫症,肯定是全部上OpenWRT了。不过有个问题实在困扰我,距主交换机比较远的几个房间(包括我的卧室)里的AP都只能沟通到百兆速率,但是明明墙里都埋的是CAT5E线,交换机也是千兆的,没理由不能上千兆。刚好因为疫情上网课的时候没事干,于是折腾之旅开始了。

首先,为了确认不是交换机侧和墙内线的问题,我直接把网线插电脑上,电脑是e1000e的网卡,Linux下各种支持不错。有ethtool可以拿来调试,经过各种测试,发现e1000e网卡大部分情况下可以沟通到千兆,少部分情况下也会降级到百兆,但是可以用ethtool强行指定回到千兆,而且跑iperf也能跑到600Mbps左右,丢包率不高,链路不算很差,看来问题出在MT7621网口的自动沟通流程上了。

然而,我们知道MT7621的网口透过了一层交换机,从CPU视角只有一个到switch的网口,而OpenWRT对switch有自己的管理机制,简单的ethtool肯定是不管用的。网上一顿搜索,找到OpenWRT下交换机的配置命令swconfig, 其中有个link选项,可以用于配置链路状态。赶紧依样画葫芦,输入 swconfig dev switch0 port 0 set link “speed 1000 duplex full“ 尝试强制进入千兆模式。不过现实光速打脸,直接报错 Failed to set attribute: Operation not supported。大概看了一下swconfig的代码,是直接对着devfs操作的,看来得干内核咯~

对着OpenWRT内核一顿猛追,找到MT7621的On-die交换机模块MT7530的驱动,swconfig set link命令是由switch_dev_ops的callback set_port_link 实现的,这个在MT7530驱动里的确没实现,得想办法搞他一发。很快找到驱动里有个get_port_link函数,是从交换机模块的PMSR(Port MII Status Register)寄存器里读取当前Port状态。从MT7620的Programming Guide里找到了这个寄存器(MT7621的手册网上找不到,MT7620也内置MT7530,不国只有百兆,缺些功能) ,发现他旁边就有个PMCR(Port MII Control Register)寄存器,其中有些”FORCE_MODE”的位域,按文字描述来看可以强制端口进入特定模式,在内核头文件里也找到了包含千兆模式的完整定义。那就简单了嘛,三下五除二用这些寄存器实现了set_port_link,直接强制半双工/双工状态,流控状态和速率,完事。马上编译OpenWRT刷进路由器测试。

然而事情并没有这么简单。刷完之后我把路由器的一个端口和电脑e1000e连接测试,发现一旦强制模式和之前Autoneg的模式不同,链路就不通了。而且在进入FORCE_MODE时并没有触发链路重联,也没有协商或强制生成新的速率,感觉是方法出了根本上的问题。郁闷至极,回去重读MT7620的手册,特别注意了MT7530交换机的结构,发现虽然Switch Port 0~4内置了ETH PHY,但这些PHY是需要软件通过MDIO访问PHY MII寄存器配置的,并且手册上标明了PHY MII控制寄存器的位域。联想到之前调Synopsys GMAC建立的理解,链路的模式设置与自动沟通应该由PHY完成。回去重新思考了一下,这里MT7530的Port MII控制寄存器指的应该是交换机MAC侧的MII接口配置,而不是PHY对RJ45接口的配置。读了一遍驱动,驱动里对MT7530面向CPU核的MII Slave接口进行的额外配置基本验证了我的猜想。回过头来,针对对外连接的MAC Port,其默认的Auto Mode就是从PHY Poll当前的链路状态以发出合适的MII信号,进入Force Mode且指定不同的链路模式会导致MAC发出与PHY模式不匹配的信号,进而导致链路不通。

为了改变链路模式,我们应该配置的是PHY的模式寄存器。PHY的MII寄存器有约定俗成的位域,其中BMCR寄存器中有AutoNeg启用/禁用和强制速率的位。又搜索了一圈代码,发现OpenWRT内核里已经有针对PHY配置模式的switch_generic_set_link函数,我只要自己实现MDIO PHY的寄存器读写函数就好了。于是满心欢喜的写完读写函数,调用这个函数来完成set_link,然后再把一个口连上e1000e测试。这下swconfig命令一下去链路立刻断开重联了,10Base-T和100Base-T都没问题,除了e1000e有一条提示 Autonegotiated half duplex but link partner cannot autoneg. Try forcing full duplex if link gets many collisions. 无法正确判断双工状态。但是一旦要求进入1000Base-T FULL,两边就一起Link Down且不再Link Up。介于AutoNeg是可以沟通出千兆速率的,应该不是物理层问题,我感到很迷茫。

实在无解,我开始翻IEEE802.3的文档。其中很多概念像天书一样,不过有一句话引起了我的注意,“1000BaseT模式仅能由自动沟通进入”。然而按我当前的操作,显然是没有启用自动沟通。ok,那么自动沟通怎么指定速率呢?继续读文档,自动沟通是通过寻找两端”Advertise”模式的交集来确定当前的模式。而PHY向LP(Link Partner, 也就是链路的对端)发送的”Advertise”模式是可以通过本侧PHY的MII_ADVERTISE和MII_CTRL1000(针对1000BaseT扩展的配置寄存器)控制的。也就是说,我们有办法告诉对端”我们支持的模式只有千兆”,来避免协商降级到百兆。按照这个思路,我写了新的set_port_link,并且测试成功,在模式间切换自如。

于是,OpenWRT打了补丁之后只要在路由器开机的时候执行一下 swconfig dev switch0 port 0 set link “speed 1000 duplex full“ 就可以保持在千兆模式咯~

相关代码已向OpenWRT发起Pull Request。

Licensed under CC BY 4.0
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy