Building U-Boot and Linux from scratch for the BeagleBone, and booting ====================================================================== * Author: Philippe Proulx [`pzhilippe.proulx@savoirzfairelinux.com` (remove `z`s)] * Date: Mon Jul 22 14:03:30 EDT 2013 Introduction ------------ Here's a really "simple" guide on how to build U-Boot and the Linux kernel from scratch for the BeagleBone SoC. This is mostly a reminder of the steps for myself and shows what I recently learned from lots of different sources and by trial and error. There are faster/automated ways to do all this. Buildroot has a BeagleBone target, although as of this date, it builds Linux 3.2. Also, the Ångström distribution, the "sort of" official BeagleBone distribution, can be used, but it's old too. This document explains the raw steps to build everything from scratch for didactic purposes. Download links -------------- As of this date, you can get the needed files from here: * **U-Boot**: * **Kernel**: `git clone git://github.com/beagleboard/kernel.git` * **Custom power firmware**: * **Latest Busybox ARMv7 binary**: * **ARM cross-compiling toolchain**: there are lots of sources for this; just make sure you get something with a Linux target, like `arm-linux-eabi`. I used the Buildroot downloaded tools (without actually using Buildroot for the rest of the process). We cannot use the vanilla kernel as is since TI made lots of patches that need to be applied which are not mainlined. This is why we're using the BeagleBoard GitHub repository (contains valid patches and scripts for BeagleBoard and BeagleBone kernels). Building U-Boot --------------- The first step is to build the bootloader, U-Boot. Two important files will come out of this process: `MLO` and `u-boot.img`. `MLO` is what the U-Boot community calls the SPL (Secondary Program Loader) and contains executable code for the second boot stage. Here are the stages as a reminder (for an MMC boot): 1. the on-board ROM initializes a few cores and searches for a file called `MLO` on the first FAT partition of the first MMC card (the one that has a socket on the BeagleBone), then loads it in memory and executes it 2. the SPL (`MLO`) initializes a few other things and searches on the same partition for `u-boot.img`, which is the third boot stage (the actual complete U-Boot program), loads it in memory and executes it 3. U-Boot starts and after 1-2 seconds, you can get a prompt `MLO` is compiled by U-Boot too and the vanilla U-Boot knows how to produce this SPL (it has a board target for this). Download U-Boot and extract it: $ wget ftp://ftp.denx.de/pub/u-boot/u-boot-latest.tar.bz2 $ tar -xjvf u-boot-latest.tar.bz2 All the available boards are listed in `boards.cfg`. In there you will find `am335x_evm`, `evm` meaning _evaluation module_. `am335x_evm` is a common name for the SoC used by the BeagleBone boards (exact SoC is an AM3359). Although it's not obvious, this configuration row points to the include file `include/configs/am335x_evm.h`, defines `SERIAL1` and also `CONS_INDEX` to 1. This file, `am335x_evm.h`, contains all the specific configurations U-Boot needs in order to build `MLO` and `u-boot.img` files that the SoC will understand. Amongst them is the `CONFIG_EXTRA_ENV_SETTINGS` definition, which embed environment variables into the U-Boot output image file. The less we put there, the more manual will be the boot process, and that's what we want here since we're doing everything from scratch to understand. In `include/configs/am335x_evm.h`, remove the following default environment variables (`CONFIG_EXTRA_ENV_SETTINGS`): * all the `*args` variables (including `bootargs`) * all the `*boot` variables * `loadaddr` * `fdtaddr` * `rdaddr` * `bootfile` * `fdtfile` * `loadbootenv` * `importbootenv` * `loadramdisk` * `loaduimagefat` * `loaduimage` * `findfdt` Also comment all the `CONFIG_BOOTCOMMAND` definition block. Save the file. This way, we have something minimalist and we're happy. To make sure you're using your own version of U-Boot, you may also change the prompt (`CONFIG_SYS_PROMPT`) for something you'll recognize. Leave the rest as is, although there's still stuff we don't need. This will at least provide us with a clean U-Boot environment, without predefined stuff we don't want/understand. We're now ready to build U-Boot's main configuration: $ make ARCH=arm CROSS_COMPILE=/path/to/your/toolchain/arm-something-something- am335x_evm_config This basically creates `include/config.h` which includes relevant files for the `am335x_evm` configuration. You will also see the additional definitions in `boards.cfg` are added at the top of `include/config.h`: #define CONFIG_SERIAL1 1 #define CONFIG_CONS_INDEX 1 We can now build U-Boot: $ make ARCH=arm CROSS_COMPILE=/path/to/your/toolchain/arm-something-something- -j4 Wait a few seconds and you will get `MLO` and `u-boot.img` in U-Boot's root directory. So far so good. In order to build the Linux kernel as an `uImage`, which is a `zImage` with the U-Boot legacy image header, we need an installed tool called `mkimage` which a kernel Makefile will call. This can be built and installed now: $ make tools # install tools/mkimage /usr/local/bin Just make sure you can execute it: $ mkimage -h You might need to restart your shell in order to rescan the `$PATH` binaries if it says something like `command not found`. Building the Linux kernel ------------------------- Let's now build Linux. First step is to clone the aforementioned GitHub repo: $ git clone git://github.com/beagleboard/kernel.git and checkout whatever version tag you want. Unfortunately, because of the patches that need to be applied, you won't get a bleeding edge kernel here. For example, let us checkout the 3.8 kernel: $ git checkout origin/3.8 -b 3.8 Then, apply the patches (long step because it actually clones Torvald's kernel, checksout the correct version and then applies lots of patches to it, which are in the `patches` directory): $ ./patch.sh After this, you shall see a `kernel` directory which contains the patched Linux source. Some steps are still required before actually building it. $ cd kernel The kernel will need a precompiled power management firmware which for some reason is maintained by the Arago Project (which belongs to TI). Put it in the `firmware` directory: $ wget http://arago-project.org/git/projects/?p=am33x-cm3.git\;a=blob_plain\;f=bin/am335x-pm-firmware.bin\;hb=HEAD -O firmware/am335x-pm-firmware.bin TI puts default `.config` files into `configs` (from the repo's root), so use BeagleBone's default `.config` like so: $ cp ../configs/beaglebone arch/arm/configs/beaglebone_defconfig and create the configuration (will create `.config`): $ make ARCH=arm CROSS_COMPILE=/path/to/your/toolchain/arm-something-something- beaglebone_defconfig We may now compile the kernel: $ make ARCH=arm CROSS_COMPILE=/path/to/your/toolchain/arm-something-something- -j4 uImage dtbs Let me explain what important files in `arch/arm/boot` those two targets will create while this is compiling: * `zImage`: this is a self-extracting compressed kernel. You execute this file, and it has an decompression algorithm to extract the rest of it, which is the actual kernel to execute. * `uImage`: this is `zImage` with a 64-byte U-Boot header so that U-Boot knows a few things when asked to boot this file (the previously installed `mkimage` tool is used to create this of the `zImage`). * `dts/am335x-bone.dtb`: the compiled device tree "blob" which the kernel must have in order to initialize its drivers and SoC-specific routines. At the end of the process, you will see: Image arch/arm/boot/uImage is ready and will be happy about it. `mkimage` will also report the load address and entry point of the created image, both of which should be 0x80008000 (external DRAM starts at 0x80000000 on the AM335x SoC). Usually, when booting (we will do this later), you need to load `uImage` and `dts/am335x-bone.dtb` at different memory locations, then ask U-Boot to boot by providing those two addresses. But there's another way to give the DTB to Linux. In `.config`, you will see CONFIG_ARM_APPENDED_DTB=y which tells the kernel to look at the end of its `zImage` for a valid DTB if you don't provide it with a memory address when booting. So you only need to concatenate `dts/am335x-bone.dtb` to `zImage` and then use `mkimage` to create a `uImage` with appended DTB from this. This is exactly what the `uImage-dtb.am335x-bone` target does: $ make ARCH=arm CROSS_COMPILE=/path/to/your/toolchain/arm-something-something- uImage-dtb.am335x-bone Now you also have `arch/arm/boot/uImage-dtb.am335x-bone`. Its size should be the size of `zImage` + the size of `dts/am335x-bone.dtb` + 64. Creating a root filesystem -------------------------- The kernel will be able to start booting with the above files, but won't be able to finish because it will complain it cannot find any root filesystem. We need a very basic one. Let us create a root minimalist filesystem from scratch. Create a new directory for the root filesystem: $ mkdir rootfs $ cd rootfs As root, create the basic directory structure: # mkdir bin dev proc sys Download the latest Busybox build for ARMv7 and make it executable: # wget http://www.busybox.net/downloads/binaries/latest/busybox-armv7l -O bin/busybox # chmod +x bin/busybox Create Busybox init and sh links: # ln -s busybox bin/init # ln -s busybox bin/sh That's really all we need for Busybox: an init and a shell (in fact, `/bin/sh` could be the init itself). We'll execute everything else by calling `busybox` directly once in that shell (think minimalist). Create a few `/dev` important nodes: # mknod dev/console c 5 1 # mknod dev/null c 1 3 So here we have it: # tree . ├── bin │   ├── busybox │   ├── init -> busybox │   └── sh -> busybox ├── dev │   ├── console │   └── null ├── proc └── sys Bringing it all together: creating the SD card ---------------------------------------------- Let's review what we have: * U-Boot's `MLO` * U-Boot's `u-boot.img` * Linux's `uImage-dtb.am335x-bone` * our root filesystem We need to put all this on a properly formatted SD card. Until the end of this document, my SD card device is `/dev/sdb`. Yours might be different: be careful here. Delete all existing SD card partitions. Use fdisk with the `d` command to delete everything and write it with `w`. You might need to unplug/replug your SD card for the changes to be seen by the kernel. Create a FAT partition and an ext4 partition: # fdisk /dev/sdb <