U-Boot, or to give its full name, Das U-Boot, began life as an open source bootloader for embedded PowerPC boards. Then, it was ported to ARM-based boards and later to other architectures, including MIPS, SH, and x86. It is hosted and maintained by Denx Software Engineering. There is plenty of information available, and a good place to start is www.denx.de/wiki/U-Boot. There is also a mailing list at <u-boot@lists.denx.de>
.
Begin by getting the source code. As with most projects, the recommended way is to clone the git archive and check out the tag you intend to use which, in this case, is the version that was current at the time of writing:
$ git clone git://git.denx.de/u-boot.git $ cd u-boot $ git checkout v2015.07
Alternatively, you can get a tarball from ftp://ftp.denx.de/pub/u-boot/.
There are more than 1,000 configuration files for common development boards and devices in the configs/
directory. In most cases, you can make a good guess of which to use, based on the filename, but you can get more detailed information by looking through the per-board README
files in the board/
directory, or you can find information in an appropriate web tutorial or forum. Beware, though, the way U-Boot is configured has undergone a lot of changes since the 2014.10 release. Double-check that the instructions you are following are appropriate.
Taking the BeagleBone Black as an example, we find that there is a likely configuration file named am335x_boneblack_defconfig
in configs/
and we find the text The binary produced by this board supports … Beaglebone Black in the board README
files for the am335x chip, board/ti/am335x/README
. With this knowledge, building U-Boot for a BeagleBone Black is simple. You need to inform U-Boot of the prefix for your cross compiler by setting the make
variable CROSS_COMPILE
and then select the configuration file using a command of the type make [board]_defconfig
, as follows:
$ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- am335x_boneblack_defconfig $ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf-
The results of the compilation are:
u-boot
: This is U-Boot in ELF object format, suitable for use with a debuggeru-boot.map
: This is the symbol tableu-boot.bin
: This is U-Boot in raw binary format, suitable for running on your deviceu-boot.img
: This is u-boot.bin
with a U-Boot header added, suitable for uploading to a running copy of U-Bootu-boot.srec
: This is U-Boot in Motorola srec
format, suitable for transferring over a serial connectionThe BeagleBone Black also requires a Secondary Program Loader (SPL), as described earlier. This is built at the same time and is named MLO
:
$ ls -l MLO u-boot* -rw-rw-r-- 1 chris chris 76100 Dec 20 11:22 MLO -rwxrwxr-x 1 chris chris 2548778 Dec 20 11:22 u-boot -rw-rw-r-- 1 chris chris 449104 Dec 20 11:22 u-boot.bin -rw-rw-r-- 1 chris chris 449168 Dec 20 11:22 u-boot.img -rw-rw-r-- 1 chris chris 434276 Dec 20 11:22 u-boot.map -rw-rw-r-- 1 chris chris 1347442 Dec 20 11:22 u-boot.srec
The procedure is similar for other targets.
Installing a bootloader on a board for the first time requires some outside assistance. If the board has a hardware debug interface, such as JTAG, it is usually possible to load a copy of U-Boot directly into RAM and set it running. From that point, you can use U-Boot commands to copy it into flash memory. The details of this are very board-specific and outside the scope of this book.
Some SoC designs have a boot ROM built in which can be used to read boot code from various external sources such as SD cards, serial interfaces, or USBs, and this is the case with the AM335x chip in the BeagleBone Black. Here is how to load U-Boot via the micro-SD card.
Firstly, format a micro-SD card so that the first partition is in FAT32 format, and mark it as bootable. If you have a direct SD slot available, the card appears as /dev/mmcblk0
, otherwise, if you are using a memory card reader, it will be seen as /dev/sdb
, or /dev/sdc
, and so on. Now, type the following command to partition the micro-SD card, assuming that the card is seen as /dev/mmcblk0
:
$ sudo sfdisk -D -H 255 -S 63 /dev/mmcblk0 << EOF ,9,0x0C,* ,,,- EOF
Format the first partition as FAT16
:
$ sudo mkfs.vfat -F 16 -n boot /dev/mmcblk0p1
Now, mount the partition you have just formatted: on some systems it is enough to simply remove the micro-SD card and then plug it back in again, on others you may have to click on an icon. On current versions of Ubuntu, it should be mounted as /media/[user]/boot
so I would copy U-Boot and the SPL to it like this:
cp MLO u-boot.img /media/chris/boot
Finally, unmount it.
With no power on the BeagleBone board, insert the micro-SD card.
Plug in the serial cable. A serial port should appear on your PC as /dev/ttyUSB0
or similar.
Start a suitable terminal program such as gtkterm
, minicom
, or picocom
and attach to the port at 115,200 bps with no flow control:
$ gtkterm -p /dev/ttyUSB0 -s 115200
Press and hold the Boot Switch button on the Beaglebone, power up the board using the external 5V power connector, and release the button after about 5 seconds. You should see a U-Boot prompt on the serial console:
U-Boot#
In this section, I will describe some of the common tasks that you can use U-Boot to perform.
Usually, U-Boot offers a command-line interface over a serial port. It gives a command prompt which is customized for each board. In the examples, I will use U-Boot#
. Typing help
prints out all the commands configured in this version of U-Boot; typing help <command>
prints out more information about a particular command.
The default command interpreter is quite simple. There is no command-line editing by pressing cursor left or right keys; there is no command completion by pressing the Tab key; there is no command history by pressing the cursor up key. Pressing any of these keys will disrupt the command you are currently trying to type and you will have to type Ctrl
+C
and start over again. The only line editing key you can safely use is the back space. As an option, you can configure a different command shell called Hush, which has more sophisticated interactive support.
The default number format is hexadecimal. For example, as shown in this command:
nand read 82000000 400000 200000
This command will read 0x200000 bytes from offset 0x400000 from the start of the NAND flash memory into RAM address 0x82000000.
U-Boot uses environment variables extensively to store and pass information between functions and even to create scripts. Environment variables are simple name=value
pairs that are stored in an area of memory. The initial population of variables may be coded in the board configuration header file, like this:
#define CONFIG_EXTRA_ENV_SETTINGS \ "myvar1=value1\0" \ "myvar2=value2\0"
You can create and modify variables from the U-Boot command line using setenv
. For example setenv foo bar
creates the variable foo
with the value bar
. Note that there is no =
sign between the variable name and the value. You can delete a variable by setting it to a null string, setenv foo
. You can print all the variables to the console using printenv
, or a single variable using printenv foo
.
Usually, it is possible to use the saveenv
command to save the entire environment to permanent storage of some kind. If there is raw NAND or NOR flash, then an erase block is reserved for this purpose, often with another used for a redundant copy to guard against corruption. If there is eMMC or SD card storage it can be stored in a file in a partition of the disk. Other options include storing in a serial EEPROM connected via an I2C or SPI interface or non-volatile RAM.
U-Boot doesn't have a filesystem. Instead, it tags blocks of information with a 64-byte header so that it can track the contents. You prepare files for U-Boot using the mkimage
command. Here is a brief summary of its usage:
$ mkimage Usage: mkimage -l image -l ==> list image header information mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image -A ==> set architecture to 'arch' -O ==> set operating system to 'os' -T ==> set image type to 'type' -C ==> set compression type 'comp' -a ==> set load address to 'addr' (hex) -e ==> set entry point to 'ep' (hex) -n ==> set image name to 'name' -d ==> use image data from 'datafile' -x ==> set XIP (execute in place) mkimage [-D dtc_options] -f fit-image.its fit-image mkimage -V ==> print version information and exit
For example, to prepare a kernel image for an ARM processor, the command is:
$ mkimage -A arm -O linux -T kernel -C gzip -a 0x80008000 \ -e 0x80008000 -n 'Linux' -d zImage uImage
Usually, you will load images from removable storage such as an SD card or a network. SD cards are handled in U-Boot by the mmc
driver. A typical sequence to load an image into memory would be:
U-Boot# mmc rescan U-Boot# fatload mmc 0:1 82000000 uimage reading uimage 4605000 bytes read in 254 ms (17.3 MiB/s) U-Boot# iminfo 82000000 ## Checking Image at 82000000 ... Legacy image found Image Name: Linux-3.18.0 Created: 2014-12-23 21:08:07 UTC Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 4604936 Bytes = 4.4 MiB Load Address: 80008000 Entry Point: 80008000 Verifying Checksum ... OK
The command mmc rescan
re-initializes the mmc
driver, perhaps to detect that an SD card has recently been inserted. Next, fatload
is used to read a file from a FAT-formatted partition on the SD card. The format is:
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
If <interface>
is mmc
, as in our case, <dev:part>
is the device number of the mmc
interface counting from zero, and the partition number counting from one. Hence <0:1>
is the first partition on the first device. The memory location, 0x82000000
, is chosen to be in an area of RAM that is not being used at this moment. If we intend to boot this kernel, we have to make sure that this area of RAM will not be overwritten when the kernel image is decompressed and located at the runtime location, 0x80008000
.
To load image files over a network you use the Trivial File Transfer Protocol (TFTP). This requires you to install a TFTP daemon, tftpd, on your development system and start it running. You also have to configure any firewalls between your PC and the target board to allow the TFTP protocol on UDP port 69 to pass through. The default configuration of tftpd allows access only to the directory /var/lib/tftpboot
. The next step is to copy the files you want to transfer to the target into that directory. Then, assuming that you are using a pair of static IP addresses, which removes the need for further network administration, the sequence of commands to load a set of kernel image files should look like this:
U-Boot# setenv ipaddr 192.168.159.42 U-Boot# setenv serverip 192.168.159.99 U-Boot# tftp 82000000 uImage link up on port 0, speed 100, full duplex Using cpsw device TFTP from server 192.168.159.99; our IP address is 192.168.159.42 Filename 'uImage'. Load address: 0x82000000 Loading: ################################################################# ################################################################# ################################################################# ###################################################### 3 MiB/s done Bytes transferred = 4605000 (464448 hex)
Finally, let's look at how to program images into NAND flash memory and read them back, which is is handled by the nand
command. This example loads a kernel image via TFTP and programs it into flash:
U-Boot# fatload mmc 0:1 82000000 uimage reading uimage 4605000 bytes read in 254 ms (17.3 MiB/s) U-Boot# nandecc hw U-Boot# nand erase 280000 400000 NAND erase: device 0 offset 0x280000, size 0x400000 Erasing at 0x660000 -- 100% complete. OK U-Boot# nand write 82000000 280000 400000 NAND write: device 0 offset 0x280000, size 0x400000 4194304 bytes written: OK
Now you can load the kernel from flash memory using nand read
:
U-Boot# nand read 82000000 280000 400000
The bootm
command starts a kernel image running. The syntax is:
bootm [address of kernel] [address of ramdisk] [address of dtb]
.
The address of the kernel image is necessary, but the address of ramdisk and dtb can be omitted if the kernel configuration does not need them. If there is a dtb but no ramdisk, the second address can be replaced with a dash (-
). That would look like this:
U-Boot# bootm 82000000 - 83000000
Plainly, typing a long series of commands to boot your board each time it is turned on is not acceptable. To automate the process, U-Boot stores a sequence of commands in environment variables. If the special variable named bootcmd
contains a script, it is run at power-up after a delay of bootdelay
seconds. If you are watching this on the serial console, you will see the delay counting down to zero. You can press any key during this period to terminate the countdown and enter into an interactive session with U-Boot.
The way that you create scripts is simple, though not easy to read. You simply append commands separated by semicolons, which must be preceded by a backslash escape character. So, for example, to load a kernel image from an offset in flash memory and boot it, you might use the following command:
setenv bootcmd nand read 82000000 400000 200000\;bootm 82000000
Let's assume that your hardware department has created a new board called "Nova" that is based on the BeagleBone Black and that you need to port U-Boot to it. You will need to understand the layout of the U-Boot code and how the board configuration mechanism works. In the 2014.10 release, U-Boot adopted the same configuration mechanism as the Linux kernel, Kconfig
. Over the next few releases, the existing configuration settings will be moved from the current location in the header files in include/configs
into Kconfig
files. As of the 2014.10 release, each board had a Kconfig
file which contains minimal information derived from the old boards.cfg
file.
The main directories you will be dealing with are:
arch
: Contains code specific to each supported architecture in directories arm, mips, powerpc, and so on. Within each architecture, there is a subdirectory for each member of the family, for example, in arch/arm/cpu
, there are directories for the architecture variants, including amt926ejs, armv7, and armv8.board
: Contains code specific to a board. Where there are several boards from the same vendor, they can be collected together into a subdirectory, hence the support for the am335x evm board, on which the BeagelBone is based, is in board/ti/am335x
.common
: Contains core functions including the command shells and the commands that can be called from them, each in a file named cmd_[command name].c
.doc
: Contains several README
files describing various aspects of U-Boot. If you are wondering how to proceed with your U-Boot port, this is a good place to start.include
: In addition to many shared header files, this contains the very important subdirectory include/configs
where you will find the majority of the board configuration settings. As the move to Kconfig
progresses, the information will be moved out into Kconfig
files but, at the time of writing, that process has only just begun.The way that Kconfig
extracts configuration information from Kconfig
files and stores the total system configuration in a file named .config
is described in some detail in Chapter 4, Porting and Configuring the Kernel. U-Boot has adopted kconfig and kbuild with one change. A U-Boot build can produce up to three binaries: a normal u-boot.bin
, a Secondary Program Loader (SPL), and a Tertiary Program Loader (TPL), each with possibly different configuration options. Consequently, lines in .config
and default configuration files can be prefixed with the codes shown in the following table to indicate which target they apply to:
None |
Normal image only |
|
SPL image only |
|
TPL image only |
|
SPL and TPL images |
|
Normal and SPL images |
|
Normal and TPL images |
|
Normal, SPL and TPL images |
Each board has a default configuration stored in configs/[board name}_defconfig
. For your Nova board, you will have to create a file named nova_defonfig.
for example, and add these lines to it:
CONFIG_SPL=y CONFIG_SYS_EXTRA_OPTIONS="SERIAL1,CONS_INDEX=1,EMMC_BOOT" +S:CONFIG_ARM=y +S:CONFIG_TARGET_NOVA=y
On the first line, CONFIG_SPL=y
causes the SPL binary, MLO, to be generated, CONFIG_ARM=y
causes the contents of arch/arm/Kconfig
to be included on line three. On line four, CONFIG_TARGET_NOVA=y
selects your board. Note that lines three and four are prefixed by +S:
so that they apply to both the SPL and normal binaries.
You should also add a menu option to the ARM architecture Kconfig
that allows people to select Nova as a target:
CONFIG_SPL=y config TARGET_NOVA bool "Support Nova!"
Each board has a subdirectory named board/[board name]
or board/[vendor]/[board name]
which should contain:
Kconfig
: Contains configuration options for the boardMAINTAINERS
: Contains a record of whether the board is currently maintained and, if so, by whomMakefile
: Used to build the board-specific codeREADME
: Contains any useful information about this port of U-Boot, for example, which hardware variants are coveredIn addition, there may be source files for board specific functions.
Your Nova board is based on a BeagleBone which, in turn, is based on a TI AM335x EVM, so, you can start by taking a copy of the am335x board files:
$ mkdir board/nova $ cp -a board/ti/am335x board/nova
Next, change the Kconfig
file to reflect the Nova board:
if TARGET_NOVA config SYS_CPU default "armv7" config SYS_BOARD default "nova" config SYS_SOC default "am33xx" config SYS_CONFIG_NAME default "nova" endif
Setting SYS_CPU
to armv7
causes the code in arch/arm/cpu/armv7
to be compiled and linked. Setting SYS_SOC
to am33xx
causes the code in arch/arm/cpu/armv7/am33xx
to be included, setting SYS_BOARD
to nova
brings in board/nova
and setting SYS_CONFIG_NAME
to nova
means that the header file include/configs/nova.h
is used for further configuration options.
There is one other file in board/nova
that you need to change, the linker script placed at board/nova/u-boot.lds
, which has a hard-coded reference to board/ti/am335x/built-in.o
. Change this to use the copy local to nova
:
diff --git a/board/nova/u-boot.lds b/board/nova/u-boot.lds index 78f294a..6689b3d 100644 --- a/board/nova/u-boot.lds +++ b/board/nova/u-boot.lds @@ -36,7 +36,7 @@ SECTIONS *(.__image_copy_start) *(.vectors) CPUDIR/start.o (.text*) - board/ti/am335x/built-in.o (.text*) + board/nova/built-in.o (.text*) *(.text*) }
Each board has a header file in include/configs
which contains the majority of the configuration. The file is named by the SYS_CONFIG_NAME
identifier in the board's Kconfig
. The format of this file is described in detail in the README
file at the top level of the U-Boot source tree.
For the purposes of your Nova board, simply copy am335x_evm.h to nova.h
to nova.h
and make a small number of changes:
diff --git a/include/configs/nova.h b/include/configs/nova.h index a3d8a25..8ea1410 100644 --- a/include/configs/nova.h +++ b/include/configs/nova.h @@ -1,5 +1,5 @@ /* - * am335x_evm.h + * nova.h, based on am335x_evm.h * * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ * @@ -13,8 +13,8 @@ * GNU General Public License for more details. */ -#ifndef __CONFIG_AM335X_EVM_H -#define __CONFIG_AM335X_EVM_H +#ifndef __CONFIG_NOVA +#define __CONFIG_NOVA #include <configs/ti_am335x_common.h> @@ -39,7 +39,7 @@ #define V_SCLK (V_OSCK) /* Custom script for NOR */ -#define CONFIG_SYS_LDSCRIPT "board/ti/am335x/u-boot.lds" +#define CONFIG_SYS_LDSCRIPT "board/nova/u-boot.lds" /* Always 128 KiB env size */ #define CONFIG_ENV_SIZE (128 << 10) @@ -50,6 +50,9 @@ #define CONFIG_PARTITION_UUIDS #define CONFIG_CMD_PART +#undef CONFIG_SYS_PROMPT +#define CONFIG_SYS_PROMPT "nova!> " + #ifdef CONFIG_NAND #define NANDARGS \ "mtdids=" MTDIDS_DEFAULT "\0" \
To build for the Nova board, select the configuration you have just created:
$ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabi- nova_defconfig $ make CROSS_COMPILE=arm-cortex_a8-linux-gnueabi-
Copy MLO
and u-boot.img
to the FAT partition of the micro-SD card you created earlier and boot the board.
We are used to the idea that booting a modern embedded processor involves the CPU boot ROM loading an SPL which loads u-boot.bin
which then loads a Linux kernel. You may be wondering if there is a way to reduce the number of steps, thereby simplifying and speeding up the boot process. The answer is U-Boot "Falcon mode", named after the Peregrine falcon which is claimed to be the fastest of all birds.
The idea is simple: have the SPL load a kernel image directly, missing out u-boot.bin
. There is no user interaction and there are no scripts. It just loads a kernel from a known location in flash or eMMC into memory, passes it a pre-prepared parameter block and starts it running. The details of configuring Falcon mode are beyond this book. If you would like more information, take a look at doc/README.falcon
.