GRUB Bootloaders User Manual

In this chapter
Role of a Bootloader page 158
Bootloader Challenges page 159
A Universal Bootloader: Das U-Boot page 164
Porting U-Boot page 172
Other Bootloaders page 183
Chapter Summary page 186
7
157
Bootloaders
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 157
158 Chapter 7 • Bootloaders
revious chapters have made reference to and even provided examples of
bootloader operations. A critical component of an embedded system, the bootloader provides the foundation from which the other system software is spawned. This chapter starts by examining the bootloader’s role in a system. We follow this with an introduction to some common features of bootloaders. Armed with this background, we take a detailed look at a popular bootloader used for embedded systems. We conclude this chapter by introducing a few of the more popular bootloaders.
Numerous bootloaders are in use today. It would be impractical in the given space to cover much detail on even the most popular ones. Therefore, we have chosen to explain concepts and use examples based on one of the more popular bootloaders in the open source community for PowerPC, MIPS, ARM, and other architectures: the U-Boot bootloader.
7.1 Role of a Bootloader
When power is first applied to a processor board, many elements of hardware must be initialized before even the simplest program can run. Each architecture and processor has a set of predefined actions and configurations, which include fetching some initialization code from an on-board storage device (usually Flash memory). This early initialization code is part of the bootloader and is responsible for breath­ing life into the processor and related hardware components.
Most processors have a default address from which the first bytes of code are fetched upon application of power and release of reset. Hardware designers use this information to arrange the layout of Flash memory on the board and to select which address range(s) the Flash memory responds to. This way, when power is first applied, code is fetched from a well-known and predictable address, and software control can be established.
The bootloader provides this early initialization code and is responsible for ini­tializing the board so that other programs can run. This early initialization code is almost always written in the processor’s native assembly language. This fact alone presents many challenges, some of which we examine here.
Of course, after the bootloader has performed this basic processor and platform initialization, its primary role becomes booting a full-blown operating system. It is
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 158
7.2 Bootloader Challenges 159
responsible for locating, loading, and passing execution to the primary operating system. In addition, the bootloader might have advanced features, such as the capa­bility to validate an OS image, the capability to upgrade itself or an OS image, and the capability to choose from among several OS images based on a developer­defined policy. Unlike the traditional PC-BIOS model, when the OS takes control, the bootloader is overwritten and ceases to exist.
1
7.2 Bootloader Challenges
Even a simple “Hello World” program written in C requires significant hardware and software resources. The application developer does not need to know or care much about these details because the C runtime environment transparently pro­vides this infrastructure. A bootloader developer has no such luxury. Every resource that a bootloader requires must be carefully initialized and allocated before it is used. One of the most visible examples of this is Dynamic Random Access Memory (DRAM).
7.2.1 DRAM Controller
DRAM chips cannot be directly read from or written to like other microprocessor bus resources. They require specialized hardware controllers to enable read and write cycles. To further complicate matters, DRAM must be constantly refreshed or the data contained within will be lost. Refresh is accomplished by sequentially read­ing each location in DRAM in a systematic manner and within the timing specifi­cations set forth by the DRAM manufacturer. Modern DRAM chips support many modes of operation, such as burst mode and dual data rate for high-performance applications. It is the DRAM controller’s responsibility to configure DRAM, keep it refreshed within the manufacturer’s timing specifications, and respond to the various read and write commands from the processor.
Setting up a DRAM controller is the source of much frustration for the newcomer to embedded development. It requires detailed knowledge of DRAM architecture, the controller itself, the specific DRAM chips being used, and the over­all hardware design. Though this is beyond the scope of this book, the interested
1
Some embedded designs protect the bootloader and provide callbacks to bootloader routines, but this is
almost never a good design approach. Linux is far more capable than bootloaders, so there is often little
point in doing so.
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 159
reader can learn more about this important concept by referring to the references at the end of this chapter. Appendix D, “SDRAM Interface Considerations,” provides more background on this important topic.
Very little can happen in an embedded system until the DRAM controller and DRAM itself have been properly initialized. One of the first things a bootloader must do is to enable the memory subsystem. After it is initialized, memory can be used as a resource. In fact, one of the first actions many bootloaders perform after memory initialization is to copy themselves into DRAM for faster execution.
7.2.2 Flash Versus RAM
Another complexity inherent in bootloaders is that they are required to be stored in nonvolatile storage but are usually loaded into RAM for execution. Again, the com­plexity arises from the level of resources available for the bootloader to rely on. In a fully operational computer system running an operating system such as Linux, it is relatively easy to compile a program and invoke it from nonvolatile storage. The runtime libraries, operating system, and compiler work together to create the infra­structure necessary to load a program from nonvolatile storage into memory and pass control to it. The aforementioned “Hello World” program is a perfect exam­ple. When compiled, it can be loaded into memory and executed simply by typing the name of the executable (
hello) on the command line (assuming, of course, that
the executable exists somewhere on your
PATH).
This infrastructure does not exist when a bootloader gains control upon power-on. Instead, the bootloader must create its own operational context and move itself, if required, to a suitable location in RAM. Furthermore, additional complexity is introduced by the requirement to execute from a read-only medium.
7.2.3 Image Complexity
As application developers, we do not need to concern ourselves with the layout of a binary executable file when we develop applications for our favorite platform. The compiler and binary utilities are preconfigured to build a binary executable image containing the proper components needed for a given architecture. The linker places startup (prologue) and shutdown (epilogue) code into the image. These objects set up the proper execution context for your application, which typically starts at
main() in your application.
160 Chapter 7 • Bootloaders
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 160
This is absolutely not the case with a typical bootloader. When the bootloader gets control, there is no context or prior execution environment. In a typical sys­tem, there might not be any DRAM until the bootloader initializes the processor and related hardware. Consider what this means. In a typical C function, any local variables are stored on the stack, so a simple function like the one in Listing 7-1 is unusable.
Listing 7-1
Simple C function
int setup_memory_controller(board_info_t *p)
{ unsigned int *dram_controller_register = p->dc_reg;
...
When a bootloader gains control on power-on, there is no stack and no stack pointer. Therefore, a simple C function similar to Listing 7-1 will likely crash the processor because the compiler will generate code to create and initialize the pointer
dram_controller_register on the stack, which does not yet exist. The boot-
loader must create this execution context before any C functions are called.
When the bootloader is compiled and linked, the developer must exercise com­plete control over how the image is constructed and linked. This is especially true if the bootloader is to relocate itself from Flash to RAM. The compiler and linker must be passed a handful of parameters defining the characteristics and layout of the final executable image. Two primary characteristics conspire to add complexity to the final binary executable image.
The first characteristic that presents complexity is the need to organize the startup code in a format compatible with the processor’s boot sequence. The first bytes of executable code must be at a predefined location in Flash, depending on the processor and hardware architecture. For example, the AMCC PowerPC 405GP processor seeks its first machine instructions from a hard-coded address of 0xFFFF_FFFC. Other processors use similar methods with different details. Some processors are configurable at power-on to seek code from one of several predefined locations, depending on hardware configuration signals.
How does a developer specify the layout of a binary image? The linker is passed a linker description file, also called a linker command script. This special file can be thought of as a recipe for constructing a binary executable image. Listing 7-2 contains a snippet from an existing linker description file in use in a popular boot­loader, which we discuss shortly.
7.2 Bootloader Challenges 161
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 161
Listing 7-2
Linker Command Script—Reset Vector Placement
SECTIONS {
.resetvec 0xFFFFFFFC : {
*(.resetvec)
} = 0xffff
...
A complete description of linker command scripts syntax is beyond the scope of this book. The interested reader is directed to the GNU LD manual referenced at the end of this chapter. Looking at Listing 7-2, we see the beginning of the defi­nition for the output section of the binary ELF image. It directs the linker to place the section of code called
.resetvec at a fixed address in the output image, start-
ing at location 0xFFFF_FFFC. Furthermore, it specifies that the rest of this section shall be filled with all ones (0xFFFF.) This is because an erased Flash memory array contains all ones. This technique not only saves wear and tear on the Flash memory, but it also significantly speeds up programming of that sector.
Listing 7-3 is the complete assembly language file from a recent U-Boot distri­bution that defines the
.resetvec code section. It is contained in an assembly lan-
guage file called
.../cpu/ppc4xx/resetvec.S. Notice that this code section
cannot exceed 4 bytes in length in a machine with only 32 address bits. This is because only a single instruction is defined in this section, no matter what config­uration options are present.
Listing 7-3
Source Definition of .resetvec
/* Copyright MontaVista Software Incorporated, 2000 */ #include <config.h>
.section .resetvec,"ax"
#if defined(CONFIG_440)
b _start_440 #else #if defined(CONFIG_BOOT_PCI) && defined(CONFIG_MIP405)
b _start_pci #else
b _start #endif #endif
162 Chapter 7 • Bootloaders
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 162
This assembly language file is very easy to understand, even if you have no assem­bly language programming experience. Depending on the particular configuration (as specified by the
CONFIG_* macros), an unconditional branch instruction (b in
PowerPC assembler syntax) is generated to the appropriate start location in the main body of code. This branch location is a 4-byte PowerPC instruction, and as we saw in the snippet from the linker command script in Listing 7-2, this simple branch instruction is placed in the absolute Flash address of 0xFFFF_FFFC in the output image. As mentioned earlier, the PPC 405GP processor fetches its first instruction from this hard-coded address. This is how the first sequence of code is defined and provided by the developer for this particular architecture and processor combination.
7.2.4 Execution Context
The other primary reason for bootloader image complexity is the lack of execution context. When the sequence of instructions from Listing 7-3 starts executing (recall that these are the first machine instructions after power-on), the resources available to the running program are nearly zero. Default values designed into the hardware ensure that fetches from Flash memory work properly and that the system clock has some default values, but little else can be assumed.
2
The reset state of each proces­sor is usually well defined by the manufacturer, but the reset state of a board is defined by the hardware designers.
Indeed, most processors have no DRAM available at startup for temporary stor­age of variables or, worse, for a stack that is required to use C program calling con­ventions. If you were forced to write a “Hello World” program with no DRAM and, therefore, no stack, it would be quite different from the traditional “Hello World” example.
This limitation places significant challenges on the initial body of code designed to initialize the hardware. As a result, one of the first tasks the bootloader performs on startup is to configure enough of the hardware to enable at least some minimal amount of RAM. Some processors designed for embedded use have small amounts of on-chip static RAM available. This is the case with the PPC 405GP we’ve been discussing. When RAM is available, a stack can be allocated using part of that
7.2 Bootloader Challenges 163
2
The details differ, depending upon architecture, processor, and details of the hardware design.
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 163
RAM, and a proper context can be constructed to run higher-level languages such as C. This allows the rest of the processor and platform initialization to be written in something other than assembly language.
7.3 A Universal Bootloader: Das U-Boot
Many open-source and commercial bootloaders are available, and many more one­of-a-kind home-grown designs are in widespread use today. Most of these have some level of commonality of features. For example, all of them have some capa­bility to load and execute other programs, particularly an operating system. Most interact with the user through a serial port. Support for various networking subsys­tems (such as Ethernet) is less common but a very powerful 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 ulti­mately results in lower development costs.
In this section, we study an existing bootloader that has become very popular in the embedded Linux community. The official name for this bootloader is Das U-Boot. It is maintained by Wolfgang Denk and hosted on SourceForge at http:// u-boot.sourceforge.net/. U-Boot has support for multiple architectures and has a large following of embedded developers and hardware manufacturers who have adopted it for use in their projects and have contributed to its development.
7.3.1 System Configuration: 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, configuration of a bootloader is done at compile time. This method signifi­cantly reduces the complexity of the bootloader, 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, and a few soft links in the source tree that select
164 Chapter 7 • Bootloaders
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 164
the correct subdirectories based on target board, architecture, and CPU. When con­figuring 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 distribution describes the details of configuration and is the best source for this information.
Configuration of U-Boot is done 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
CFG_XXXX. In general, configura-
tion options (
CONFIG_XXX) are user configurable and enable specific U-Boot oper-
ational features. Configuration settings (
CFG_XXX) are usually 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.
Numerous features and modes of operation can be selected by adding definitions to the board-configuration file. Listing 7-4 contains a partial configuration header file for a fictitious board based on the PPC 405GP processor.
7.3 A Universal Bootloader: Das U-Boot 165
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 165
Listing 7-4
Partial U-Boot Board-Configuration Header File
#define CONFIG_405GP /* Processor definition */ #define CONFIG_4XX /* Sub-arch specification, 4xx family */
#define CONFIG_SYS_CLK_FREQ 33333333 /* PLL Frequency */ #define CONFIG_BAUDRATE 9600 #define CONFIG_PCI /* Enable support for PCI */ ... #define CONFIG_COMMANDS (CONFIG_CMD_DFL | CFG_CMD_DHCP) ... #define CFG_BASE_BAUD 691200
/* The following table includes the supported baudrates */ #define CFG_BAUDRATE_TABLE \
{1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400}
#define CFG_LOAD_ADDR 0x100000 /* default load address */ ... /* Memory Bank 0 (Flash Bank 0) initialization */ #define CFG_EBC_PB0AP 0x9B015480 #define CFG_EBC_PB0CR 0xFFF18000
#define CFG_EBC_PB1AP 0x02815480 #define CFG_EBC_PB1CR 0xF0018000 ...
Listing 7-4 gives 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, CPU family (4xx), PLL clock frequency, serial port baud rate, and PCI support. We have included examples of configuration variables (
CONFIG_XXX) and configuration settings
(
CFG_XXX). The last few lines are actual processor register values required to initial-
ize the external bus controller for memory banks 0 and 1. You can see that these values can come only from a 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. The interested reader can learn much more by looking at the U-Boot code directly, especially the excellent README file.
166 Chapter 7 • Bootloaders
hall_150015_ch07.qxd 8/25/06 3:16 PM Page 166
Loading...
+ 22 hidden pages