How to Build Your Own Tailor-made IoT Linux OS (Part 3)
Replacing the Bootloader
This is the last part of this series, see guide Part 1 and Part 2.
Raspberry Pi comes with its own bootloader built-in, but on a real IoT device, you need to select and configure a bootloader that will load the kernel, so let’s get things interesting.
RPI Bootloading Process
First, let’s understand how the current RPI bootloader process works.
Raspberry Pi has a fairly complicated boot process with two bootloaders. The first one resides in built-in ROM and is responsible for starting the GPU. The GPU executes bootcode.bin, the second bootloader, which, in the end, runs the kernel.
Stage 1
To reduce cost, the Raspberry Pi (models A & B) omits any onboard, non-volatile memory used to store the bootloaders, Linux kernels, and file systems as seen in more traditional embedded systems. Rather, an SD/MMC card slot is for this purpose. The Raspberry PI compute module has 4GB eMMC flash onboard.
Stage 1 boot is in the on-chip ROM. Loads Stage 2 in the L2 cache. The Raspberry Pi’s Broadcom BCM2835 system on a chip (SoC) powers up with its ARM1176JZF-S 700 MHz processor held in reset. The VideoCore IV GPU core is responsible for booting the system. It loads the first stage bootloader from a ROM embedded within the SoC. The first stage bootloader is to load the second stage bootloader (bootcode.bin) from a FAT32 or FAT16 file system on the SD card.
Stage 2
The second stage bootloader, bootcode.bin, executes on the VideoCore GPU and loads the third stage bootloader, start.elf.
Stage 3
The third stage bootloader, start.elf, is where all the action happens. It starts by reading config.txt, a text file containing configuration parameters for both the VideoCore (Video/HDMI modes, memory, console frame buffers, etc.) and loading of the Linux kernel (load addresses, device tree, uart/console baud rates, etc.). start.elf can load any file named zImage (the compiled kernel image).
In short, the standard (non-U-Boot) boot process is as follows:
Note that on the RPI, the GPU initializes the system and performs the boot’s initial stages.
RPI Bootloading Process with U-Boot
To trick the RPI bootloader to load U-Boot, set start.elf into loading the U-Boot image instead; use the compiled u-boot.bin to pass as a kernel image. This is the first thing that runs on the ARM processor.
The boot process with U-Boot enabled is as follows:
Compile U-Boot Using Buildroot
Yes, you can download the U-Boot repository and compile it yourself manually, but for that, you have Buildroot to automate it and make it part of your build process. So, configure Buildroot and enter the Bootloaders menu:
And run make.
At the end of the compilation you should see the U-Boot binary under the output/images directory:
Preparing Files
- Prepare config.txt:
Change it from:
kernel=zImage
to:
kernel=u-boot.bin - Don’t forget to add the enable_uart=1 line to the config.txt file as well.
- Prepare the boot commands for U-Boot:
When U-Boot starts, it needs certain configuration to know what to boot next and how, instead of typing it manually each time, prepare it so U-Boot will read this configuration when it starts: - Create a new boot_commands.txt file and enter:
mmc dev 0
fatload mmc 0:1 ${kernel_addr_r} zImage
fatload mmc 0:1 ${fdt_addr_r} bcm2710-rpi-3-b.dtb
setenv bootargs root=/dev/mmcblk0p2 rootfstype=ext4 console=tty1
console=ttyAMA0,115200 earlyprintk rootwait noinitrd
fdt addr ${fdt_addr} && fdt get value bootargs /chosen bootargs
bootz ${kernel_addr_r} — ${fdt_addr}
5. Compile the commands to the U-Boot format:
mkimage -A arm -T script -C none -n “Boot script” -d boot_commands.txt boot.scr.uimg
6. Copy the newly created boot.scr.uimg and the u-boot.bin files into the /boot partition on your SD card.
Mazel Tov! You’re Now Using U-Boot Bootloader
Save all changes. if you’re running on Linux, then type in the sync command to make sure all changes are flushed.
Eject the SD card safely, put it into the Raspberry Pi, make sure that UART cable plugs in, and that using screen/putty you’ve connected to your COM or /dev/ttyS* port.
Turn on the board, and you should receive an output similar to the following:
Bonus: Static Compilation of Packages
Forgot to add a specific package and don’t want the hassle of recompiling the entire firmware image just for that? You may use static compilation to get your software running.
Compiling Netcat Statically
Take netcat as an example, first download the source code:
wget https://netix.dl.sourceforge.net/project/netcat/netcat/0.7.1/netcat-0.7.1.tar.gztar -xzvf netcat-0.7.1.tar.gzcd netcat-0.7.1# Configure for static compilation:
./configure — build x86_64-pc-linux-gnu — host arm-linux-gnueabi LDFLAGS=“-static -fPIC”# Compile:
make
Congrats! now you may copy your compiled file to the IoT device and run it.
If you would like to reduce its size, you may strip it from its symbols using arm-linux-gnueabi-strip:
Summary
This post has walked you through the steps of using a serial connection, creating your own Linux IoT embedded image, from the bootloader and all the way to the kernel and installed packages and drivers using an automated build system.
There’s a lot more to learn about creating your own firmware image and using Buildroot and its neat features. I hope I’ve provided you with the basics you need to start.
Good luck!