Setting up U-Boot environment
Booting system with U-Boot, understand loading/storing environment variables.
1. Objective
- Understand u-boot booting environment.
- How the variables are load/store and what are they used for.
- Using QEMU to demonstrate bootflow.
2. Environment variables
U-Boot uses environment variables to keep the source code as generic as possible. These environment can be stored in several locations, depends on building configuration.
- NOR/NAND flash memory.
- eMMC/SD card.
- SPI flash.
- EEPROM.
- RAM.
- Or ENV in the boot FAT/ext4 filesystem.
The location is defined at the config file, for example, qemu_arm64_defconfig
:
1
2
3
4
5
6
7
# CONFIG_ENV_IS_IN_EEPROM is not set
# CONFIG_ENV_IS_IN_FAT is not set
# CONFIG_ENV_IS_IN_EXT4 is not set
CONFIG_ENV_IS_IN_FLASH=y
# CONFIG_ENV_IS_IN_NAND is not set
# CONFIG_ENV_IS_IN_NVRAM is not set
# CONFIG_ENV_IS_IN_REMOTE is not set
The config file also tell u-boot how to loads these variables:
1
2
3
4
CONFIG_ENV_SOURCE_FILE=""
CONFIG_ENV_SIZE=0x40000
CONFIG_ENV_SECT_SIZE=0x40000
CONFIG_ENV_ADDR=0x4000000
You can also load env variables at runtime by using setenv
, printenv
commands, and env_get()
, env_set()
, saveenv()
in C.
2.1. Environment variable purposes
U-Boot support variety kind of booting. These environment variables are used to control boot behavior: how the system boots, what it boots, and where from.
2.2. Boot configuration
bootcmd
is used to store boot commands that are run if the user does not enter the shell. This example load kernel image from mmc device 0, partition 1 and then call bootz
to boot kernel with fdt address:
1
2
setenv boot_linux 'ext4load mmc 0:1 ${kernel_addr_r} /boot/zImage; bootz ${kernel_addr_r} - ${fdt_addr_r}'
setenv bootcmd 'run boot_linux'
bootargs
is used to specify kernel command line.
boot_targets
this variable list devices to scan, used by bootflow
command to specify list of device and order to scan.
2.3. Kernel/image locations
kernel_addr_r
,fdt_addr_r
,ramdisk_addr_r
– RAM addresses to load kernel, DTB, and initrd.kernel_addr
,fdt_addr
,ramdisk_addr
– Flash locations that kernel, DTB, and initrd are stored.*_high
– restrict positioning of images.
For example:
1
2
3
4
5
6
7
8
fdt_addr=0x40000000
fdt_high=0xffffffff
initrd_high=0xffffffff
kernel_addr_r=0x40400000
loadaddr=0x40200000
pxefile_addr_r=0x40300000
ramdisk_addr_r=0x44000000
scriptaddr=0x40200000
2.4. Device/Filesystem booting
root
,mmcdev
,mmcroot
,mmcpart
– Define rootfs location and MMC partition.ethaddr
,ipaddr
,serverip
– Used for network booting (e.g., TFTP, NFS).
2.5. hardware configuration
baudrate
,stdin
,stdout
,stderr
– Serial console settings.ethaddr
– MAC address.serial#
– Device serial number.
3. Store/Load environment variables
There are some ways to store/load these variables:
- Built-in the u-boot binary.
- Get from memory devices.
- Get from an environment file that is stored in the filesystem.
3.1. Built-in default variables
The default environment variables are define in this file include/env_default.h
. Some are get from the .config
file:
1
2
3
4
5
6
7
8
9
char default_environment[] = {
#ifdef CONFIG_USE_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
/* ... */
}
Specific additional board configurations are defined at include/<board>.h
, for example include/pxa1908.h
:
1
2
3
4
5
6
#define CFG_SYS_SDRAM_BASE 0x1000000
#define CFG_SYS_INIT_RAM_ADDR 0x10000000
#define CFG_SYS_INIT_RAM_SIZE 0x4000
#define CFG_SYS_NS16550_IER 0x40
#define CFG_SYS_BAUDRATE_TABLE { 115200, 230400, 460800, 921600 }
#define CFG_EXTRA_ENV_SETTINGS "bootcmd=bootm $prevbl_initrd_start_addr\0"
Some variables are defined in specify board environment file board/<vendor>/<board>/<board>.env
. When building the target, u-boot generate variables from that file into the macro CONFIG_EXTRA_ENV_TEXT
and then adding them into the default_environment
array:
1
2
3
4
5
6
7
8
char default_environment[] = {
/* ... */
#ifdef CONFIG_EXTRA_ENV_TEXT
/* This is created in the Makefile */
CONFIG_EXTRA_ENV_TEXT
#endif
/* ... */
}
For example, qemu-arm
architecture has defined the board/emulation/qemu-arm/qemu-arm.env
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* SPDX-License-Identifier: GPL-2.0+ */
/* environment for qemu-arm and qemu-arm64 */
stdin=serial,usbkbd
stdout=serial,vidconsole
stderr=serial,vidconsole
fdt_high=0xffffffff
initrd_high=0xffffffff
fdt_addr=0x40000000
scriptaddr=0x40200000
pxefile_addr_r=0x40300000
kernel_addr_r=0x40400000
ramdisk_addr_r=0x44000000
boot_targets=qfw usb scsi virtio nvme dhcp
That and then is defined into C macro, generated in the file include/generated/environment.h
:
1
#define CONFIG_EXTRA_ENV_TEXT "boot_targets=qfw usb scsi virtio nvme dhcp\0fdt_addr=0x40000000\0fdt_high=0xffffffff\0initrd_high=0xffffffff\0kernel_addr_r=0x40400000\0pxefile_addr_r=0x40300000\0ramdisk_addr_r=0x44000000\0scriptaddr=0x40200000\0stderr=serial,vidconsole\0stdin=serial,usbkbd\0stdout=serial,vidconsole\0"
3.2. Variables stored in memory devices
Sometimes variables can be stored on non-volatile store device. And the offsets to query them are enabled at compile time. For example, the variables can be stored at a well-known block in MMC, or a fixed memory in flash. To load them from a storage device, we must enable the corresponding config, the offset and the size configuration also. For example, we spend flash memory address from 0x04000000 to 0x04400000
to store u-boot variables:
1
2
3
CONFIG_ENV_IS_IN_FLASH=y
CONFIG_ENV_ADDR=0x4000000
CONFIG_ENV_SIZE=0x40000
3.3. Environment files
Sometimes it’s hard to change these variables if they’re stored in the binary itself or in the memory raw, we need some extra tools to access them. U-Boot provides an other way to make the change much easier, get these environment variables from a file that is stored in the filesystem.
Currently, uboot supports getting environment files from 2 filesystem types: fat and ext4. The environment file name, by default, is /uboot.env
. Here is an example of getting environment file from a mmc device 0, ext4 partition 1.
1
2
3
4
CONFIG_ENV_IS_IN_EXT4=y
CONFIG_ENV_EXT4_FILE="/my-env.env"
CONFIG_ENV_EXT4_INTERFACE="mmc"
CONFIG_ENV_EXT4_DEVICE_AND_PART="0:1"
If the environment is get from a file, some default variables might be ignored.
If your storage device requires device driver or bus that are initialized later, u-boot can not load the env file in early stage. For example, your device is an USB storage device that connect to AHCI bus U-boot can not load the file from them.
4. Booting system
By default, if the user don’t enter shell, uboot read the bootcmd
to perform booting. There several methods to boot:
boot
– Boot default, i.e., run ‘bootcmd’.bootd
– Boot default, i.e., run ‘bootcmd’.bootdev
– Boot devices.bootefi
– Boots an EFI payload from memory.bootelf
– Boot from an ELF image in memory.bootflow
– Boot flows.booti
– Boot Linux kernel ‘Image’ format from memory.bootm
– Boot application image from memory.bootmeth
– Boot methods.bootp
– Boot image via network using BOOTP/TFTP protocol.bootstd
– Standard-boot operation.bootvx
– Boot vxWorks from an ELF image.bootz
– Boot Linux zImage image from memory.
4.1. bootflow
method
This method is one of the newest introduced in newer U-boot versions. It’s used for:
- Auto-discovering bootable devices and partitions.
- Automatically finding boot scripts, kernel image, and dtb.
- Generic booting method across platforms.
1
2
3
4
5
6
7
8
9
10
11
12
=> help bootflow
bootflow - Boot flows
Usage:
bootflow scan [-abeGl] [bdev] - scan for valid bootflows (-l list, -a all, -e errors, -b boot, -G no global)
bootflow list [-e] - list scanned bootflows (-e errors)
bootflow select [<num>|<name>] - select a bootflow
bootflow info [-ds] - show info on current bootflow (-d dump bootflow)
bootflow read - read all current-bootflow files
bootflow boot - boot current bootflow
bootflow menu [-t] - show a menu of available bootflows
bootflow cmdline [set|get|clear|delete|auto] <param> [<value>] - update cmdline
This command read the variable boot_targets
for list of devices. Some way to boot:
bootflow scan -b
– Scan in the list, and boot if meet the first bootable one.bootflow select [<num>|<name>]
– select device to boot and then boot withbootflow boot
.
For example, simple setup that scan the list qfw usb scsi virtio nvme dhcp
in order and perform booting:
1
2
boot_targets=qfw usb scsi virtio nvme dhcp
bootcmd=bootflow scan -lb
qfw
is QEMU firmware information command, only supported when running on QEMU.
4.2. Boot configuration files
The standard format for boot configuration files is that of extlinux.conf
, as handled by U-Boot’s syslinux
(disk) or pxe boot
(network).
The boot method with extlinux is enabled by CONFIG_BOOTMETH_EXTLINUX=y
. U-boot scans the device list in boot_targets
one by one and looking for the configure file /boot/extlinux/extlinux.conf
. It then try to parse the config files and load images, if some error occurs, u-boot scans for the next device.
4.2.1. extlinux.conf
format
This file follow the syslinux format that contains boot information and:
- Kernel image.
- Device Tree.
- Initrd.
- Kernel command lines.
1
2
3
4
5
6
7
DEFAULT linux
TIMEOUT 5
LABEL linux
KERNEL /boot/vmlinuz
INITRD /boot/initrd.img
APPEND root=/dev/sda1 quiet
4.3. Booting with EFI
When built with EFI support, U-Boot acts like a UEFI firmware (For more detail about EFI, visit this blog Bootloader UEFI):
- Start and initializes devices (mmc, usb, virtio, etc.).
- Scan for ESP partition.
- Try to run
EFI/BOOT/BOOTAA64.EFI
(file name depends on architecture). - The bootloader and then taking care all the rest.
For example:
1
2
fatload mmc 0:1 ${loadaddr} /EFI/BOOT/BOOTAA64.EFI
bootefi ${loadaddr} $fdtcontroladdr
Or Let u-boot automatically use EFI bootmgr:
1
bootefi bootmgr
5. U-boot emulation with QEMU
U-boot has supported QEMU platform for some architectures (x86, arm, risc, etc). To test loading environment variables we will run u-boot with qemu, emulate a block device and let’s the u-boot take the environment file from that. First let’s build u-boot from source:
1
2
git clone https://github.com/u-boot/u-boot
make -j$(nproc) ARCH=arm CROSS_COMPILE=<toolchain_prefix_path> qemu_arm64_defconfig
We get the default configuration for qemu_arm64_defconfig
, assume that our block device is a virtio device, and we make a ext4 partition on that. So There are some configurations need to change:
1
2
3
4
CONFIG_ENV_IS_IN_EXT4=y
CONFIG_ENV_EXT4_INTERFACE="virtio"
CONFIG_ENV_EXT4_DEVICE_AND_PART="0:0"
CONFIG_ENV_EXT4_FILE="/my-uboot.env"
CONFIG_ENV_IS_IN_EXT4=y
enable loading environment from an ext4 partition, the device interface is virtio
, the device number is 0, the partition index is 0, and we want to read /my-uboot.env
as a environment file. If you want to change the built-in variables. Make the change in board/emulation/qemu-arm/qemu-arm.env
, for example, adding one more variable our_var=test
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* SPDX-License-Identifier: GPL-2.0+ */
/* environment for qemu-arm and qemu-arm64 */
stdin=serial,usbkbd
stdout=serial,vidconsole
stderr=serial,vidconsole
fdt_high=0xffffffff
initrd_high=0xffffffff
fdt_addr=0x40000000
scriptaddr=0x40200000
pxefile_addr_r=0x40300000
kernel_addr_r=0x40400000
ramdisk_addr_r=0x44000000
boot_targets=qfw usb scsi virtio nvme dhcp
our_var=test
Now build the u-boot binary:
1
make -j$(nproc) ARCH=arm CROSS_COMPILE=.<toolchain_prefix_path>
5.1. Making an environment file
Let’s say we want to add these variables:
1
2
var=test
varboot=test
Now the u-boot cannot understand raw file, the env must be in the u-boot env format. So we make text file my-uboot.env.txt
that contains those variables. To convert the file into uboot format, we use the uboot tool mkenvimage
. Note that the environment file size MUST match the config CONFIG_ENV_SIZE
, for example:
1
CONFIG_ENV_SIZE=0x40000
Now you have to create an env file and fill it up until reach 0x40000, this is done by mkenvimage
tool:
1
tools/mkenvimage -s 0x40000 -o my-uboot.env my-uboot.env.txt
5.2. Running QEMU
We need to emulate a block device virtio
, Let make an image first:
1
2
dd if=/dev/zero of=boot.img bs=1k count=20480
mkfs.ext4 boot.img
Now mounting it into a mount point and copy the my-uboot.env
to it:
1
2
3
4
5
sudo mkdir -p /mnt/boot
sudo mount -o loop boot.img /mnt/boot
sudo cp my-uboot.env /mnt/boot/
sudo sync
sudo umount /mnt/boot
Now we have a drive image. Let’s run qemu with u-boot and virtio:
1
qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -bios u-boot.bin -drive file=boot.img,if=none,format=raw,id=hd0 -device virtio-blk-device,drive=hd0
Enter the u-boot console, and print environment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
U-Boot 2025.04-01160-g71f497a6d390-dirty (Apr 19 2025 - 23:41:00 -0800)
DRAM: 128 MiB
Core: 51 devices, 14 uclasses, devicetree: board
Flash: 64 MiB
Loading Environment from EXT4...
=> print
ethaddr=52:54:00:12:34:56
fdtcontroladdr=4659adc0
var=test
varboot=test
Environment size: 73/262140 bytes
You see our environment is actually there along with some variables that is set by u-boot itself, and because there’s no bootcmd
, u-boot doesn’t automatically boot. In case the env file is not there, default built-in variables will be load. You might see full the list with our new variable our_var=test
like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
=> print
print
arch=arm
baudrate=115200
board=qemu-arm
board_name=qemu-arm
boot_targets=qfw usb scsi virtio nvme dhcp
bootcmd=bootflow scan -lb
bootdelay=2
cpu=armv8
ethaddr=52:54:00:12:34:56
fdt_addr=0x40000000
fdt_high=0xffffffff
fdtcontroladdr=4659adc0
initrd_high=0xffffffff
kernel_addr_r=0x40400000
loadaddr=0x40200000
our_var=test
preboot=usb start
pxefile_addr_r=0x40300000
ramdisk_addr_r=0x44000000
scriptaddr=0x40200000
stderr=serial,vidconsole
stdin=serial,usbkbd
stdout=serial,vidconsole
usb_ignorelist=0x1050:*,
vendor=emulation
Environment size: 527/262140 bytes