Building and Booting Upstream Linux and U-Boot for Raspberry Pi 2/3 ARM Boards

My home automation setup will make use of Arduinos and also embedded Linux devices. I’m currently looking into a few boards to see if any meet my criteria. Previously I looked at the Orange Pi One, now I’m looking at the Raspberry Pi 2 (which is compatible with the 3).

The most important factor for me is that the device must be supported in upstream Linux (preferably stable, but mainline will do) and U-Boot. I do not wish to use any old, crappy, vulnerable vendor trees!

The Raspberry Pi needs little introduction. It’s a small ARM device, created for education, that’s taken the world by storm and is used in lots of projects.

Raspberry Pi 2, powered by USB with 3v UART connected

Raspberry Pi 2, powered by USB with 3v UART connected

The Raspberry Pi actually has native support for booting a kernel, you don’t have to use U-Boot. However, one of the neat things about U-Boot is that it can provide netboot capabilities, so that you can boot your device from images across the network (we’re just going to use it to boot a kernel and initramfs, however).

One of the other interesting things about the Raspberry Pi is that there are lots of ways to tweak the device using a config.txt file.

The Raspberry Pi 3 has a 64bit CPU, however it is probably best run in 32bit mode (as a Raspberry Pi 2) as 64bit userland is not particularly advanced in ARM world, yet.

Fedora 25 will finally support Raspberry Pi 2 and 3 (although not all peripherals will be supported right away).

Connecting UART

The UART on the Raspberry Pi uses the GND, TX and RX connections which are on the GPIO pins (see above). Plug the corresponding cables from a 3.3V UART cable onto these pins and then into a USB port on your machine.

Your device will probably be /dev/ttyUSB0, but you can check this with dmesg just after plugging it in.

Now we can simply use screen to connect to the UART, but you’ll have to be in the dialout group.

sudo gpasswd -a ${USER} dialout
newgrp dialout
screen /dev/ttyUSB0 115200

Note that you won’t see anything just yet without an SD card that has a working bootloader. We’ll get to that shortly!

Partition the SD card

First things first, get yourself an SD card.

The card needs to have an msdos partition table with a smallish boot partition (formatted FAT32). The binary U-Boot file will sit there, called kernel.img, along with some other bootloader files. You can use the rest of the card for the root file system (but we’ll boot an initramfs, so it’s not needed).

Assuming your card is at /dev/sdx (replace as necessary, check dmesg after plugging it in if you’re not sure).

sudo umount /dev/sdx* # makes sure it's not mounted
sudo parted -s /dev/sdx \
mklabel msdos \
mkpart primary fat32 1M 30M \
mkpart primary ext4 30M 100%

Now we can format the partitions (upstream U-Boot supports ext3 on the boot partition).
sudo mkfs.vfat /dev/sdx1
sudo mkfs.ext4 /dev/sdx2

Next, mount the boot partition to /mnt, this is where we will copy everything to.
sudo mount /dev/sdx1 /mnt

Leave your SD card plugged in, we will need to copy the bootloader to it soon!

Upstream U-Boot Bootloader

Install the arm build toolchain dependencies.

sudo dnf install \
gcc-arm-linux-gnu \
binutils-arm-linux-gnu \
uboot-tools

We need to clone upstream U-Boot Git tree. Note that I’m checking out the release directly (-b v2016.09.01) but you could leave this off to get master, or change it to a different tag if you want.
cd "${HOME}"
git clone --depth 1 -b v2016.09.01 git://git.denx.de/u-boot.git
cd u-boot

There are default configs for both Raspberry Pi 2 and 3, so select the one you want.
# Run this for the Pi 2
CROSS_COMPILE=arm-linux-gnu- make rpi_2_defconfig
# Run this for the Pi 3
CROSS_COMPILE=arm-linux-gnu- make rpi_3_32b_defconfig

Now, compile it.
CROSS_COMPILE=arm-linux-gnu- make -j$(nproc)

Now, copy the u-boot.bin file onto the SD card, and call it kernel.img (this is what the bootloader looks for).

sudo cp -iv u-boot.bin /mnt/kernel.img

Proprietary bootloader files

Sadly, the Raspberry Pi cannot boot entirely on open source software, we need to get the proprietary files from Broadcom and place them on the SD card also.

Clone the Raspberry Pi Foundation’s GitHub repository.
cd "${HOME}"
git clone --depth 1 https://github.com/raspberrypi/firmware

Copy the minimum set of required files to the SD card.
sudo cp -iv firmware/boot/{bootcode.bin,fixup.dat,start.elf} /mnt/

Finally, unmount the SD card.
sync && sudo umount /mnt

OK, now our bootloader should be ready to go.

Testing our bootloader

Now we can remove the SD card from the computer and plug it into the powered off Raspberry Pi to see if our bootloader build was successful.

Switch back to your terminal that’s running screen and then power up the Pi. Note that the device will try to netboot by default, so you’ll need to hit the enter key when you see a line that says the following.

(Or you can just repeatedly hit enter key in the screen console while you turn the device on.)

Note that if you don’t see anything, swap the RX and TX pins on the UART and try again.

With any luck you will then get to a U-Boot prompt where we can check the build by running the version command. It should have the U-Boot version we checked out from Git and today’s build date!

Raspberry Pi running U-Boot

Raspberry Pi running U-Boot

Hurrah! If that didn’t work for you, repeat the build and writing steps above. You must have a working bootloader before you can get a kernel to work.

If that worked, power off your device and re-insert the SD card into your computer and mount it at /mnt.

sudo umount /dev/sdx* # unmount everywhere first
sudo mount /dev/sdx1 /mnt

Creating an initramfs

Of course, a kernel won’t be much good without some userspace. Let’s use Fedora’s static busybox package to build a simple initramfs that we can boot on the Raspberry Pi.

I have a script that makes this easy, you can grab it from GitHub.

Ensure your SD card is plugged into your computer and mounted at /mnt, then we can copy the file on!

cd ${HOME}
git clone https://github.com/csmart/custom-initramfs.git
cd custom-initramfs
./create_initramfs.sh --arch arm --dir "${PWD}" --tty ttyAMA0

This will create an initramfs for us in your custom-initramfs directory, called initramfs-arm.cpio.gz. We’re not done yet, though, we need to convert this to the format supported by U-Boot (we’ll write it directly to the SD card).

gunzip initramfs-arm.cpio.gz
sudo mkimage -A arm -T ramdisk -C none -n uInitrd \
-d initramfs-arm.cpio /mnt/uInitrd

Now we have a simple initramfs ready to go.

Upstream Linux Kernel

Clone the mainline Linux tree (this will take a while). Note that I’m getting the latest tagged release by default (-b v4.9-rc1) but you could leave this off or change it to some other tag if you want.

cd ${HOME}
git clone --depth 1 -b v4.9-rc1 \
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

Or, if you want to try linux-stable, clone this repo instead.
git clone --depth 1 -b v4.8.4 \
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux

Now go into the linux directory.
cd linux

Building the kernel

Now we are ready to build our kernel!

Load the default kernel config for the sunxi boards.
ARCH=arm CROSS_COMPILE=arm-linux-gnu- make bcm2835_defconfig

If you want, you could modify the kernel config here, but it’s not necessary.
ARCH=arm CROSS_COMPILE=arm-linux-gnu- make menuconfig

Build the kernel image and device tree blob.
ARCH=arm CROSS_COMPILE=arm-linux-gnu- make -j$(nproc) zImage dtbs

Mount the boot partition and copy on the kernel and device tree file.
sudo cp -iv arch/arm/boot/zImage /mnt/
sudo cp -iv arch/arm/boot/dts/bcm2836-rpi-2-b.dtb /mnt/

Bootloader config

Next we need to make a bootloader file, boot.cmd, which tells U-Boot what to load and boot (the kernel, device tree and initramfs).

The bootargs line says to output the console to serial and to boot from the ramdisk. Variables are used for the memory locations of the kernel, dtb and initramfs.

Note that the root device is /dev/root which is required for the initramfs.

cat > boot.cmd << EOF
fatload mmc 0 \${kernel_addr_r} zImage
fatload mmc 0 \${fdt_addr_r} bcm2836-rpi-2-b.dtb
fatload mmc 0 \${ramdisk_addr_r} uInitrd
setenv bootargs console=ttyAMA0,115200 earlyprintk root=/dev/root \
rootwait panic=10
bootz \${kernel_addr_r} \${ramdisk_addr_r} \${fdt_addr_r}
EOF

Compile the bootloader file and output it directly to the SD card at /mnt.
sudo mkimage -C none -A arm -T script -d boot.cmd /mnt/boot.scr

Now, unmount your SD card.

sudo umount /dev/sdx*

Testing it all

Insert it into the Raspberry Pi and turn it on! Hopefully you’ll see it booting the kernel on your screen terminal window.

You should be greeted by a login prompt. Log in with root (no password).

Login prompt

Login prompt

That’s it! You’ve built your own Linux system for the Raspberry Pi!

Networking

Log in as root and give the Ethernet device (eth0) an IP address on your network.

Now test it with a tool, like ping, and see if your network is working.

Here’s an example:

Networking on Raspberry Pi

Networking on Raspberry Pi

Memory usage

There is clearly lots more you can do with this device…

Raspberry Pi Memory Usage

Raspberry Pi Memory Usage

Enjoy!

16 Responses to “Building and Booting Upstream Linux and U-Boot for Raspberry Pi 2/3 ARM Boards”


  • I followed the steps on raspberry pi 3. It booted uboot and tried to book kernel zImage..but hanged at Starting kernel….

  • Can you provide some of your configurations?

  • Hi Chris,
    Sorry for the late response. It is booting now. I have a serial console connected. “uname -a” output:
    Linux buildroot 4.9.4 #1 SMP Tue Jan 17 20:55:03 PST 2017 armv7l GNU/Linux
    Only problem is when I build a latest stable upstream kernel, I do not see more than 1 core, even though SMP is shown in the output of “uname -a”. I chose SMP in kernel features options in menuconfig.
    # cat /proc/cpuinfo
    processor : 0
    model name : ARMv7 Processor rev 4 (v7l)
    BogoMIPS : 38.40
    Features : half thumb fastmult vfp edsp vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
    CPU implementer : 0x41
    CPU architecture: 7
    CPU variant : 0x0
    CPU part : 0xd03
    CPU revision : 4

    Hardware : BCM2835
    Revision : 0000
    Serial : 00000000bf829e3f
    # uname -a
    Linux buildroot 4.9.4 #1 SMP Tue Jan 17 20:55:03 PST 2017 armv7l GNU/Linux

  • Hi,
    I find that you are creating a ram file system and booting from ram file system itself. How to boot using a root file system?. How do I compile a root file system and boot from it in raspberry pi?

  • If you want to boot from a ramfs, then you can copy the contents of the cpio to the partition and then you need to modify your uboot config to tell it to boot from the partition (e.g. mmcblk1p2) and the bootz line. Something like:

    cat > boot.cmd < < EOF fatload mmc 0 \${kernel_addr_r} zImage fatload mmc 0 \${fdt_addr_r} bcm2836-rpi-2-b.dtb setenv bootargs console=ttyAMA0,115200 earlyprintk root=/dev/mmcblk1p2 \ rootwait panic=10 bootz \${kernel_addr_r} - \${fdt_addr_r} EOF

  • Hi,
    I tried to create a initramfs as per your custom-initramfs script.
    The file is created and I follow to unzip it and make a u-boot compatible image.

    However, the u-boot tries to load it but it says :
    Wrong Ramdisk Image format!
    I tried with -C gzip, and couple of other option like creating the .gx cpio, with compress, without compress and none of them are in correct format.

    What is the proper format for initramfs for RPi2/3?

    I am using y custom kernel 4.4.50 with initramfs enabled.
    Also all formats like gz, bz2 and others also enabled.

    U-boot loads fine and if I do not use initramfs in boot script I also can boot to my rootfs but with initramfs enabled in boot script it always drop to u-boot prompe and say that the Ramdisk Image is Wrong Format!

    Any advise?

  • Hi,
    I tried to compile and use 4.9-rc8 kernel and all was working!
    However, my u-boot compile (v2017) also was fine.

    My question is that, as 4.9 does not have compatible module and is not supported by RPi yet, I have to use to kernel, one with initramfs(zImage) and another for rootfs itself(kernel7.img).

    As this feature is part of the core Kernel, is there any patch to be able to use the 4.4.50 kernel forinstance for both initramfs and rootfs?!

    I am also very Ubuntu oriented. Is there any tools or script like what you have for Fedora based one to generate the initramfs based on Ubuntu?

    Many thanks….

  • Hi,

    what is kernel_addr_r and fdt_addr_r?

    what values I should I use for those two, I am using raspberrypi 2 model b board.

    Can anybody help me.

    Thanks

  • Thanks Chris,
    I built a linux successfully for rpi with the above mentioned steps. but I couldnt get the uboot messages on the serial terminal. Is there any aditional configuration for uboot to made be and built?

  • You just use those, they are variables that should automatically be expanded in uboot.

  • I think it should be a gzip compressed cpio. So it’s not unzip, it should be gunzip?

  • The script to create the initramfs should also work on Ubuntu. Did you try it? What errors did you have?

  • Hi,
    thanks a lot for this tutorial. I followed all the steps for the u-boot but when testing it in my Pi3 i see nothing on the console. I did it already twice. What can be the problem?

    Some Info:
    **********

    ->Instead of rpi_3_32b_defconfig, I used rpi_3_defconfig
    ->Also i used CROSS_COMPILE=gcc-linaro-6.3.1-2017.02-x86_64_aarch64- elf/bin/aarch-elf-
    ->I am using debian jessie system.

    Thanks

  • Are you using the UART, or HDMI to see the console?

    -c

  • Just a guess, but perhaps the binary firmware doesn’t know how to execute a 64bit kernel.img? Can you try to get it working with the 32bit version as per the post and make sure everything else is working, e.g. the firmware, u-boot and output on serial. Then try the 64bit one.

  • Hi,
    thanks
    I figured the solution to the problem.
    For 64-bit U-boot, one has to creat a config.txt file with the following in the fat partition:
    arm_control=0x200
    enable_uart=1

    Then it works.

    source:https://kernelnomicon.org/?p=682

Leave a Reply