- 7.1 Role of a Bootloader
- 7.2 Bootloader Challenges
- 7.3 A Universal Bootloader: Das U-Boot
- 7.4 Porting U-Boot
- 7.5 Device Tree Blob (Flat Device Tree)
- 7.6 Other Bootloaders
- 7.7 Summary
7.3 A Universal Bootloader: Das U-Boot
Many open source and commercial bootloaders are available, and many more one-of-a-kind homegrown designs are in widespread use today. Most of these have some level of commonality of features. For example, all of them have some capability to load and execute other programs, particularly an operating system. Most interact with the user through a serial port. Support for various networking subsystems (such as Ethernet) is a very powerful but less common feature.
Many bootloaders are specific to a particular architecture. The capability of a bootloader to support a wide variety of architectures and processors can be an important feature to larger development organizations. It is not uncommon for a single development organization to have multiple processors spanning more than one architecture. Investing in a single bootloader across multiple platforms ultimately results in lower development costs.
This section studies an existing bootloader that has become very popular in the embedded Linux community. The official name of this bootloader is Das U-Boot. It is maintained by Wolfgang Denx and hosted at www.denx.de/wiki/U-Boot. U-Boot supports multiple architectures and has a large following of embedded developers and hardware manufacturers who have adopted it for use in their projects and who have contributed to its development.
7.3.1 Obtaining U-Boot
The simplest way to get the U-Boot source code is via git. If you have git installed on your desktop or laptop, simply issue this command:
$ git clone git://git.denx.de/u-boot.git
This creates a directory called u-boot in the directory in which you executed this command.
If you don't have git, or you prefer to download a snapshot instead, you can do so through the git server at denx.de. Point your browser to http://git.denx.de/ and click the "summary" link on the first project, u-boot.git. This takes you to a summary screen and provides a "snapshot" link, which generates and downloads a tarball that you can install on your system. Select the most recent snapshot, which is at the top of the "shortlog" list.
7.3.2 Configuring U-Boot
For a bootloader to be useful across many processors and architectures, some method of configuring the bootloader is necessary. As with the Linux kernel itself, a bootloader is configured at compile time. This method significantly reduces the complexity of the binary bootloader image, which in itself is an important characteristic.
In the case of U-Boot, board-specific configuration is driven by a single header file specific to the target platform, together with a few soft links in the source tree that select the correct subdirectories based on target board, architecture, and CPU. When configuring U-Boot for one of its supported platforms, issue this command:
$ make <platform>_config
Here, platform is one of the many platforms supported by U-Boot. These platform configuration targets are listed in the top-level U-Boot makefile. For example, to configure for the Spectrum Digital OSK, which contains a TI OMAP 5912 processor, issue this command:
$ make omap5912osk_config
This configures the U-Boot source tree with the appropriate soft links to select ARM as the target architecture, the ARM926 core, and the 5912 OSK as the target platform.
The next step in configuring U-Boot for this platform is to edit the configuration file specific to this board. This file is found in the U-Boot ../include/configs subdirectory and is called omap5912osk.h. The README file that comes with the U-Boot source code describes the details of configuration and is the best source of this information. (For existing boards that are already supported by U-Boot, it may not be necessary to edit this board-specific configuration file. The defaults may be sufficient for your needs. Sometimes minor edits are needed to update memory size or flash size, because many reference boards can be purchased with varying configurations.)
U-Boot is configured using configuration variables defined in a board-specific header file. Configuration variables have two forms. Configuration options are selected using macros in the form of CONFIG_ XXXX . Configuration settings are selected using macros in the form of CONFIG_SYS_ XXXX . In general, configuration options (CONFIG_ XXX ) are user-configurable and enable specific U-Boot operational features. Configuration settings (CONFIG_SYS_ XXX ) usually are hardware-specific and require detailed knowledge of the underlying processor and/or hardware platform. Board-specific U-Boot configuration is driven by a header file dedicated to that specific platform that contains configuration options and settings appropriate for the underlying platform. The U-Boot source tree includes a directory where these board-specific configuration header files reside. They can be found in .../include/configs from the top-level U-Boot source directory.
You can select numerous features and modes of operation by adding definitions to the board-configuration file. Listing 7-4 is a partial configuration header file for the Yosemite board based on the AMCC 440EP processor.
Listing 7-4. Portions of the U-Boot Board-Configuration Header File
/*--------------------------------------------------------------- * High Level Configuration Options *---------------------------------------------------------------*/ /* This config file is used for Yosemite (440EP) and Yellowstone (440GR)*/ #ifndef CONFIG_YELLOWSTONE #define CONFIG_440EP 1 /* Specific PPC440EP support */ #define CONFIG_HOSTNAME yosemite #else #define CONFIG_440GR 1 /* Specific PPC440GR support */ #define CONFIG_HOSTNAME yellowstone #endif #define CONFIG_440 1 /* ... PPC440 family */ #define CONFIG_4xx 1 /* ... PPC4xx family */ #define CONFIG_SYS_CLK_FREQ 66666666 /* external freq to pll */ <...> /*----------------------------------------------------------------------- * Base addresses -- Note these are effective addresses where the * actual resources get mapped (not physical addresses) *-------------------------------------------------------------------*/ #define CONFIG_SYS_FLASH_BASE 0xfc000000 /* start of FLASH */ #define CONFIG_SYS_PCI_MEMBASE 0xa0000000 /* mapped pci memory*/ #define CONFIG_SYS_PCI_MEMBASE1 CONFIG_SYS_PCI_MEMBASE + 0x10000000 #define CONFIG_SYS_PCI_MEMBASE2 CONFIG_SYS_PCI_MEMBASE1 + 0x10000000 #define CONFIG_SYS_PCI_MEMBASE3 CONFIG_SYS_PCI_MEMBASE2 + 0x10000000 <...> #ifdef CONFIG_440EP #define CONFIG_CMD_USB #define CONFIG_CMD_FAT #define CONFIG_CMD_EXT2 #endif <...> /*---------------------------------------------------- * External Bus Controller (EBC) Setup *----------------------------------------------------*/ #define CONFIG_SYS_FLASH CONFIG_SYS_FLASH_BASE #define CONFIG_SYS_CPLD 0x80000000 /* Memory Bank 0 (NOR-FLASH) initialization */ #define CONFIG_SYS_EBC_PB0AP 0x03017300 #define CONFIG_SYS_EBC_PB0CR (CONFIG_SYS_FLASH | 0xda000) /* Memory Bank 2 (CPLD) initialization */ #define CONFIG_SYS_EBC_PB2AP 0x04814500 #define CONFIG_SYS_EBC_PB2CR (CONFIG_SYS_CPLD | 0x18000) <...>
Listing 7-4 gives you an idea of how U-Boot itself is configured for a given board. An actual board-configuration file can contain hundreds of lines similar to those found here. In this example, you can see the definitions for the CPU (CONFIG_440EP), board name (CONFIG_HOSTNAME), clock frequency, and Flash and PCI base memory addresses. We have included examples of configuration variables (CONFIG_ XXX ) and configuration settings (CONFIG_SYS_ XXX ). The last few lines are actual processor register values required to initialize the external bus controller for memory banks 0 and 1. You can see that these values can come only from detailed knowledge of the board and processor.
Many aspects of U-Boot can be configured using these mechanisms, including what functionality will be compiled into U-Boot (support for DHCP, memory tests, debugging support, and so on). This mechanism can be used to tell U-Boot how much and what kind of memory is on a given board, and where that memory is mapped. You can learn much more by looking at the U-Boot code directly, especially the excellent README file.
7.3.3 U-Boot Monitor Commands
U-Boot supports more than 70 standard command sets that enable more than 150 unique commands using CONFIG_CMD_* macros. A command set is enabled in U-Boot through the use of configuration setting (CONFIG_*) macros. For a complete list from a recent U-Boot snapshot, consult Appendix B, "U-Boot Configurable Commands." Table 7-1 shows just a few, to give you an idea of the capabilities available.
Table 7-1. Some U-Boot Configurable Commands
Command Set |
Description Commands |
CONFIG_CMD_FLASH |
Flash memory commands |
CONFIG_CMD_MEMORY |
Memory dump, fill, copy, compare, and so on |
CONFIG_CMD_DHCP |
DHCP support |
CONFIG_CMD_PING |
Ping support |
CONFIG_CMD_EXT2 |
EXT2 file system support |
To enable a specific command, define the macro corresponding to the command you want. These macros are defined in your board-specific configuration file. Listing 7-4 shows several commands being enabled in the board-specific configuration file. There you see CONFIG_CMD_USB, CONFIG_CMD_FAT, and CONFIG_CMD_EXT2 being defined conditionally if the board is a 440EP.
Instead of specifying each individual CONFIG_CMD_* macro in your own board-specific configuration header, you can start from the full set of commands defined in .../include/config_cmd_all.h. This header file defines every command available. A second header file, .../include/config_cmd_default.h, defines a list of useful default U-Boot command sets such as tftpboot (boot an image from a tftpserver), bootm (boot an image from memory), memory utilities such as md (display memory), and so on. To enable your specific combination of commands, you can start with the default and add and subtract as necessary. Listing 7-4 adds the USB, FAT, and EXT2 command sets to the default. You can subtract in a similar fashion, starting from config_cmd_all.h:
#include "condif_cmd_all.h" #undef CONFIG_CMD_DHCP #undef CONFIG_CMD_FAT #undef CONFIG_CMD_FDOS <...>
Take a look at any board-configuration header file in .../include/configs/ for examples.
7.3.4 Network Operations
Many bootloaders include support for Ethernet interfaces. In a development environment, this is a huge time saver. Loading even a modest kernel image over a serial port can take minutes versus a few seconds over an Ethernet link, especially if your board supports Fast or Gigabit Ethernet. Furthermore, serial links are more prone to errors from poorly behaved serial terminals, line noise, and so on.
Some of the more important features to look for in a bootloader include support for the BOOTP, DHCP, and TFTP protocols. If you're unfamiliar with these, BOOTP (Bootstrap Protocol) and DHCP (Dynamic Host Configuration Protocol) enable a target device with an Ethernet port to obtain an IP address and other network-related configuration information from a central server. TFTP (Trivial File Transfer Protocol) allows the target device to download files (such as a Linux kernel image) from a TFTP server. References to these protocol specifications are listed at the end of this chapter. Servers for these services are described in Chapter 12, "Embedded Development Environment."
Figure 7-1 illustrates the flow of information between the target device and a BOOTP server. The client (U-Boot, in this case) initiates the exchange by sending a broadcast packet searching for a BOOTP server. The server responds with a reply packet that includes the client's IP address and other information. The most useful data includes a filename used to download a kernel image.
Figure 7-1 BOOTP client/server handshake
In practice, dedicated BOOTP servers no longer exist as stand-alone servers. DHCP servers included with your favorite Linux distribution also support BOOTP protocol packets and are almost universally used for BOOTP operations.
The DHCP protocol builds on BOOTP. It can supply the target with a wide variety of configuration information. In practice, the information exchange is often limited by the target/bootloader DHCP client implementation. Listing 7-5 shows a DHCP server configuration block identifying a single target device. This is a snippet from a DHCP configuration file from the Fedora Core 2 DHCP implementation.
Listing 7-5. DHCP Target Specification
host coyote { hardware ethernet 00:0e:0c:00:82:f8; netmask 255.255.255.0; fixed-address 192.168.1.21; server-name 192.168.1.9; filename "coyote-zImage"; option root-path "/home/sandbox/targets/coyote-target"; } ...
When this DHCP server receives a packet from a device matching the hardware Ethernet address contained in Listing 7-5, it responds by sending that device the parameters in this target specification. Table 7-2 describes the fields in the target specification.
Table 7-2. DHCP Target Parameters
DHCP Target Parameter |
Purpose |
Description |
host |
Hostname |
Symbolic label from the DHCP configuration file |
hardware ethernet |
Ethernet hardware address |
Low-level Ethernet hardware address of the target's Ethernet interface |
fixed-address |
Target IP address |
The IP address that the target will assume |
netmask |
Target netmask |
The IP netmask that the target will assume |
server-name |
TFTP server IP address |
The IP address to which the target will direct requests for file transfers, the root file system, and so on |
filename |
TFTP filename |
The filename that the bootloader can use to boot a secondary image (usually a Linux kernel) |
root-path |
NFS root path |
Defines the network path for the remote NFS root mount |
When the bootloader on the target board has completed the BOOTP or DHCP exchange, these parameters are used for further configuration. For example, the bootloader uses the target IP address (fixed-address) to bind its Ethernet port to this IP address. The bootloader then uses the server-name field as a destination IP address to request the file contained in the filename field, which, in most cases, represents a Linux kernel image. Although this is the most common use, this same scenario could be used to download and execute manufacturing test and diagnostics firmware.
It should be noted that the DHCP protocol supports many more parameters than those detailed in Table 7-2. These are simply the more common parameters you might encounter for embedded systems. See the DHCP specification referenced at the end of this chapter for complete details.
7.3.5 Storage Subsystems
Many bootloaders support the capability of booting images from a variety of nonvolatile storage devices in addition to the usual Flash memory. The difficulty in supporting these types of devices is the relative complexity in both hardware and software. To access data on a hard drive, for example, the bootloader must have device driver code for the IDE controller interface, as well as knowledge of the underlying partition scheme and file system. This is not trivial and is one of the tasks more suited to full-blown operating systems.
Even with the underlying complexity, methods exist for loading images from this class of device. The simplest method is to support the hardware only. In this scheme, no knowledge of the file system is assumed. The bootloader simply raw-loads from absolute sectors on the device. This scheme can be used by dedicating an unformatted partition from sector 0 on an IDE-compatible device (such as CompactFlash) and loading the data found there without any structure imposed on the data. This is a simple configuration for loading a kernel image or other binary image from a block storage device. Additional partitions on the device can be formatted for a given file system and can contain complete file systems. After the kernel boots, Linux device drivers can be used to access the additional partitions.
U-Boot can load an image from a specified raw partition or from a partition with a file system structure. Of course, the board must have a supported hardware device (an IDE subsystem), and U-Boot must be so configured. Adding CONFIG_CMD_IDE to the board-specific configuration file enables support for an IDE interface, and adding CONFIG_CMD_BOOTD enables support for booting from a raw partition. If you are porting U-Boot to a custom board, you will likely have to modify U-Boot to understand your particular hardware.
7.3.6 Booting from Disk
As just described, U-Boot supports several methods for booting a kernel image from a disk subsystem. This simple command illustrates one of the supported methods:
=> diskboot 0x400000 0:0
To understand this syntax, you must first understand how U-Boot numbers disk devices. The 0:0 in this example specifies the device and partition. In this simple example, U-Boot performs a raw binary load of the image found on the first IDE device (IDE device 0) from the first partition (partition 0) found on this device. The image is loaded into system memory at physical address 0x400000.
After the kernel image has been loaded into memory, the U-Boot bootm command (boot from memory) is used to boot the kernel:
=> bootm 0x400000