尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

U-Boot网络协议栈三剑客:net层、MAC驱动与PHY管理详解

U-Boot网络协议栈三剑客:net层、MAC驱动与PHY管理详解 搞过Linux网卡驱动的人第一次打开U-Boot的网络代码大概率会懵一阵。没有NAPI没有中断甚至没有一套完整的协议栈一条tftpboot指令下去数据到底怎么从网线进到内存里我之前在这个问题上绕了不少时间后来把net、mac、phy这层关系一条条拆开才算是真正看懂了这套东西。这篇文章就按这个思路来从U-Boot网络协议层的net框架说起一路往下梳理到MAC控制器驱动再到PHY芯片的协商与管理。如果你正被U-Boot下ping不通、tftpboot超时这类问题折磨或者想快速搭一套自己的网卡驱动这篇应该能帮你节省不少时间。U-Boot的网络子系统说复杂也复杂说简单其实也简单。复杂在于它不像Linux那样有完整的网络栈和统一的驱动模型很多历史包袱和平台差异混在一起简单在于它的核心逻辑就是一个循环收包按协议分发的轮询模型。把这条主链路抓住剩下的都是锦上添花。1. 一条tftpboot命令引发的数据流全景先从最常见的场景入手你在U-Boot命令行敲下tftpboot 0x20000000 zImage按下回车。这个动作背后数据是怎么从TFTP服务器一路流到内存里的1.1 命令行到net_loop的入口命令解析在cmd/net.c里do_tftpb最终会调用net_loop(TFTP)。这个net_loop是U-Boot网络协议层的心脏几乎所有网络命令ping、dhcp、nfs、tftp最终都汇聚到这里。net_loop做的事情本质上是一个大循环不断调用网卡驱动收包收到包之后判断协议类型然后丢给对应的处理回调函数。循环退出的条件取决于协议栈的状态——比如TFTP传输完成了回调函数会把NetState置为NETLOOP_SUCCESS循环跳出命令返回。do_tftpb - net_loop(TFTP) - tftp_start() // 构造TFTP请求指定服务器IP和文件名 - while (net_loop() 内部循环) { net_try_packet() // 调用DM框架收包 按ethertype分发给ARP/IP处理 按IP protocol分发给UDP/TCP处理 按UDP端口分发给TFTP/DHCP/...处理 }这里要特别注意U-Boot的收包是纯轮询的没有中断机制。也就是说网卡收到数据后不会主动通知CPUCPU必须在net_loop循环里反复去读网卡的接收描述符或状态寄存器。这也是U-Boot网络栈和Linux最大的区别。1.2 数据在MAC和PHY层面的走向当U-Boot要发送一个TFTP请求包时协议栈构造好以太网帧目的MAC、源MAC、EtherType、IP头、UDP头、TFTP数据然后把这段内存地址交给MAC驱动。MAC控制器做的事情有这么几件把内存里的数据搬进FIFO或DMA描述符加上前导码和帧校验序列FCS通过MII/RMII/RGMII接口把数据逐比特发给PHY芯片。PHY芯片再做编码比如1000BASE-T的4D-PAM5、线路驱动最后把电信号送上网线。收包方向正好相反。所以一条tftpboot命令实际上横跨了三层层次职责U-Boot里对应的代码net层协议解析、ARP、ICMP、UDP/TFTP状态机net/net.c, net/tftp.c, net/arp.cMAC层以太网控制器驱动、DMA描述符、寄存器操作drivers/net/designware.c, fec_mxc.c 等PHY层物理层协商、链路状态、MDIO总线管理drivers/net/phy/phy.c, 各PHY驱动1.3 为什么说这是一条捷径式设计理解这套分层之后调试的思路就很清晰了ping不通先确认mii info能不能看到PHY能则说明MAC到PHY的接口没问题然后看PHY有没有link up有则说明物理链路没问题再然后把矛头指向协议层检查IP配置、ARP缓存、超时重传。U-Boot这么做有个好处它不需要Linux那种复杂的软中断、NAPI调度、内存管理。嵌入式的U-Boot阶段网络只用来传kernel、传ramdisk、刷flash吞吐量要求不高轮询完全够用。反而因为模型简单出问题时定位链路很短一条while循环跟着走就能找到根因。2. net层的轮询机制没有中断的协议栈如何跑起来net_loop是整个U-Boot网络协议栈的中枢值得单独拆开看。我说它是个大循环但里面有几个关键机制状态机、超时处理、协议分发、ARP缓存。搞懂这四个net层就算是拿下了。2.1 net_loop的状态枚举与退出条件net_loop里有个枚举enum net_loop_state包含四个值NETLOOP_CONTINUE、NETLOOP_RESTART、NETLOOP_SUCCESS、NETLOOP_FAIL。NETLOOP_CONTINUE正常收包处理中循环继续。NETLOOP_RESTART有些协议需要重新发起比如DHCP失败重新广播或者ARP超时重试。NETLOOP_SUCCESS协议完成比如TFTP收到最后一个数据块且ACK成功。NETLOOP_FAIL超时或错误比如TFTP服务器返回错误码。每个协议的回调函数在合适的时机设置这些状态。TFTP的tftp_handler收到数据后会调用tftp_handle_block传输完成就net_set_state(NETLOOP_SUCCESS)。整个过程没有多线程没有信号量全靠全局变量和回调指针驱动简洁到让人有点不适应。2.2 net_try_packet与收包分发链路net_loop每轮循环调用net_try_packet()这个函数最终调到网卡驱动的recv回调拿到一个包后进入net_process_received_packet()。这里有个很多人容易忽略的细节U-Boot检查包长度不是简单的len 64就行还要考虑net_eth_hdr_size。比如802.1Q VLAN tag会让以太网头多4个字节所以解析MAC地址、EtherType、IP头部时全部要用net_eth_hdr配合偏移量去算。如果你在驱动里手动改过包缓冲区的对齐方式很容易在这里踩坑。按EtherType分发的逻辑大致如下0x0806 - ARP处理进入 arp_receive() 0x0800 - IP处理进入 NetReceiveIP() 其他 - 可能丢弃或走自定义处理IP层再按protocol字段分IPPROTO_ICMP进ping_receiveIPPROTO_UDP进NetReceiveUDP。UDP层再按端口号分发TFTP的udp_packet_handler、DHCP的dhcp_handler就这么挂上去的。2.3 超时机制协议状态机的隐形驱动U-Boot网络协议层没有时钟中断驱动超时和重传靠什么实现答案是在循环里查时间戳。每个协议在发起时注册一个超时回调net_set_timeout_handler(time, handler)。net_loop每轮循环调用net_try_packet之后会检查当前时间timer_get_us或者get_timer是否超过了设定的deadline。超时了就执行注册的handler这个handler里可以做重传也可以直接判死。比如ARP请求发出后net_arp_wait会注册一个超时处理如果几百毫秒内没收到ARP应答就报ARP timeout并进入失败状态。这个设计是我觉得U-Boot网络最精妙的部分——用最简单的轮询时间戳实现了一套完整的超时重传状态机代码量和内存开销都小到可以忽略。2.4 ARP缓存与协议依赖关系还有一个高频考点是ARP。在U-Boot里net_arp_cache是一个固定大小的表存储IP到MAC的映射。发送IP包之前协议层先查ARP缓存net_route() - 查ARP表 命中 - 直接用映射的MAC构造以太网头 未命中 - 广播ARP请求挂起当前包的发送等待ARP应答这里有个很经典的坑tftpboot第一次总是卡一下然后才启动传输很多人以为系统死机了。其实那几百毫秒就是ARP请求和应答的等待时间。如果服务器和你不在同一广播域ARP永远不通TFTP自然永远起不来——这时候设置serverip、ipaddr在同一网段是最基本的操作。3. MAC驱动与DM框架从eth_ops到收发描述符搞清楚net层之后再往下就是MAC控制器的驱动。U-Boot较新版本全面切换到Driver ModelDM之后网卡驱动的写法发生了很大变化。老版本那种eth_register手动填充struct eth_device的方式虽然还在但已经边缘化现在主流的做法是用UCLASS_ETH配合ofdata_to_platdata自动绑定设备树节点。3.1 UCLASS_ETH下的驱动骨架一个MAC驱动首先要声明U_BOOT_DRIVER核心是ops里的几个回调static const struct eth_ops dwmac_ops { .start dwmac_start, .send dwmac_send, .recv dwmac_recv, .free_pkt dwmac_free_pkt, .stop dwmac_stop, .write_hwaddr dwmac_write_hwaddr, };start初始化MAC控制器、配置DMA、申请描述符、启动收发。send把要发送的数据包交给DMA触发发送。recv检查描述符是否有Received包有则返回数据指针和长度。free_pkt释放接收缓冲让DMA可以继续往这个描述符写数据。stop关掉DMA和MAC。write_hwaddr写MAC地址到控制器寄存器。DM框架的好处是设备与驱动绑定、资源管理统一。比如eth_init不再需要遍历一张手动维护的设备链表而是通过uclass_get_device(UCLASS_ETH, seq, dev)来找设备。设备树里ethernet...节点就自动成为网络设备。3.2 发送路径的典型实现以DesignWare GMAC写法的dwmac_send为例数据发送没有Linux里skb那种复杂管理内存就是一块简单的DMA缓冲区。核心逻辑大致是这样1. 把要发送的数据长度和指针填充到发送描述符TDES 2. 设置TDES0的OWN bit 1表示描述符归DMA管 3. 写DMAC_TXPOLL寄存器触发DMA轮询 4. 等待OWN bit清零轮询等待发送完成 5. 返回成功有个细节由于U-Boot的发送是同步的send回调里通常会死等发送完成。如果是千兆以太网一个1500字节的包在GMII接口上传输也就几微秒等这几十微秒完全可接受。但如果你的驱动在某个平台上发送一直不完成先别急着看寄存器查查时钟配置——MAC主频不对DMA轮询根本跑不起来。3.3 接收路径与cache一致性接收路径稍微复杂。DMA把收到的数据写进接收描述符指向的缓冲区然后清掉OWN bit。CPU要读这部分数据在带cache的ARM平台上就得特别小心rx_complete (rdes0 DESC_OWN) ? 0 : 1; if (rx_complete) { cache invalidate_range(rx_buffer, len); // 关键先失效cache再读数据 net_process_received_packet(rx_buffer, len); cache clean_range(rx_buffer, len); // 数据被修改后要写回 udevice 的 recv 返回随后驱动清OWN }很多第一次移植网卡驱动的人ping有时通有时不通或者收到的kernel镜像CRC不对多数就是cache一致性没处理。U-Boot的flush_cache/invalidate_dcache_range按需调用就行但顺序绝对不能乱读数据前invalidate写数据后clean然后再把OWN置1。3.4 设备树与MAC地址的获取顺序MAC驱动probe时还有一个容易被忽略的流程MAC地址从哪来。U-Boot的优先级大致是环境变量ethaddr设备树节点的local-mac-address或mac-address属性驱动从SoC的fuse/efuse区域读取板厂一般烧录在这里都没有则用CONFIG_RANDOM_MACADDR随机生成这个顺序在U-Boot的eth_env_get_enetaddr和dm_eth_write_hwaddr之间有套默认逻辑。如果你换了新板子ethaddr环境变量没设网卡又没烧录MAC初始化时会打一行警告然后随机生成一个。这种情况在局域网里多台板子同时启动时容易出问题——MAC地址冲突ARP表会乱。所以我给的建议是板子出厂环境变量里写死ethaddr或者在设备树里固定local-mac-address。不要让U-Boot每次启动随机生成否则后面接NFS或做批量升级时一个广播域里会出现幽灵MAC。4. PHY状态机与MDIO总线link up背后的真相MAC往下一层就是PHY。很多人把U-Boot的PHY管理当成只要读一下状态寄存器就完事实际上U-Boot里PHY是一个带状态机的实体处理着自动协商、link检测、速率/双工切换这些事。这一节重点讲清楚PHY框架和调试手段。4.1 MDIO总线与PHY设备的发现MDIO也叫MII管理接口是CPU访问PHY寄存器的通道一般两根线MDC时钟、MDIO数据。U-Boot把MDIO总线抽象为struct mii_dev核心操作是struct mii_dev { int (*read)(struct mii_dev *bus, int addr, int devad, int reg); int (*write)(struct mii_dev *bus, int addr, int devad, int reg, u16 val); ... };MAC驱动的probe阶段会注册一条MDIO总线比如mdiobus_register。然后phy_connect会在总线上扫描指定地址找PHY。mii info命令能把总线上所有PHY地址和厂商ID扫出来这个命令在调板阶段就是救命的。常见PHY的厂商IDOUI有这么几个PHY芯片常见地址厂商ID备注Realtek RTL8211F0,1,40x001cc9很常见RGMIIMarvell 88E15120,1,40x000141支持RGMII/SGMIIMicrel KSZ90310,40x002214注意内部delay配置Broadcom BCM542100,1,20x00356e常见于服务器板4.2 phy_connect、phy_startup与时序phy_connect的核心参数是接口模式phy_interface_t。这个参数千万不能随便填它决定了MAC和PHY之间是MII、RMII、RGMII还是SGMII以及RGMII要不要内部加delay。以RGMII为例接口模式有四种变体PHY_INTERFACE_MODE_RGMII // 外部加delayPHY/MAC均不加 PHY_INTERFACE_MODE_RGMII_ID // PHY内部加TX和RX delay PHY_INTERFACE_MODE_RGMII_TXID // 只加TX delay PHY_INTERFACE_MODE_RGMII_RXID // 只加RX delay为什么delay这么讲究因为RGMII标准里数据是在时钟上升沿和下降沿双沿采样的但PCB走线长度不同会导致时钟和数据的相位偏差。如果PHY和MAC之间没人加delay高速信号千兆125MHz时钟很容易采样错位。现象往往就是吉比特速率下ping不通百兆反而正常。所以移植时先从phy mode rgmii-id这类带delay的模式试起再根据实际波形调整是最稳妥的路径。phy_startup会启动PHY状态机核心是genphy_update_link和自动协商genphy_parse_link。U-Boot的PHY状态机比Linux精简主要包括状态含义退出条件PHY_STATE_ANEG启动自动协商向ANAR写入advertise能力发起协商PHY_STATE_ANEG_WAIT等待协商完成读BMSR的ANEG完成位或超时PHY_STATE_ANEG_DONE协商完成解析link partner能力PHY_STATE_LINK_UP链路建立link状态被拉低则回到ANEG4.3 PHY寄存器速读调试PHY绕不开寄存器。最常用的几个Reg 0BMCR复位、速度、duplex、auto-neg开关。0x8000是soft reset写下去PHY会重启协商。Reg 1BMSRlink状态bit2、协商完成bit5、能力集。Reg 2/3PHY ID厂商识别。Reg 4ANAR本端通告的能力。Reg 5ANLPAR对端通告的能力。我调板时最常用的命令组合是mii device // 查看当前MDIO总线 mii info // 扫描总线上的PHY mdio read 0 0 // 读PHY地址0的reg 0 mdio read 0 1 // 看link状态和协商状态mii info扫不到PHY那物理层问题基本跑不了检查顺序PHY复位脚是不是被拉死了、供电电压对不对、MDIO上拉电阻焊没焊、MDC时钟有没有输出。不要一上来就怀疑代码PHY扫不到九成是硬件或设备树配置问题。4.4 PHY驱动匹配与download固件U-Boot里PHY驱动靠phy_drivers数组匹配。常见的PHY driver在drivers/net/phy/下每个驱动文件会注册U_BOOT_PHY_DRIVER或静态挂到phy_drivers链表。匹配依据就是前面说的厂商ID。有个坑值得提很多PHY芯片的driver没写全跑到一半会掉到generic phy。generic phy能完成基本的协商和link up但可能读不到正确的速率/双工能力。比如某些Marvell PHY需要特定寄存器配置才能跑上千兆如果被generic phy接管默认只能协商到百兆。这时候不要慌先在phy_connect之后、phy_startup之前手动往PHY的配置寄存器写值验证带宽上来了再考虑写一个完整driver。5. 速通后的实战排查从mii info到tftpboot全链路梳理完net、mac、phy三个层面之后最大的价值在于排查问题时有章法。这一节给出一套我调板时反复使用的排查链路照着走大部分网络问题能在十分钟内定位。5.1 第一轮物理层和MAC层体检拿到一块新板子先别急着敲tftpboot。按这个顺序来1. mii info - 能列出PHY - 继续 - 看不到PHY - 查硬件复位、电源、MDIO、设备树PHY地址 2. mdio read phy_addr 1 - bit2 1link up- 继续 - link down - 插网线/换对端/查PHY协商配置 3. 配置IP setenv ipaddr 192.168.1.10 setenv serverip 192.168.1.1 setenv netmask 255.255.255.0 4. ping 192.168.1.1 - 通 - net层和协议栈OK可以tftpboot - 不通 - 抓包看板子有没有发ARP再分析这一轮能过滤掉大部分物理层问题。很多ping不通最后查出来是对端网口是光口、板子却插了电口或者交换机端口被关了这种问题看代码永远找不到答案。5.2 第二轮MAC层专项如果mii info有PHY、link也up了但ping不通MAC层嫌疑最大。怎么缩小范围看U-Boot日志里的这两处MAC初始化有没有报错、DMA的RX描述符有没有溢出。很多MAC驱动把RX描述符数量定义得很小比如4个网络一忙就丢包。ping这种单包流量看不出问题tftpboot传输大文件会疯狂丢包、重传、最终超时。如果ping小包能通但tftpboot大文件失败先去看RX描述符数量和free_pkt逻辑八成有描述符用尽缓冲区没归还DMA。还有一种情况MAC地址寄存器没写对。看板子端发出的帧目的MAC是否对端MAC、源MAC是否是ethaddr。U-Boot下可以直接用mdio看寄存器但更直接的方法是主机上开wireshark抓包。抓到的板子发的帧如果源MAC是00:00:00:00:00:00那就是write_hwaddr根本没被调用或寄存器没生效。5.3 第三轮协议层和超时问题物理层和MAC层都OK剩下就是net层的问题。最典型的有三个ARP请求发出但无响应检查是不是跨网段了或对端防火墙屏蔽了ICMP和ARP。TFTP服务器连不上/超时先确认serverip是不是填对了以及bootfile文件名是否存在。U-Boot默认blksize是512字节有些服务器对大文件支持不好可以用tftpboot命令带blocksize参数调整。U-Boot收到包但协议不处理这个最难查需要开日志。U-Boot的CONFIG_LOG和CONFIG_DEBUG_UART是两套东西网络层调试一般用debug()宏和CONFIG_LOGLEVEL配合。在net/net.c里把NET_DEBUG打开你能看到每个包的EtherType、IP头、UDP端口协议层问题基本一目了然。5.4 常见误区与翻车现场最后聊几个我踩过的、也是群里经常有人问的误区。误区一PHY地址乱猜。设备树里phy-handle指向的节点reg属性就是PHY地址。但如果你的板子PHY实际地址和设备树不一致phy_connect会失败报PHY not found。这时候mii info扫一下真实地址对齐设备树就行。别在驱动代码里硬编码去试6、7、8个地址直接改设备树的reg。误区二mdio命令指定总线。板子上有多个网口时要先mii device选中对应的MDIO总线再执行mii info。很多人两块网卡PHY明明在bus 1上却在bus 0上扫自然扫不到。误区三环境变量污染。ipaddr、serverip、netmask这些环境变量保存在环境分区里。你改了代码重新烧写U-Boot环境变量可能还是旧的。出现诡异的IP、MAC地址先env default -a然后saveenv再重新设置网络变量。误区四preboot脚本自动执行。有些板子在bootcmd里会自动跑dhcp或tftpboot你手动敲命令时以为没有流量其实网络已被脚本占用。net_loop是单实例的U-Boot不会并发处理网络。遇到明明没人用它却报network is busy去翻bootcmd和preboot。6. 一些值得深挖的进阶方向到这里net层、MAC层、PHY层的主链路已经完整走了一遍。如果你接下来要做网卡驱动移植或者定制网络协议还有几个方向可以继续深入。链路层自定义协议。U-Boot的net_loop支持自定义协议回调你完全可以注册一个自己的UDP端口处理函数实现比如板间通信、固件加密传输。入口在net/net.c里NetReceiveUDP的端口分发处挂一个自己的handler即可。这比在Linux用户态写socket要直接得多而且没有内核模块的调试负担。MAC驱动的性能调优。默认的U-Boot网络性能受限于轮询和单包处理。如果刷flash要追求更高吞吐可以调大DMA描述符数量、开启MAC的ring模式、减少net_process_received_packet中的内存拷贝。很多SoC的U-Boot能跑到八九百Mbps瓶颈往往不在网卡而在协议层的逐包处理。PHY的EEE和Wake-on-LAN支持。如果板子要过功耗测试PHY的EEE节能以太网和MAC的WOL都需要在U-Boot阶段就配置好。这部分寄存器每个PHY都不一样建议直接读对应PHY的数据手册而不是靠猜。SGMII/QSGMII接口。有些交换芯片和CPU之间走SGMII而不是RGMII这时候PHY层配置复杂不少涉及PCS层、SerDes、时钟恢复。如果板子用的是这类链路前面的RGMII经验只能参考得单独研究MAC的SerDes配置。我个人在实际调板中的体会是U-Boot网络这套东西最大的难点从来不是某个寄存器或某个驱动而是你有没有把net、mac、phy三层的边界画清楚。只要每一层都能独立验证剩下的就是逐层递进的排查而已。最后再多说一句调网络问题wireshark永远是最好的朋友。U-Boot端打不出来的包主机端抓一次就全清楚了。
返回列表