Skip to main content
Version: BSP 6.x.y

Best Practices with U-Boot Environment Variables


The environment of U-Boot is part of the U-Boot bootloader that allows users to store and retrieve configuration settings for their embedded systems.

This article points out a few best practices while working with the U-Boot Environment Variables. There are several potential sources of the environment information that is presented in U-Boot, and it is important to understand how it works to avoid issues. To learn how to build your U-Boot version, please refer to Build U-Boot and Linux Kernel from Source Code.

The configuration settings in the U-Boot are typically represented as environment variables. These variables hold values related to system behavior, device drivers, network configurations, and more. Some common use cases for the U-Boot environment variables include setting up network parameters (e.g., IP address), configuring storage devices (e.g., boot source), and enabling/disabling hardware components (e.g., UART). The environment also heavily influence how the module will boot.

Sources for U-Boot Environment Variables

The U-Boot works by storing the configuration settings in a key-value store in RAM. Users can make changes to the variables, and these can be saved at specific sectors or partitions of the flash memory.

Default Environment Variables

The first source of the environment information comes from U-Boot default environment variables. This default environment is set in compilation time and it is built into the U-Boot binary. Its contents are selected based on the current configuration and the board-specific configuration. The default environment is located at include/env_default.h and one example of board-specific configuration can be found on verdin-imx8mp.h:

/* Initial environment variables */                                                                                                                          
"bootcmd_mfg=fastboot 0\0" \
"boot_file=Image\0" \
"boot_scripts=" BOOT_SCRIPT "\0" \
"boot_script_dhcp=" BOOT_SCRIPT "\0" \
"console=ttymxc2\0" \
"fdt_board=dev\0" \
"initrd_addr=0x43800000\0" \
"initrd_high=0xffffffffffffffff\0" \
"setup=setenv setupargs console=tty1 console=${console},${baudrate} " \
"consoleblank=0 earlycon\0" \
"update_uboot=askenv confirm Did you load flash.bin (y/N)?; " \
"if test \"$confirm\" = \"y\"; then " \
"setexpr blkcnt ${filesize} + 0x1ff && setexpr blkcnt " \
"${blkcnt} / 0x200; mmc dev 2 1; mmc write ${loadaddr} 0x0 " \
"${blkcnt}; fi\0"

The default environment will always be present in a board and is the environment that is used when the board is flashed.

Second Source of Environment Variables

The second source of the environment comes from a predefined location (at compilation time) in the board’s non-volatile memory (eMMC or NAND). This location is also defined in U-Boot configuration. As part of the boot process, U-Boot will check the Cyclic Redundancy Check (CRC) of the configured environment area. If the CRC matches, the environment valid and it is loaded from the flash, overwriting the default environment.

Best practices when dealing with U-Boot Environment Variables

When dealing with U-Boot Environment Variables, one need to be careful changing the variables and saving it. Here is a list of possible different scenarios to be taken into account during your development:

Writing variables only changes the RAM representation

When using setenv and printenv on U-Boot serial console, you are dealing with the RAM representation of the environment. Changes made with setenv will only be valid for the current U-Boot instance. After a reset, either the default environment or the persisted environment is loaded. To ensure that the changes persist, we need to manually call saveenv, which will write the entire environment to the preconfigured non-volatile location. After this procedure, there’s a valid image of the environment on non-volatile memory and the default environment is not recalled anymore.

It’s possible to permanently change the mac address of the module

U-Boot uses environment variables to store the mac address that is used by the ethernet controller. The user can intentionally change the ethaddr variable and saveenv to make the module use a new mac address. The user can also non-intentionally do that by blindly copying the full contents of the environment from one module and saving it to a different module.

To prevent that, never copy the entire environment from one module to replicate it to other modules. Select the variables that need to be changed and leave the rest alone, or at least make sure to not include ethaddr* variables. Another option would be to disable the CONFIG_ENV_OVERWRITE or enable CONFIG_OVERWRITE_ETHADDR_ONCE variables to protect the mac address from unintended changes.

Variables can be altered by the U-Boot code during boot

As part of the board-specific initialization code, there can be environment variables that are dynamically calculated and setup for specific purposes. Here’s an example of that, from the initialization code of apalis-imx8_defconfig:

CONFIG_PREBOOT="test -n ${fdtfile} || setenv fdtfile ${soc}-apalis${variant}-${fdt_board}.dtb"

In this example, we’re using the PREBOOT feature in u-boot, which allows us to run some code right before the board boots. We check the existence of the ${fdtfile} which at this point can only exist if the environment was previously changed and persisted with saveenv. If the variable doesn’t exist, it’s set to a default value but this change is not saved. This means that while there’s no save on the environment, the ${fdtfile} will always take its default value when booting.

When the environment is saved, it can have unexpected consequences

As previously explained, when saveenv is used, an image of the entire environment is saved to persistent memory, not just the variable that was changed. This can have unexpected consequences, for instance:

Take the previous example of the ${fdtfile} variable, which is updated dynamically upon boot. Now picture you changed an unrelated variable ${foo} and you want this persisted over boots so you use saveenv. From this point on, the ${fdtfile} variable is not being dynamically set anymore, it’s statically set to whatever value it had at the point you used saveenv. If you were relying on the dynamic aspect of this and just changing another variable like ${fdtboard} to get a different device tree loaded, it would stop working.

The persistent environment does not necessarily reflect exactly what’s being used during boot

We have tools that can be used on the linux console to interact with the environment of u-boot and are included in our reference images (fw_printenv and fw_setenv). These tools only interact with the persistent copy of the environment. Because variables can be calculated dynamically in u-boot, the environment we can see using the linux tools can show differences compared to the one used in u-boot.

Another interesting fact related to the linux environment of u-boot tools appears when they are used but there’s no environment currently persisted in memory.

In this case, typing fw_printenv will still output an environment file, but this is the default environment from u-boot, but now built in the fw_printenv tool. If both u-boot and the linux tools are compiled from the same source and using the same configuration file, you'll get the same default environment of u-boot loaded when u-boot is running. This is the case with Toradex's reference images, and it would also be the case in images built from our standard BSP.

Using Toradex Easy Installer to write the environment of u-boot

Now that you have a good understanding of how the environment of u-boot works and all the caveats associated with it, you can better understand how the u_boot_env feature of the Toradex Easy Installer works.

The user must provide a fully working environment file that will be used to setup the u-boot environment variables of the flashed image. No default environment will be merged with the contents of the custom environment file. Users can use Toradex environment file as a reference.

For more information, please access How to Customize Toradex Easy Installer Boot and Toradex Easy Installer Configuration Files.

Send Feedback!