U-Boot bootloader with boot menu

History

During early startup, the RPi2 and 3 firmware expects (by default) a file kernel7.img on the boot partition that should contain a valid kernel and boots it with the arguments in cmdline.txt. However, multiple kernels can be installed and the user would have to manually update the file kernel7.img depending on the desired kernel. To provide a convenient environment, I tried to installed Grub on the device which automatically boots the most recent kernel. Unfortunately, I was not able to get it to work on the RPi2 although a version for ARM exists. Hence, I built U-Boot for the RPi2 and wrote a small script update-uboot similar to the handy update-grub that looks for available kernels and initrd images and creates an U-Boot config that presents a boot menu at startup and boots the most recent kernel after a timeout of 2 seconds.

While developing this menu, the RPi2 freezed randomly during U-Boot execution. It turned out that the default scriptaddr variable caused the boot menu to be loaded at address 0x0 but something else is using the memory in this area and hence caused problems. After I changed the address to 0x00100000 the boot process worked smoothly. With U-Boot 201601, this has been fixed upstream.

Configuration

The U-Boot package installs a script into /etc/kernel/postinst.d/ that calls /usr/sbin/update-uboot after a kernel package was installed. First, update-uboot loads the following configuration file:

/etc/uboot.cfg:

### configure on which devices the u-boot output is shown
### e.g., to enable HDMI output also for non-error messages, add ",lcd" below
# UBOOT_STDOUT=serial

### override device tree set by firmware
# UBOOT_DEVICE_TREE=

### default entry in the menu. Entries are enumerated as "bootX" where X is the
### X-th entry in the menu. Default is "boot0", i.e. the first entry
# UBOOT_DEFAULT=boot0

### timeout after which the default entry will be booted
# UBOOT_MENU_TIMEOUT=2

### default kernel arguments. If a file /boot/cmdline-${kernel_version} exists,
### its content overrides the default arguments
# UBOOT_KERNEL_ARGS="rootfstype=btrfs console=ttyAMA0,115200 console=tty1 selinux=0 smsc95xx.turbo_mode=N dwc_otg.lpm_enable=0 kgdboc=ttyAMA0,115200 elevator=noop quiet splash logo.nologo"

### if set to 1, prepend the default kernel arguments with the kernel arguments
### set by the firmware
# UBOOT_PREPEND_FW_KERNEL_ARGS=1

### partition with the root filesystem (will be passed to the kernel)
### default is the second partition on the SD card
# UBOOT_ROOT_PART=/dev/mmcblk0p2

### include a custom configuration file, e.g., to generate custom bootloader
### entries. See uboot.user.cfg for an example. Make sure this file is executable
# UBOOT_USER_CFG=/etc/uboot.user.cfg

### if GPIO pin 21 is high, execute UBOOT_FAILSAFE_COMMAND. If this command does
### return, normal boot procedure continues.
# UBOOT_FAILSAFE_GPIO_PIN=21

### If set, this file should be removed by the booted operating system. If this
### file is still present during the next boot, u-boot will execute
### UBOOT_FAILSAFE_COMMAND. If UBOOT_FAILSAFE_COMMAND returns, normal boot
#### procedure continues.
# UBOOT_FAILSAFE_FILE=boot-failure-check

### run script failsafe.scr (see update-uboot how to generate such a script)
# UBOOT_FAILSAFE_COMMAND="fatload mmc 0:1 0x02050000 failsafe.scr; source 0x02050000"

### run the second kernel in the menu
# UBOOT_FAILSAFE_COMMAND="run boot1"

### look for and execute a boot script on ${target}, where target can be:
### dhcp, mmc0, pxe or usb0
# UBOOT_FAILSAFE_COMMAND="run bootcmd_${target}"

In case you are upgrading the uboot package, please add the following line to /boot/config.txt:

device_tree_address=0x00000100

This will instruct the RPi's firmware to load the device tree (you can think of this as a replacement of the BIOS on PCs) to a fixed address until uboot can determine the varying address used by recent firmwares automatically.

Handling faults

Starting with version 201601, the update-uboot script contains two mechanisms to alter the boot process if the default process somehow fails. If activated, the first mechanism checks a GPIO pin if it is high and if yes, the U-Boot command in the UBOOT_FAILSAFE_COMMAND variable is executed during boot instead. The default config above contains three examples, the first loads and executes a failsafe.scr script instead of the normal boot.scr from the SD card (see update-uboot how such a script is generated). The second command would start the second boot entry and the third command would start the original U-Boot boot process that looks for executable scripts on the given device/target.

If UBOOT_FAILSAFE_FILE is set, U-Boot will write a file with the given name to the boot partition. If the file does not already exist, normal boot procedure continues. If the file exists, the chosen UBOOT_FAILSAFE_COMMAND will be executed instead. If you want to use this mechanism, make sure the operating system will remove the file after a successfull boot. For example, you could put rm /boot/boot-failure-check into /etc/rc.local.

Boot logo

With version 201601, this U-Boot package also contains experimental splash image support. Call /usr/bin/uboot-create-logo.sh with a PNG or JPG image and the script will create a splash.bmp file in the current work directory. Copy this file to /boot and U-Boot will automatically load this image. Make sure you have installed the netpbm package before calling the logo script. Please note that RPi2+3 support is not complete yet, e.g., drawing an image might take around 1 second.

Booting with U-Boot

After the U-Boot initialization messages, you will see a menu like this on the serial console:

Available boot options:
-----------------------
   "run boot0" will boot vmlinuz-4.1.16-rpi
   "run boot1" will boot vmlinuz-4.1.15-rpi

writing boot-failure-check
1 bytes written
Will execute "run boot0" in 2 seconds, abort with CTRL+c...

By default, output to HDMI screen is disabled and only in case of an error a message is shown. This can be changed by setting UBOOT_STDOUT in /etc/uboot.cfg accordingly.

Please note, while this U-Boot version is compiled with USB keyboard support, it seems like such keyboards do not work yet. This might change with improving support of the RPi2. Hence, to manually alter the boot process, you will either need a serial connection or you have to use one of the failsafe mechanisms above, e.g., connect a switch or button to the GPIO pins.

Serial console with the RPi3

In the default configuration of the RPi3, the pins that were previously connected to the UART interface for the serial console are now connected to the Bluetooth interface. This change can be undone by loading a device tree overlay in the config.txt but this overlay is only applied by the Linux kernel currently. Hence, you can use the serial console with Linux but you cannot see the menu or control uboot over the UART interface yet until support for applying the overlay is added to uboot.

The system-on-chip of the RPi2 and 3 has two UART interfaces: a full-featured interface (PL011) and a limited interface (called mini UART). In the RPi2, the PL011 interface is connected to the UART GPIO pins 14 and 15. In the RPi3, the PL011 interface is connected to the Bluetooth device. In order to use the serial console on the RPi3, you have two choices. Either you disable the Bluetooth device and reroute the UART interface to GPIO pins 14 and 15, or you attach the Bluetooth module to the mini UART.

To apply one of the approaches, ensure you have installed at least rpi-firmware-20160309 installed (or download the overlay files from the official GitHub repository) and then add one of the following to /boot/config.txt:

dtoverlay=pi3-disable-bt

dtoverlay=pi3-miniuart-bt

See also: 1, 2, 3

social