news 2026/10/3 19:49:29

U-Boot网络子系统深度解析:从net、mac到phy的调试与移植实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
U-Boot网络子系统深度解析:从net、mac到phy的调试与移植实战

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.1,U-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是ARP,0x0800是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 GMAC(dwmac)为例,它的驱动在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总线的时序与访问方式

MDIO(Management Data Input/Output)是CPU和PHY之间的管理通道,通常和MDC(Management 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的寄存器1(BMSR)和寄存器4(ANAR)等控制。U-Boot的PHY驱动在phy_startup里会启动自协商,然后等待链路建立。

等待链路建立的过程可能很慢,尤其是千兆PHY。U-Boot默认的等待时间可能不够,导致phy_startup返回失败。可以在PHY驱动里增加重试次数或延长等待时间。另外,有些PHY需要先软复位再配置,复位后要等待足够的时间才能访问寄存器。

如果链路协商失败,可以尝试强制设置速率和双工模式。通过mii write直接写PHY的寄存器0(BMCR)来禁用自协商并设置固定速率。但这种方式需要确认对端也配置成相同的固定模式,否则会出现半双工/全双工不匹配导致的大量丢包。

5. 设备树里的MAC-PHY连接:phy-handle与phy-mode的坑

5.1 设备树节点的标准写法

一个典型的MAC和PHY设备树节点长这样:

ethernet@fe300000 { 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-phy@0 { 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 2(Link Status)是否置位。如果链路状态为0,检查网线和对端设备。如果链路状态为1但ping不通,检查phy-mode和速率配置。

6.3 第三步:抓包与寄存器对比

U-Boot没有tcpdump,但可以在eth_send和eth_rx里加打印,看数据帧是否真的发出去了。发送方向,打印目标MAC地址和帧长度;接收方向,打印收到的帧类型和源MAC。如果发送有打印但接收没有,说明数据帧发出去了但对端没响应,可能是链路质量问题。

对比PHY寄存器是终极手段。用mii dump 0可以打印PHY所有标准寄存器的值。重点关注寄存器0(BMCR)、寄存器1(BMSR)、寄存器4(ANAR)、寄存器5(ANLPAR)。如果自协商完成但速率不对,检查ANAR和ANLPAR的协商结果。

7. 移植新PHY驱动时容易忽略的细节

7.1 PHY驱动的注册与匹配

U-Boot的PHY驱动通过U_BOOT_PHY_DRIVER宏注册,或者通过phy_driver结构体数组注册。驱动里定义phy_id和phy_id_mask,phy_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 11(Power Down)默认置位,需要清除才能正常工作。

8. 几个能直接抄的调试命令与配置片段

8.1 常用命令速查

命令作用示例
mii info扫描MDIO总线上的PHYmii info
mii read读PHY寄存器mii read 0 1
mii write写PHY寄存器mii write 0 0 0x1140
mii dump打印PHY所有标准寄存器mii dump 0
mdio list列出MDIO总线mdio list
ethaddr查看MAC地址ethaddr
ethact查看当前活动网络设备ethact
dm tree查看驱动模型树dm tree

8.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-phy@0 { 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 saveenv

ethaddr如果板子上没有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供电都正常,再看软件配置。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 19:34:57

OpenClaw 动态 - 2026-W11:ClawHub Skills 与 gateway 的 PR 实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 19:28:46

大模型——基于Spring AI服务,开发MCP服务并接入TaoToken统一通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 19:24:56

备孕可以养宠物吗?

邵阳不少小夫妻家里都养着猫或者狗&#xff0c;一开始备孕&#xff0c;家里长辈就反复说“养猫养狗会导致胎儿畸形&#xff0c;必须把宠物送走”&#xff0c;很多人夹在舍不得宠物和担心宝宝健康之间两难&#xff0c;甚至因为养宠物的事和家人闹矛盾&#xff0c;邵阳汇恩生殖健…

作者头像 李华