以太网驱动大概是嵌入式Linux里最"看起来简单、做起来全是坑"的一块。我见过太多人第一次调以太网,硬件同事说"网口已经通了,你跑个ping就行",结果板子插上网线,ifconfig里连个eth0都看不到,或者看到了但死活拿不到IP,再或者能ping通但一传大文件就丢包。这期就把我在多个平台上调以太网驱动的完整经验摊开讲,从MAC和PHY的分工、设备树怎么写、MDIO总线怎么扫、PHY驱动怎么匹配,一直到DMA描述符、吞吐量上不去怎么排查,尽量把每个"为什么"都讲透。
这篇适合已经写过字符设备驱动、能看懂设备树、但一碰到网络子系统就发怵的嵌入式工程师。如果你正在用i.MX、Zynq、全志、瑞芯微这类平台,或者自己画了一块带W5500、带外部PHY的板子,这篇里的排查链路基本可以直接套用。我不会只给你结论,而是把"为什么这么配""这个参数怎么算出来的""报这个错说明卡在哪一层"都讲清楚,让你下次遇到新平台也能自己推。
1. 先把MAC、PHY、MDIO这三者的关系理清楚
很多人调以太网调得痛苦,根子在于没搞清楚数据通路和控制通路是两条完全独立的线。数据走的是MAC和PHY之间的高速接口,控制走的是MDIO/MDC这条慢速管理总线。这两条线任何一条不通,表现出的现象完全不同,但新手往往混在一起猜,效率极低。
1.1 数据面:MAC和PHY之间到底怎么传数据
MAC(Media Access Control)是SoC内部集成的以太网控制器,负责组帧、CRC校验、DMA搬运;PHY是外部那颗小芯片,负责把数字信号变成差分模拟信号发到网线上,反过来把网线上的信号还原成数字。两者之间在百兆时代常用MII/RMII,千兆时代用GMII/RGMII,现在很多新平台直接上SGMII或者RGMII加内部延时。
RGMII是重灾区。它把GMII的8位数据线砍到4位,靠双沿采样(DDR)在125MHz时钟下跑出1000Mbps。代价是发送和接收各有一组时钟,数据相对时钟的相位关系必须对齐,否则采样点落在数据跳变沿上,直接大量误码。这就是为什么RGMII有"延时"这个概念——要么PHY内部加延时,要么MAC侧加延时,要么PCB走线绕等长,三者必须有一个来保证采样窗口。
我踩过最典型的一个坑:某平台RGMII能link up,ethtool看速率是1000M,但一ping就丢一半包。查了半天是MAC侧和PHY侧都开了延时,等于延了两次,采样点直接偏出窗口。解决办法是二选一,通常优先在PHY侧配置延时(通过PHY寄存器),MAC侧保持直通。这个"两边都开"的错误在抄参考设计时特别容易犯,因为参考板可能用的是PHY侧延时,而你照抄了MAC侧延时的设备树属性。
1.2 控制面:MDIO总线是怎么找到PHY的
MDIO是一条两线总线,MDC是时钟,MDIO是双向数据。MAC作为主设备,通过它读写PHY的寄存器。每个PHY在总线上有一个5位的地址(0到31),所以一条MDIO理论上最多挂32个PHY。设备树里那个phy-handle或者phy地址,指的就是这个。
扫描PHY有个很实用的技巧:如果不知道板子上PHY的地址是多少,可以在uboot或者内核里遍历0到31,读PHY的ID寄存器(寄存器2和3)。标准PHY的ID寄存器2的高16位一般是0x0000到0x001F之间的厂商OUI,寄存器3是型号。如果读出来是0x0000或者0xFFFF,说明这个地址上没有PHY,或者MDIO根本没通。
MDIO不通的典型现象是:ifconfig能看到eth0(因为MAC驱动加载了),但PHY一直link down,ethtool eth0显示"Link detected: no",而且dmesg里可能有"MDIO bus read timeout"之类的报错。这时候要查的是MDC时钟频率(一般不超过2.5MHz)、MDIO的上拉电阻(通常1.5k到10k)、以及设备树里MDIO节点的寄存器范围有没有写对。
1.3 为什么PHY驱动要单独匹配
Linux的PHY子系统是独立于MAC驱动的。MAC驱动注册一个mdio_bus,然后PHY设备挂上去,内核用PHY的ID去匹配phy_driver。这意味着即使MAC驱动写对了,如果PHY驱动没匹配上,PHY也不会被正确初始化,可能一直处于默认的百兆模式或者功耗状态。
匹配靠的是PHY ID。内核里drivers/net/phy/下面有一堆厂商驱动,比如marvell.c、realtek.c、micrel.c。如果你的PHY是通用型号,genphy驱动能兜底,但只能做最基本的操作,像RGMII延时、特定功耗模式这些就配不了。所以选PHY时尽量选内核主线有驱动的型号,能省掉大量自己写PHY驱动的时间。
提示:判断PHY驱动有没有匹配上,看
/sys/class/mdio_bus/*/下面的设备,或者dmesg | grep -i phy。如果打印的是"Generic PHY",说明用的是兜底驱动,功能会受限。
2. 设备树里以太网节点的每个字段都要能说出理由
设备树是以太网驱动能不能起来的第一道关。我见过太多人从别的板子抄一段以太网节点,改个地址就编译,结果各种玄学问题。其实每个属性都有明确含义,理解了就不用猜。
2.1 compatible和reg决定了MAC驱动能不能probe
compatible字符串必须和内核里MAC驱动的of_match_table对上。比如i.MX6的FEC是fsl,imx6q-fec,Zynq的是xlnx,gem或者cdns,zynq-gem。这个字符串写错,驱动根本不会probe,dmesg里连MAC初始化的打印都没有。
reg是MAC寄存器的物理地址和长度。这个必须和SoC手册里的内存映射一致。如果地址写错,驱动probe时会去读一片无效地址,轻则读回全0或全F,重则触发总线错误把内核搞挂。
2.2 phy-mode和phy-handle是最容易配错的两个
phy-mode告诉MAC用哪种接口连PHY,取值有mii、rmii、gmii、rgmii、rgmii-id、rgmii-txid、rgmii-rxid、sgmii等。这里的-id后缀就是延时配置:rgmii-id表示收发都加延时,rgmii-txid只加发送延时,rgmii-rxid只加接收延时。前面说的"两边都开延时"的坑,往往就是phy-mode写了rgmii-id,同时PHY驱动里又配了延时。
phy-handle指向一个PHY子节点,里面写reg = <地址>。如果PHY地址是硬件固定的(比如通过上下拉电阻决定),这里必须写对。有些平台支持自动扫描,但显式指定更可靠。
2.3 时钟和复位属性不能漏
以太网MAC需要时钟才能工作。设备树里通常有clocks和clock-names,比如ipg、ahb、ptp这些。时钟没配好,MAC寄存器读写会失败或者行为异常。有些平台还需要assigned-clocks和assigned-clock-rates来设置时钟频率。
复位属性resets和reset-names也很关键。PHY和MAC都可能需要复位。如果PHY复位没释放,MDIO读出来永远是0。我遇到过一次,PHY的复位GPIO在设备树里漏了,结果PHY一直处于复位态,MDIO扫描全0,查了两天才发现是复位没拉高。
&fec1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rgmii-id"; phy-handle = <ðphy0>; fsl,magic-packet; status = "okay"; mdio { #address-cells = <1>; #size-cells = <0>; ethphy0: ethernet-phy@0 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <0>; reset-gpios = <&gpio1 25 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <100000>; }; }; };上面这段里,reset-assert-us和reset-deassert-us是PHY复位时序,很多PHY要求复位拉低至少10ms,释放后等100ms才能访问。这两个值不对,PHY可能初始化失败。这是很典型的"文档里不显眼但实际很要命"的参数。
3. 从link down到能ping通:一条完整的排查链路
以太网不通的现象五花八门,但排查是有固定顺序的。我把它总结成"从下往上、从静到动":先确认硬件和时钟,再看MDIO能不能读到PHY,再看PHY能不能link up,最后看数据能不能收发。
3.1 第一步:确认MAC驱动probe成功
dmesg | grep -i eth或者dmesg | grep -i fec(按平台换关键字)。如果看到类似"eth0: Freescale FEC PHY driver"的打印,说明MAC驱动起来了。如果什么都没有,回去查compatible和status。status必须是okay,很多人从参考设计抄过来忘了改,一直是disabled。
3.2 第二步:确认MDIO能读到PHY
ifconfig -a看有没有eth0。有eth0但PHY没识别,dmesg里会有"no PHY found"或者PHY ID读出来是0xffffffff。这时候用mdio-tools或者直接写个小程序读PHY寄存器。如果读不到,查MDC/MDIO的引脚复用(pinctrl)、上拉、以及PHY供电。
PHY供电经常被忽略。有些PHY有多个电源域,内核电压和IO电压都要正常。用万用表量一下PHY的VDD和VDDIO,确认在规格范围内。我遇到过一次PHY的1.2V内核电压没起来,MDIO能读到ID但link一直down,查供电才发现是LDO使能脚没拉高。
3.3 第三步:确认PHY link up
ethtool eth0看"Link detected"。如果是no,先确认网线和对端设备没问题(换根线、换个交换机口)。然后看PHY的BMCR(寄存器0)和BMSR(寄存器1),确认自协商有没有完成。有些老交换机不支持自协商,需要强制设置速率和双工模式,用ethtool -s eth0 speed 100 duplex full autoneg off。
link up了但速率不对,比如千兆PHY只协商到百兆,通常是RGMII延时或者走线问题。可以试着调整phy-mode的延时配置,或者用PHY的寄存器手动调延时。
3.4 第四步:确认能收发数据
link up之后udhcpc或者静态配IP,然后ping。ping不通先看ARP能不能通(arping),再看路由和防火墙。如果ARP通但ping不通,可能是ICMP被挡了。如果小包通大包不通,基本是MTU或者DMA描述符的问题。
大包丢包还有一个常见原因是DMA描述符数量不够或者缓冲区对齐问题。有些MAC要求接收缓冲区按特定字节对齐(比如64字节),没对齐会导致DMA搬运异常。这个在驱动里通常有NET_IP_ALIGN处理,但如果自己改过驱动要特别注意。
| 现象 | 最可能的原因 | 排查手段 |
|---|---|---|
| 没有eth0 | compatible错、status非okay、时钟缺失 | dmesg、检查设备树 |
| 有eth0但PHY ID为0xffffffff | MDIO不通、PHY复位未释放、供电异常 | 量MDIO波形、查复位GPIO、量供电 |
| PHY ID正常但link down | 网线/对端问题、自协商失败、延时配置错 | 换线、ethtool、调phy-mode |
| link up但ping不通 | IP配置、ARP、路由 | ifconfig、arping、route |
| 小包通大包不通 | MTU、DMA描述符、缓冲区对齐 | ping -s、查驱动DMA配置 |
4. 吞吐量上不去,问题往往不在你以为的地方
link up、能ping通只是及格线。真正做产品时,吞吐量是硬指标。千兆网口跑出来只有100Mbps,或者跑一会儿就掉速,这类问题比"完全不通"更难查,因为它涉及DMA、中断、CPU负载、协议栈多个层面。
4.1 先分清是MAC侧瓶颈还是PHY侧瓶颈
用iperf3打流,同时看ethtool -S eth0的统计。如果tx_packets在涨但tx_bytes涨得慢,说明发的是小包,可能是应用层问题。如果PHY侧统计有CRC错误或者丢包,说明物理层信号质量有问题,要查走线和延时。
CPU占用率也是关键指标。千兆线速下,如果用一个核跑协议栈,很容易跑满。top看ksoftirqd和%soft,如果软中断占用很高,说明中断处理是瓶颈。解决办法是开NAPI(大部分驱动默认开)、多队列(RSS)、或者把中断绑到不同核。
4.2 DMA描述符和缓冲区怎么影响吞吐
MAC通过DMA描述符环和内存交互。描述符数量太少,高吞吐下DMA来不及搬运就丢包;太多则占用内存且增加cache压力。一般接收描述符给256到512个,发送给256个左右比较均衡。
缓冲区大小也有讲究。以太网标准MTU是1500,加上帧头开销,缓冲区一般给1536到2048字节。如果开了巨帧(jumbo frame),要相应加大到9K左右。缓冲区没对齐会导致DMA效率下降,有些MAC要求缓冲区起始地址按16字节或64字节对齐。
我实测过一个案例:某平台千兆只能跑300Mbps,查了半天是接收描述符只给了64个,高吞吐下DMA环很快被填满,新包直接丢。把接收描述符加到512个,吞吐直接上到900Mbps以上。这个参数在驱动里通常是宏定义,改起来很简单,但不知道的人会一直往协议栈方向查。
4.3 中断合并和NAPI的取舍
中断合并(interrupt coalescing)是指攒够一定数量的包或者等一段时间再触发中断,减少中断次数。ethtool -c eth0可以看和设置。合并参数调大,CPU占用降下来,但延迟增加;调小则相反。做吞吐测试时可以把合并调激进一点,做实时性要求高的场景则要调小。
NAPI是Linux网络收包的核心机制,中断触发后关中断,改用轮询收包,收完再开中断。这个机制在高吞吐下能大幅降低中断开销。大部分主流MAC驱动都支持NAPI,但有些老驱动或者自己写的驱动可能没开,这时候吞吐上不去是必然的。
注意:调吞吐量时一定要用
iperf3这种能打满带宽的工具,别用scp或者wget,它们受磁盘和协议开销影响太大,测出来的数不准。
5. 几个真实踩过的坑和对应的解法
前面讲的是体系化的排查思路,这一节专门讲几个我实际踩过、且很有代表性的坑。这些坑的共同特点是:现象很迷惑,根因很隐蔽,但一旦知道就再也不会犯。
5.1 PHY地址冲突导致两个网口只有一个能用
有块板子有两个网口,共用一个MDIO总线。调试时发现eth0正常,eth1死活不通。查设备树发现两个PHY的reg都写成了0。MDIO总线上地址必须唯一,两个PHY同地址会互相干扰。改成0和1之后两个口都正常了。这个坑的迷惑性在于,eth0能用会让你以为MDIO和驱动都没问题,从而忽略地址冲突。
5.2 时钟频率不对导致MDIO读写随机失败
某平台MDIO偶尔能读到PHY,偶尔读不到,非常随机。查MDC时钟发现分频系数配错,MDC跑到了5MHz以上,超过PHY规格的2.5MHz上限。降下来之后稳定了。MDIO对时序敏感,MDC太快会导致建立/保持时间不够,读出来的数据就是错的。这个坑的随机性很强,容易被误判成硬件接触不良。
5.3 电源管理导致休眠唤醒后网口失效
产品要求低功耗,系统休眠时把PHY也断电了。唤醒后发现网口不通,ethtool显示link down。原因是PHY断电后寄存器全部丢失,唤醒后没有重新初始化。解决办法是在唤醒回调里重新配置PHY,或者休眠时只让PHY进低功耗模式而不是完全断电。这个坑在消费类产品里很常见,因为功耗指标卡得严。
5.4 网线质量导致的"玄学丢包"
有次测试一直有零星丢包,换了板子、换了PHY配置都没用。最后换了根网线好了。劣质网线的差分阻抗不达标,千兆下误码率飙升。这个坑的教训是:调以太网时,网线和对端设备一定要用确定没问题的,否则会把水搅浑,让你在软件里白查半天。
6. 写给准备自己画以太网板子的人
如果你不只是调驱动,还要参与硬件设计或者选型,这几个点值得提前注意,能省掉后面大量的调试时间。
PHY选型优先选内核主线有成熟驱动的型号,比如Marvell 88E1512、Realtek RTL8211、Micrel KSZ9031这些,资料多、坑少。RGMII走线要严格等长,数据线相对时钟的偏差控制在规格内,一般要求±50mil以内。MDIO的上拉电阻别省,1.5k到10k都行,但必须有。
电源设计上,PHY的模拟电源和数字电源要分开滤波,否则噪声会耦合进信号,表现为丢包或者link不稳。复位电路要保证上电时PHY可靠复位,复位时间参考PHY手册,别拍脑袋。
晶振也很关键。PHY通常需要25MHz晶振,频偏要控制在±50ppm以内。晶振质量差会导致link不稳定或者协商不到千兆。这个在打样阶段就要验证,别等量产才发现。
最后说个心态问题:以太网调试急不得,它涉及硬件、时钟、总线、驱动、协议栈多个层次,任何一个层次出问题现象都可能是"不通"。按"硬件供电→时钟→MDIO→PHY link→数据收发→吞吐"这个顺序一层层确认,每层都用确定的手段验证,比东一榔头西一棒子高效得多。我自己调新平台时,基本就是拿这个清单从头过一遍,大部分问题在MDIO和PHY link这两步就能定位。