
1. 从一次网口不亮说起U-Boot网络子系统的整体骨架板子上电串口打印一路跑到U-Boot命令行ping一下网关结果返回ping failed; host 192.168.1.1 is not alive。这种场景做过嵌入式的人基本都遇到过。网口灯不亮、PHY识别不到、MDIO读写超时、MAC地址读出来全是FF问题可能出在硬件、设备树、驱动、配置任意一环。U-Boot的网络子系统看起来只是几条命令实际上从net核心层到mac控制器驱动再到phy芯片驱动是一条完整的调用链。把这条链梳理清楚排错效率能提升一个量级。U-Boot的网络栈和Linux内核的网络栈设计思路不同。内核追求通用性和性能分层抽象做得很重U-Boot追求的是能尽快把内核拉起来所以网络部分做得精简但该有的层次一个不少。整体上可以分成四层最上面是命令行接口层也就是ping、tftp、dhcp、nfs这些命令往下是网络协议层处理ARP、IP、ICMP、UDP、TCP这些协议再往下是网络设备抽象层也就是struct eth_device和struct udevice这套东西最底下是具体的MAC控制器驱动和PHY驱动。理解这个分层的关键在于搞清楚数据包从命令到硬件的完整路径。当你敲下ping 192.168.1.1U-Boot会构造一个ICMP echo请求包经过IP层封装、ARP解析目标MAC地址然后调用eth_send把数据帧交给网络设备层。网络设备层找到对应的eth_device调用它的send回调这个回调就是MAC控制器驱动注册进去的函数。MAC驱动把数据帧写入硬件发送描述符触发发送PHY负责把电信号送出去。接收方向反过来PHY收到信号MAC产生接收中断或轮询到接收描述符驱动把数据交给上层协议栈处理。这条链路里net层负责协议和命令mac层负责控制器寄存器操作和DMA描述符管理phy层负责通过MDIO总线配置PHY芯片的工作模式、速率、双工等参数。三者通过标准的接口结构体解耦这也是为什么同一套U-Boot代码能支持不同厂商的MAC和PHY组合。提示U-Boot的网络初始化不是在上电时自动完成的而是在第一次执行网络命令时才触发eth_initialize。这意味着如果你在board_init_r阶段就想去访问网络是拿不到可用设备的。关键词里的net、mac、phy、MDIO正好对应了这条链上的四个核心概念。net是协议和命令的入口mac是数据帧的搬运工phy是物理信号的翻译官MDIO是CPU和PHY之间的管理通道。把这四个东西的关系理清楚U-Boot网络问题基本就能定位到具体环节了。2. net层到底管了什么命令、协议与设备注册的三角关系2.1 网络命令的注册与执行路径U-Boot的命令系统基于U_BOOT_CMD宏注册。以ping命令为例它在cmd/net.c里通过U_BOOT_CMD(ping, 2, 1, do_ping, ...)注册。do_ping函数做的事情很直接解析参数、调用NetLoop(PING)、根据返回值打印结果。NetLoop是整个网络操作的核心调度函数它负责设置协议处理函数、启动超时定时器、循环调用eth_rx收包、处理状态机。NetLoop的工作机制值得展开说。它内部维护一个NetRxPackets数组作为接收缓冲区通过net_set_timeout_handler设置超时回调。每次循环先检查是否有待发送的包有就调用eth_send发出去然后调用eth_rx尝试收包收到后交给当前协议的处理函数。整个过程是同步阻塞的没有中断驱动的异步处理这也是U-Boot网络代码比内核简单得多的原因。tftp命令的路径类似但多了一层文件传输协议的状态机。dhcp命令则涉及UDP广播和DHCP协议交互。不管哪个命令最终都会走到eth_send和eth_rx这两个最底层的收发接口。2.2 协议栈的精简实现U-Boot的协议栈只实现了必要的部分。ARP是必须的因为没有ARP就没法把IP地址解析成MAC地址。IP层只做基本的收发和校验不处理分片重组。ICMP实现了echo请求和应答够ping用就行。UDP是tftp和dhcp的基础。TCP在较新的U-Boot版本里有实现但功能有限主要用于fastbootover network这类场景。这种精简带来一个实际影响U-Boot的ping命令只能ping通同一网段的设备跨网段需要网关支持而U-Boot默认不处理网关路由。如果你发现能ping通同网段但ping不通外网先检查ipaddr、netmask、gatewayip这三个环境变量是否都设置正确。协议处理函数的注册通过net_set_protocol_handler完成。每个协议模块在初始化时把自己的处理函数挂到全局链表上。收包时NetReceive根据以太网帧类型字段比如0x0806是ARP0x0800是IP分发给对应的处理函数。这个分发逻辑在net/net.c的NetReceive函数里是整个协议栈的入口。2.3 eth_device与udevice的注册机制在较老的U-Boot版本里网络设备用struct eth_device表示通过eth_register注册到全局链表eth_devices。每个eth_device包含send、recv、init、halt等回调函数指针以及MAC地址、设备名、私有数据等字段。MAC驱动在probe阶段填充这些回调并调用eth_register。新版本U-Boot引入了驱动模型Driver Model网络设备用struct udevice表示通过U_BOOT_DRIVER和U_BOOT_DEVICE宏声明。驱动模型的好处是设备树可以直接描述硬件连接关系PHY和MAC的关联通过phy-handle属性自动建立。但这也带来一个常见问题如果设备树里PHY节点写错了驱动probe会失败但错误信息可能只显示eth0 not found不会直接告诉你PHY的问题。eth_initialize函数负责在第一次网络操作时初始化所有注册的网络设备。它会遍历设备链表调用每个设备的init回调然后打印设备信息。如果你看到No ethernet found说明设备注册阶段就出了问题需要检查驱动是否编译进去、设备树节点是否使能。3. mac控制器驱动描述符、DMA与寄存器操作的实际细节3.1 MAC驱动的基本结构一个典型的MAC控制器驱动包含这几个部分probe函数负责从设备树读取寄存器基地址、时钟、复位引脚等资源初始化硬件start函数配置MAC工作模式、使能收发send函数把数据帧写入发送描述符并触发发送recv函数检查接收描述符并取出数据帧stop函数关闭MAC。以常见的DesignWare GMACdwmac为例它的驱动在drivers/net/dwc_eth_qos.c。probe阶段会做这些事情通过dev_read_addr获取寄存器基地址通过clk_get_by_index获取时钟通过reset_get_by_index获取复位控制然后调用eqos_start初始化。eqos_start里会配置DMA模式、设置MAC地址、使能收发通道。MAC地址的读取是个容易出问题的地方。有些板子把MAC地址存在EEPROM里有些存在OTP区域有些直接从设备树读。如果读出来是00:00:00:00:00:00或者FF:FF:FF:FF:FF:FF说明读取逻辑有问题。U-Boot提供了eth_env_get_enetaddr从环境变量读MAC地址的机制如果环境变量里没有才会去调用驱动的read_rom_hwaddr回调。3.2 DMA描述符环的管理MAC控制器通过DMA描述符环和CPU交互数据。发送方向驱动维护一个发送描述符数组每个描述符指向一个数据缓冲区包含状态位和控制位。发送时驱动把数据帧拷贝到缓冲区设置描述符的OWN位表示归硬件所有然后写发送轮询寄存器通知硬件。硬件发送完成后清除OWN位驱动回收描述符。接收方向类似驱动预先分配好接收缓冲区和描述符设置OWN位给硬件。硬件收到帧后写入缓冲区清除OWN位并产生中断或设置状态位。驱动轮询到描述符归CPU所有后把数据交给上层。这里有个实际经验描述符环的大小和对齐要求因控制器而异。有些控制器要求描述符按特定字节对齐如果对齐不对DMA会静默失败表现为发送无反应或接收丢包。在移植新MAC驱动时先确认描述符的对齐要求和缓冲区大小限制。3.3 发送与接收的完整流程发送流程上层调用eth_send网络设备层找到对应的udevice调用驱动的send回调。驱动检查发送描述符是否有空闲有就把数据拷贝进去设置描述符状态写硬件寄存器触发发送。然后等待发送完成或直接返回取决于驱动实现。U-Boot的发送通常是同步等待的因为网络命令对实时性要求不高。接收流程eth_rx被NetLoop循环调用。驱动检查接收描述符的OWN位如果归CPU所有说明有数据到达。驱动从缓冲区取出数据帧检查长度和状态位然后返回给上层。上层根据以太网帧类型分发给ARP或IP处理函数。这里有个容易忽略的点U-Boot的eth_rx每次只取一个包。如果硬件接收队列里积压了多个包需要多次调用才能全部取出。NetLoop的循环频率决定了收包速度如果循环太慢接收缓冲区可能溢出。在调试丢包问题时可以在eth_rx里加打印看每次调用是否都能取到包。4. phy芯片驱动MDIO总线上的寄存器读写与链路协商4.1 MDIO总线的时序与访问方式MDIOManagement Data Input/Output是CPU和PHY之间的管理通道通常和MDCManagement Data Clock配合使用。MDIO是双向数据线MDC是时钟线。CPU通过MDIO发送读写命令和地址PHY响应并返回数据。标准MDIO帧格式包含32位前导码、2位起始码、2位操作码、5位PHY地址、5位寄存器地址、2位转向位和16位数据。U-Boot里MDIO总线的抽象是struct mii_dev通过mdio_alloc和mdio_register注册。MAC驱动在probe阶段通常会注册一个MDIO总线然后PHY驱动通过phy_connect连接到这个总线上。phy_connect会扫描MDIO总线上的PHY地址读取PHY的ID寄存器寄存器2和3匹配已知的PHY驱动。MDIO读写超时是最常见的PHY问题。如果MDIO总线没有正确初始化或者PHY芯片没有供电读写操作会一直等待直到超时。U-Boot的mii命令可以手动读写PHY寄存器是调试PHY问题的利器。mii read addr reg读寄存器mii write addr reg val写寄存器。4.2 PHY ID的读取与驱动匹配PHY芯片有两个ID寄存器PHY Identifier 1寄存器2和PHY Identifier 2寄存器3。这两个寄存器组合起来形成一个32位的ID高16位是OUI组织唯一标识符低16位是厂商自定义的型号和版本。U-Boot的PHY驱动通过phy_id和phy_id_mask来匹配。比如Realtek RTL8211的ID是0x001cc912驱动里会定义#define PHY_ID_RTL8211F 0x001cc916这样的宏。如果PHY ID读出来是0x0000或0xFFFF说明MDIO通信有问题。可能的原因包括MDIO引脚复用没配置、PHY地址不对、PHY没供电、MDIO总线上拉电阻缺失。先确认硬件原理图看MDIO和MDC连到了哪个GPIO然后在设备树里检查pinctrl配置。PHY地址的确定也有讲究。有些板子通过PHY的PHYAD引脚上下拉来设置地址有些通过设备树里的reg属性指定。如果设备树里写的地址和硬件实际地址不一致phy_connect会找不到PHY。用mii info命令可以扫描MDIO总线上所有地址看哪个地址有响应。4.3 链路协商与速率双工配置PHY和链路对端比如交换机或路由器之间需要进行自协商确定速率10/100/1000Mbps和双工模式半双工/全双工。自协商通过PHY的寄存器1BMSR和寄存器4ANAR等控制。U-Boot的PHY驱动在phy_startup里会启动自协商然后等待链路建立。等待链路建立的过程可能很慢尤其是千兆PHY。U-Boot默认的等待时间可能不够导致phy_startup返回失败。可以在PHY驱动里增加重试次数或延长等待时间。另外有些PHY需要先软复位再配置复位后要等待足够的时间才能访问寄存器。如果链路协商失败可以尝试强制设置速率和双工模式。通过mii write直接写PHY的寄存器0BMCR来禁用自协商并设置固定速率。但这种方式需要确认对端也配置成相同的固定模式否则会出现半双工/全双工不匹配导致的大量丢包。5. 设备树里的MAC-PHY连接phy-handle与phy-mode的坑5.1 设备树节点的标准写法一个典型的MAC和PHY设备树节点长这样ethernetfe300000 { compatible rockchip,rk3399-gmac; reg 0x0 0xfe300000 0x0 0x10000; clocks cru SCLK_MAC; clock-names stmmaceth; phy-mode rgmii; phy-handle phy0; mdio { #address-cells 1; #size-cells 0; phy0: ethernet-phy0 { reg 0; }; }; };phy-mode指定MAC和PHY之间的接口类型常见的有rgmii、rmii、mii、sgmii等。phy-handle指向MDIO总线下的PHY节点。reg属性指定PHY地址。phy-mode写错是高频问题。RGMII接口有rgmii、rgmii-id、rgmii-txid、rgmii-rxid几种变体区别在于TX和RX方向是否添加内部延迟。如果延迟配置不对会出现链路能建立但ping不通或者丢包严重。具体用哪种模式取决于PHY和MAC之间的PCB走线长度和PHY芯片的默认延迟设置。5.2 phy-mode不匹配的典型症状rgmii和rgmii-id的区别在于后者会在TX和RX方向都添加约2ns的内部延迟。如果PCB走线很短不需要额外延迟用rgmii-id会导致采样点偏移表现为大量CRC错误和丢包。反过来如果走线较长需要延迟补偿用rgmii会导致接收数据不稳定。判断方法用mii read读取PHY的扩展状态寄存器看是否有CRC错误计数。如果有大量错误尝试切换phy-mode。另外有些PHY芯片可以通过寄存器配置内部延迟这时候设备树里用rgmii延迟在PHY驱动里配置。5.3 MDIO总线注册失败的排查MDIO总线注册失败通常表现为phy_connect返回NULL或者mii命令找不到总线。排查步骤先确认设备树里MDIO节点是否存在且使能然后检查MAC驱动的probe函数是否调用了mdio_alloc和mdio_register最后用mdio list命令看总线是否注册成功。如果MDIO总线注册了但PHY扫描不到检查PHY的供电和复位。有些板子的PHY复位引脚由GPIO控制需要在设备树里配置reset-gpios并在驱动里正确释放复位。如果复位引脚一直拉着PHY不会响应MDIO。6. 实战排错链路从ping失败到定位PHY寄存器6.1 第一步确认网络设备是否注册串口输入ethaddr看MAC地址是否设置输入ethact看当前活动设备。如果ethact为空说明没有网络设备注册成功。这时候需要检查U-Boot配置里是否使能了对应的MAC驱动设备树是否传递给了U-Boot。用dm tree命令可以查看驱动模型树找到ethernet节点看是否probe成功。如果显示[]表示probe成功[-]表示失败。probe失败通常是时钟、复位、寄存器地址等资源获取失败。6.2 第二步用mii命令直接读写PHY如果网络设备注册了但ping不通先用mii info扫描PHY。正常应该看到类似这样的输出PHY 0x00: OUI 0x001CC, Model 0x12, Rev 0x06, 1000baseT, FDX如果没有任何输出说明MDIO通信失败。用mii read 0 2读PHY ID寄存器1如果返回0xFFFF或0x0000确认MDIO引脚配置和PHY供电。如果PHY能识别但链路不通用mii read 0 1读BMSR寄存器看bit 2Link Status是否置位。如果链路状态为0检查网线和对端设备。如果链路状态为1但ping不通检查phy-mode和速率配置。6.3 第三步抓包与寄存器对比U-Boot没有tcpdump但可以在eth_send和eth_rx里加打印看数据帧是否真的发出去了。发送方向打印目标MAC地址和帧长度接收方向打印收到的帧类型和源MAC。如果发送有打印但接收没有说明数据帧发出去了但对端没响应可能是链路质量问题。对比PHY寄存器是终极手段。用mii dump 0可以打印PHY所有标准寄存器的值。重点关注寄存器0BMCR、寄存器1BMSR、寄存器4ANAR、寄存器5ANLPAR。如果自协商完成但速率不对检查ANAR和ANLPAR的协商结果。7. 移植新PHY驱动时容易忽略的细节7.1 PHY驱动的注册与匹配U-Boot的PHY驱动通过U_BOOT_PHY_DRIVER宏注册或者通过phy_driver结构体数组注册。驱动里定义phy_id和phy_id_maskphy_connect时用(phy_id mask) (driver-phy_id mask)来匹配。如果mask设置不对可能匹配到错误的驱动。新PHY芯片如果没有现成驱动可以先用通用PHY驱动genphy跑通基本功能。通用驱动支持标准的自协商和链路检测但不支持厂商特有的功能。如果通用驱动能跑通说明MDIO和基本配置没问题再基于通用驱动添加厂商特定代码。7.2 复位和时钟的时序要求很多PHY芯片对上电复位时序有要求。比如复位引脚需要保持低电平至少10ms释放后需要等待50ms才能访问寄存器。如果U-Boot的复位驱动没有满足这个时序PHY可能处于未初始化状态。时钟方面有些PHY需要外部提供25MHz或50MHz参考时钟。如果时钟没使能或频率不对PHY不会工作。在设备树里检查clocks和clock-names属性确认时钟驱动正确配置。7.3 低功耗模式与唤醒部分PHY芯片默认进入低功耗模式需要写特定寄存器才能唤醒。如果MDIO能读到ID但链路始终不通检查PHY的电源管理寄存器。有些PHY的BMCR寄存器bit 11Power Down默认置位需要清除才能正常工作。8. 几个能直接抄的调试命令与配置片段8.1 常用命令速查命令作用示例mii info扫描MDIO总线上的PHYmii infomii read读PHY寄存器mii read 0 1mii write写PHY寄存器mii write 0 0 0x1140mii dump打印PHY所有标准寄存器mii dump 0mdio list列出MDIO总线mdio listethaddr查看MAC地址ethaddrethact查看当前活动网络设备ethactdm tree查看驱动模型树dm tree8.2 设备树配置模板gmac { phy-mode rgmii-id; phy-handle phy0; clock_in_out input; snps,reset-gpio gpio3 15 GPIO_ACTIVE_LOW; snps,reset-active-low; snps,reset-delays-us 0 10000 50000; status okay; mdio { compatible snps,dwmac-mdio; #address-cells 1; #size-cells 0; phy0: ethernet-phy0 { reg 0; phy-is-integrated; }; }; };snps,reset-delays-us三个值分别表示复位前延迟、复位保持时间、复位后延迟单位微秒。这个配置对很多PHY都适用可以根据PHY手册调整。8.3 环境变量配置setenv ipaddr 192.168.1.100 setenv netmask 255.255.255.0 setenv gatewayip 192.168.1.1 setenv serverip 192.168.1.10 setenv ethaddr 00:11:22:33:44:55 saveenvethaddr如果板子上没有EEPROM存储可以在环境变量里手动设置。注意有些U-Boot配置会检查ethaddr是否合法如果格式不对会拒绝设置。9. 个人在实际调试中积累的几条经验第一条先确认硬件再调软件。网口不亮先量PHY供电和复位引脚MDIO读不到先看上拉电阻和走线。我遇到过好几次是硬件焊接问题软件调了半天白费功夫。第二条mii dump的输出要会看。寄存器1的bit 2是链路状态bit 5是自协商完成。如果自协商完成但链路状态为0说明对端没发链路脉冲检查网线和对端设备。如果自协商没完成检查ANAR和ANLPAR的协商能力位。第三条phy-mode的延迟配置宁可用rgmii-id先试。大部分PCB走线都需要一定延迟补偿rgmii-id能覆盖多数场景。如果出现大量丢包再尝试去掉延迟。第四条U-Boot的网络初始化时机很关键。有些板子在board_init_r阶段就调用了网络命令但此时PHY还没复位完成。可以在网络命令前加个延时或者把PHY复位放到更早的阶段。第五条不同U-Boot版本的网络子系统差异很大。2016.05之前的版本用eth_device之后的版本逐步迁移到驱动模型。移植代码时先确认版本别把老版本的驱动直接往新版本上套。第六条MDIO总线的时钟频率不能太高。标准MDIO最高2.5MHz有些MAC控制器可以分频到更低。如果MDIO读写不稳定尝试降低MDC频率。在设备树里通常有clock-frequency属性可以配置。第七条PHY的LED配置也能反映链路状态。如果PHY的Link LED不亮说明物理链路没建立这时候调软件没用。先确认网线、对端设备、PHY供电都正常再看软件配置。