U-Boot:环境变量

U-Boot 支持通过 环境变量 进行用户配置,这些变量可以通过保存到持久化存储设备(例如闪存)而实现持久化。

  • 环境变量(Environment Variables) 本质是 键值对(key-value),是 U-Boot 中一种轻量级的配置机制,类似于 shell 中的环境变量,但专为嵌入式启动阶段设计。
  • 持久化(persistent): U-Boot 启动时运行在 RAM 中,若不保存,所有修改都会在重启后丢失。

环境变量通过 env set(别名为 setenv)设置,通过 env print(别名为 printenv)打印,并通过 env save(别名为 saveenv)保存到持久化存储中。

  • env set 若不带值,可用于从环境中删除一个变量。
  • 只要尚未执行保存操作,你所操作的始终是内存中的副本 (通常是 gd->env_addr 指向的缓冲区)。
  • 万一包含环境变量的 Flash 区域被意外擦除,U-Boot 会提供一个默认环境(自动回退到编译时内嵌的默认环境)。
  • 保留旧命令(如 setenv)是为了向后兼容。现代 U-Boot 推荐使用统一前缀 env xxx,体现模块化命令设计思想。
  • 在 U-Boot 命令行中输入 help env 可以查看 env 命令详细说明。

部分配置 由环境变量控制,因此通过设置这些变量即可调整 U-Boot 的行为(例如自动启动延时、从 TFTP 自动加载等)。

在量产设备中,应考虑锁定环境变量(通过 CONFIG_ENV_IS_NOWRITE 或硬件写保护),防止恶意篡改启动参数。

基于文本文件定义环境变量

U-Boot 中 基于 文本文件(.env 文件)定义默认环境变量的机制,这是现代 U-Boot(特别是 v2021.10 之后)推荐的、更清晰可维护的默认环境配置方式。

一个开发板的默认环境变量是通过一个采用简单文本格式的 .env 环境文件创建的。该文件的基础文件名由 CONFIG_ENV_SOURCE_FILE 宏定义;若该宏为空,则使用 CONFIG_SYS_BOARD

该文件必须位于开发板对应的目录下,并具有 .env 扩展名

  • 旧的定义环境变量的方式是在 C 源码中(下一节会讲到),但配置 ≠ 代码,环境变量属于"系统策略",应与"实现逻辑"解耦。使用纯文本文件便于版本控制、差异对比、非程序员参与(如测试工程师修改启动参数)。

  • CONFIG_ENV_SOURCE_FILE 是优先选项,允许项目自定义文件名(如 my_custom.env);若未定义,则回退到以板级名称(CONFIG_SYS_BOARD)命名的文件,保证通用性。最终的文件路径为:

    html 复制代码
    board/<vendor>/<board>/<CONFIG_ENV_SOURCE_FILE>.env 
    或者:
    board/<vendor>/<board>/<CONFIG_SYS_BOARD>.env
  • 构建系统(Makefile/Kbuild)会在编译时自动查找该路径下的 .env 文件,并通过脚本(如 tools/env2text.py)将其转换为 C 数组嵌入固件。所以这些版本的 U-Boot 编译会用到 Python。

.env 文件是一个纯文本文件,你可以在其中以 var=value 的形式编写环境变量。支持空行和多行变量。转换脚本会查找那些从第 1 列开始、紧随其后是一个字符串、且该字符串后立即跟一个等号(=)的行。等号前不允许有空格。建议对脚本内容进行缩进,使得只有形如 var= 的部分出现在行首。比如:

bash 复制代码
mmcboot=
	echo Booting from mmc ...;
	run mmcargs;
	if test ${sec_boot} = yes; then
		if run true; then
			run boot_os;
		else
			echo ERR: failed to authenticate;
		fi;

若要向一个变量追加额外文本,可使用 var+=value 语法。该文本会在构建(make)过程中被合并到该变量中,并以单一值的形式提供给 U-Boot。

.env 文件支持 C 风格注释。支持空行和多行变量,并且还可以使用标准的 C 预处理器指令以及来自板级配置的 CONFIG_ 宏定义。

例如,对于 snapper9260 开发板,你需要创建一个名为 board/bluewater/snapper9260.env 的文本文件,其中包含环境变量内容。

bash 复制代码
stdout=serial
#ifdef CONFIG_VIDEO
stdout+=,vidconsole
#endif
bootcmd=
    /* U-Boot script for booting */

    if [ -z ${tftpserverip} ]; then
        echo "Use 'setenv tftpserverip a.b.c.d' to set IP address."
    fi

    usb start; setenv autoload n; bootp;
    tftpboot ${tftpserverip}:
    bootm
failed=
    /* Print a message when boot fails */
    echo CONFIG_SYS_BOARD boot failed - please check your image
    echo Load address is CONFIG_SYS_LOAD_ADDR

对于一组开发板共用的配置,可以使用 #include 指令引入位于 include/env 目录下的公共文件,该文件包含环境变量设置。例如:

c 复制代码
#include <env/ti/mmc.env>

U-Boot 不会自动去除引号 !!!环境变量值本身不需引号,U-Boot 内部按字符串处理

比如在 .config 中定义了配置选项:

bash 复制代码
CONFIG_DEFAULT_DEVICE_TREE="sun7i-a20-pcduino3"

则:

bash 复制代码
fdtfile=CONFIG_DEFAULT_DEVICE_TREE.dtb

会被拼接为:

bash 复制代码
fdtfile="sun7i-a20-pcduino3.dtb"

因为有引号,U-Boot 在加载设备树时会因找不到带引号的文件名而失败!

目前没有通用的方法可以去除引号。

旧式基于 C 语言定义环境变量

传统上,默认环境变量在 include/env_default.h 中创建,并可通过各种 CONFIG 宏定义进行扩展。

  • U-Boot 早期将默认环境变量硬编码为一个 C 字符串数组,位于 include/env_default.h。例如:

    c 复制代码
    #define DEFAULT_ENVIRONMENT \
    "bootdelay=2\0" \
    "baudrate=115200\0" \
    CONFIG_EXTRA_ENV_SETTINGS\
    "\0"

特别地,你可以在板级文件中定义 CONFIG_EXTRA_ENV_SETTINGS 来添加环境变量。

  • CONFIG_EXTRA_ENV_SETTINGS 是一个 宏拼接点,通常在板级头文件(如 include/configs/myboard.h)中定义:

    c 复制代码
    #define CONFIG_EXTRA_ENV_SETTINGS\
    "fdtfile=myboard.dtb\0" \
    "bootcmd=run mmcboot\0"

鼓励板级维护者迁移到基于文本的环境变量方式,因其更易于维护。旧式基于 C 语言缺点是:

  • 配置与代码耦合
  • 字符串拼接易出错(漏 \0、引号不匹配等)。
  • 无法使用注释、条件编译复杂逻辑受限。

"distro-board" 脚本目前仍依赖旧式环境变量,因此请改用 "Standard Boot" 方案。

  • "distro-board" 脚本:指 U-Boot 的 tools/distro_bootcmd.h 机制,用于实现类似 Linux 发行版的通用启动流程(自动探测 SD/eMMC/USB/NFS 等)。该机制内部依赖 CONFIG_EXTRA_ENV_SETTINGS 注入 distro_bootcmd 变量。(目前已淘汰)
  • "Standard Boot":指开发者自行定义 bootcmd 等变量(通常通过 .env 文件),而非依赖发行版兼容模式。

如果同时定义 了基于文本的环境变量文件和旧式 C 语言环境变量,则旧式 C 环境中的变量将覆盖文本文件中设置的同名变量。

环境变量列举

某些设备 配置选项 可通过 环境变量 进行设置。在许多情况下,默认环境中的变量值来源于某个 CONFIG 宏定义------详情请参见 include/env_default.h

autostart:加载镜像后自动启动

若其值为真,通过 xxx-boot 命令加载的镜像将自动启动

  • autostart 是以 1yYtT 开头的字符串,比如 "y"、"yes"、"t"、"true",则为,否则为假。

  • 如果变量不存在,返回 -1。注意,-1 在 C 中表示真,也就是说:如果不定义这个变量,则默认为真

    c 复制代码
    int env_get_yesno(const char *var)
    {
    	char *s = env_get(var);
    
    	if (s == NULL)
    		return -1;
    	return (*s == '1' || *s == 'y' || *s == 'Y' || *s == 't' || *s == 'T') ?
    		1 : 0;
    }
  • xxx-boot是一系列加载镜像并具备启动意图 的命令,它们是:

    • bootelf ------ 从内存中的 ELF 镜像启动
    • bootp ------ 通过 BOOTP/TFTP 协议从网络启动镜像
    • dhcp ------ 通过 DHCP/TFTP 协议从网络启动镜像
    • diskboot ------ 从 IDE 设备启动
    • nboot ------ 从 NAND 设备加载镜像
    • nfs ------ 通过 NFS 协议从网络启动镜像
    • rarpboot ------ 通过 RARP/TFTP 协议从网络启动镜像
    • scsiboot ------ 从 SCSI 设备启动
    • tftpboot ------ 通过 TFTP 协议从网络启动镜像
    • usbboot ------ 从 USB 设备启动

    注意:loadbloadxloady 等纯加载命令 不受 autostart 影响,因为它们仅用于传输数据,无启动语义。

  • 技术原理:在 xxx-boot 命令内部,U-Boot 首先下载镜像到指定位置,然后检查 autostart,若为真,则立即执行 bootm ${addr}

baudrate:波特率

用于设置 UART 的波特率------其默认值为 CONFIG_BAUDRATE(该宏默认为 115200)。

bootdelay:启动延时

c 复制代码
#ifndef CONFIG_BOOTDELAY
#define CONFIG_BOOTDELAY	1						// mx6_common.h
#endif
"bootdelay="	__stringify(CONFIG_BOOTDELAY)		// env_default.h

在自动执行 bootcmd 之前等待的延时(单位:秒)。在此期间,用户可选择进入命令行 shell(若启用了 CONFIG_AUTOBOOT_MENU_SHOW=y,则显示启动菜单):

  • 设为 0:无延时自动启动镜像,但可通过按键中断;
  • 设为 -1:完全禁用自动启动镜像,会进入 U-Boot 的 Shell,常用于测试;
  • 设为 -2:无延时自动启动镜像,且不检测任何中断请求。

默认值由 CONFIG_BOOTDELAY 定义。若启用了 CONFIG_OF_CONTROL=y,则设备树中 /config/bootdelay 节点的值会覆盖此环境变量。

bootcmd:启动命令

若用户在 启动延时 期间未进入 shell,则执行此命令。通常是一条或多条命令组成的脚本,是 整个启动链的"最后一公里",连接 bootloader 与操作系统。例如:

  1. 使用使用文本文件定义环境变量

    bash 复制代码
    // 定义在 .env 文件中
    mmcboot=
        mmc dev 0;
        fatload mmc 0:1 ${kernel_addr_r} Image;
        fatload mmc 0:1 ${fdt_addr_r} myboard.dtb;
        booti ${kernel_addr_r} - ${fdt_addr_r}
    
    bootcmd=run mmcboot
  2. 使用旧式 C 语言定义环境变量

    c 复制代码
    "bootcmd="	CONFIG_BOOTCOMMAND			// env_default.h
    
    #define CONFIG_BOOTCOMMAND \			// mx6ullevk.h
       "run findfdt;" \
       "mmc dev ${mmcdev};" \
       "mmc dev ${mmcdev}; if mmc rescan; then " \
    	   "if run loadbootscript; then " \
    		   "run bootscript; " \
    	   "else " \
    		   "if run loadimage; then " \
    			   "run mmcboot; " \
    		   "else run netboot; " \
    		   "fi; " \
    	   "fi; " \
       "else run netboot; fi"

bootargs:传递给操作系统的命令行参数

c 复制代码
"bootargs="	CONFIG_BOOTARGS					// mx6ullevk.h

对 Linux 而言,bootargs 就是 内核命令行(kernel cmdline),决定根文件系统、控制台、内存布局等。例如:

bash 复制代码
bootargs=console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw
  • U-Boot 在调用 bootz/bootm 时,将 bootargs 写入设备树的 chosen 节点或 ATAGS(旧版方法)
  • 必须包含 console=:否则内核可能无串口输出。
  • 使用 rootwait:确保 MMC/USB 设备初始化完成后再挂载 rootfs。

bootfile:镜像文件名

通过 TFTP 加载的镜像文件名。

  • 当执行 tftpboot ${addr}(无文件名)时,U-Boot 会自动使用 bootfile 的值作为默认文件名。
  • 默认值通常为 "uImage" 或 "Image",取决于板级配置。

bootm_low:限制 bootm 命令可以访问的内存范围

该变量以十六进制数形式给出,定义了 bootm 命令允许使用的最低地址。

  • bootm_low 所定义的地址同时也是 Linux 内核初始内存映射的基地址
  • bootm_size(最大可用内存大小)共同定义了一个 安全内存窗口,防止 bootm 使用保留区域

bootsize:限制 bootm 命令可以访问的内存范围

该变量以十六进制数形式给出,定义了 bootm 命令允许使用的内存区域大小。

bootretry 和 bootstopkeysha256、 bootdelaykey、 bootstopkey:增强的自动启动配置

基础的自动启动映象相关配置包括:

  • bootdelay:设置倒计时秒数
  • bootcmd:倒计时结束后要执行的命令

这些基础功能的流程是:

  • 倒计时内按任意键 → 自动启动停止 → 进入 U-Boot 命令行
  • 这适合开发,但不适合产品环境,这时因为:
    • 串口噪声:意外的产生了一个中断信号,设备进入 U-Boot
    • 按键干扰:用户不懂系统,随便按了一个键,设备进入 U-Boot
    • 配置中断:远程使用 U-Boot 配置设备,但期间断网了,一直停留在 U-Boot

所以需要让自动启动更加安全,可控,而不是被任何字符中断。U-Boot 提供了一些增强功能。

重新开始自动启动流程(bootretry 环境变量)

当自动启动被打断进入 U-Boot 命令行后,如果用户 长时间不输入命令 ,U-Boot 会 重新开始自动启动流程

bash 复制代码
bootretry=CONFIG_BOOT_RETRY_TIME					// socfpga_secu.env
  • CONFIG_BOOT_RETRY_TIME:启动重新自动启动的超时时间,单位:秒
  • 只要用户执行一个命令 → 超时计时器重置
  • CONFIG_BOOT_RETRY_MIN:定义最小超时时间,不允许被设置得太低

在配置了 CONFIG_BOOT_RETRY_TIME 的前提下,如果使能配置项 CONFIG_RESET_TO_RETRY,则:

  • bootretry 超时后,将直接 重启板子
使用特定字符串才能阻止自动启动(bootdelaykey 和 bootstopkey 环境)
  • bootdelaykey:输入指定字符串(宏 CONFIG_AUTOBOOT_DELAY_STR 指定)进入 U-Boot 命令行,如果设置了"重新开始自动启动流程",则超时时间达到后,会再次自动启动
  • bootstopkey:输入指定字符串(宏 CONFIG_AUTOBOOT_STOP_STR 指定)进入 U-Boot 命令行,不会有超时。

无论哪种模式,都需要

  • 配置宏:CONFIG_AUTOBOOT_KEYED=y
  • 配置提示字符串:CONFIG_AUTOBOOT_PROMPT="Press xxx to abort autoboot in %d seconds\n"

测试,以 U-Boot 2016 为例 (硬件为 i.mx6ull)

  1. configs/mx6ull_alientek_emmc_defconfig 中添加:

    c 复制代码
    CONFIG_AUTOBOOT_KEYED=y
    CONFIG_AUTOBOOT_STOP_STR="k"
    CONFIG_AUTOBOOT_PROMPT="Press k to abort autoboot in %d seconds\n"
  2. 重新编译:

    bash 复制代码
    make mx6ull_alientek_emmc_defconfig
    ARCH=arm CROSS_COMPILE=/usr/local/bin/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- make -j12
  3. 将编译后的文件拷贝到 TFTP 共享文件目录:

    bash 复制代码
    sudo cp u-boot.imx ~/linux/tftpboot/
  4. 通过 tftp 更新 U-Boot:

    bash 复制代码
    tftp 80800000 u-boot.imx
    mmc dev 1 0
    mmc write 80800000 2 2c6
    mmc partconf 1 1 0 0

    注:我编译后的 u-boot.imx 大小 363520 字节,363520/512 = 710 = 0x2c6

加密(哈希)保护的 stop key(bootstopkeysha256)

功能和 bootstopkey 相同,只不过阻止自动启动的字符串不会明文放在环境变量,而是存储 sha256 哈希值。当用户输入字符串后,U-Boot 会做 sha256,然后跟 bootstopkeysha256 做比较,相同则进入U-Boot 命令行,不会有超时。

button_cmd_0, button_cmd_0_name, ..., button_cmd_N, button_cmd_N_name

用于在启动过程中检测到某个按钮被按下时,映射并执行对应的命令。详见 CONFIG_BUTTON_CMD。例如:

bash 复制代码
button_cmd_0_name=recovery
button_cmd_0=run recovery_cmd
button_cmd_1_name=update
button_cmd_1=run update_cmd

若启动时检测到"recovery 按钮"被按下,则执行 recovery_cmd ,如路由器"复位键进入恢复模式"。

updatefile:自动软件更新机制

TFTP 服务器上软件更新文件的位置,由自动软件更新功能使用。详情请参阅 doc/README.update 文档。

以下给出这个文档中的描述解析:

1. 这个功能是什么?

U-Boot 提供一个自动软件更新(Auto-update)机制

每次开机时,U-Boot 会尝试从 TFTP 服务器下载一个文件(文件名由环境变量 updatefile 指定)。

下载成功后:解析该文件(它是一个 FIT 文件),校验每个更新内容的 SHA-1 校验值,并根据 FIT 中描述的地址信息,将更新内容自动写入 NOR Flash

一句话总结:只要服务器上放好更新文件,设备开机时就能自动升级固件。

2. 这个功能依赖什么?

要启用自动更新,需要:

c 复制代码
#define CONFIG_UPDATE_TFTP 1

另外必须启用:

  • NOR Flash 支持
  • FIT 文件支持:CONFIG_FIT
  • 设备树解析库:CONFIG_OF_LIBFDT

➡ 这些都是自动从 FIT 文件提取更新内容所必需的。

3. 自动更新涉及哪些关键配置?

(1) CONFIG_UPDATE_LOAD_ADDR

  • TFTP 下载的默认地址是环境变量 loadaddr
  • loadaddr 没有设置,则使用 CONFIG_UPDATE_LOAD_ADDR
  • 默认值:0x100000

也就是说 updatefile 会临时下载到这个区域。

(2) TFTP 链接超时配置:CONFIG_UPDATE_TFTP_CNT_MAXCONFIG_UPDATE_TFTP_MSEC_MAX

因为设备每次启动都会尝试连接 TFTP,所以不能等太久:

  • CONFIG_UPDATE_TFTP_MSEC_MAX:每次连接等待多少毫秒(默认:100ms)
  • CONFIG_UPDATE_TFTP_CNT_MAX:最多尝试连接多少次(默认:0次,即基本不重试)

➡ 防止没有服务器时导致开机慢。

4. updatefile 是如何制作的?(FIT 格式)

updatefile 必须是 FIT 文件 (.itb)

FIT 文件可包含多个更新,例如:

  • U-Boot
  • Kernel
  • Ramdisk
  • DTB

制作 FIT 文件需要:

  • 一个 .its 描述文件
  • 支持 binary include 的 dtc 版本

制作命令:

bash 复制代码
mkimage -f update_uboot.its update_uboot.itb
5. 示例:使用 updatefile 自动更新 U-Boot

如果你有:

bash 复制代码
u-boot.bin
update_uboot.its

运行:

bash 复制代码
mkimage -f update_uboot.its update_uboot.itb

update_uboot.itb放到 TFTP 服务器:

例如:/tftpboot/update_uboot.itb

然后在 U-Boot 中设置:

bash 复制代码
setenv updatefile /tftpboot/update_uboot.itb
saveenv

之后,一旦开机且 serverip 指向的服务器可用,设备会自动:

  • 下载 updatefile
  • 校验 SHA-1
  • 替换闪存中的 U-Boot

注意风险:

  • 必须确保 u-boot.bin 是可用的
  • 必须确保闪存写入地址正确
  • 否则可能会使设备无法开机
6. 多文件更新示例(update3.its)

这个例子展示如何同时更新:

  • kernel
  • ramdisk
  • dtb

制作流程与上面相同,只是 .its 内容更复杂。

7. 使用 DFU + TFTP 的另一种更新方式

文档最后提到:

  • 也可以用 DFU(USB 方式)结合 TFTP 来更新固件
  • 详细见 doc/README.dfutftp

这是另外一种更新手段,与 updatefile 是并行的功能。

autoload:自动加载镜像(bootp 和 dhcp 命令)

若设为 "no"(即任何以 'n' 开头的字符串),"bootp" 和 "dhcp" 命令将仅从 BOOTP 服务器查询配置信息,而不会尝试加载任何镜像。

默认情况下,dhcp 命令会:

  • 获取 IP、网关、TFTP 服务器地址;
  • 自动下载 bootfile 指定的文件。

设置 autoload=no 后,仅执行第 1 步,常用于:

  • 获取网络参数后手动下载不同镜像;

注意:tftpboot、nfs 等命令不受 autoload 影响,它只作用于 bootp/dhcp。

fdt_high:FDT加载地址的上限约束

若设置此变量,将限制设备树(FDT)在启动时被复制到的最高地址。例如,若你的系统在物理地址 0x10000000 处有 1GB 内存,但 Linux 内核仅将前 704MB 视为"低端内存"(low memory),你可能需要将 fdt_high 设为 0x3C000000 (0x100000000 - 0x3C000000,共 704MB),使设备树 blob 被复制到这 704MB 低端内存的最高地址,以便 Linux 内核在启动过程中能够访问它。

若将其设为特殊值 0xffffffff(32 位机器)或 0xffffffffffffffff(64 位机器),则启动时完全不复制设备树。要使此方式生效,设备树必须位于可写内存中,其末尾需有足够填充空间供 U-Boot 添加所需信息,且该内存必须能被内核访问。然而,强烈不建议使用此方式,因为它会阻止 U-Boot 确保设备树起始地址正确对齐,而未对齐的设备树将导致操作系统启动失败。

fdtcontroladdr:控制设备树的地址

若设置此变量,它表示当启用了 CONFIG_OF_CONTROL 时,U-Boot 所使用的控制设备树(control FDT)的地址。

  • ONFIG_OF_CONTROL 表示 U-Boot 从设备树获取自身配置(而非从 CONFIG_ 宏)。
  • fdtcontroladdr 指向这个"控制设备树"的内存地址(通常由 BootROM 或前一级 bootloader 加载)

initrd_high:限制 initrd 镜像的放置位置

initrd 就是一个存放在内存中的临时小型根文件系统,辅助内核加载真正的根文件系统。

若未设置此变量,initrd 镜像将被复制到 RAM 中尽可能高的地址;这通常是期望的行为,因为它允许 initrd 使用最大可能的内存大小。

如果出于某些原因,你需要确保 initrd 镜像加载在 CFG_SYS_BOOTMAPSZ 限制之下,可将此变量设为 "no"、"off" 或 "0"。或者,你也可以将其设为一个最大上限地址(U-Boot 仍会检查该地址不会覆盖 U-Boot 自身的栈和数据区)。

例如,当你的系统有 16MB RAM,并希望为 Linux 保留 4MB 不使用,可通过在 bootargs 中添加 mem=12M 实现。但此时你必须确保 initrd 镜像也放置在前 12MB 内------可通过以下命令实现:

bash 复制代码
setenv initrd_high 00c00000   # 0x00C00000 = 12MB

若将 initrd_high 设为 0xffffffff(32 位机器)或 0xffffffffffffffff(64 位机器),这表示告知 U-Boot:所有地址对 Linux 内核都是合法的,包括 flash 内存中的地址。此时 U-Boot 完全不会复制 ramdisk。这可用于减少系统启动时间,但要求你的 Linux 内核支持此特性。然而,这种用法要求用户确保 initrd 与镜像其他部分(如 Linux 内核的 BSS 段)无重叠。不应默认启用,仅应在部署优化阶段谨慎使用。

在一些嵌入式应用中,内核和根文件系统都在 Flash 上,所有驱动都编译进内核,这时是不需要 initrd 的。

ipaddr:设备 IP 地址

设备的本机 IP 地址,用于 TFTP/NFS 等网络操作(tftpboot 命令)。若未设置,dhcpbootp 命令会自动获取并设置它。

serverip:TFTP 服务器 IP 地址

用于 tftpboot 命令。

loadaddr:默认加载地址

"bootp"、"rarpboot"、"tftpboot"、"loadb" 或 "diskboot" 等命令的默认加载地址。注意,最优默认值因架构而异。例如,在 32 位 ARM 上,通常使用内存起始地址的一个偏移量,因为 Linux 内核 zImage 自带解压器,我们最好避开其工作区域。

  • ARM Linux 内核(zImage 格式)不是直接执行,它先会在内存里自己解压成一个更大的 Image,然后再跳到解压后的内核执行。

  • 这个解压动作会使用内存的一部分区域。

  • 如果你把 zImage 加载得太靠近内存起始(比如 i.mx6ull 的内存起始地址 0x80000000),很有可能解压出来的 Image 会覆盖 zImage 自身或覆盖其他数据,导致系统无法启动。

  • 所以 U-Boot 为 zImage 选择的加载地址(loadaddr)必须往后挪一点,比如 i.mx6ull 会设置:

    bash 复制代码
    #define CONFIG_LOADADDR		0x80800000			// mx6common.h,留出 8MB
    "loadaddr="	__stringify(CONFIG_LOADADDR)		// env_default.h

loads_echo:回显接收数据

loads 命令通过串口 XMODEM 协议接收二进制文件。

bash 复制代码
"loads_echo="	__stringify(CONFIG_LOADS_ECHO)		// env_default.h

若启用 CONFIG_LOADS_ECHO,U-Boot 会在接收时回显字符(用于调试)。

ethprime:设置默认首选网络接口

控制哪个网络接口被优先使用(即作为默认首选接口,启动默认值)。

如果你的板子有多个网卡,且希望确保 TFTP/NFS 从特定接口加载内核,请务必设置 ethprime=FEC。

ethact:设置当前活动的网络接口

ethact 控制当前处于活动状态的网络接口。例如,你可以执行以下操作:

bash 复制代码
=> setenv ethact FEC  
=> ping 192.168.0.1   # 流量通过 FEC 接口发送  
=> setenv ethact SCC  
=> ping 10.0.0.1      # 流量通过 SCC 接口发送

ethact 是运行时变量,允许用户在 U-Boot 命令行中手动切换当前使用的网络接口,而无需重启。与之对应的是 ethprime,是"启动默认值";

ethrotate:路由网络接口

当设为 "no" 时,U-Boot 不会遍历所有可用的网络接口,而仅使用当前选定的接口。当该变量未设置,或设置为除 "no" 以外的任何值时,U-Boot 会依次尝试所有可用的网络接口。

netretry:重试网络接口策略

  • 当设为 "no" 时,每次网络操作要么成功,要么立即失败,不进行重试。
  • 当设为 "once" 时,网络操作会在所有可用网络接口各尝试一次后,若仍无成功,则宣告失败。

httpdstp:设置 http 端口号

如果设置了该变量,其值将被用作 HTTP 请求的 TCP 目标端口,取代默认的 80 端口。

U-Boot 支持通过 httpget 命令从 Web 服务器下载文件(如内核镜像)。默认使用标准 HTTP 端口 80,但很多嵌入式部署使用非标端口(如 8080、8000)。

仅影响 HTTP:该变量不影响 TFTP、NFS、DHCP 等其他协议。

phy_aneg_timeout:设置 PHY 自动协商超时时间

如果设置了该变量,其指定的值将覆盖 CONFIG_PHY_ANEG_TIMEOUT 配置项。该变量的数值格式和单位与 CONFIG_PHY_ANEG_TIMEOUT 相同,分别为"十进制"和"毫秒"。

CONFIG_PHY_ANEG_TIMEOUT 的默认值对大多数应用场景已足够,但某些对端设备(link-partner)由于其所使用的以太网 PHY 芯片特性,可能需要更长的超时时间。当然,如果应用场景有特殊需求,也可以将该超时时间缩短。

  • U-Boot 中 CONFIG_PHY_ANEG_TIMEOUT 通常设为 5000 ms(5 秒)
  • aneg:自动协商(Auto-Negotiation)

rng_seed_size

指定添加到设备树节点 /chosen/rng-seed 中的随机值的字节长度。该变量以十进制数字形式给出。若未设置,则默认使用 64 字节。

  • 现代 Linux 内核(尤其是启用 CONFIG_RANDOM_TRUST_BOOTLOADER 时)会从设备树的 /chosen/rng-seed 获取初始熵(entropy),用于初始化内核的随机数生成器(CRNG)。
  • 若 U-Boot 有硬件 RNG(如 SoC 内置 TRNG),可生成真随机数填充此字段,提升系统安全性。

silent_linux:控制 Linux 是否静默启动(不输出信息)

如果设置了该变量,则通过在 Linux 内核命令行中添加 console= 来指示 Linux 以静默方式启动。

  • 若其值为 "yes",则 Linux 将被设为静默启动;
  • 若为 "no",则不会设为静默;
  • 若未设置,则当 U-Boot 控制台本身处于静默状态时,Linux 也会被设为静默启动。

U-Boot 的 silent 环境变量控制自身是否输出日志。silent_linux 决定是否将这种"静默"传递给 Linux。

console= 的作用:清空内核的控制台设备列表,使 printk 输出不显示在串口/VGA 上(但仍可通过 dmesg 查看)。

开发阶段应设为 "no",否则无法看到内核 panic 或驱动错误信息。

产品发布推荐启用:设为 "yes" 可隐藏技术细节,呈现干净启动画面。

若你的 bootargs 中已包含 console=ttyS0,...,则 silent_linux=yes 会覆盖它(因 console= 清空了列表)。需注意顺序或手动拼接。

tftpsrcp:固定 TFTP 源端口号

如果设置了该变量,其值将被用作 TFTP 通信的 UDP 源端口。

  • 标准 TFTP(RFC 1350)使用 UDP。客户端向服务器的 69 号端口发送请求,但服务器会从一个临时高端口回传数据,客户端也需用一个临时端口接收。
  • 默认情况下,U-Boot 让操作系统(或网络栈)自动分配源端口(ephemeral port)。
  • 设置 tftpsrcp 可固定源端口,便于防火墙策略配置或网络调试。

tftpdstp:固定 TFTP 目标端口号

如果设置了该变量,其值将被用作 TFTP 的 UDP 目标端口,取代默认的 69 号端口。

tftpblocksize:设置 TFTP 数据块大小

指定 TFTP 传输使用的数据块大小;若未设置,则使用 TFTP 服务器的默认块大小。

tftptimeout:设置 TFTP 超时时间

TFTP 数据包的重传超时时间(单位:毫秒,最小值为 1000,即 1 秒)。该值定义了在多长时间内未收到响应即视为丢包,从而触发重传。默认值为 5000(5 秒)。在网络丢包率较高或 TFTP 服务器不可靠的情况下,降低此值可使下载更快成功。

tftptimeoutcountmax:设置 TFTP 最大超时重传次数

单次文件传输过程中允许的最大超时次数(无单位,最小值为 0)。该值定义了在放弃传输前最多可发生多少次超时。默认值为 10,而 0 表示"不允许任何超时"(即首次超时即失败)。在高丢包率网络、不可靠 TFTP 服务器或客户端硬件不稳定的场景下,增大此值有助于下载最终成功。

tftpwindowsize:设置 TFTP 窗口大小

如果设置了该变量,其值将被用作 TFTP 的窗口大小,遵循 RFC 7440 规范。这意味着在向服务器发送确认(ACK)之前,我们可以连续接收的数据块数量。

  • 传统 TFTP:1 块 → ACK → 1 块(停等),带宽利用率极低。
  • RFC 7440(Windowed TFTP):允许"滑动窗口"机制。例如 tftpwindowsize=4 表示:
    客户端可连续接收 Block 1~4,然后一次性发 ACK(4),服务器接着发 Block 5~8......

bootpretryperiod:设置 BOOTP/DHCP 重试周期时间

BOOTP/DHCP 协议发送重试请求的时间窗口(单位:毫秒,无符号整数)。若未设置,该周期将采用默认值(28000 毫秒,即 28 秒),或若定义了 CONFIG_NET_RETRY_COUNT,则基于该配置计算得出。此环境变量的值优先级高于基于 CONFIG_NET_RETRY_COUNT 计算出的值。

镜像位置相关变量

注意:这些变量并非所有开发板都必须定义 。某些开发板目前为相同用途使用了其他变量名,而另一些开发板则可能将这些变量名用于其他目的。这些变量中的大多数仅是一组被广泛采用的变量名 ,它们出现在其他变量的定义中(如 bootcmd),但在 U-Boot 源代码中并未被硬编码引用

也就是说,以下介绍的环境变量是厂商广泛使用,但是 U-Boot 并不使用。

  • 指定 TFTP 服务器上该镜像文件的文件名
    • bootfile:Linux 内核文件名
    • fdtfile:设备树二进制文件名
    • ramdiskfile:ramdisk 文件名
  • 镜像的加载地址(RAM 中)
    • kernel_addr_r:Linux 内核
    • fdt_addr_r:设备树
    • ramdisk_addr_r:ramdisk
  • 镜像的存储物理地址或偏移量
    • kernel_addr:Linux 内核
    • fdt_addr:设备树
    • ramdisk_addr:ramdisk

在设置 kernel_addr_rfdt_addr_rramdisk_addr_r 的 RAM 加载地址时,需考虑多种约束条件:

  • 负载(payload)自身的要求:例如,设备树(Device Tree)必须加载到 8 字节对齐的地址,这是其规范所强制规定的。类似地,操作系统也可能对镜像在内存中的位置施加限制------Linux 在《Booting ARM Linux》和《Booting AArch64 Linux》等文档中对此有明确说明。
  • 实践性约束:我们无法预知用户使用的各镜像的具体大小,但必须确保它们互不重叠,否则会相互覆盖导致损坏。此外,若某个镜像被加载到操作系统 BSS 段(未初始化数据区)所在区域,同样可能引发冲突。

因此,我们必须确保默认地址值:

  • 极低概率导致启动失败;
  • 附带充分说明,以便用户可根据实际需求优化启动速度,或适配小内存系统。

不同架构具有不同的约束条件。为确保向前兼容性,我们必须严格遵循已公开的官方文档要求。以下通过具体示例,说明如何为不同平台设定合理的默认值。

TI OMAP2PLUS(ARMv7)示例:

bash 复制代码
loadaddr=0x82000000
kernel_addr_r=${loadaddr}
fdt_addr_r=0x88000000
ramdisk_addr_r=0x88080000
bootm_size=0x10000000   # 256 MiB

首先要记住的是:DRAM 起始地址为 0x80000000。我们将默认加载地址设为从内存起始处偏移 32 MiB 的位置(即 0x82000000),并将 kernel_addr_r 指向该地址。这样做的原因是:Linux 的 zImage 自解压程序通常可以因此避免执行额外的重定位(relocation)操作。此外,该地址必须位于内存前 128 MiB 范围内。

接下来,我们将 fdt_addr_r 设为从内存起始处偏移 128 MiB 的位置(即 0x88000000)。这一位置由 Linux 内核文档所建议,并且由于其他架构限制,内核本身极不可能覆盖该区域。

然后,我们为设备树预留最多 512 KiB 的空间,之后才放置 initramfs(ramdisk)。

最后,我们规定所有镜像都应位于内存前 256 MiB 范围内,以便 U-Boot 在必要时可对它们进行重定位,确保满足对齐等要求。之所以选择 256 MiB 这一数值,是因为我们知道该系列平台中极少有内存小于 256 MiB 的型号。理论上,该上限甚至可设为 768 MiB 而仍能保证内核可见所有数据,但我们依然选择了我们认为最安全的假设。

特殊环境变量

这些变量只能设置一次(通常在电路板制造阶段完成)。一旦设定,U-Boot 拒绝删除或覆盖这些变量,除非在板级配置中启用了 CONFIG_ENV_OVERWRITE。

serial#

包含硬件标识信息,例如设备类型字符串和/或序列号。

ethaddr

以太网 MAC 地址。若启用了 CONFIG_REGEX=y,则还支持 eth*addr(其中 * 为整数,如 eth0addr, eth1addr)。

外部环境变量文件

配置选项 CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE 提供了一种绕过 U-Boot 内部环境生成机制的方法。若启用该选项,则 CONFIG_ENV_DEFAULT_ENV_TEXT_FILE 指定一个文件名,该文件的内容将被转换为 U-Boot 的默认环境变量,从而完全取代 env_default.h 中定义的标准默认环境。

文件格式与 mkenvimage 工具所接受的格式相同:每行包含 key=value 形式的键值对。空行以及以 # 开头的注释行将被忽略。

未来的工作可能会将此功能与基于文本的环境变量机制统一,甚至可能将 env_default.h 的内容迁移至一个文本文件中。

相关推荐
iriczhao2 个月前
【u-boot】u-boot的分区支持
c·u-boot·bootloader·引导加载
jz_ddk2 个月前
[嵌入式] U-Boot 环境变量深度解析:从 QSPI 到 eMMC 的 Linux 启动完整指南
linux·运维·服务器·嵌入式·环境变量·u-boot·内核加载
小武~4 个月前
uboot armv8 启动流程之 linker script
u-boot
小武~7 个月前
ARMV8 RK3399 u-boot TPL启动流程分析 --crt0.S
linux·u-boot·rockchip·arm-v8·tpl
小武~7 个月前
ARMV8 RK3399 u-boot TPL启动流程分析 --start.S
linux·rk3399·u-boot·rockchip
Nuttx_Fan_now9 个月前
一篇文章讲解清楚ARM9芯片启动流程
linux·mpu·u-boot·bootloader·arm9
xunknown1 年前
编译和运行qemu-uboot-arm64单板的Armbian系统
linux·arm开发·ubuntu·qemu·u-boot·armbian
林政硕(Cohen0415)2 年前
U-Boot menu菜单分析
linux·嵌入式·u-boot
张世争2 年前
RK3568 学习笔记 : 精简 u-boot env 默认复杂的多种引导启动设置
rk3568·u-boot·env