Sunday, April 21, 2013

MIPS Bootstrapping

Bootstrapping is the process of taking a CPU just out of reset, fetching and executing instructions serially, to a more complex running environment. The program that does that is called a "Boot loader" or "Boot strap code" or simply "Boot code".

First Instruction Fetch

When power is applied to a processor and it comes out of reset, it fetches its first instruction from an address that is hardwired. This address is known as the "Boot Vector" or the "Reset Vector". The MIPS processors' boot vector is located at physical address 0x1FC00000. The MIPS processors have MMU enabled as soon as they are powered on. The MIPS core thus presents a virtual address of 0xBFC00000. The MMU translates this address to physical address of 0x1FC00000, the boot vector. This translation again is hardwired. Typically, a boot device is present at this address and responds to the read request of the processor2. See Firgure 1.  The offset 0 of bootstrap code in Figure 1 may not always be true. This may be changed by your hardware designer by pulling the physical address lines high, there by changing the final physical address.

A boot device is a permanent storage device which gives random access on reads, just like RAM. NOR flash, NVRAM etc. are a few boot devices.

U-Boot: The Boot Loader

U-Boot is a boot loader from Denx Software Engineering. This boot loader supports multiple architecture including MIPS. We can draw a general outline on what U-Boot does as part of bootstrapping MIPS. Board specific and other details will be left out.

U-Boot can be divided in two stages: Stage 1 and Stage 2. 

Stage 1 Loader

Stage 1 is completely or partially written in assembly language. A program written in C language requires memory to be up and stack to set in stack register. When the CPU is executing the stage 1 code, usually at that time the RAM is not available and thus the C code cannot run. For this reason, first few instructions are coded in assembly language.

How soon can the assembly code can jump to C, depends on what features does CPU provide. Some of the MIPS implementations give a way to lock the cache lines and use them as stack. Some have seperate SRAM which can be used as temporary stack. In these cases, assembly can quickly use setup these temporary stacks and jump to C code. Otherwise, all code to initialize RAM has to be written in assembly, which can be quite a pain. Whatever the case is, all this time CPU is running by fetching instructions directly from boot device. The accesses to boot device are not fast as RAM and thus CPU cannot continue doing so. Thus the next step for stage 1 loader is to copy the rest of the code from the boot device to RAM. Before processor can start executing from RAM, the recently copied code in RAM is relocated. What now runs in RAM can be called as the stage 2 loader.

Stage 2 Loader

The stage 2 loader is responsible for doing the following:

  1. Prepare and initialize the other subsystems like other storage devices, USB, Networks etc.
  2. The evironment variables are initialized.
  3. Give the user prompt for further commands or autoboot a predefined operating system.

Loading Linux Kernel

U-Boot usually loads Linux as any other ELF binary. The ELF header is parsed and the entry point is taken from the ELF header. The kernel entry function pointer declaration below, shows how U-Boot passes the control over to Linux and what information is provided along with.

void (*theKernel)(int, char **, char **, int *);

U-Boot passes 4 arguments when it calls the Linux kernel. The following are the arguments passed:
  1. Number of arguments.
  2. Linux arguments.
  3. Linux environment variables.
  4. Nothing (0).

As part of environment variables, information like memsize, initrd start, flash start, etc is passed over. The Linux arguments contains whatever is defined in bootargs environtment variable of U-Boot. The 4 arguments are passed via a0, a1, a2 and a3 registers.