Section 16.2. Custom Linux for Your Board


16.2. Custom Linux for Your Board

When we ported U-Boot to a new hardware platform in Chapter 7, "Bootloaders," we found the configuration that most closely matched our new board and borrowed from that port. We use a similar technique to port Linux to our new board. We assume that the chosen CPU is already supported in the kernel. Porting to a new CPU is significantly more challenging and beyond the scope of this book.

We have chosen to port Linux to a custom controller board based on the Freescale MPC5200 32-bit embedded PowerPC processor. Looking through the default configurations from a recent Linux release (as depicted in Listing 16-2), we find one that contains the MPC5200 CPU. Because it appears that this is the only configuration that supports this processor, we use it as our baseline.

The hardware platform that we use for this exercise was supplied courtesy of United Electronic Industries. The board is called the PowerDNA Controller. It has a simple block diagram, containing onboard Flash memory, dynamic RAM, a serial port, and a variety of I/O devices, mostly integrated into the MPC5200 processor. Figure 16-1 is the block diagram of the PowerDNA Controller.

Figure 16-1. UEI PowerDNA Controller board


16.2.1. Prerequisites and Assumptions

The Linux kernel makes some fundamental assumptions when it is passed control from a bootloader. Most important among them is that the bootloader must have initialized the DRAM controller. Linux does not participate in chip-level SDRAM controller setup. Linux assumes that system RAM is present and fully functional. The PowerDNA Controller we are targeting contains the U-Boot bootloader, which has initialized the CPU, DRAM, and other related hardware required for minimal system operation.

The bootloader should also initialize the system memory map. This is usually done via a set of processor registers that define what chip select signals are active within a given memory address range. Chapter 3 in the Freescale MPC5200 User's Guide describes the registers used for this task.

The bootloader might have additional hardware-related initialization tasks. On some boards, the kernel assumes that the serial port is configured. This makes it possible to display early kernel boot messages to the serial port, long before the kernel's own serial driver has been installed. Some architectures and hardware platforms contain functions such as *_serial_putc(), which can send strings to a serial port that has been preconfigured by the bootloader or by some simple early kernel setup code. You can find examples of this in the PowerPC architecture branch using grep and searching for CONFIG_SERIAL_TEXT_DEBUG.

In summary, the fundamental prerequisite for porting Linux to our new board is that a bootloader has been ported and installed on our board, and any board-specific low-level hardware initialization has been completed. It is not necessary to initialize devices for which Linux has direct device driver support, such as Ethernet controllers or I2C controllers; the kernel handles these.

It is a good idea to configure and build your Linux kernel for the board closest to your own. This provides you with a known good starting pointa Linux kernel source tree configured for your board that compiles without error. Recall from Chapter 5, "Kernel Initialization," the command to compile a Linux 2.6 kernel:

$ make ARCH=ppc CROSS_COMPILE=ppc_82xx- uImage


This command line results in a Linux bootable image compatible with the U-Boot bootloader. The uImage target specifies this.

16.2.2. Customizing Kernel Initialization

Now that we have a baseline kernel source tree from which to start, let's determine where to begin customizing for our particular board. We discovered that for the PowerPC architecture, the board-specific files reside in a directory called .../arch/ppc/platforms. Of course, this is not strictly necessary, but if you ever intend to submit your patches to the Linux kernel development community for consideration, proper form and consistency matter!

We find in the platforms directory a file called lite5200.c. It's a fairly simple file, containing two data structures and five functions. Listing 16-3 presents the functions from this file.

Listing 16-3. Functions from 5200 Platform File

lite5200_show_cpuinfo()  /* Prints user specified text string */ lite5200_map_irq()       /* Sets h/w specific INT logic routing */ lite5200_setup_cpu()     /* CPU specific initialization */ lite5200_setup_arch()    /* Arch. specific initialization */ platform_init()          /* Machine or board specific init */

Let's look at how these functions are used. We briefly examined the low-level kernel initialization in Chapter 5. Here we look at the details for a particular architecture. Details differ between architectures, but when you can navigate one, the others will be easier to learn.

From Chapter 5, we saw the early flow of control on power-up. The bootloader passed control to the kernel's bootstrap loader, which then passed control to the Linux kernel via the kernel's head.o module. Here the platform-specific initialization begins. Listing 16-4 reproduces the pertinent lines from .../arch/ppc/kernel/head.S.

Listing 16-4. Calling Early Machine Initialization

      ... /*  * Do early bootinfo parsing, platform-specific initialization,  * and set up the MMU.  */       mr    r3,r31       mr    r4,r30       mr    r5,r29       mr    r6,r28       mr    r7,r27       bl    machine_init       bl    MMU_init       ...

Here you can see the assembly language call to machine_init. Of particular significance is the setup of the registers r3 through r7. These registers are expected to contain well-known values, which you will see momentarily. They were stored away very early in the boot sequence to the PowerPC general-purpose registers r27 tHRough r31. Here they are reloaded from these stored values.

The machine_init() function is defined in a C file called setup.c, in the same architecture-specific kernel directory: .../arch/ppc/kernel/setup.c. The start of this routine is reproduced here in Listing 16-5.

Listing 16-5. Function machine_init() in setup.c

void __init machine_init(unsigned long r3, unsigned long r4, unsigned long r5,             unsigned long r6, unsigned long r7) { #ifdef CONFIG_CMDLINE        strlcpy(cmd_line, CONFIG_CMDLINE, sizeof(cmd_line)); #endif /* CONFIG_CMDLINE */ #ifdef CONFIG_6xx        ppc_md.power_save = ppc6xx_idle; #endif #ifdef CONFIG_POWER4        ppc_md.power_save = power4_idle; #endif       platform_init(r3, r4, r5, r6, r7);       if (ppc_md.progress)              ppc_md.progress("id mach(): done", 0x200); }

There is some very useful knowledge in this simple function. First, notice that the parameters to machine_init() represent the PowerPC general-purpose registers r3 through r7.[2] You saw that they were initialized just before the machine language call to machine_init. As you can see from Listing 16-5, these register values are passed unmodified to platform_init(). We need to modify this function for our platform. (We have more to say about that in a moment.)

[2] By convention, parameters in C are passed in these PowerPC registers.

Listing 16-5 also contains some machine-specific calls for power-management functions. If your kernel is configured for PowerPC 6xx support (CONFIG_6xx defined in your .config file), a pointer to a machine-specific power-management function (ppc6xx_idle) is stored in a structure. Similarly, if your kernel is configured for a PowerPC G5 core (CONFIG_POWER4), a pointer to its machine-specific power-management routine is stored in the same structure member. This structure is described in Section 16.3.3, "Machine-Dependent Calls."

16.2.3. Static Kernel Command Line

One of the more interesting operations in the machine_init() function reproduced in Listing 16-5 is to store the default kernel command line. This operation is enabled if CONFIG_CMDLINE is enabled in your kernel configuration. On some platforms, the bootloader does not supply the kernel command line. In these cases, the kernel command line can be statically compiled into the kernel. Figure 16-2 illustrates the configuration options for this.

Figure 16-2. Default kernel command line


Enable "Default bootloader kernel arguments" in the configuration in Figure 16-2 and edit the "Initial kernel command string" as shown. This results in a set of entries in the .config file, as shown in Listing 16-6.

Listing 16-6. Configuration for Default Kernel Command Line

... CONFIG_CMDLINE_BOOL=y CONFIG_CMDLINE="console=ttyS0 root=/dev/ram0 rw" ...

The ellipses in Listing 16-6 indicate that we have taken only a small snippet of the .config file. When these configuration symbols are processed by the kernel build system, they become entries in the .../include/linux/autoconf.h file, as detailed in Listing 16-7.

Listing 16-7. File autoconf.h Entries for Default Kernel Command Line

...   #define CONFIG_CMDLINE_BOOL 1   #define CONFIG_CMDLINE "console=ttyS0 root=/dev/ram0 rw" ...

Now referring back to Listing 16-5, we have the following line:

strlcpy(cmd_line, CONFIG_CMDLINE, sizeof(cmd_line));


You can see that this kernel-based string-copy function copies the string defined by CONFIG_CMDLINE into a global kernel variable called cmd_line. This is important because many functions and device drivers might need to examine the kernel command line early in the boot sequence. The global variable cmd_line is hidden away at the start of the .data section, defined in the assembler file .../arch/ppc/kernel/head.S.

A subtle detail is worth mentioning here. Looking back at Listing 16-4, we see that the machine_init assembly language call is made before the call to MMU_init. That means that any code we are able to run from machine_init is executed in a context with limited support for accessing memory. Many of today's processors that contain an MMU cannot access any memory without some initial mapping via hardware registers in the processor.[3] Typically, a small amount of memory is made available at boot time to accommodate loading and decompressing the kernel and a ramdisk image. Trying to access code or data beyond these early limits will fail. Each architecture and platform might have different early limits for accessing memory. Values on the order of 8 to 16MB are not untypical. We must remember that any code we execute from machine_init, including our platform initialization, takes place in this context. If you encounter data access errors (PowerPC DSI exception[4]) while debugging your new kernel port, you should immediately suspect that you have not properly mapped the memory region your code is trying to access.

[3] The AMCC PPC405 is a perfect example of this. The interested reader is encouraged to examine the BAT registers in this processor.

[4] Refer to the Programming Environments Manual referenced at the end of this chapter for details of the PowerPC DSI exception.



Embedded Linux Primer(c) A Practical Real-World Approach
Embedded Linux Primer: A Practical Real-World Approach
ISBN: 0131679848
EAN: 2147483647
Year: 2007
Pages: 167

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net