Porting Linux to a PowerPC Board
Booting an operating system seems easy. You just power on the system, and after a while, you'll see a prompt on the console which indicates the system is running. If we look into the booting internals, we get a more complicated view. Booting includes hardware initialization and software startup, operations which differ from board to board. Different booting code exists for each kind of board in the Linux architecture-dependent source code directory, that is, linux/arch/. For a new board, we usually have to add a new booting sequence.
A typical embedded board has no floppy and no hard disk. Code and data are initially put in ROM or can be downloaded through a network connection. We have designed a general approach to booting such a system from ROM.
Our approach is to divide the booting into two stages supported by two separate loaders. One is called the image loader iloader, and the other is the Linux kernel loader kloader. iloader is ROM-able. That is, after the system is powered up, it starts, does necessary hardware initialization, then moves the Linux kernel loader from ROM to the proper location in RAM. kloader starts running once iloader finishes. First, it does more hardware initialization. Then, it sets up the environment for booting the Linux kernel by uncompressing the Linux kernel image. Finally, it jumps to the kernel code to begin the main Linux startup sequence.
To make things clearer, consider elinux. The final elinux image, which we call an elinux ball, is packed as a single file containing three items:
statically linked iloader executable binary
our zImage consisting of the uncompressed kernel image vmlinux.bin.gz plus kloader
compressed root file system image ramdisk.gz
The size of an elinux ball depends on how many services and programs are included. In our experiments, it is limited to 2MB, which is big enough for most situations. If larger programs are needed, they can be downloaded after the system is up. It is good to keep the ball small. The packing is simply done by a tool called packbd (packing binaries and data images). The elinux ball is obtained using the command:
packbd iloader kloader vmlinux.bin.gz ramdisk.gz\ elinuxiloader is the entry point to start the elinux ball. Because it is ROM-able, the whole elinux ball is also ROM-able. However, we don't have to put it into ROM to boot the system. Actually, in our development, we use the native networking service TFTP to download the elinux ball into a RAM area and start execution.
The implementations of iloader, kloader and packbd are straightforward for any system, except for hardware initialization which usually requires more effort.
Kernel modification is the hardest part of porting. Fortunately, Linux has been designed to be portable, with its sources well-organized into a tree structure. Once you have made considerable investigations into the kernel sources, the things to do become clear. As we mentioned earlier, changes and even new pieces of code are required when porting Linux to a new board. Basically, all board-dependent code must be modified or adjusted even if we use a Linux-supported processor. Changes are also needed when we have new requirements or any bug fixes. Most are concentrated in a few files, so hopefully this can help us conveniently catch up with new releases.
We used the experimental kernel version 2.1.132 PPC port for elinux. Almost all changes are limited to the board-dependent parts, that is, in the subdirectory linux/arch/ppc for our PowerPC board. Dozens of changes have been made; many for adapting to the new hardware, other things for bug fixes and new requirements such as new memory mapping.
Major changes for elinux include those for hardware initialization, PCI bus initialization, memory management, timer processing and interrupt processing.
Hardware initialization is the ugliest part in elinux. The loader iloader should do the most essential part of the job, such as initialization of the memory controller and PCI controller. However, our implementation of iloader simply ignores this part because we find it is unnecessary to do it again after the board's ROM code has done it. Of course, iloader has to do it if iloader is the first code to run from ROM. The kernel initialization does others such as memory protection and bus device initialization.
The original PPC port talks with PCI devices through interfaces with the BIOS. For our board, we assume there is no similar thing. We just leave the interfaces empty at the beginning. Whenever we add a new PCI device, we write code directly to set up the related base addresses, IRQs and access methods.
A few considerations are necessary for memory management. Although we don't need any changes in the major parts for memory management such as virtual memory management and paging, we do have modification requests. They are mainly for setting up the particular memory sizes and ranges, re-arranging the memory during the kernel startup, the use of PowerPC BAT (Block Address Translation) register pairs and memory mappings between physical and virtual addresses.
For timer processing, two things are modified. One is to adjust the parameters to set the PowerPC's decrementer to suit the board's bus rate. This decrementer is used to generate a timer interrupt every jiffy time (10 milliseconds). The other change is to provide the interface with direct access to the Real Time Clock (RTC) on board.
Another major change is for the interrupt controller. This controller is simple and controls only 16 IRQs through a status register, a mask register and a latch register. All the registers are 16 bits. Each bit corresponds to one IRQ. New simple code is added to handle it.
We have successfully relocated the kernel in the virtual space by redefining the symbol KERNELBASE as a Makefile macro. This involves a few changes in the kernel initialization code. Since we are able to relocate the kernel, we can reserve the space for some special purposes. For example, to load the kernel at the address 0xa0000000 instead of the default address 0xc0000000, we just define KERNELBASE in the top Makefile this way:
KERNELBASE = 0xa0000000
Minor changes are made in various Makefiles. These are necessary because we need new rules to create the elinux ball, we have a few new files to compile and link, and we found bugs in the Makefile when doing cross-compiling.
As for device drivers, we are concerned only with the serial driver for the console port. Other drivers may be added later if necessary, such as a LAN driver and drivers to control customized devices. During our experiments, we communicate with elinux using minicom over a serial port. We use the serial driver from the Linux code drivers/char/serial.c. A minor change is made for adjusting the baud rate, and another change is made in its header file since the serial port has a different IRQ number.
After all is done properly, we see through the console that elinux starts and runs happily.
ELINUX VERSION 0.001 March 1999 Start booting Linux on Experiment Board ... ... (omitted long booting messages) # (we start ash after the kernel is up)
- High-Availability Storage with HA-LVM
- DNSMasq, the Pint-Sized Super Dæmon!
- Localhost DNS Cache
- Real-Time Rogue Wireless Access Point Detection with the Raspberry Pi
- Days Between Dates: the Counting
- You're the Boss with UBOS
- The Usability of GNOME
- Linux for Astronomers
- Multitenant Sites
- PostgreSQL, the NoSQL Database