2. U-Boot网络协议栈基础

说实话,很多人刚开始接触U-Boot网络功能时,都觉得它就是个简单的tftp下载工具。但你要真这么想,后面踩坑是迟早的事。我最早做项目时也这么认为,直到有一次板子在网络启动阶段死活连不上服务器,我才老老实实把协议栈翻了个底朝天。

U-Boot的网络协议栈,说白了就是一个精简版的TCP/IP协议族。它不追求完整,只求够用。毕竟Bootloader的任务就是快速把内核拉下来,没必要搞得太复杂。

2.1 协议栈的整体架构

U-Boot的网络协议栈是分层设计的。从上到下大概是这么个结构:

+----------------------------+
|     应用层协议              |
|  (TFTP, NFS, DHCP, Ping)   |
+----------------------------+
|     传输层协议              |
|  (UDP, 部分ICMP)           |
+----------------------------+
|     网络层协议              |
|  (IP, ARP, ICMP)           |
+----------------------------+
|     链路层                  |
|  (以太网帧封装/解封装)      |
+----------------------------+
|     网络设备驱动层          |
|  (net_device + eth_ops)    |
+----------------------------+

嗯,这里要注意一点:U-Boot没有实现完整的TCP协议。为什么?因为TCP握手太慢了,而且状态机复杂。你想想看,Bootloader启动时间就那么几秒,谁等你三次握手?所以U-Boot只用UDP,简单粗暴。

核心设计思想:U-Boot网络协议栈是单线程、轮询模式的。没有中断,没有多任务。每个网络操作都是同步阻塞的——发一个包,等一个回应,超时就重试。

我个人习惯把这个架构叫做「轻量级协议栈」。它不处理乱序包,不处理分片重组,甚至连校验和都是可选的。但正是这种精简,让它能在几百KB的代码空间里跑起来。

2.2 支持的协议详解

U-Boot支持的协议不多,但个个都是硬通货。我列个表,大家看得清楚些:

协议 用途 U-Boot中的实现文件 备注
ARP IP地址到MAC地址的解析 net/arp.c 只支持请求和应答,不支持免费ARP
IP 网络层数据报封装 net/net.c 不支持分片,不支持选项字段
UDP 无连接传输 net/net.c 校验和可选,默认关闭
ICMP Ping功能 net/ping.c 只处理Echo请求和应答
TFTP 文件传输 net/tftp.c 支持TFTP和TFTP扩展选项
NFS 网络文件系统 net/nfs.c 仅支持NFS v2/v3,只读挂载
DHCP 动态IP获取 net/dhcp.c 兼容BOOTP

2.2.1 ARP协议

ARP在U-Boot里实现得很「朴素」。它维护一个简单的ARP缓存表,但只有一条记录——就是网关的MAC地址。为什么?因为U-Boot通常只跟一个服务器通信,没必要搞个完整的ARP表。

我曾经在一个项目里遇到过ARP问题:板子和服务器在同一个交换机上,但服务器配置了多个VLAN。U-Boot发ARP请求,服务器回了,但U-Boot不认。查了半天,发现是服务器回应的MAC地址是VLAN接口的,不是物理接口的。最后在服务器上加了一条静态ARP条目才搞定。

小技巧:如果U-Boot的ping不通,但ARP能解析到MAC,那问题大概率不在网络层。检查一下你的TFTP服务器配置或者防火墙规则。

2.2.2 IP与UDP

IP层在U-Boot里就是个「搬运工」。它负责把上层数据包封装成IP报文,然后交给网卡驱动发送。接收时也一样,解封装后根据协议类型分发给上层。

UDP层更简单。它不维护连接状态,不保证可靠传输。发出去就不管了,收不到就超时重试。这种设计在Bootloader场景下完全够用——你想想,下载内核失败了,大不了重启再试一次。

2.2.3 TFTP协议

TFTP是U-Boot网络启动的主力协议。它基于UDP,使用固定端口(69)进行连接建立,然后动态协商数据传输端口。

U-Boot的TFTP客户端支持以下特性:

  • 标准的TFTP RRQ/WRQ流程
  • TFTP选项扩展(blksize、tsize、timeout)
  • 自动协商块大小(默认512字节,可扩展到1468字节)
  • 超时重传机制(默认5次重试)
// TFTP下载流程伪代码
tftp_download() {
    1. 发送RRQ请求到服务器端口69
    2. 等待服务器分配数据传输端口
    3. 接收第一个数据块(块号1)
    4. 发送ACK(块号1)
    5. 重复步骤3-4,直到收到小于块大小的数据包
    6. 发送最后一个ACK
    7. 下载完成
}

这里有个坑:U-Boot的TFTP默认块大小是512字节,但你可以通过环境变量tftpblocksize调整。我建议在千兆网络环境下改成1468字节,能明显提升下载速度。

注意:有些TFTP服务器对块大小协商支持不好。如果你改了块大小后下载失败,先恢复成默认值试试。我遇到过一台老旧的TFTP服务器,只认512字节的块大小。

2.2.4 NFS协议

NFS在U-Boot里是个「高级功能」。它允许板子通过网络挂载根文件系统,对于开发阶段特别有用。但说实话,U-Boot的NFS实现比较「脆弱」——只支持NFS v2和v3,而且只能读不能写。

我记得有一次调试内核,想用NFS挂载根文件系统。结果U-Boot报错:NFS: server returned error: 13。查了半天,发现是NFS服务器导出的目录权限设置不对。U-Boot的NFS客户端默认使用root用户ID(0)去访问,如果服务器配置了root_squash,就会拒绝访问。

解决方案:在服务器端关闭root_squash,或者给anonuid和anongid设置合适的值。

2.3 网络设备驱动模型

U-Boot的网络设备驱动模型,我称之为「极简驱动框架」。它不像Linux那样有复杂的设备树和总线模型,而是用了一个非常直接的方式:

  1. 每个网卡对应一个struct eth_device结构体
  2. 驱动实现struct eth_ops中的回调函数
  3. 通过eth_register()注册到全局链表
  4. 通过eth_set_current()选择当前使用的网卡
// eth_device结构体(简化版)
struct eth_device {
    char name[16];              // 设备名称,如"eth0"
    unsigned char enetaddr[6];  // MAC地址
    int state;                  // 设备状态
    
    // 驱动回调函数
    int (*init)(struct eth_device *);
    int (*send)(struct eth_device *, void *packet, int length);
    int (*recv)(struct eth_device *, void *packet, int length);
    void (*halt)(struct eth_device *);
    
    struct eth_device *next;    // 链表指针
};

这个模型的好处是简单。你写一个新网卡的驱动,只需要实现4个函数:init(初始化)、send(发送)、recv(接收)、halt(关闭)。然后调用eth_register()注册进去就行了。

但简单也有简单的代价。这个模型不支持中断,不支持DMA(虽然有些驱动自己实现了),不支持多队列。所有操作都是轮询的——recv函数会一直循环检查硬件寄存器,直到收到数据或超时。

驱动开发要点:

  • init函数要完成硬件复位、MAC地址设置、PHY初始化
  • send函数要把数据从内存拷贝到发送FIFO,然后触发发送
  • recv函数要轮询接收FIFO,有数据就拷贝到缓冲区
  • halt函数要关闭硬件,释放资源

嗯,这里还要提一下PHY驱动。U-Boot的PHY驱动也是独立于MAC驱动的。它通过phy_device结构体管理,支持自动协商、速度/双工模式设置。如果你的板子网络不通,先检查PHY有没有正确初始化——我见过太多因为PHY配置不对导致的问题了。

2.4 数据包处理流程

最后,我画了一张图,展示U-Boot网络协议栈的数据包处理流程。这张图我当年做驱动移植时反复看过很多遍:

U-Boot网络协议栈数据包处理流程 发送路径 接收路径 应用层 (TFTP/NFS/DHCP) 应用层 (TFTP/NFS/DHCP) UDP层 (封装/解封装) UDP层 (封装/解封装) IP层 (路由/校验) IP层 (校验/分派) ARP层 (地址解析) ARP层 (地址解析) 以太网帧封装 以太网帧解封装 网卡驱动 (eth_ops.send) 网卡驱动 (eth_ops.recv) 物理层 (PHY / MII / RMII)

这张图展示了数据包从应用层到物理层的完整旅程。发送时从上往下走,每层加上自己的头部;接收时从下往上走,每层剥掉头部。说白了,就是标准的协议栈分层处理,只不过U-Boot把它简化到了极致。

好了,关于U-Boot网络协议栈的基础知识就讲到这里。这些内容看起来简单,但都是实际调试中绕不开的基础。下一节我们会深入TFTP的细节,看看怎么配置环境变量、怎么优化传输速度。

可可精品资料库