An introduction to Embedded Linux, BeagleBoard & its Linux kernel port
Jon Masters takes a break from his usual kernel column format this month to introduce us to the world of embedded Linux with an overview of the BeagleBoard and its Linux kernel port…
Porting the kernel
Linux has been available for the ARM architecture for many years now. The original ‘port’ was done by Russell King, and he is still the maintainer through whom all ARM kernel patches generally must pass. The work was much harder then than it would be now, since at the time the Linux kernel was still very Intel-centric. In fact, modern Linux kernels come with a handy reference example called asm-generic that shows all of the header files and kernel interfaces that a new architecture port should provide. Once the kernel has been ported to a given architecture, it is necessary to implement support for a specific platform based upon that architecture.
To understand platforms, consider that the PC is a platform originally from IBM and built upon the Intel x86 processor, while the Intel Moorestown is an x86 processor but it is used to create non-PC platforms. The difference is that ‘PC’ implies the presence of certain standard devices, ACPI and UEFI system descriptor tables, a PCIe system bus and so forth. Similarly, Texas Instruments implements the ARM architecture in its OMAP processors, while BeagleBoard is a particular variant of the OMAP3 platform. Thus Beagle uses generic ARM architecture code in the arch/arm directory, but it also makes use of platform code in arch/arm/mach-omap2/board-omap3beagle.c.
No matter the architecture, kernel code execution generally begins (after being loaded by the bootloader – more on that later) in a file written in assembly language, and known as head.S (for historical reasons). In the case of ARM, this file is arch/arm/kernel/head.S, at a location identified very plainly with the comment, ‘Kernel startup entry point’. When this code begins running, the processor has few of the required features enabled, such as virtual memory or caching of instructions and data. It may even be executing code that has been loaded at an address that differs from the one at which the code was compiled to run at. Thus, to avoid a silent crash, the first thing the kernel does is to work out what memory location it was really loaded into. It then carefully avoids making any regular function calls until the virtual memory logic has been enabled, at which point the kernel is running at the correct (virtual) location.
The first useful thing the kernel does is to (carefully, using special memory-location-accounting logic) call the functions __lookup_processor_type and __lookup_machine_type, which are used to perform various ‘fixups’ – literally hacks to replace kernel code or enable and disable certain features. After this, the kernel initialises a region of memory for use as a stack from C code and jumps into the first real C function, the top-level generic ‘start_kernel’, in init/main.c.
This function does a lot of the same things on every platform, one of which is to call into a function called setup_arch, which calls setup_machine. Later, it calls a function do_basic_setup, which causes customize_machine to run by virtue of a feature known as an ‘initcall’ (the customize_machine function is marked specially). This causes init_machine to run, which in the case of BeagleBoard is the function omap3_beagle_init, contained within the board-omap3beagle.c file mentioned previously. The setup here includes initialising various Beagle devices, but for the most part simply informs the kernel that devices exist for which standard drivers are loaded later.
You can dig deeper into the startup process by following the code using a tool such as Cscope, or LXR. You can also purchase a special hardware debugger and monitor the startup of the BeagleBoard. This author uses an inexpensive ‘Flyswatter’ JTAG debugger from Tincantools and the open source OpenOCD utility, but this is still under development and only recommended for the brave. Much more expensive, commercial hardware debuggers are available if you have a hardware budget to play with.
Compiling the kernel
During its development, the kernel must be frequently recompiled for the BeagleBoard. The kernel ships with a powerful build system, Kbuild, which is what generates the .config files that are often saved in /boot on Linux systems. It is also able to understand how to drive a cross-compiler.
Typically, the process of porting the kernel will lead to the creation of a ‘defconfig’ default configuration file that contains options for the target, and you should be able to get one of these from the BeagleBoard website, or from an existing BeagleBoard kernel. By copying the appropriate defconfig file to the top-level kernel directory and running…
$ make ARCH=”arm” oldconfig
…the Kbuild system is instructed to import the existing build configuration, bearing in mind (through the use of an environmental variable) that it is looking at an ARM target, even if the configuration is taking place on a regular desktop PC. It is possible to modify the configuration by running:
$ make ARCH=”arm” menuconfig
With a working configuration, the kernel can be built using an ARM cross compiler. These are available from many different sources, including your desktop distribution (which might be shipping an older version), and from the kernel.org website here. You can, for example, extract the 4.5.1 version of the ARM GCC compiler called x86_64-gcc-4.5.1-nolibc_arm-unknown-linux-gnueabi.tar.bz2 into /data/toolchains/crosstool and invoke the compiler on the configured kernel sources using the following command (replace -j4 with -jNUMBER_OF_CPUS):
$ make ARCH=”arm” CROSS_COMPILE=”/data/toolchains/gcc-4.5.1-nolibc/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-” -j4 uImage
The output from the build varies by architecture. In the case of ARM, this is a file called uImage contained in the arch/arm/boot directory of the kernel source. The file is automatically generated (from the standard zImage file the kernel would otherwise create), using the ‘mkimage’ command from the uboot-tools package (which might need to be installed on your system). uImage contains a standard compressed kernel image wrapped with a binary header in a format that the U-Boot bootloader used by BeagleBoard can understand (more on that shortly).
Installing the kernel
BeagleBoard, like most embedded Linux projects now, uses a bootloader called U-Boot to load the kernel immediately after power on. You can think of it as a cross between the BIOS on your PC and the GRUB bootloader, since it not only performs early system memory and device initialisation, but also provides an interactive command interface (press a key during boot) from which various options can be configured or kernel images loaded. By default, it follows a built-in boot script (or loads one from storage) that tells it which kernel image to load, and where in memory to load it.
U-Boot passes various optional parameters into the kernel as it loads it, using some of the 32 processor registers to store the ‘machine number’ (a unique identifier of the type of machine, such as BeagleBoard) and various other possible pieces of data that the kernel can later read.
In the case of the latest ‘xM’ BeagleBoard, the previous NAND flash is done away with, so there is no flash within which to store the U‑Boot bootloader permanently. Fortunately, the processor used on the BeagleBoard features a special ability to load a small piece of code from a certain kind of FAT file system on the microSD card (without software support!). This small piece of code is called ‘MLO’ and it is about 24K in size. It must also live in the very first sector of the file system, which means that it must be the first file written to the first partition (which must be FAT) of the microSD card right after it is formatted.
You can try to copy this file later, but it will never end up physically in the right location. Therefore, you must follow the instructions on the BeagleBoard wiki when first setting up a new microSD card or you will find that U-Boot never loads, neither does your kernel image, and you see only garbled output on the serial port. Assuming that the bootloader is correctly installed, you can copy the uImage file that was generated previously onto the microSD card. Do this either by replacing the existing uImage file contained therein, or by editing the U-Boot ‘boot.cmd’ file (or similar) and then regenerating the binary version ‘boot.scr’ used at boot time with ‘mkimage’ according to the instructions on the BeagleBoard wiki. Once this is done, the kernel should be able to boot, but before you do so, you will need to install any kernel modules you built and regenerate any initramfs images. This author uses an NFS mount to perform a ‘make modules_install’ from the BeagleBoard, followed by a call to update-initramfs and then mkimage (to turn the new /boot/initrd.img into a uInitrd usable by U-Boot), but the exact procedure will depend upon the choice of distribution you install onto your board.
ARM Architecture explained
BeagleBoard is based upon the ARM Cortex-A8 System-on-Chip (SoC) processor. This means that BeagleBoard uses software compiled using the ARM Instruction Set Architecture (‘ARM arch’), an alternative to Intel’s x86 that was created by Cambridge (UK) company ARM Holdings. Originally, the latter was known as ‘Acorn RISC Machines’, since it was connected with early work on the BBC educational computers of the 1980s, but today ARM stands for ‘Advanced RISC Machines’. The company dominates the embedded computing space, in which its market share can be as high as 80 per cent (depending on sector). That’s not bad considering ARM Holdings doesn’t actually make any processors! It designs ARM processor cores and licenses the intellectual property to manufacturers – such as Texas Instruments, which implements it in its OMAP family of SoC processors, along with added on-chip peripherals, such as USB, audio, and video controllers.
The ARM architecture has evolved over the past several decades, to the point that it is now a clean 32-bit architecture that uses simple (RISC) fixed-width processor instructions and follows a so-called Harvard architecture model in which instructions and data are handled through separate memory paths (using common RAM, but separate Instruction and Data caches within the processor) for efficiency. ARM is a bi-endian architecture, which means (like PowerPC and others) it can run in either big- or little-endian mode. Generally, ARM Linux systems run in little-endian mode, like the x86, but they don’t have to do so. The latest version 7 of the architecture as implemented in the Cortex-A8 remains backwards compatible, while introducing some additional instructions that help to support multiple processors (as in the Cortex-A9) and other software optimisations that help operating systems such as Linux. You can learn more about the architecture by reading the ARM references at www.arm.com. If you do, check out the way ARM uses coprocessors (such as cp15) to implement virtual memory, which is a pretty neat hack.
Running interpreted code such as Python, Perl or even Java (bytecode) can be done on an ARM-based system without modifying it, but this is not true of compiled code, such as the Linux kernel. The kernel must be compiled for whatever target architecture it will run on, and it must include certain low-level startup code that initialises various hardware devices contained within the system, such as the memory controllers and IO devices, using specially hand-crafted assembly language code that runs before the system is capable even of running regularly compiled code (before things like the assumed heap, stack and so forth that C expects to exist have been set up).
Generally, the kernel is compiled using a cross-compiler – a special version of GCC that is able to run on a fast desktop PC, creating binary code that will run on an ARM processor instead of the PC processor.
That’s all for this month. Next month’s column will feature a summary of the exciting new features in the latest 2.6.38 kernel…
Click here for your chance to win a 12 month LCN.com Linux VPS Enterprise Package worth £500!















Love these boards. I’ve been using it’s big brother, the PandaBoard for a home server for a few months now. The websites have Ubuntu Install instructions for people who want a bit more of a consumer/software dev toy too.
Very nice. Lots of explanation and article starts from the basics. Can’t wait for the next column.