Flameman/uboot

From eLinux.org
< Flameman
Revision as of 11:42, 31 July 2011 by Garbear (talk | contribs) (Das U-Boot: The Universal Boot Loader: Layout fixes)
Jump to: navigation, search

Das U-Boot: The Universal Boot Loader

         Believe me, I have been there, done that 
         -- programming in U-Boot at pulling my hair out. 
         Staying up nights and ignoring the family 
         Now, I just sell these boards, 
         life is too short to always be angry. 
         Word of advice: If you are developing software, 
         you should really have a JTAG programmer. 
         Can't live without a JTAG programmer.


Exciting new embedded Linux devices are appearing at an astonishing rate. From tiny 3 inch "Gumstix" boards to PDAs and smart-phones embedded Linux is everywhere. Installing and booting Linux on these wildly varying boards is quite a chore. Without a good boot loader these machines are just complicated hunks of silicon with nothing to do. That's where Das U-Boot, a Free Software universal boot loader, steps in.

A boot loader, sometimes referred to as a boot monitor, is a small piece of software that executes soon after powering up a computer. On your desktop Linux PC you may be familiar with lilo or grub, which resides on the master boot record (MBR) of your hard drive. After the PC BIOS performs various system initializations it executes the boot loader located in the MBR. The boot loader then passes system information to the kernel and then executes the kernel. For instance, the boot loader tells the kernel which hard drive partition to mount as root.

In an embedded system the role of the boot loader is more complicated since these systems do not have a BIOS to perform the initial system configuration. The low level initialization of microprocessors, memory controllers, and other board specific hardware varies from board to board and CPU to CPU. These initializations must be performed before a Linux kernel image can execute.

At a minimum an embedded loader provides the following features:

  • Initializing the hardware, especially the memory controller.
  • Providing boot parameters for the Linux kernel.
  • Starting the Linux kernel

Additionally, most boot loaders also provide "convenience" features that simplify development:

  • Reading and writing arbitrary memory locations.
  • Uploading new binary images to the board's RAM via a serial line or Ethernet
  • Copying binary images from RAM to FLASH memory

Das U-Boot

Das U-Boot is a GPL'ed cross-platform boot loader shepherded by project leader Wolfgang Denk and backed by an active developer and user community. U-Boot provides out-of-the-box support for hundreds of embedded boards and a wide variety of CPUs including PowerPC, ARM, XScale, MIPS, Coldfire, NIOS, Microblaze, and x86. You can easily configure U-Boot to strike the right balance between a rich feature set and a small binary footprint.

U-Boot has its origins in the 8xxROM project, a boot loader for 8xx PowerPC systems by Magnus Damm. When bringing that project to Sourceforge in 2000, current project leader Wolfgang Denk renamed the project 'PPCBoot," since Sourceforge did not allow project names to begin with a digit.

The openness and utility of PPCBoot fanned the flames of its popularity, driving developers to port PPCBoot to new architectures. By September, 2002, PPCBoot supported four different ARM processors, and the name PPCBoot was becoming quaint. In November, 2002, the PPCBoot team retired the project, which led directly to the surfacing of "Das U-Boot."

The strength of the Free Software development process is clearly evident in the success of U-Boot. The four freedoms expressed by the FSF's Free Software Definition directly fuel U-Boot's impressive progress and wide spread deployment.

You can jump start your next embedded Linux project using U-Boot to take care of the low-level board initializations, allowing you to focus on the core of your embedded application. Downloading images and flashing kernels should be the least of your worries. If the need arises, however, you have the source code, and can add support for new hardware or add a special feature.

Prerequisites

Before building and installing U-Boot you need a cross-development tool chain for your target architecture. The term tool chain is a bit squishy, but generally means a C/C++ compiler, an assembler, a linker/loader, associated binary utilities and header files for a specific architecture, like PowerPC or ARM. Collectively these programs are called a tool chain.

You are probably familiar with the tool chain on your desktop Linux PC. That tool chain runs on an x86 platform and generates binaries for an x86 platform. A cross-development tool chain executes on one CPU architecture, but generates binaries for a different architecture. In my case the host architecture is x86 while the target architecture is ARM and PowerPC. Sometimes this process is also referred to as cross-compiling.

While it is possible to configure and build a tool chain from source it is time consuming and the myriad of configuration options makes it quite error prone. I highly recommend using a pre-built tool chain from a Linux vendor. The Embedded Linux Development Kit (ELDK), also by Wolfgang Denk, contains the tool chains used in this article.

Using cross-development tools makes it an absolute dream to develop embedded systems using Linux as the host development workstation. No need to maintain machines running other operating systems in order to run the compiler.

Configuring and Building

Building U-Boot for a supported platform is very straight forward, very similar to the familiar "untar, configure, make" method used by many software projects. To setup a default configuration for a particular board type this at the shell prompt after untarring the U-Boot tarball,

localhost:$ cd u-boot
localhost:$ make mrproper
localhost:$ make _config

where <board> matches the board you want to build. This step setups the CPU architecture and board type.

You can fine tune the default configuration for your particular environment and board by editing the configuration file, "include/configs/<board>.h". This file contains several C-preprocessor #define macros that you can modify for your needs. Some common options you might want to change are:

/* Serial port configuration */
#define CONFIG_BAUDRATE         19200
#define CFG_BAUDRATE_TABLE      { 9600, 19200 }

/* Network configuration */
#define CONFIG_IPADDR           10.0.0.11
#define CONFIG_NETMASK          255.255.255.0
#define CONFIG_SERVERIP         10.0.0.1 

These definitions are self explanatory, except for maybe CONFIG_SERVERIP, which is the IP address U-Boot uses for TFTP and NFS. Don't worry too much about these right now as you can change them later when U-Boot is running. For the complete list of configuration options see u-boot/README.

Now to build the binary image, u-boot.bin, type the following:

cd u-boot
make

You have several options for installing the binary image on your target board, and which option you select depends on your board and development environment. You might use a BDM/JTAG programmer, an existing vendor's boot loader or even an older version of U-Boot. While this step is crucial it is beyond the scope of this article to describe this procedure. From here on I will assume you have successfully installed U-Boot on your target board.

Getting Started

The next step is configuring your host workstation for serial communications with your target platform. On my system I have a DB9 serial cable connected to /dev/ttyS0. U-Boot expects the serial line to be set for 8 data bits, 1 stop bit, no parity and no handshake.

You can use your favorite serial communications program to connect to U-Boot. I prefer to use Kermit and a tiny Kermit script from within an emacs shell buffer. I put the following Kermit script into a file called "serial-term" and make the file executable:

#!/usr/bin/kermit
echo connecting /dev/ttyS0 .....
set line /dev/ttyS0
set FLOW AUTO
set speed 19200
set serial 8n1
SET CARRIER-WATCH OFF
connect 

I like running serial-term from within an emacs shell because emacs keeps track of my command history, which the U-Boot shell does not support. Trust me, while developing you will be hitting the reset button on your board a lot and want to "up arrow" to the previous load command you just entered.

The user interface to U-Boot consists of a command line interrupter, much like a Linux shell prompt. When connected via a serial line you can interactively enter commands and see the results. After power on the initial u-boot prompt looks like this:

U-Boot 1.1.2 (Aug  3 2004 - 17:31:20)
RAM Configuration:
Bank #0: 00000000  8 MB
Flash:  2 MB
In:    serial
Out:   serial
Err:   serial
u-boot # 

This tells you that you have 8 megabytes of RAM starting at address 0x00000000, two megabytes of Flash and that the C-library file streams stdin, stdout and stderr are connected to the serial line.

You can receive more information about our board be issuing the board info command, bdinfo. In the folowing the commands typed by me appear in bold.

u-boot # bdinfo
DRAM bank = 0x00000000
-> start = 0x00000000
-> size = 0x00800000
ethaddr = 00:40:95:36:35:33
ip_addr = 10.0.0.11
baudrate = 19200 bps

Here you see the see the RAM configuration information again, the Ethernet MAC address, the IP address and serial port baud rate.

Environment Variables

Much like a traditional Linux shell the U-Boot shell uses environment variables to tailor its operation. The U-Boot commands to manipulate environment variables have the same names as the BASH shell. For instance printenv and setenv behave the same as their BASH shell counterparts.

In the following example you will dump the current environment variables using the "printenv" command and change the IP address of the TFTP server using the "setenv" command.

u-boot # printenv
baudrate=19200
ethaddr=00:40:95:36:35:33
netmask=255.255.255.0
ipaddr=10.0.0.11
serverip=10.0.0.1
stdin=serial
stdout=serial
stderr=serial
u-boot # setenv serverip 10.0.0.2
u-boot # printenv serverip
serverip=10.0.0.2

You can create short shell scripts by storing a sequence of U-Boot commands, separated by semicolons, in an environment variable. To execute the script use the "run" command followed by the variable name. This can be handy to automate repetitive tasks during development.

Network Commands

Having a network connection on your boot loader is very convenient during development. If your project requires several networked boards they can all download and boot the same kernel image from a centralized server. When you update the kernel you only need to update the single copy on the server and not each board individually.

U-Boot supports TFTP (Trivial FTP), a stripped down FTP that does not require user authentication, for downloading images into the board's RAM. The "tftp" command needs two pieces of information, the name of the file to download and where in memory to store the file as shown in the following example:

u-boot # tftp 8000 u-boot.bin
From server 10.0.0.1; our IP address is 10.0.0.11
Filename 'u-boot.bin'.
Load address: 0x8000
Loading: ###################
done
Bytes transferred = 95032 (17338 hex)

The size and location of the downloaded image are stored in the fileaddr and filesize environment variables for possible latter use by other shell commands and scripts. U-Boot also supports NFS so you can download images from your existing NFS server as well.

Flash Commands

Some embedded projects only have access to a network while being programmed "in the factory". When deployed in the field the boards boot a kernel stored in the flash memory. The board can be updated in the field by reprogramming the flash memory with a new kernel. U-Boot offers several commands for programming, erasing and protecting the flash memory.

To see what type of flash memory your board has enter the flinfo command:

u-boot # flinfo
Bank # 1: AMD Am29LV160DB 16KB,2x8KB,32KB,31x64KB
Size: 2048 KB in 35 Sectors
Sector Start Addresses:
S00 @ 0x01000000 ! S01 @ 0x01004000 !
S02 @ 0x01006000 ! S03 @ 0x01008000 !
S04 @ 0x01010000 ! S05 @ 0x01020000 !
S06 @ 0x01030000 S07 @ 0x01040000
...
S32 @ 0x011D0000 S33 @ 0x011E0000
S34 @ 0x011F0000

The output carries quite a lot of information. Immediately you see the flash manufacturer, part number and sector layout. This particular part begins with a 16KB sector at address 0x01000000, followed by two 8KB sectors, a 32KB sector and 31 64KB sectors for a total of 2 megabytes in 35 sectors.

The exclamation points following sectors 0 through 5 indicate that those sectors are "protected". In this example sectors 0 through 4 contain the code for U-Boot itself, and sector 5 is used to store the environment variables. Any attempt to program these sectors without first unlocking them will fail. This offers some level of protection from "rm -rf /" type mistakes when programming the flash.

Continuing the TFTP example, let's assume the file you uploaded is a new version of U-Boot. You need to first unlock flash sectors 0 through 4 before programming the flash. Type "protect off 1:0-4", which instructs U-Boot to allow write access to flash bank 1, sectors 0 through 4.

u-boot # protect off 1:0-4
Un-Protect Flash Sectors 0-4 in Bank # 1

Next you must prepare the flash sectors for programming by erasing them. Enter "erase 1:0-4", which tells U-Boot to erase sectors 0 through 4 of flash bank 1.

u-boot # erase off 1:0-4
Erase Flash Sectors 0-4 in Bank # 1
Erasing Sector 0 @ 0x01000000 ... done
Erasing Sector 1 @ 0x01004000 ... done
Erasing Sector 2 @ 0x01006000 ... done
Erasing Sector 3 @ 0x01008000 ... done
Erasing Sector 4 @ 0x01010000 ... done
[end courier]

To program the flash memory you need to copy the image from RAM to the address of flash sector 0, 0x01000000, using the cp command. You will use the byte version of the command to copy the specified number of bytes. In this case you can use the fileaddr and filesize environment variables, which contains the RAM address and number of bytes loaded by the last TFTP command. Type cp.b ${fileaddr} 1000000 ${filesize} at the u-boot prompt.

u-boot # cp.b ${fileaddr} 1000000 ${filesize}
Copy to Flash... ................ done

Finally restore the write protection on flash sectors 0 through 4 by typing protect on 1:0-4 at the U-Boot prompt.

u-boot # protect on 1:0-4
Protect Flash Sectors 0-5 in Bank # 1

You have just updated the U-Boot code for you board. The next reboot will run the newly uploaded U-Boot code. Well done!

The final flash related command is the saveenv command, which like the name implies saves your current environment variables to a reserved flash sector. This allows your environment variables to persist across power cycles and reboots. You might want do this after updating the server IP address or when adding a new script. Type saveenv to save your environment.

Saving Environment to Flash...
Un-Protected 1 sectors
Erasing Flash...
Erasing Sector 5 @ 0x01020000 ... done
Erased 1 sectors
Writing to Flash... ................ done
Protected 1 sectors

As you can see the saveenv command bundles together the un-protect, erase, copy and protect steps you covered in the previous example.

Booting

OK, now you know how to load your image into RAM or flash. Next you want to run your kernel and boot into Linux. Most kernel images expect to start executing from a known location in memory, so you need to load your image into the correct place. The U-Boot distribution provides a nice utility for the host system called "mkimage" for just this purpose.

After creating your kernel image use mkimage to add a tiny header containing the load and execute address for the image, like this (all on one line):

localhost:$ mkimage -A arm -O linux -T kernel -C none -a 0x8000 -e 0x8000 -n "Linux 2.6.6" -d linux.bin uImage.bin

This command appends a small header containing the load and execute address 0x8000 to your kernel image and creates a new file called uImage.bin. The header also contains a CRC32 checksum, checked later during the image load. Remember the above command is run on your development workstation, not the embedded target.

You can now upload uImage.bin to your board and use the bootm command to boot your image. In the following example let's assume you stored uImage.bin at address 0x01030000 in the flash memory.

u-boot # bootm 1030000
## Booting image at 01030000 ...
Image Name: Linux 2.6.6
Image Type: ARM Linux Kernel Image
Data Size: 700256 Bytes = 683.8 kB
Load Address: 00008000
Entry Point: 00008000
Verifying Checksum ... OK
Starting kernel ...

And off you go! U-Boot uses the header information to copy your image to the correct location and then to start execution at the specified address. Notice that U-Boot also verifies the CRC32 checksum contained in the header before executing the kernel.

Conclusion

This introduction to the basic features gives you feel for the power and utility of "Das U-Boot". For the more advanced features consult the project README file and visit the U-Boot Wiki. I hope you consider using U-Boot on your next embedded project.


Resources

  • Das U-Boot project [1]
  • DENX U-Boot Guide [2]
  • Free Software Foundation's "Free Software Definition" [3]
  • TFTP [4]

Pretty mini manual

The following was taken from the u-boot README.

Monitor Commands - Overview

go      - start application at address 'addr'
run     - run commands in an environment variable
bootm   - boot application image from memory
bootp   - boot image via network using BootP/TFTP protocol
tftpboot- boot image via network using TFTP protocol
               and env variables "ipaddr" and "serverip"
               (and eventually "gatewayip")
rarpboot- boot image via network using RARP/TFTP protocol
diskboot- boot from IDE devicebootd   - boot default, i.e., run 'bootcmd'
loads   - load S-Record file over serial line
loadb   - load binary file over serial line (kermit mode)
md      - memory display
mm      - memory modify (auto-incrementing)
nm      - memory modify (constant address)
mw      - memory write (fill)
cp      - memory copy
cmp     - memory compare
crc32   - checksum calculation
imd     - i2c memory display
imm     - i2c memory modify (auto-incrementing)
inm     - i2c memory modify (constant address)
imw     - i2c memory write (fill)
icrc32  - i2c checksum calculation
iprobe  - probe to discover valid I2C chip addresses
iloop   - infinite loop on address range
isdram  - print SDRAM configuration information
sspi    - SPI utility commands
base    - print or set address offset
printenv- print environment variables
setenv  - set environment variables
saveenv - save environment variables to persistent storage
protect - enable or disable FLASH write protection
erase   - erase FLASH memory
flinfo  - print FLASH memory information
bdinfo  - print Board Info structure
iminfo  - print header information for application image
coninfo - print console devices and informations
ide     - IDE sub-system
loop    - infinite loop on address range
mtest   - simple RAM test
icache  - enable or disable instruction cache
dcache  - enable or disable data cache
reset   - Perform RESET of the CPU
echo    - echo args to console
version - print monitor version
help    - print online help
?       - alias for 'help'

Environment Variables

U-Boot supports user configuration using Environment Variables which can be made persistent by saving to Flash memory.

Environment Variables are set using "setenv", printed using "printenv", and saved to Flash using "saveenv". Using "setenv" without a value can be used to delete a variable from the environment. As long as you don't save the environment you are working with an in-memory copy. In case the Flash area containing the environment is erased by accident, a default environment is provided.

Some configuration options can be set using Environment Variables:

  baudrate      - see CONFIG_BAUDRATE

  bootdelay     - see CONFIG_BOOTDELAY

  bootcmd       - see CONFIG_BOOTCOMMAND

  bootargs      - Boot arguments when booting an RTOS image

  bootfile      - Name of the image to load with TFTP

  autoload      - if set to "no" (any string beginning with 'n'),
                  "bootp" will just load perform a lookup of the
                  configuration from the BOOTP server, but not try to
                  load any image using TFTP

  autostart     - if set to "yes", an image loaded using the "bootp",
                  "rarpboot", "tftpboot" or "diskboot" commands will
                  be automatically started (by internally calling
                  "bootm")

  initrd_high   - restrict positioning of initrd images:
                  If this variable is not set, initrd images will be
                  copied to the highest possible address in RAM; this
                  is usually what you want since it allows for
                  maximum initrd size. If for some reason you want to
                  make sure that the initrd image is loaded below the
                  CFG_BOOTMAPSZ limit, you can set this environment
                  variable to a value of "no" or "off" or "0".
                  Alternatively, you can set it to a maximum upper
                  address to use (U-Boot will still check that it
                  does not overwrite the U-Boot stack and data).

                  For instance, when you have a system with 16 MB
                  RAM, and want to reseve 4 MB from use by Linux,
                  you can do this by adding "mem=12M" to the value of
                  the "bootargs" variable. However, now you must make
                  sure, that the initrd image is placed in the first
                  12 MB as well - this can be done with

                  setenv initrd_high 00c00000

  ipaddr        - IP address; needed for tftpboot command

  loadaddr      - Default load address for commands like "bootp",
                  "rarpboot", "tftpboot" or "diskboot"

  loads_echo    - see CONFIG_LOADS_ECHO

  serverip      - TFTP server IP address; needed for tftpboot command

  bootretry     - see CONFIG_BOOT_RETRY_TIME

  bootdelaykey  - see CONFIG_AUTOBOOT_DELAY_STR

  bootstopkey   - see CONFIG_AUTOBOOT_STOP_STR

The following environment variables may be used and automatically updated by the network boot commands ("bootp" and "rarpboot"), depending the information provided by your boot server:

  bootfile      - see above
  dnsip         - IP address of your Domain Name Server
  gatewayip     - IP address of the Gateway (Router) to use
  hostname      - Target hostname
  ipaddr        - see above
  netmask       - Subnet Mask
  rootpath      - Pathname of the root filesystem on the NFS server
  serverip      - see above

There are two special Environment Variables:

  serial#       - contains hardware identification information such
                  as type string and/or serial number
  ethaddr       - Ethernet address

These variables can be set only once (usually during manufacturing of the board). U-Boot refuses to delete or overwrite these variables once they have been set once.

Please note that changes to some configuration parameters may take only effect after the next boot (yes, that's just like Windoze :-).

Note for Redundant Ethernet Interfaces =

Some boards come with redundand ethernet interfaces; U-Boot supports such configurations and is capable of automatic selection of a "working" interface when needed. MAC assignemnt works as follows:

Network interfaces are numbered eth0, eth1, eth2, ... Corresponding MAC addresses can be stored in the environment as "ethaddr" (=>eth0), "eth1addr" (=>eth1), "eth2addr", ...

If the network interface stores some valid MAC address (for instance in SROM), this is used as default address if there is NO corresponding setting in the environment; if the corresponding environment variable is set, this overrides the settings in the card; that means:

  • If the SROM has a valid MAC address, and there is no address in the environment, the SROM's address is used.
  • If there is no valid address in the SROM, and a definition in the environment exists, then the value from the environment variable is used.
  • If both the SROM and the environment contain a MAC address, and both addresses are the same, this MAC address is used.
  • If both the SROM and the environment contain a MAC address, and the addresses differ, the value from the environment is used and a warning is printed.
  • If neither SROM nor the environment contain a MAC address, an error is raised.

Image Formats

The "boot" commands of this monitor operate on "image" files which can be basicly anything, preceeded by a special header; see the definitions in include/image.h for details; basicly, the header defines the following image properties:

  • Target Operating System (Provisions for OpenBSD, NetBSD, FreeBSD, 4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX
    • Currently supported: Linux, NetBSD, VxWorks, QNX).
  • Target CPU Architecture (Provisions for Alpha, ARM, Intel x86, IA64, MIPS, MIPS, PowerPC, IBM S390, SuperH, Sparc, Sparc 64 Bit;
    • Currently supported: PowerPC).
  • Compression Type (Provisions for uncompressed, gzip, bzip2;
    • Currently supported: uncompressed, gzip).
  • Load Address
  • Entry Point
  • Image Name
  • Image Timestamp

The header is marked by a special Magic Number, and both the header and the data portions of the image are secured against corruption by CRC32 checksums.

Linux Support

Although U-Boot should support any OS or standalone application easily, Linux has always been in the focus during the design of U-Boot.

U-Boot includes many features that so far have been part of some special "boot loader" code within the Linux kernel. Also, any "initrd" images to be used are no longer part of one big Linux image; instead, kernel and "initrd" are separate images. This implementation serves serveral purposes:

  • the same features can be used for other OS or standalone applications (for instance: using compressed images to reduce the Flash memory footprint)
  • it becomes much easier to port new Linux kernel versions because lots of low-level, hardware dependend stuff are done by U-Boot
  • the same Linux kernel image can now be used with different "initrd" images; of course this also means that different kernel images can be run with the same "initrd". This makes testing easier (you don't have to build a new "zImage.initrd" Linux image when you just change a file in your "initrd"). Also, a field-upgrade of the software is easier now.

Building a kernel Image

With U-Boot, "normal" build targets like "zImage" or "bzImage" are not used. If you use recent kernel source, a new build target "uImage" will exist which automatically builds an image usable by U-Boot. Most older kernels also have support for a "pImage" target, which was introduced for our predecessor project PPCBoot and uses a 100% compatible format. Example:

        make TQM850L_config
        make oldconfig
        make dep
        make uImage

The "uImage" build target uses a special tool (in 'tools/mkimage') to encapsulate a compressed Linux kernel image with header information, CRC32 checksum etc. for use with U-Boot. This is what we are doing:

  • build a standard "vmlinux" kernel image (in ELF binary format):
  • convert the kernel into a raw binary image:
        ${CROSS_COMPILE}-objcopy -O binary \
                                 -R .note -R .comment \
                                 -S vmlinux linux.bin
  • compress the binary image:
    
             gzip -9 linux.bin
  • package compressed binary image for U-Boot:
        mkimage -A ppc -O linux -T kernel -C gzip \
                -a 0 -e 0 -n "Linux Kernel Image" \
                -d linux.bin.gz uImage


The "mkimage" tool can also be used to create ramdisk images for use with U-Boot, either separated from the Linux kernel image, or combined into one file. "mkimage" encapsulates the images with a 64 byte header containing information about target architecture, operating system, image type, compression method, entry points, time stamp, CRC32 checksums, etc.

"mkimage" can be called in two ways: to verify existing images and print the header information, or to build new images.

In the first form (with "-l" option) mkimage lists the information contained in the header of an existing U-Boot image; this includes checksum verification:

        tools/mkimage -l image
          -l ==> list image header information

The second form (with "-d" option) is used to build a U-Boot image from a "data file" which is used as image payload:

        tools/mkimage -A arch -O os -T type -C comp -a addr -e ep \
                      -n name -d 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'

Right now, all Linux kernels use the same load address (0x00000000), but the entry point address depends on the kernel version:

  • 2.2.x kernels have the entry point at 0x0000000C,
  • 2.3.x and later kernels have the entry point at 0x00000000.

So a typical call to build a U-Boot image would read:

      -> tools/mkimage -n '2.4.4 kernel for TQM850L' \
      > -A ppc -O linux -T kernel -C gzip -a 0 -e 0 \
      > -d /opt/elsk/ppc_8xx/usr/src/linux-2.4.4/arch/ppc/coffboot/vmlinux.gz \
      > examples/uImage.TQM850L
        Image Name:   2.4.4 kernel for TQM850L
        Created:      Wed Jul 19 02:34:59 2000
        Image Type:   PowerPC Linux Kernel Image (gzip compressed)
        Data Size:    335725 Bytes = 327.86 kB = 0.32 MB
        Load Address: 0x00000000
        Entry Point:  0x00000000

To verify the contents of the image (or check for corruption):

        -> tools/mkimage -l examples/uImage.TQM850L
        Image Name:   2.4.4 kernel for TQM850L
        Created:      Wed Jul 19 02:34:59 2000
        Image Type:   PowerPC Linux Kernel Image (gzip compressed)
        Data Size:    335725 Bytes = 327.86 kB = 0.32 MB
        Load Address: 0x00000000
        Entry Point:  0x00000000

NOTE: for embedded systems where boot time is critical you can trade speed for memory and install an UNCOMPRESSED image instead: this needs more space in Flash, but boots much faster since it does not need to be uncompressed:

        -> gunzip /opt/elsk/ppc_8xx/usr/src/linux-2.4.4/arch/ppc/coffboot/vmlinux.gz
        -> tools/mkimage -n '2.4.4 kernel for TQM850L' \
        > -A ppc -O linux -T kernel -C none -a 0 -e 0 \
        > -d /opt/elsk/ppc_8xx/usr/src/linux-2.4.4/arch/ppc/coffboot/vmlinux \
        > examples/uImage.TQM850L-uncompressed
        Image Name:   2.4.4 kernel for TQM850L
        Created:      Wed Jul 19 02:34:59 2000
        Image Type:   PowerPC Linux Kernel Image (uncompressed)
        Data Size:    792160 Bytes = 773.59 kB = 0.76 MB
        Load Address: 0x00000000
        Entry Point:  0x00000000


Similar you can build U-Boot images from a 'ramdisk.image.gz' file when your kernel is intended to use an initial ramdisk:

        -> tools/mkimage -n 'Simple Ramdisk Image' \
        > -A ppc -O linux -T ramdisk -C gzip \
        > -d /LinuxPPC/images/SIMPLE-ramdisk.image.gz examples/simple-initrd
        Image Name:   Simple Ramdisk Image
        Created:      Wed Jan 12 14:01:50 2000
        Image Type:   PowerPC Linux RAMDisk Image (gzip compressed)
        Data Size:    566530 Bytes = 553.25 kB = 0.54 MB
        Load Address: 0x00000000
        Entry Point:  0x00000000

Installing a Image

To downloading a U-Boot image over the serial (console) interface, you must convert the image to S-Record format:

       objcopy -I binary -O srec examples/image examples/image.srec

The 'objcopy' does not understand the information in the U-Boot image header, so the resulting S-Record file will be relative to address 0x00000000. To load it to a given address, you need to specify the target address as 'offset' parameter with the 'loads' command.

Example: install the image to address 0x40100000 (which on the TQM8xxL is in the first Flash bank):

       => erase 40100000 401FFFFF

       .......... done
       Erased 8 sectors

       => loads 40100000
       ## Ready for S-Record download ...
       ~>examples/image.srec
       1 2 3 4 5 6 7 8 9 10 11 12 13 ...
       ...
       15989 15990 15991 15992
       [file transfer complete]
       [connected]
       ## Start Addr = 0x00000000

You can check the success of the download using the 'iminfo' command; this includes a checksum verification so you can be sure no data corruption happened:

       => imi 40100000

       ## Checking Image at 40100000 ...
          Image Name:   2.2.13 for initrd on TQM850L
          Image Type:   PowerPC Kernel Image (gzip compressed)
          Data Size:    335725 Bytes = 327 kB = 0 MB
          Load Address: 00000000
          Entry Point:  0000000c
          Verifying Checksum ... OK

Boot

The "bootm" command is used to boot an application that is stored in memory (RAM or Flash). In case of a Linux kernel image, the contents of the "bootargs" environment variable is passed to the kernel as parameters. You can check and modify this variable using the "printenv" and "setenv" commands:

=> printenv bootargs
bootargs=root=/dev/ram

=> setenv bootargs root=/dev/nfs rw nfsroot=10.0.0.2:/LinuxPPC nfsaddrs=10.0.0.99:10.0.0.2
=> printenv bootargs
bootargs=root=/dev/nfs rw nfsroot=10.0.0.2:/LinuxPPC nfsaddrs=10.0.0.99:10.0.0.2
=> bootm 40020000
## Booting Linux kernel at 40020000 ...
   Image Name:   2.2.13 for NFS on TQM850L
   Image Type:   PowerPC Linux Kernel Image (gzip compressed)
   Data Size:    381681 Bytes = 372 kB = 0 MB
   Load Address: 00000000
   Entry Point:  0000000c
   Verifying Checksum ... OK
   Uncompressing Kernel Image ... OK
kernel 2.2.13
Boot arguments: root=/dev/nfs rw nfsroot=10.0.0.2:/LinuxPPC nfsaddrs=10.0.0.99:10.0.0.2
time_init: decrementer frequency = 187500000/60
Calibrating delay loop... 49.77 BogoMIPS
Memory: 15208k available (700k kernel code, 444k data, 32k init) [c0000000,c1000000]

...


If you want to boot a kernel with initial ram disk, you pass the memory addreses of both the kernel and the initrd image (PPBCOOT format!) to the "bootm" command:

        => imi 40100000 40200000
 
        ## Checking Image at 40100000 ...
           Image Name:   2.2.13 for initrd on TQM850L
           Image Type:   PowerPC Linux Kernel Image (gzip compressed)
           Data Size:    335725 Bytes = 327 kB = 0 MB
           Load Address: 00000000
           Entry Point:  0000000c
           Verifying Checksum ... OK
 
        ## Checking Image at 40200000 ...
           Image Name:   Simple Ramdisk Image
           Image Type:   PowerPC Linux RAMDisk Image (gzip compressed)
           Data Size:    566530 Bytes = 553 kB = 0 MB
           Load Address: 00000000
           Entry Point:  00000000
           Verifying Checksum ... OK
 
 
        => bootm 40100000 40200000
        ## Booting Linux kernel at 40100000 ...
           Image Name:   2.2.13 for initrd on TQM850L
           Image Type:   PowerPC Linux Kernel Image (gzip compressed)
           Data Size:    335725 Bytes = 327 kB = 0 MB
           Load Address: 00000000
           Entry Point:  0000000c
           Verifying Checksum ... OK
           Uncompressing Kernel Image ... OK
        ## Loading RAMDisk Image at 40200000 ...
           Image Name:   Simple Ramdisk Image
           Image Type:   PowerPC Linux RAMDisk Image (gzip compressed)
           Data Size:    566530 Bytes = 553 kB = 0 MB
           Load Address: 00000000
           Entry Point:  00000000
           Verifying Checksum ... OK
           Loading Ramdisk ... OK
        kernel 2.2.13 
        Boot arguments: root=/dev/ram
        time_init: decrementer frequency = 187500000/60
        Calibrating delay loop... 49.77 BogoMIPS
        ...
        RAMDISK: Compressed image found at block 0
        VFS: Mounted root (ext2 filesystem).
 
        bash#

Notes from other sources

... as this is not obvious from the standard u-boot behavior, to upload a properly formed u-boot kernel or ramdisk image, use "loadb k', or 'loadb r'...

Motorola MPC820 Bring-up. A short war story

This document describes steps we took to bring up Linux OS on a PowerPC (MPC8260) based target board. The board is part of the TdSoft Voice Access Gateway (TdGate) enabling connection of a V5 Class telephone switch the remote Network Access (NA). Or IAD's via ATM network (For more information see www.tdsoft.com site). Besides a dedicated ATM (OC3), V5 (E1) and back-plane HDLC communication lines the board includes external RS232 and Ethernet (10/100 BaseT) ports used for debug purposes.

Step 1. Getting the Toolchain

Before we could even start putting Linux on this system, there was a need to build a toolchain (cross-compiler, assembler, linker and other standard GNU tools) that could generate Linux image for the target PowerPC board on a standard x86 Host PC. In addition we also had to decide about the operating system for our Host. We chose to run the Host on Linux too as it would simplify the development process and because it has a great variety of suitable free software development tools, much more than any other operating system.

As a basis for the toolchain we decided to use the uClibc (http://www.uclibc.org). We downloaded the latest version from the project's CVS (If we hadn’t used Linux in a Host, we would have to look for a CVS client for Windows; another good reason for Linux in the Host). At first the CVS client did not login; lesson 1 to remember - have port #431 open for output on your firewall.

After opening the port all the toolchain sources have been downloaded (We used the buildroot module that contained the toolchain, root file system and optionally the Kernel). Configuration was easy, we just needed to edit the Makefile at the source chain tree root. The Makefile has many options; all of them are documented very well. We choosed to use Kernel 2.4.xx. We had an option for GCC version 3.3 or GCC version 2.95, since the Kernel documentation says that compiling under versions other then 2.95 may give unpredictable results, we chose GCC 2.95. There were also a number of options for root file system type; we stayed with the default (ext2fs).

Step 2. Compiling the Toolchain

This step was supposed to be the easiest part... The compilation starts by issuing the make command in the toolchain directory. This downloads the sources for all system parts, that is by itself a long process as it connects by itself via the Internet to get the sources of the various programs required for compilation. After downloading the sources the compilation begins.

Everything was OK until kernel compilation began. For some reason the Kernel was not configured for PowerPC but it used a compiler and assembler for PowerPC. This resulted, of course, in errors as the PowerPC assembler doesn’t understand the i386 assembly code. Looking into the Linux Makefile revealed the problem; we had to set it manually for PowerPC.

The kernel was found under build_powerpc/linux-2.4.26-pre5-erik. Lines starting with ARCH in kernel Makefile were updated to ARCH := ppc. The kernel can be compiled alone as well my issuing the make command from kernel directory.

At this stage we started playing with Linux configuration (make xconfig) to feet what we need. However this proved to be a waste of time. It was very difficult to get the required configuration as many modules depend on each other and the kernel need to be compiled again and again... We ended up by going back to the default configuration. Step 3. Building Root File System.

Linux must have a root file system. The uClibc toolchain builds a root file system by itself; there was only a need to compress it into a .gz file used for initrd. At least we thought so, that was proven to be a half-truth.

Now there are two options:

  • Use this file as initrd
  • Compile it into the Kernel using make zImage.initrd

There are other options that at this stage we did not know, more on this later.

Step 4. Downloading Kernel and Root File System to the Target (Thats where the real fun starts)

The target board had a resident bootloader used for booting a VxWorks application. One of its options was to download the application via the external Ethernet connecting using FTP. It was supposed to be easy, just download the Kernel and Initrd, prepare a special record with parameters required by Linux Kernel startup procedure and transfer control to the Kernel... Of course, it didn’t work; otherwise this wouldn't be an interesting story...

What actually happened was that immediately after passing control to Kernel the system got an exception and restarted. The reason was that the Kernel startup tried to find the load address from the ELF header of Kernel image. Since Linux is using the MMU, the addresses in the ELF header are supposed to be a virtual addresses and not physical memory addresses. The load address in the header was: 0xC0000000 (yes, it is not a mistake, it is 7 zeros...) obviously we did not have physical memory at this address; we only had 128Mb of physical memory mapped from 0x000000000. We tried to set the address in the ELF file to 0x00000000 (by updating the resident VxWorks bootloader before passing the control to Linux). For some reason it did not work.

Then we decided not to spend time on fixing the problem, but to use a “standard” U-boot loader that was supposed to work with Linux Kernel on another MPC8260 card. The whole download sequence should be work as follows: resident VxWorks bootloader downloads the U-Boot, U-Boot downloads the Kernel and Initrd and passes control to Kernel. Eventually, it worked; however it wasn’t that simple... (In the final configuration U-boot can reside in the onboard flash and not downloaded via FTP). Step 5. Configuring U-boot.

U-Boot is a GPL licensed SW boot loader that was originally designed for Linux loading (and many other RTOS’s) for many different HW architectures. It even used some code from Linux (The beauty of GPL)

From reading the U-Boot Readme file we understood that it is very simple to configure U-Boot to any supported board. If the board is not supported it should not be too difficult by doing some fixes in the original code. In our case we had to do some changes in the RS232 and Ethernet U-Boot drivers. (The default 8260 settings differed from our board)

First we tried to compile the U-Boot using our original toolchain (See steps 1 and 2). As expected things didn’t go so smooth...

We still don't know what was exactly the problem was but when we removed the line compiling the test hello.c program the compilation succeeded.

Few days later we saw U-Boot booting. The next step was generation of U-Boot compatible Kernel image and loading it. Step 6. Generation U-Boot Compatible Kernel Image

U-Boot is using it's own uImage Kernel image that is actually the original vmlinux ELF file with a special U-Boot header. The uImage is generated by using a special tool named mkimage compiled along with U-Boot.

However, before this there was a need to set MKIMAGE variable (see linux/scripts/mkuboot.sh file) according to the to the actual mkimage path.

It was also needed to set the configuration variables (CONFIG_SERVERIP, CONFIG_IPADDR, CONFIG_ETHADDR, CONFIG_BOOTDELAY, CONFIG_BOOTCOMMAND, CONFIG_BOOTARGS etc.) according to the real values used in the board and Host PC running the TFTP server.

We also prepared a U-boot compatible Initrd image (using instructions in the U-boot README file), again this file is the standard initrd image with a special U-boot header.

After that we had both Kernel and Initrd images suitable for U-Boot booting the next step was simply to download them and start the Kernel. Step 7. Booting the Kernel

Well, do not prepare the party yet; the work is far from being done...

It seemed that U-boot was really downloading the file and uncompressing it, but after control was transferred to Kernel we were get an exception once again... Looking again at the U-boot code revealed that the IMMR register was set to a lower address; according to the Linux documentation it should be set to 0xF0000000. That wasn't so simple to change. We ended up with changing it in the first ROM resident bootloader, but anyway it did boot U-Boot again and we started again booting the kernel, but again another exception...

Ok, we now had to dive into the kernel sources. We didn’t have an ICE that can work with Linux. So how do you debug a program without a debugger? We started looking at the Kernel sources. The first step was to activate printouts, but the Kernel didn’t reach the step where it initializes the serial output so we could not use the printk () kernel function.

We decided to add code that will light some debug LEDs we had on the board, this way was not recommended by the U-Boot mailing list members but we couldn’t find a better way. We added a code that will light a LED and will stop after that (using a jump to itself). We started by putting this code in the very beginning (arch/ppc/kernel/head.S) and then moved farther inside the kernel.

We ended up finding that the exception happens when the kernel was trying to jump to a function using an uninitialized function pointer. How can this be? Diving deeper into the code showed us that this function pointer was supposed to be initialized by the bootloader, but our bootloader did not have any place to initialize this pointer. Looking again at the Linux configuration revealed that for some reason CONFIG_8260 was not set so actually we where using a Kernel image compiled for a different machine and not the 8260.

However this was not the end of our troubles. We changed the configuration and compiled the Kernel once again. It wasn't that easy since that operation broke some other parts of the configuration. After we booted the Kernel and now a different story we didn’t get an exception but we didn't see anything on the terminal.

If it’s boring to read it, just imagine how frustrating it is to do the same thing over and over again with almost no advance? Step 8. Booting in the Dark

Before we dived again into the kernel, we wanted to make sure the kernel was actually working so we connected our ICE (remember that it didn’t support symbolic debugging with Linux. How do you use an ICE that does not support symbolic debugging to find out the exact symbol? We stopped the ICE after few second expecting it to be somewhere near the 'panic' symbol, as we did not have initrd loaded so it could not find a root file system.

We found the address it was stopped on; we added 0xC0000000, which is the virtual start address on the Kernel and looked at System.map. The address was few bytes after the panic symbol, this made us sure that the Kernel was working but for some reason couldn’t write to the serial line. Step 9. Teaching the Kernel to Speak

First we had to find where exactly the code for serial port initialize resides, the default was to look at arch/ppc/8xx_io/uart.c but this was the code for the 860 and not 8260. It was very similar but this code was actually not linked into our image so there was no point to change it.

The actual code was residing in arch/ppc/cpm2_io/uart.c. It was very similar to the other uart.c file so similar that one may think you got the wrong file on the editor… Looking at the comment in the head of the file shows us that this code was written by Dan Malek of MontaVista software using the 8xx_io/uart.c as a base so this explained why it was so similar.

Apparently the 8260 has several communication controllers and the Kernel used the default one but not the controller we where connected to. This was a simple change in few #define statements in the file.

But this did not solve the problem... there was a point of light as it did show few random characters on the terminal. It looked like it was writing something but probably with wrong baud rate.

Ok, so the natural way to go is the rs_8xx_init() function that for some reason was close to the bottom of the file. It seemed that although the baud rate was set up correctly in the board information structure (a structure that was transferred by the boot loader to the Kernel) this function wasn't reading the right value or maybe it did not read the actual structure since for some reason it got a wrong pointer.

It wasn’t so easy to find where this pointer is read. To save time we used a brute force method here and hard coded the value into this function. And..."Let there be light". Step 10. The Kernel is Talking

The Kernel did write some lines indicating that it is booting but it didn’t get to the stage where it really starts the init process. It seemed to be stacked in a loop after starting calibration of the Real Time Clock. It seems that it could not calculate the value of ticks_per_jiffy that is the number of clocks per kernel time unit (1/HZ which is in this case 0.01 sec. Again, this was a problem with the board info structure that was not read correctly. As before, we used a brute force method to set the clock value in m8260_calibrate_decr() function in the arch/ppc/kernel/m8260_setup.c file that did help.

After that it proceeded to the step where the Kernel was trying to load the initrd ramdisk.

It seemed that the Kernel has found the ramdisk at the right address, however it complained on a bad magic number at certain offset. A simple change in the kernel arguments, adding ramdisk_size=60000 solved this problem.

Now it seemed that the problems went from Kernel space to User space. Why?

The error now was "bummer can’t open /dev/tty1" that repeated for tty2 and tty3.

We assumed that opening /dev/ttyx is done from processing inittab, which is done by the init process. Step 11. We are in User Space

Well, while being in user space it should be easier to solve problems (or at least that's what we thought). The file system image was under the 'root' subdirectory in the toolchain build_powerpc subdirectory. This seemed like a standard Linux filesystem tree; so we went to the etc/inittab file. According to this file it was trying to open /dev/tty1 /dev/tty2 etc.

We did not have those devices or actually we had them but the Kernel code for opening them was disabled during our tests, so we could either enable the Kernel code or delete those lines.

Since anyway we didn't have more then one serial port, we decided to delete them (comment them out). Then we built the filesystem again and rebooted the system.

The miracle happened and we have seen a login prompt. The ceiling was high enough so we did not hurt our heads jumping from joy :-) Step 12. But We Need Ethernet…

Apparently we have a working Linux system so what next? The first step was networking activation, as the system has to communicate with the real world after all.

Running ifconfig showed us one interface (lo), which means that the system has networking, enabled but can only talk to itself... We went again to the Kernel configuration (it seems that the Kernel space problems are not over yet) and tried again to look for any hints. This was relatively easy as for some reason we did not enable 8260 networking.

Again, a new Kernel was recompiled and booted but ifconfig again showed one interface (lo). Trying to do ifconfig eth0 returned an error; the interface was unrecognized. To make the long story short (it took us few hours to find it) we had to enable networking on FCC1 in the Kernel configuration.

Booting the kernel again after adding the lines:

null::sysinit:/sbin/ifconfig eth0 172.16.1.106

null::sysinit:/sbin/route add -net 172.16.1.106 eth0

gave us a working network. We could now ping from our target to the Linux host and vice versa. Step 13. Linux is Running. What Next?

Ok, we now have a Linux system working, meaning it displayed a prompt on the terminal and we could use the core utilities... However this system has to do a little more than displaying a prompt; we need to run some programs on it. We tried to write a simple “Hello world” program, put it in the filesystem image and reboot the system, it works.

We made a list of things we need: Telnet, debugger and it looks great to have an ftp client too.

FTP client or sort of, we actually had; busybox knows how to work as wget, it's only a matter of configuration. As for Telnet, busybox can also do it but at that time we did not know it, so we downloaded telnetd from netkit (ftp://ftp.uk.linux.org/pub/linux/Networking/netkit). We compiled it using our toolchain and downloaded it to the target. It worked, however it can’t wait for a connection by itself, it needed to be spawned from inetd or xinetd. So we downloaded inetd from netkit, compiled and configured it to run telnetd, downloaded it to the target and it works.

Encouraged by how easy it was to download software compile and run it, we continued to GDB, but this was not as easy as the other parts... Step 14. Compiling GDB

The uClibc buildroot makefile contained an option to install GDB, so was supposed to be very easy to install it. However nothing is as easy as it looks.

We uncommented the gdb line and ran make again, it downloaded GDB and configured it, however it failed during the configuration as it could not find ncurses. At the beginning we thought it was related to the host ncurses and thus it was strange that it failed. The problem was we did not install ncurses for our target.

After installing the ncurses it turned out that it compiles gdb using the cross compile while we expected only gdbserver to be compiled using the cross compiler.

We passed over each directory in gdb and compiled it manually with the host gcc and only the gdbserver which have no dependencies was compiled with the cross compiler.

Looks a short story, but it took about 2 days to find it all out...

Step 15. Summary

It was not a short and easy journey, but it was fun and gave us a good working system. What remains now is only to do the real work and write the application.

We hope this description will help other people to make this porting easier.