52884.fb2 Embedded Linux Primer: A Practical, Real-World Approach - читать онлайн бесплатно полную версию книги . Страница 21

Embedded Linux Primer: A Practical, Real-World Approach - читать онлайн бесплатно полную версию книги . Страница 21

Chapter 16. Porting Linux

It is not difficult to port Linux to a new hardware platform. The Linux source tree contains ports for numerous boards spanning more than 20 architectures and many more individual processors. Knowing where to start is often the hardest part.

This chapter covers the basics of porting Linux to a custom board providing support for basic Ethernet and serial console operation. We examine the organization of the Linux source code from an architectural and platform perspective. We then delve into the early kernel initialization code to understand the mechanisms provided for platform initialization. Finally, we look at a typical porting effort to a custom PowerPC hardware platform.

16.1. Linux Source Organization

Not too long ago, there were numerous homes[106] for the various versions of Linux. There was a dedicated place for the PowerPC version of Linux, one for the ARM version, and so on. This wasn't necessarily by design, but by necessity. It took time to merge the various architecture infrastructure and features into the mainline kernel, and having a separate source tree meant quicker access to the latest features in a given architecture.

The kernel developers have gone to great lengths to unify the Linux kernel source code to bring together the disparate architectures under one common source tree. With few exceptions, this is the case today with the Linux 2.6 source. It is possible to download and compile working kernels for a variety of processors and industry-standard reference boards directly from www.kernel.org.

16.1.1. The Architecture Branch

In Chapter 4, "The Linux Kernel: A Different Perspective," we introduced the overall structure of the Linux kernel source tree. We spend the majority of this chapter examining the architecture-specific branch of the Linux kernel sources. Listing 16-1 shows the contents of .../arch from a recent kernel snapshot. As we pointed out in Chapter 4, the .../arch subdirectory is the second largest in terms of size, and in a recent Linux release, the largest in terms of file count (excluding the .../include directory). Only the .../drivers subdirectory is larger in size.

Listing 16-1. Linux Kernel .../arch Directory Listing

[chris@pluto linux]$ ls ./arch

alpha  cris   i386  m68k       parisc  s390  sparc    v850

arm    frv    ia64  m68knommu  ppc     sh    sparc64  x86_64

arm26  h8300  m32r  mips       ppc64   sh64  um       xtensa

From this listing, you can see support for 24 separate architectures within the Linux kernel. We refer to each as an architecture branch to facilitate our discussions.

Each architecture branch has some common components. For example, each top-level architecture branch contains a Kconfig file. You will recall from Chapter 4 that Kconfig drives the kernel configuration utility. Of course, each top-level architecture branch also has a corresponding makefile. All the top-level architectures contain a kernel subdirectory because a number of kernel features are architecture dependent. All but two contain an mm subdirectory. This is where the architecture-dependent memory management code is found.

Many top-level architecture branches contain a boot subdirectory, which is used to build (through its own makefile) a specific bootable target for that architecture. Many also contain mach-* subdirectories. These are used to hold code for specific machines or hardware platforms. Another subdirectory that appears frequently in the architecture branch is configs. This subdirectory exists for many of the more popular architectures and contains default configurations for each supported hardware platform.

Throughout the rest of this chapter, we focus our discussion and examples on the PowerPC architecture. It is one of the most popular, with support for many processors and boards. Listing 16-2 shows the contents of the configs directory for the .../arch/ppc PowerPC branch of a recent Linux kernel release.

Listing 16-2. PowerPC configs Directory Contents

[chris@pluto linux]$ ls ./arch/ppc/configs/

ads8272_defconfig   IVMS8_defconfig        prpmc750_defconfig

apus_defconfig      katana_defconfig       prpmc800_defconfig

bamboo_defconfig    lite5200_defconfig     radstone_defconfig

bseip_defconfig     lopec_defconfig        redwood5_defconfig

bubinga_defconfig   luan_defconfig         redwood6_defconfig

chestnut_defconfig  mbx_defconfig          rpx8260_defconfig

common_defconfig    mpc834x_sys_defconfig  rpxcllf_defconfig

cpci405_defconfig   mpc8540_ads_defconfig  rpxlite_defconfig

cpci690_defconfig   mpc8548_cds_defconfig  sandpoint_defconfig

ebony_defconfig     mpc8555_cds_defconfig  spruce_defconfig

ep405_defconfig     mpc8560_ads_defconfig  stx_gp3_defconfig

est8260_defconfig   mpc86x_ads_defconfig   sycamore_defconfig

ev64260_defconfig   mpc885ads_defconfig    TQM823L_defconfig

ev64360_defconfig   mvme5100_defconfig     TQM8260_defconfig

FADS_defconfig      ocotea_defconfig       TQM850L_defconfig

gemini_defconfig    pmac_defconfig         TQM860L_defconfig

hdpu_defconfig      power3_defconfig       walnut_defconfig

ibmchrp_defconfig   pplus_defconfig

Each one of these entries in the configs directory of the PowerPC architecture branch represents a specific port to a hardware platform. For example, walnut_defconfig defines the default configuration for the AMCC Walnut PPC405 evaluation platform. The mpc8540_ads_defconfig file represents the default configuration for the Freescale MPC8540 ADS evaluation board. As described in Chapter 4, to build a kernel for these reference platforms you first configure your kernel source tree with these configuration defaults, as follows:

$ make ARCH=ppc CROSS_COMPILE=ppc_85xx- mpc8540_ads_defconfig

This invocation of make (from the top-level Linux directory) configures the kernel source tree with a default configuration for the Freescale MPC8540 ADS evaluation board.

One aspect of the Linux kernel source tree that has not achieved significant unification is the way in which each architecture handles platform-specific files. In the PowerPC branch, you find a platforms directory that contains platform-specific code. Looking through this directory, you will see many source files named after the respective hardware platform. There are also a few subdirectories under .../arch/ppc/platforms for specific PowerPC variants.

In contrast, the ARM branch contains a series of mach-* directories, each representing a specific hardware platform, while the MIPS branch has a set of subdirectories named for a specific platform.

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.[107] 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.)

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.[108] 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[109]) 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.

16.3. Platform Initialization

Following is a quick review of the code flow during early initialization. Figure 16-3 shows the flow of execution from the bootloader or bootstrap loader to your platform-initialization code.

Figure 16-3. Platform initialization flow of control

The files head.S and setup.c are both found in the .../arch/ppc/kernel directory for the PowerPC architecture. Our custom platform-initialization file will be placed in the .../arch/ppc/platforms directory. In Figure 16-3, it is represented by the file myplat.c. We are now in a position to examine the platform-specific initialization file in detail.

In Listing 16-3, we listed the functions in the lite5200.c platform-initialization file. Every function except platform_init() is declared as static. Therefore, as shown in Figure 16-3, this is the entry point for the platform-initialization file. The rest of the functions in the file are referenced only from within the file itself.

Let's examine the entry function platform_init(). Listing 16-8 reproduces the platform_init() function from the lite5200.c file.

Listing 16-8. Lite5200 platform_init Function

void __init platform_init(unsigned long r3, unsigned long r4, unsigned long r5, unsigned long r6, unsigned long r7) {

/* Generic MPC52xx platform initialization */

/* TODO Create one and move a max of stuff in it. Put this init in the syslib */

 struct bi_record *bootinfo = find_bootinfo();

 if (bootinfo) parse_bootinfo(bootinfo);

 else { /* Load the bd_t board info structure */

  if (r3) memcpy((void*)&__res,(void*)(r3+KERNELBASE), sizeof(bd_t));

#ifdef CONFIG_BLK_DEV_INITRD

  /* Load the initrd */

  if (r4) {

   initrd_start = r4 + KERNELBASE;

   initrd_end = r5 + KERNELBASE;

  }

#endif

 /* Load the command line */

  if (r6) {

   *(char *)(r7+KERNELBASE) = 0;

   strcpy(cmd_line, (char *)(r6+KERNELBASE));

  }

 }

 /* PPC Sys identification */

 identify_ppc_sys_by_id(mfspr(SPRN_SVR));

 /* BAT setup */

 mpc52xx_set_bat();

 /* No ISA bus by default */

 isa_io_base = 0;

 isa_mem_base = 0;

 /* Powersave */

 /* This is provided as an example on how to do it. But you need to be aware that NAP disable bus snoop and that may be required for some devices to work properly, like USB

 ... */

 /* powersave_nap = 1; */

 /* Setup the ppc_md struct */

 ppc_md.setup_arch = lite5200_setup_arch;

 ppc_md.show_cpuinfo = lite5200_show_cpuinfo;

 ppc_md.show_percpuinfo = NULL;

 ppc_md.init_IRQ = mpc52xx_init_irq;

 ppc_md.get_irq = mpc52xx_get_irq;

#ifdef CONFIG_PCI

 ppc_md.pci_map_irq = lite5200_map_irq;

#endif

 ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;

 ppc_md.setup_io_mappings = mpc52xx_map_io;

 ppc_md.restart = mpc52xx_restart;

 ppc_md.power_off = mpc52xx_power_off;

 ppc_md.halt = mpc52xx_halt;

 /* No time keeper on the LITE5200 */

 ppc_md.time_init = NULL;

 ppc_md.get_rtc_time = NULL;

 ppc_md.set_rtc_time = NULL;

 ppc_md.calibrate_decr = mpc52xx_calibrate_decr;

#ifdef CONFIG_SERIAL_TEXT_DEBUG

 ppc_md.progress = mpc52xx_progress;

#endif

}

This function contains much of the customizing that is required for this particular platform. It starts by searching for board-specific data supplied by the bootloader. We defer discussion of the details of this until Section 16.3.2, "Board Information Structure."

Following this, if your kernel is configured for an initial ramdisk (initrd)[110], the start and end addresses of the ramdisk image are saved. Notice that they are passed in the PowerPC general-purpose registers r4 and r5 by convention. It is the bootloader's responsibility to pass the initrd addresses in these registers. Later, the kernel will use these addresses to load the initrd image from raw memory (where the bootloader placed it, or a nonvolatile Flash image) into an internal kernel ramdisk structure.

Next we see code to store the kernel command line, whose address is passed into platform_init() via registers r6 and r7, marking the start and end addresses, respectively. This differs from the method described earlier for storing a static kernel command line in one specific detail: this kernel command line was passed to platform_init() from the bootloader, as opposed to being compiled into the kernel.

Copying the initrd and kernel command line is very straightforward. Basically, the registers passed in from the bootloader contain the memory addresses where these data structures reside. There is one minor subtlety, however. You may have already wondered about the purpose of the constant KERNELBASE. Understanding this is key to grasping one of the more complex parts of the boot sequence.

The addresses the bootloader provides are physical addresses. This means they are the real hardware addresses where the data resides in the memory chips. The bootloader typically operates without support for virtual memory. However, the kernel itself is statically linked to a well-known, user-configured base address. This address is KERNELBASE. (The value itself is not relevant to the discussionit is user configurable but virtually never changed from its default value of 0xC0000000.)

This sets up an interesting situation in head.S. When the kernel is decompressed and relocated to RAM (usually to location 0), all of its code and data symbols are linked at the kernel's virtual address, KERNELBASE. This can be seen by examining the kernel symbol map file, produced during the kernel build process, System.map.[111] However, the execution context prior to enabling the MMU is such that physical addresses are real hardware addresses. This means that all the code prior to enabling the MMU and virtual memory mapping must be relocatable, and access to symbols must be fixed up. This involves adding an offset to the symbol's address to access it. An example will make this clear.

16.3.1. Early Variable Access

Let's assume that a code segment very early in the boot process needs to access the variable cmd_line so early that we're executing in 1:1 real to physical mapping. As pointed out earlier, this variable is defined in head.S and will end up in the .data section when the kernel is linked. From the Linux kernel's System.map file, you can find the linked addresses for cmd_line :

$ cat System.map | grep cmd_line

   c0115000 D cmd_line

If we were running in real = physical mode (MMU disabled) and accessed this variable using its symbol, we would be trying to read or write to an address greater than 3GB. Most smaller embedded systems don't have any RAM in this region, and the results would be undefined or would result in a crash. Even if we had physical RAM at that address, it is unlikely that it would be mapped and accessible this early in the boot process. So we have to adjust our reference to this variable to access it.

Listing 16-9 reproduces a code snippet from head.S that does just that.

Listing 16-9. Variable Reference Fixup

relocate_kernel:

      addis r9,r26,klimit@ha /* fetch klimit */

      lwz   r25,klimit@l(r9)

      addis r25,r25,-KERNELBASE@h

This code snippet from the PowerPC head.S is a good example of the issue we are describing. The variable klimit represents the end of the kernel image. It is defined elsewhere as char *klimit. Therefore, it is a pointerit is an address that contains an address. In Listing 16-9, we fetch the address of klimit, sum it with a previously calculated offset that is passed in r26, and deposit the resulting value in register r9. Register r9 now contains the high-order 16 bits of the adjusted address of klimit, with the low-order bits zeroed.[112] It was adjusted by the offset value previously calculated and passed in register r26.

In the next line, the lwz instruction uses register r9 together with the offset of klimit (the lower 16 bits of the klimit address) as an effective address from which to load register r25. (Remember, klimit is a pointer, and we are interested in the value that klimit points to.) Register r25 now holds the pointer that was stored in the variable klimit. In the final line of Listing 16-9, we subtract the kernel's linked base address (KERNELBASE) from r25 to adjust the pointer to our actual physical address. In C, it would look like this:

unsigned int *tmp; /* represents r25 */

tmp = *klimit;

tmp -= KERNELBASE;

In summary, we referenced a pointer stored in klimit and adjusted its value to our real (physical) address so we can use its contents. When the kernel enables the MMU and virtual addressing, we no longer have to worry about thisthe kernel will be running at the address where it was linked, regardless of where in physical memory it is actually located.

16.3.2. Board Information Structure

Many bootloaders are used for PowerPC platforms, but there is still no unified way to pass in board-specific data such as serial port baud rate, memory size, and other low-level hardware parameters that the bootloader has configured. The platform-initialization file from Listing 16-8 supports two different methods, data stored as struct bi_record and data stored as struct bd_info.[113] Both methods provide similar results: hardware-specific data is passed from the bootloader to the kernel in these structures.

From Listing 16-8, here is the code snippet that saves the bootloader-supplied hardware configuration:

struct bi_record *bootinfo = find_bootinfo();

if (bootinfo) parse_bootinfo(bootinfo);

else {

 /* Load the bd_t board info structure */

 if (r3) memcpy((void*)&__res,(void*)(r3+KERNELBASE), sizeof(bd_t));

First, we search for a special tag that identifies the data structure as a struct bi_record. If that is found, the bootinfo pointer is set to the address of the start of the bootinfo records. From there, the records are parsed and the hardware related data is gathered. This can be seen by inspecting .../arch/ppc/kernel/setup.c. Currently, bi_records can contain the kernel command line, the start and end address of the initrd image, the machine type, and the size of the memory. Of course, you can extend this for your own requirements.

If no bi_record data is found, the PowerPC architecture expects this data in the form of U-Boot board information structure, or bd_info. It is the bootloader's responsibility to construct this data structure and pass the address in register r3. Currently, many bits of hardware information are available in the bd_info structure, including information on DRAM, FLASH, SRAM, processor clock rates, bus frequencies, serial port baud rate setting, and more.

The bi_record structure can be examined in .../include/asm-ppc/bootinfo.h, and the bd_info structure can be found in .../include/asm-ppc/ppcboot.h.

It is the responsibility of the platform-initialization routines to make use of any of the data that might be necessary to complete the hardware setup, or to communicate it to the kernel. For example, platform_init() sets up a pointer to a function whose name reveals its purpose. The code from Listing 16-8 is reproduced here:

ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;

Looking at the function mpc52xx_find_end_of_memory(), which is found in .../arch/ppc/syslib/mpc52xx_setup.c, we find the following:

u32 ramsize = __res.bi_memsize;

if (ramsize == 0) {

 ... /* Find it another way */

}

return ramsize;

The __res data structure above is the board information structure, whose address was passed to us from the bootloader in register r3 above. As you can see, the generic setup code stored the residual data (as it is often called) passed in by the bootloader, but it's up to the machine or platform-specific code to make use of it.

16.3.3. Machine-Dependent Calls

Many common routines that the kernel needs either for initialization or for operation are architecture and machine (CPU) dependent. From the platform_init() function reproduced in Listing 16-8, we saw the following:

...

/* Setup the ppc_md struct */

ppc_md.setup_arch = lite5200_setup_arch;

ppc_md.show_cpuinfo = lite5200_show_cpuinfo;

ppc_md.show_percpuinfo = NULL;

ppc_md.init_IRQ = mpc52xx_init_irq;

ppc_md.get_irq = mpc52xx_get_irq;

#ifdef CONFIG_PCI

ppc_md.pci_map_irq = lite5200_map_irq;

#endif

ppc_md.find_end_of_memory = mpc52xx_find_end_of_memory;

ppc_md.setup_io_mappings = mpc52xx_map_io;

ppc_md.restart = mpc52xx_restart;

ppc_md.power_off = mpc52xx_power_off;

ppc_md.halt = mpc52xx_halt;

...

Lines similar to these make up the rest of the platform_init() function. Here the bulk of the platform-specific needs are communicated to the Linux kernel. The global variable ppc_md, of type struct machdep_calls, provides the hooks to easily customize the Linux kernel for a PowerPC platform. This variable is declared in .../arch/ppc/kernel/setup.c. Many places in the PowerPC-specific kernel branch call functions indirectly through this structure. For example, Listing 16-10 reproduces a portion of .../arch/ppc/kernel/setup.c, which contains support for the restart, power-off, and halt functions:

Listing 16-10. Generic PowerPC Machine Functions

void machine_restart(char *cmd) {

#ifdef CONFIG_NVRAM

 nvram_sync();

#endif

 ppc_md.restart(cmd);

}

void machine_power_off(void) {

#ifdef CONFIG_NVRAM

 nvram_sync();

#endif

 ppc_md.power_off();

}

void machine_halt(void) {

#ifdef CONFIG_NVRAM

 nvram_sync();

#endif

 ppc_md.halt();

}

These functions are called via the ppc_md structure and contain the machine- or platform-specific variants of these functions. You can see that some of these functions are machine specific and come from mpc52xx_* variants of the functions. Examples of these include mpc52xx_restart and mpc52xx_map_io. Others are specific to the hardware platform. Examples of platform-specific routines include lite5200_map_irq and lite5200_setup_arch.

16.4. Putting It All Together

Now that we have a reference from which to proceed, we can create the necessary files and functions for our own custom board. We copy the Lite5200 platform files for our baseline and modify them for our custom PowerPC platform. We'll call our new platform PowerDNA. The steps we will perform for this custom port are as follows:

1. Add a new configuration option to ...arch/ppc/Kconfig.

2. Copy lite5200.* to powerdna.* as a baseline.

3. Edit new powerdna.* files as appropriate for our platform.

4. Edit .../arch/ppc/Makefile to conditionally include powerdna.o.

5. Compile, load, and debug!

You learned how to add a configuration option to Kconfig in Chapter 4. The configuration option for our new PowerDNA port is detailed in Listing 16-11.

Listing 16-11. Configuration Option for PowerDNA

config POWERDNA

       bool "United Electronics Industries PowerDNA"

       select PPC_MPC52xx

       help

         Support for the UEI PowerDNA board

This Kconfig entry is added just below the entry for LITE5200 because they are related.[114] Figure 16-4 illustrates the results when the configuration utility is invoked.

Figure 16-4. Machine type option for PowerDNA

Notice that when the user selects POWERDNA, two important actions are performed:

1. The CONFIG_PPC_MPC52xx configuration option is automatically selected. This is accomplished by the select keyword in Listing 16-11.

2. A new configuration option, CONFIG_POWERDNA, is defined that will drive the configuration for our build.

The next step is to copy the files closest to our platform as the basis of our new platform-initialization files. We have already decided that the Lite5200 platform fits the bill. Copy lite5200.c to powerdna.c, and lite5200.h to powerdna.h. The difficult part comes next. Using the hardware specifications, schematics, and any other data you have on the hardware platform, edit the new powerdna.* files as appropriate for your hardware. Get the code to compile, and then proceed to boot and debug your new kernel. There is no shortcut here, nor any substitute for experience. It is the hard work of porting, but now at least you know where to start. Many tips and techniques for kernel debugging are presented in Chapter 14, "Kernel Debugging Techniques."

To summarize our porting effort, Listing 16-12 details the files that have been added or modified to get Linux running on the PowerDNA board.

Listing 16-12. PowerDNA New or Modified Kernel Files

linux-2.6.14/arch/ppc/configs/powerdna_defconfig

linux-2.6.14/arch/ppc/Kconfig

linux-2.6.14/arch/ppc/platforms/Makefile

linux-2.6.14/arch/ppc/platforms/powerdna.c

linux-2.6.14/arch/ppc/platforms/powerdna.h

linux-2.6.14/drivers/net/fec_mpc52xx/fec.c

linux-2.6.14/drivers/net/fec_mpc52xx/fec.h

linux-2.6.14/drivers/net/fec_mpc52xx/fec_phy.h

linux-2.6.14/include/asm-ppc/mpc52xx.h

The first file is the default configuration, which enables a quick kernel configuration based on defaults. It is enabled by invoking make as follows:

$ make ARCH=ppc CROSS_COMPILE=<cross-prefix> powerdna_defconfig

We've already discussed the changes to the Kconfig file. Modification to the makefile is trivialthe purpose is to add support for the new kernel configuration based on CONFIG_POWERDNA. The change consists of adding a single line:

obj-$(CONFIG_POWERDNA) += powerdna.o

The heart of the changes come in the powerdna.[c|h] files and changes to the FEC (Fast Ethernet Controller) layer. There were minor differences between powerdna.c and lite5200.c, the file from which it was derived. Two primary issues required changes. First, PCI was disabled because it is not used in the PowerDNA design. This required some minor tweaking. Second, the PowerDNA design incorporates an unmanaged Ethernet physical-layer chip that required slight changes in the hardware setup and the FEC layer. This work constituted the majority of the porting effort. The patch file consists of 1120 lines, but the bulk of those lines are the default configuration, which is only a convenience for the developer and is not strictly necessary. Removing that, the patch reduces to 411 lines.

16.4.1. Other Architectures

We examined the details of how a given platform fits into the kernel, and the facilities that exist for porting to a new board. Our reference for this chapter and the discussions within came from the PowerPC architecture branch of the kernel. The other architectures differ in many detailed aspects of how various hardware platforms are incorporated, but the concepts are similar. When you have learned how to navigate a single architecture, you have the knowledge and tools to learn the details of the other architectures.

16.5. Chapter Summary

• Porting Linux to a custom board based on a Linux-supported CPU can be relatively straightforward. There is no substitute for experience and knowledge of the Linux code base and your hardware platform.

• Starting from a working reference configuration based on a hardware platform already supported provides an excellent basis for your own modifications.

• Understanding the flow of initialization code is the key to an easy porting effort. We made every effort to leave all generic kernel code untouched and to modify only those files necessary for the platform itself. A significant part of this chapter is devoted to this early flow of control related to platform initialization.

• Make doubly certain that your low-level hardware platform initialization is correct before proceeding. If you find yourself debugging in some obscure part of the Linux slab allocator, for example, it's a good bet you've messed something up with your hardware memory initialization.

• This chapter focused primarily on the PowerPC architecture branch of the Linux kernel. Learning the details of one architecture paves the way for understanding the rest.

16.5.1. Suggestions for Additional Reading

Programming Environments Manual for 32-Bit Implementations of the PowerPC Architecture

MPCFPE32B/AD 12/2001 REV 2

Freescale Semiconductor, Inc.

MPC5200 User's Guide

MPC5200UG Rev 3 01/22005

Freescale Semiconductor, Inc.


  1. By "homes," we mean a public source code repository, such as a web server on the Internet.

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

  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.

  5. The initial ramdisk, or initrd, was introduced in Chapter 6.

  6. We introduced the System.map file in Chapter 4.

  7. For details of PPC assembly language syntax, see Section 16.5.1, "Suggestions for Additional Reading" at the end of this chapter.

  8. Each method has its own roots. The struct bd_info originated in U-Boot, and struct bi_record was an attempt to unify across all platforms. Both are supported by many platforms.

  9. To preserve space, we temporarily removed many machine types in Figure 16-4 prior to LITE5200.