4、DHCP协议在U-Boot中的应用:自动获取IP地址,DHCP选项解析与自定义
网络升级,第一步是什么?
你得让板子有个IP地址。手动设?调试阶段还行,产线上几千块板子你一个个敲?不现实。所以DHCP几乎是U-Boot网络部署的标配。
这一章,我们就聊聊DHCP在U-Boot里到底怎么跑,怎么拿到IP,怎么解析那些选项,以及——怎么塞进去我们自己想要的东西。
4.1 U-Boot里的DHCP客户端:比你想象得简单
U-Boot的DHCP客户端实现,说白了就是一个有限状态机。它不像Linux里那么复杂,没有租约续期,没有地址冲突检测。U-Boot的DHCP只做一件事:发Discover,收Offer,发Request,收Ack。拿到IP就完事。
核心代码在 net/dhcp.c 里。我刚开始看的时候也觉得奇怪,怎么这么短?后来想明白了——U-Boot是bootloader,不是操作系统。它不需要长期维护一个网络栈,够用就行。
U-Boot DHCP 基本流程:
- 调用
dhcp()函数,触发状态机 - 广播 DHCPDISCOVER
- 等待 DHCPOFFER(超时可重试)
- 发送 DHCPREQUEST
- 接收 DHCPACK,提取 IP、网关、DNS 等信息
- 设置环境变量
ipaddr、gatewayip、netmask、dnsip
你可能会问:如果服务器不回包怎么办?嗯,U-Boot有重试机制,默认5次,每次超时2秒。我曾经在一个电磁环境很差的产线上遇到过,板子死活拿不到IP。后来发现是网线太长,信号衰减太厉害。换了根短网线,问题解决。这种坑,你不实际踩一次,看代码是看不出来的。
4.2 DHCP选项解析:不只是IP地址
DHCP报文里除了IP地址,还有很多选项。U-Boot会解析哪些?我列个表给你看。
| 选项代码 | 名称 | U-Boot 处理方式 |
|---|---|---|
| 1 | 子网掩码 | 设置 netmask |
| 3 | 路由器(网关) | 设置 gatewayip |
| 6 | DNS 服务器 | 设置 dnsip |
| 12 | 主机名 | 设置 hostname |
| 15 | 域名 | 设置 domainname |
| 42 | NTP 服务器 | 设置 ntpserverip |
| 43 | 厂商特定选项 | 需要自定义解析(重点) |
你看,U-Boot默认只解析了最常用的几个。但实际项目中,我们往往需要更多。比如,我想通过DHCP告诉板子:你的固件服务器IP是多少,你的设备ID是什么。这些信息,标准DHCP选项里没有,就得用厂商特定选项(Option 43)。
我的习惯: 在DHCP服务器上配置Option 43,里面塞一个自定义的JSON字符串或者二进制结构体。U-Boot端解析出来,设置到环境变量里,后续脚本直接引用。这样,不同设备只需要改DHCP服务器配置,不用改U-Boot代码。
4.3 自定义DHCP选项解析:动手改代码
好,理论说完了,我们动手。假设我想解析Option 43,从中提取一个“固件服务器IP”和一个“设备类型”。
首先,找到 net/dhcp.c 里的 dhcp_process_options() 函数。这个函数遍历DHCP报文的选项区,遇到已知选项就处理。我们要做的,就是在这里加一个case。
// 在 dhcp_process_options() 中添加
case 43: /* 厂商特定选项 */
// 假设我们的格式:前4字节是固件服务器IP,后1字节是设备类型
if (len >= 5) {
// 提取IP
u32 fw_server_ip = (ptr[0] << 24) | (ptr[1] << 16) |
(ptr[2] << 8) | ptr[3];
// 设置环境变量
char ip_str[16];
sprintf(ip_str, "%d.%d.%d.%d",
ptr[0], ptr[1], ptr[2], ptr[3]);
env_set("fwserverip", ip_str);
// 提取设备类型
u8 dev_type = ptr[4];
char type_str[4];
sprintf(type_str, "%d", dev_type);
env_set("devtype", type_str);
}
break;
代码不复杂,对吧?但有几个坑,我当年都踩过。
我曾经犯过的错:
- 没有检查
len就读取数据,结果收到一个空Option 43,直接访问越界,板子死机。 - 环境变量名用了大写,但U-Boot脚本里引用时用了小写,死活匹配不上。
- 忘记在
dhcp()函数返回前调用env_apply(),环境变量写进去了但没生效。
嗯,这些都是血泪教训。你改代码的时候,记得加上长度检查,环境变量名统一用小写,改完后跑一遍 saveenv 确认持久化。
4.4 DHCP与网络部署的整合
拿到IP和自定义信息之后,怎么用?我一般写一个U-Boot脚本,自动完成整个升级流程。
# 在 U-Boot 脚本中
if dhcp; then
echo "Got IP: ${ipaddr}"
echo "FW Server: ${fwserverip}"
echo "Device Type: ${devtype}"
# 根据设备类型选择固件
if test "${devtype}" = "1"; then
setenv fw_file "firmware_type1.bin"
elif test "${devtype}" = "2"; then
setenv fw_file "firmware_type2.bin"
else
echo "Unknown device type!"
exit 1
fi
# 从固件服务器下载
tftp ${loadaddr} ${fwserverip}:${fw_file}
if test $? -eq 0; then
# 烧写固件
sf probe 0
sf erase 0x0 ${filesize}
sf write ${loadaddr} 0x0 ${filesize}
echo "Upgrade done!"
fi
else
echo "DHCP failed, fallback to static IP"
setenv ipaddr 192.168.1.100
setenv gatewayip 192.168.1.1
fi
你看,整个流程就串起来了。DHCP不只是拿个IP,它是整个部署流程的入口。通过自定义选项,你可以把配置下发的权力从U-Boot代码转移到DHCP服务器上。产线上换一种设备?改DHCP服务器配置就行,U-Boot不用重新编译。
4.5 知识体系总览
下面这张图,把DHCP在U-Boot中的角色和流程画清楚了。你对照着看,应该能一目了然。
4.6 调试技巧:抓包是王道
最后,分享一个调试技巧。DHCP出问题时,别光盯着U-Boot日志。在PC上开Wireshark,抓一下网络包,看看服务器到底回了什么。
我遇到过最诡异的一次:U-Boot明明收到了DHCPACK,但 ipaddr 就是不对。抓包一看,服务器回了两个Option 3(网关),U-Boot只取了第一个,而第一个是0.0.0.0。嗯,DHCP服务器配置有问题。这种问题,看代码是看不出来的,必须看报文。
调试命令备忘:
dhcp— 触发DHCP流程,打印详细日志printenv ipaddr gatewayip netmask— 查看获取到的网络参数printenv fwserverip devtype— 查看自定义选项解析结果- 在
dhcp.c中加debug()打印,开启CONFIG_DHCP_DEBUG
好了,DHCP这块就聊到这儿。你回去可以试试:搭一个DHCP服务器,配一个自定义Option 43,然后在U-Boot里把它解析出来。跑通了,你就真正掌握了U-Boot网络部署的第一步。