1. U-Boot网络协议栈全景概览:从启动到网络就绪的完整链路

做嵌入式开发的朋友,十有八九都跟U-Boot打过交道。但说实话,很多人对U-Boot网络协议栈的理解,停留在「能ping通就行」的阶段。我早年做一款工业网关时,就因为这个认知吃过亏——板子启动后网络时通时断,查了三天才发现是协议栈初始化顺序出了问题。

这一章,咱们就把U-Boot网络协议栈从启动到就绪的完整链路拆开来看。你想想看,一个嵌入式设备从上电到能通过TFTP下载内核,中间到底经历了什么?

1.1 整体架构:三层模型

U-Boot的网络协议栈,说白了就是三层结构。跟Linux内核的网络栈比,它精简得多,但核心逻辑是一样的。

U-Boot网络协议栈三层架构 应用层 TFTP客户端 | NFS客户端 | Ping | DHCP客户端 | HTTP客户端 传输层 UDP(net/udp.c) | TCP(net/tcp.c,部分支持) 网络层 & 链路层 ARP(net/arp.c) | IP(net/net.c) | ICMP | 以太网驱动 注:U-Boot以UDP为主,TCP支持有限,主要用于HTTP客户端

这张图我建议你存下来。每次调网络问题,先定位是哪个层出的问题,能省不少时间。

1.2 启动链路:从复位到网络就绪

U-Boot启动后,网络协议栈不是一下子就准备好的。它有一条清晰的初始化链路。我把它拆成6个关键步骤:

步骤 阶段 关键函数/操作 说明
1 板级初始化 board_init() 配置GPIO、时钟、复位MAC控制器
2 驱动初始化 eth_initialize() 扫描并注册所有以太网设备
3 PHY探测 phy_init() 检测PHY芯片,协商链路速率
4 MAC上电 eth_open() 启动MAC控制器,准备收发
5 协议栈就绪 net_init() 初始化ARP缓存、UDP端口等
6 网络可用 等待用户命令 可执行ping、tftp等操作

关键点:步骤2和步骤3之间,经常出问题。我遇到过一块板子,PHY芯片的复位时序不对,导致eth_initialize()执行时PHY还没准备好。后来在board_init()里加了50ms延时才解决。

1.3 核心数据结构:一切从这里开始

U-Boot网络协议栈的核心,是几个全局数据结构。你调优的时候,80%的时间都在跟它们打交道。

1.3.1 全局网络状态:net_state

这个变量控制着协议栈的当前状态。说白了,就是一个状态机:

enum net_loop_state {
    NET_LOOP_STOPPED = 0,    // 网络停止
    NET_LOOP_START,          // 开始网络操作
    NET_LOOP_RETRY,          // 重试中
    NET_LOOP_TIMEOUT,        // 超时
    NET_LOOP_SUCCESS,        // 成功
    NET_LOOP_FAIL            // 失败
};

我个人习惯在调试时,把这个状态打印出来。很多网络问题,一看状态就知道卡在哪一步。

1.3.2 网络缓冲区:net_rx_packets[]

U-Boot使用一个环形缓冲区来接收数据包。默认大小是 PKTBUFSRX,通常是4个包。每个包最大 PKTSIZE(1518字节)。

注意:这个环形缓冲区是U-Boot网络性能的瓶颈之一。我曾经在一个高吞吐场景下,因为缓冲区太小导致丢包。调大 PKTBUFSRX 后,问题立刻解决。

1.3.3 ARP缓存:arp_table[]

U-Boot的ARP缓存很小,默认只有几个条目。每次ARP请求都会阻塞当前网络操作。这也是为什么你ping一个不存在的IP时,U-Boot会卡住好几秒。

1.4 数据流:一个TFTP请求的完整旅程

光讲理论没意思。咱们看一个实际例子——U-Boot执行 tftp 0x20000000 kernel.bin 时,数据是怎么流动的:

  1. 应用层发起:do_tftp() 构造TFTP读请求包,调用 net_send_packet()
  2. 传输层封装:UDP头被填充,源端口随机,目标端口69
  3. 网络层封装:IP头被填充,源IP=板子IP,目标IP=服务器IP
  4. 链路层封装:ARP查询目标MAC,填充以太网头
  5. 驱动发送:MAC控制器将数据包通过PHY发送到网线
  6. 接收响应:服务器回包,经过同样的路径反向解析
  7. 数据交付:TFTP客户端将数据写入内存地址0x20000000

小技巧:在 net/net.c 中打开 DEBUG 宏,可以打印每个收发数据包的详细信息。我调试网络问题时,第一步就是开这个宏。

1.5 常见启动网络失败场景

做项目这么多年,我总结了几种最常见的U-Boot网络启动失败场景:

现象 可能原因 排查方法
eth_initialize() 找不到设备 设备树配置错误,或驱动未编译 检查 CONFIG_DM_ETH 和驱动宏
PHY链路协商失败 PHY地址不对,或复位时序问题 用示波器看PHY中断引脚,或强制固定速率
能ping通但tftp超时 MTU不匹配,或防火墙拦截 抓包分析,检查UDP端口是否被阻
网络时通时断 电源噪声,或网线接触不良 检查PHY供电,换网线测试

嗯,这里要特别提一下PHY地址的问题。我记得有一次,硬件工程师把PHY地址改成了0x1e,但驱动里写死的是0x00。结果板子死活检测不到PHY。后来我在 phy_find_by_mask() 里加了扫描逻辑才找到。

1.6 本章小结

U-Boot网络协议栈,说白了就是一套精简但完整的TCP/IP实现。从板级初始化到网络就绪,中间有6个关键步骤。核心数据结构就那么几个——net_state、环形缓冲区、ARP缓存。理解这些,你就掌握了调优的基础。