U-Boot网络调试实战:从net层到MAC再到PHY的完整排查指南 搞过U-Boot网络的人应该都有体会明明在Linux底下网络跑得好好的一进U-Boot就各种不通ping不通、tftp超时、MAC地址读出来是一串F甚至网口干脆不识别PHY。这类问题我前前后后踩了无数回最后发现根源大多不是硬件坏了而是没把U-Boot这套精简版网络栈的分层逻辑理顺。这篇文章就围绕U-Boot网络从net层到MAC再到PHY的完整链路把每层干什么、怎么分工、调试时先查什么都给你盘清楚适合正在移植板子、调网口驱动、或者被u-boot网络问题折磨的嵌入式工程师参考。我会把实际调试中用到的命令、寄存器、排查思路一并写出来照着操作基本能定位九成以上问题。1. U-Boot网络栈的整体骨架三层分工各管一摊1.1 先从net核心层说起U-Boot的net层在源码里对应net/目录net.c是核心。它管的是协议逻辑构造ARP请求、解析ARP应答、组ICMP Echo、处理TFTP的WRQ/DATA/ACK交互还包括环境变量里的serverip、ipaddr、netmask这些参数的管理。这一层有一个核心概念就是尽力不感知具体硬件细节它只依赖一个抽象接口eth_send()发一帧、eth_recv()收一帧。这一点和Linux的网络协议栈很像只是砍掉了socket、路由表、邻居表这些硕大子系统保留了一个极简版本的收发包循环。U-Boot的net_loop()会一直轮询调用eth_rx()处理收到的包再把需要发出的包交给eth_send()。如果底层驱动说自己有数据net层才会去处理没有数据就继续跑自己的超时逻辑。你要理解一点U-Boot的网络栈是单线程、非抢占、无中断驱动的。也就是说TFTP传输过程中CPU被占住包是一轮一轮查询收上来的。这和Linux的NAPI、软中断完全是两个世界。很多驱动移植过来第一件事就是把中断模式的收包改成轮询模式否则U-Boot基本没法用。1.2 MAC层net层和PHY之间的桥梁MAC层在U-Boot里体现为以太网控制器驱动DMA控制器、FIFO、MAC地址寄存器、MDIO控制器都在这一层。U-Boot的MAC驱动在drivers/net/下常见的像designware.cDW GMAC、fec_mxc.cNXP i.MX系列FEC、macb.cAtmel/Microchip、sunxi_emac.cAllwinner它们都封装成struct eth_ops接口。MAC驱动干的活很明确初始化MAC控制器配置DMA描述符descriptor ring把待发送的数据包搬到TX描述符启动DMA发送接收时从RX描述符取包交给上层通过MDIO/MII总线访问PHY寄存器做协商、读状态管理MAC地址寄存器确保源MAC能填对注意MAC驱动负责的是数据链路层的控制器部分它不负责把电信号变成线路上的差分信号那是后面PHY的事。所以你可以把MAC理解为会讲数据帧协议但不会开口说话的层真正开口的是PHY。1.3 PHY层最后一公里的模拟前端PHYPhysical Layer是物理层收发器板子上那颗独立芯片或者集成在CPU/交换芯片内部的以太网PHY。它负责将MAC发来的并行数据编码成线路上的差分信号负责自协商Auto-Negotiation确定速率、双工模式提供链路状态检测Link up/down提供MDI/MDIX自动翻转等等功能U-Boot对PHY的管理在drivers/net/phy/下核心数据结构是struct phy_device。它通过MDIO总线读写PHY寄存器phy_connect()把PHY挂到MAC上phy_startup()完成协商并设置MAC侧的速率和双工模式。这里有个很关键的实现细节PHY驱动通常只干配置和状态检测数据流本身不经过PHY驱动的buffer数据包始终是在MAC的DMA描述符和FIFO之间流动PHY只是透传。所以遇到丢包、错包问题不要总觉得是PHY驱动在搞鬼很多时候PHY层面只是信号质量不好的背锅侠。1.4 为什么不直接让MAC驱动裸调PHY有人会问U-Boot体量这么小为什么不像早期江湖代码那样把PHY初始化直接写在MAC驱动里而是单独搞一套phy_device框架答案就在可移植性上。同一颗PHY芯片好比说RTL8211F、AR8033可能被用在几十种板卡上接在NXP、TI、Qualcomm不同MAC控制器后面。如果PHY驱动写死在MAC驱动里每移植一块板子就要复制改一遍PHY初始化代码维护成本直接爆炸。U-Boot的phy_device框架和Linux的phy subsystem思路一致PHY驱动只负责怎么操作这颗PHY芯片MAC驱动只负责怎么操作这个MAC控制器。两者通过MDIO总线连接phy_connect()用PHY地址加PHY ID去找匹配的驱动phy_startup()把协商结果再告诉MAC。这样一份PHY驱动可以服务所有MACMAC驱动也不必关心最终选了哪颗PHY。2. 一次完整的数据通路从ICMP请求到网线信号2.1 应用程序视角ping到底做了什么假设你插好网线在U-Boot命令行敲一个ping 192.168.1.100。net层会先查ARP表发现没有目标IP对应的MAC地址于是先发一个ARP请求广播问谁是192.168.1.100请告诉192.168.1.10。对端回应ARP应答后net层把IP-MAC对应关系记录下来再组ICMP Echo Request调eth_send()发出去。在eth_send()里面实际发生的事比想象中多一些。U-Boot的eth_send()会先拿到当前eth_current的struct udevice然后调用eth_get_ops(dev)-send(dev, packet, length)。这个send回调就是MAC驱动实现的它拿到net层组好的完整以太网帧目标MAC、源MAC、类型字段、payload把它写进TX描述符指向的DMA缓冲区然后置位描述符启动发送。2.2 数据包过MAC控制器以DesignWare GMAC为例designware.c里dw_eth_send()干的事是这样的把待发送数据按128字节对齐的要求拷贝到DMA TX缓冲区写描述符的状态位和长度字段然后读一个GMAC_TX_POLL寄存器触发DMA搬运。DMA引擎会把缓冲区里的数据按字节流推给MAC内核MAC内核负责加前导码、加CRC、加帧间隙然后推给GMII/RGMII接口。这里有个很容易被忽略的点MAC可能要求缓冲区在特定地址边界对齐比如32字节、64字节对齐。DMA无法访问的地址范围也是坑。你往malloc出来的地址放数据没问题但如果缓冲区来自奇怪的堆地址DMA写回可能出错。所以我一般建议网卡缓冲区分配走memalign别裸用malloc。2.3 信号最终从PHY出去MAC通过RGMII/GMII接口把并行数据送到PHYPHY内部完成编码1000BASE-T用PAM5100BASE-TX用MLT-310BASE-T用曼彻斯特编码加扰、电平转换然后推上双绞线。对端PHY解码还原成帧交给对端MAC上协议栈最终返回ICMP应答。所以一次ping的完整路径是net层组包 - eth_send() - MAC驱动写DMA描述符 - DMA搬运到MAC内核 - MAC加前导/CRC - RGMII送PHY - PHY编码上线路 - 对端解码 - ...收包路径就是完全反过来。MAC收到完整帧之后DMA把数据放到RX描述符缓冲区产生接收中断U-Boot里可能只置标志位或回调net层轮询到有包调用eth_rx()把数据取走。net_process_received_packet()会解析以太网类型字段IP包走ARP/ICMP/TFTP对应的处理函数。2.4 收发路径上的关键判断点数据通路上的每个环节都可以有一个调试探测点层关键的判断依据常用调试手段net核心层是否组了包、是否发出去net_loop日志、debug宏eth_hdr结构检查MAC驱动DMA描述符是否正常、中断标志/轮询标志读MAC中断状态寄存器、描述符地址与状态字MAC-PHY接口RGMII时钟、TXEN、TXD信号是否正常示波器/逻辑分析仪抓RGMII波形PHY寄存器链接状态、协商速率、中断状态mdio命令读写PHY寄存器物理线路线序、链路信号网线测试仪、对端交换机指示灯实际工作中我从PHY寄存器开始看然后逐层往上基本能快速收窄范围。如果你一上来就扒代码往往看半天也找不到问题在哪因为你跳过了看现场信号这个环节。3. 关键数据结构与驱动接口别被“U-Boot没有驱动模型”带偏了3.1 eth_ops与udevice驱动模型下的网卡抽象现代U-Boot从2016年之后基本都支持DMdriver model里网卡驱动分两个层面UCLASS_ETH表示以太网控制器struct eth_ops定义操作接口UCLASS_MDIO表示MDIO总线struct mdio_ops定义MDIO读写操作。网卡驱动核心要实现的回调包括start(dev)初始化MAC、分配描述符、启动收发stop(dev)关闭网卡、释放缓冲区send(dev, packet, length)发送一帧recv(dev, flags)检查是否有收包有则返回包地址free_pkt(dev, packet, length)释放接收缓冲区read_rom_hwaddr(dev)从板载eFuse/EEPROM读取MAC地址可选这些回调是MAC驱动实现的重点。调网卡驱动时你只要保证这几件事start之后能正常收发包、send能把数据可靠送出去、recv能被net轮询及时调用、MAC地址能正确读到。3.2 phy_device与phy_driverPHY侧的抽象struct phy_device里最重要的字段是addrPHY地址、phy_idPHY识别ID、supported支持的速率/双工能力、advertising广播的能力、link当前链接状态、speed、duplex协商结果。struct phy_driver则定义操作函数集probe、config、startup、shutdown以及read_status、config_aneg、config_init这些核心函数。一颗新PHY的移植其实就是照着drivers/net/phy/realtek.c或atheros.c这些模板把寄存器初始化序列填对把read_status里的状态解析写对。很多人移植PHY驱动时只抄config里的寄存器配置不看read_status。结果就是PHY配置好了但U-Boot读到的link始终是0。这时候你要去读PHY的BMSR寄存器寄存器1看看bit2是不是1如果硬件已经link up而驱动读出来是down那就要检查read_status实现是否读对了寄存器位。3.3 MDIO总线的设备树固定配置设备树里以太网控制器节点的基本长这样gmac0 { status okay; phy-mode rgmii-id; phy-handle phy0; mdio { #address-cells 1; #size-cells 0; phy0: ethernet-phy0 { reg 0; reset-gpios gpio0 12 GPIO_ACTIVE_LOW; reset-delay-us 10000; }; }; };phy-mode里的rgmii-id表示RGMII接口且由PHY内部提供发送和接收的时钟延迟。如果MAC侧有延迟你就要用rgmii-txid、rgmii-rxid或者rgmii时钟延迟配置错了链路能起来但跑千兆会时不时掉包这类问题极其隐蔽我后面会专门说。reg 0是PHY的MDIO地址这个地址由PHY芯片的ADDR引脚上下拉决定。如果你的PHY实际地址是1或4但设备树写0那你用mdio read会读到全F也就是无应答网卡初始化时phy_connect()会失败。所以遇到PHY读不到先确认MDIO地址和硬件配置对不对。3.4 环境变量对网络行为的影响U-Boot网络层的行为受环境变量影响很大调网络问题先看一眼环境变量往往能少走弯路ethaddrMAC地址如果没设置或设置成00:00:00:00:00:00ARP和DHCP都可能直接失败ipaddr本机IPping自己都不同先查这个serveripTFTP服务器地址配错了看起来像TFTP超时netmask子网掩码影响ARP广播范围autoload如果为yesdhcp命令成功后会尝试下载启动文件很多人以为是网络问题其实是autoload在搞事我见过最经典的一个坑板子ethaddr是空的U-Boot自动从fuse里读了一个MAC地址但是烧写新固件时fuse被锁死读出来全F于是一切ARP、DHCP全部失败看起来就像网卡坏了。查了半天最后发现只是ethaddr全F手工setenv ethaddr 00:11:22:33:44:55保存之后立刻恢复正常。4. 实战排查三板斧不动代码先看状态4.1 第一板斧mdio命令直接读PHY寄存器U-Boot自带MDIO调试命令这块必须熟练。进入U-Boot命令行执行U-Boot mdio list它会列出当前MDIO总线上的PHY设备。如果这里列出的PHY和你板上实际的不一样显示两个PHY或者一个都没有先查设备树里mdio节点和PHY的reg。U-Boot mdio read 0 2这是读PHY地址0的寄存器2PHY ID高16位。合理的返回值类似0x001cRealtek RTL8211系列或0x004dMarvell 88E1512/AR8033等。如果读出来是0xffff说明PHY没应答问题大概率在PHY电源、复位脚、MDIO地址或MDIO拉电阻上。另一个必看寄存器是BMSR寄存器1bit2是链路状态。mdio read 0 1后看bit2是否为1。如果你插着网线但这一位是0那从PHY角度说链路就没建立可能是线序、对端设备或者PHY配置问题。一块常见PHY寄存器的速查表我贴在这里寄存器地址名称关键位0BMCR控制bit13-14速率选择bit8全双工bit12自协商使能1BMSR状态bit2链路状态bit5自协商完成bit6-11能力2-3PHY ID厂商代码与型号4ANAR自协商通告bit5-7 千兆bit8-11百兆/十兆5ANLPAR对端能力对端通告的能力6ANER扩展状态自协商错误指示9千兆控制千兆主从、千兆能力4.2 第二板斧mii命令与mii dump全量查看如果你的U-Boot较老没有mdio命令那mii命令同样能干这活。mii info会显示PHY基本信息和协商结果mii dump可以打印PHY所有寄存器非常直观。U-Boot mii info U-Boot mii dump 0 0建议第一次拿到一块新板子时把mii dump的输出完整保存下来作为健康基线。之后调坏了对照这份基线很快就能看出哪个寄存器不对。我每移植一块板子都会做这件事尤其是ANAR、BMSR、PHY ID这几个寄存器能帮你少踩很多坑。4.3 第三板斧net list和ethact确认当前网卡U-Boot支持多网卡net list列出所有UCLASS_ETH设备ethact设置当前活动网卡。如果在双网卡板子上你明明在调试eth1但默认网卡是eth0那ping不通太正常了。U-Boot net list U-Boot setenv ethact eth1 U-Boot ping 192.168.1.100这里有个坑很多人只改ethprime或ethact但启动脚本里又用tftptftp命令会重新选择网卡。确保你设置的当前网卡和实际使用的环境变量一致否则你会看到tftp用的不是我在调的网卡这种迷惑行为。4.4 实测量化没有示波器也能判断问题层当板子ping不通且PHY寄存器显示link已经up这时候问题集中在MAC驱动和net层。有两种低成本验证工具一是tftp一个大文件200MB以上统计传输速度如果速度远远低于协商速率说明可能有重传/丢包二是直接打开U-Boot编译时的CONFIG_DEBUG或CONFIG_NET_DEBUG看收发统计。还有一种更狠的验证方法在MAC驱动send回调里打印每个包的length和描述符状态字对照对端tcpdump抓包。如果描述符状态显示已经写完且回读无误但对端什么都没收到那问题基本锁定在MAC到PHY的接口RGMII时序、时钟、引脚mux配置。5. 高频问题与排查经验速查5.1 ping不通的九个检查点把ping不通按现象拆可以排列组合成一个速查清单现象优先排查ping对端IP不通但ping自己通检查ipaddr、netmask、ARP是否解析成功ping别人不通但PHY link up检查MAC地址、vlan配置、对端防火墙大包不通小包通检查MTU、巨型帧支持、RGMII时序第一次通第二次不通检查ARP缓存是否过期、下一条是否被net_loop超时清掉一直超时net_loop卡住检查phy_startup是否成功link是否被反复检测网口灯亮但ping不通检查DMA描述符、MAC中断状态寄存器只有tftp能通ping不通检查ICMP处理是否被裁剪CONFIG_CMD_PING板子冷启动不通热启动通检查PHY复位时序、供电稳定性按这个顺序排查80%的问题能在十分钟内定位。5.2 PHY id读不出来mdio read返回全F或者phy_connect()报PHY not found。排查顺序检查PHY芯片供电是否正常很多PHY有独立的1.0V/1.8V供电电压不对会完全无应答。检查PHY复位引脚。复位引脚一直拉低PHY永远处于复位状态自然读不到。U-Boot设备树里的reset-gpios配了没有reset-delay-us够不够我见过复位释放后只等100us就访问PHY结果失败的一般至少等10ms才稳。检查MDC/MDIO引脚mux。很多SoC的MDIO引脚是复用的默认功能不是MDIO必须在设备树或board代码里设置为GPIO/MDIO功能。检查MDIO总线上的上拉电阻。MDIO规范要求上拉如果板子漏了读ID偶尔能读通多读几次就全F这类问题很隐蔽。最后看一眼PHY的ADDR引脚。如果PHY地址由引脚决定硬件上ADDR0浮空或下拉那地址可能不是你预期的那个。5.3 RGMII时序问题千兆掉包的最大元凶换一块新板子或新PHY最常见的疑难杂症就是百兆正常千兆启动后频繁丢包或者干脆link up后网卡完全不通。RGMII接口的时钟延迟配置不对是主因。RGMII标准里MAC和PHY在各自时钟边沿附近采数据但PCB走线会引入延迟导致时序不满足。解决方法是选择在哪一侧加延迟设备树phy-mode的选项直接对应这个选择rgmii不加延迟适用于MAC内部已经加延迟或者走线非常短的板子rgmii-idPHY内部加TX和RX两个延迟rgmii-txid只加TX延迟rgmii-rxid只加RX延迟改phy-mode不用改代码编译一次就能测。我建议调试时先试rgmii-id如果还有问题再逐个试rgmii-txid和rgmii-rxid。还有一种情况是U-Boot配置对了但Linux下又不同因为Linux可能由phy驱动覆盖了延迟配置这类问题比U-Boot本身更头疼。5.4 TFTP超时的真正原因TFTP看起来协议简单实际超时原因多得很serverip设错或者根本不通表现为连续超时后报T FTP errorUDP回包被防火墙吃掉Linux的tftp-hpa默认端口69但回包来自随机端口Windows防火墙经常拦U-Boot侧看起来就是超时对端tftp服务没启动或者目录权限不对表现为能发出WRQ但一直等不到ACKU-Boot侧缓冲区分配失败大文件下载到特定地址触发内存覆盖导致收包异常优先做的是在服务器端开tcpdump看U-Boot的请求到没到。没到就是网络不通到了不答应就是TFTP服务或防火墙问题。5.5 MAC地址全Fethaddr全F会让ARP直接失效。原因常见几种CONFIG_NET_RANDOM_ETHADDR没开板上eFuse没有烧录MAC导致读取失败驱动脚本从EEPROM读MAC的时序不对读出来全F环境变量没保存下次启动又空处理方式确认硬件是否有烧录MAC的eFuse或EEPROM临时用setenv ethaddr设置一个有效MAC产品量产时必须解决eFuse/EEPROM读MAC的问题否则每一台板子的MAC都一样网络直接崩。6. 把U-Boot网络调顺的几个实践心得U-Boot的网络栈是嵌入式开发里少有的麻雀虽小五脏俱全的精简系统。我调过不少板子之后最大的体会是先在U-Boot里把网络打通后面Linux的网络问题基本都能少一半。因为U-Boot的网络链路更短变量更少排查起来比Linux容易得多。反过来如果你在U-Boot里都调不通贸然去调Linux大概率会被各种驱动的自动协商策略绕晕。调试顺序我建议固定成一条流水线先确认PHY能被MDIO访问到再确认PHY link up并读出协商速率接着确认MAC能发包收包最后才去net层查协议问题。每一个环节都有明确的证据PHY的寄存器值、DMA描述符状态、对端tcpdump抓包、U-Boot日志。不要猜不要跳步按顺序验证。最后说一个技巧给板子写一份网络自检脚本把mdio list、mdio read关键寄存器、mii info、ping、tftp这些命令打包成一个U-Boot脚本每次拿到新板子先跑一遍输出保存下来。移植过程中任何一次改动后对比一下这份基线能非常快地发现是不是什么东西悄悄变了。我自己的板子公司后来把所有新板卡的验收都集成进了这个自检流程省下的排查时间比当初写脚本花掉的时间多十倍不止。