Getting a Linux-based board up and running can be a daunting task even for an avid Linux user and microcontroller expert. There are several moving parts, and an in-depth knowledge of the Linux ecosystem is pretty much a must.
Never fear though, if you already have some experience using Linux as a development platform, this post will give you all the pointers required for getting that login prompt to show up on your board, in the simplest possible way.
A world of possibilities
When deciding to create a custom Linux distribution for our board, we immediately find ourselves at a fork in the road: should we build it completely from scratch, or look for something that could help us on the perilous journey ahead?
The former path is a rewarding, although often excruciatingly long, experience. It gives the user an insight on the inner workings of its machine, from which he can later benefit in several ways. But it’s also a task that requires a good amount of pre-existing knowledge of an operating system’s internals, and it is definitely not the most efficient way of setting up a custom Linux distro. If you are up to the task however, LFS is that way.
The latter option is instead a regularly travelled road. Several tools have been perfected in the last decade or so to deal with the custom distro problem. Some of them are easier to use, but lack flexibility, while others can do almost anything, given enough time and expertise. They are called Linux build systems, and they take it upon themselves to do all the heavy lifting for you: downloading all the required source code, getting a toolchain, building all the pieces and putting them together. All you need to do is feed them some configuration files and watch them spin!
We will take the safe route and choose one of these tools for our first build. Despite their variety, nowadays it basically boils down to two contenders:
- Buildroot is simple and easy-to-use™, while still providing all the sought-after features in a custom Linux distro build tool
- Yocto is far from easy to use, but it is extremely flexible, and gets the most support from the industry leaders
For a beginner, Buildroot is definitely the right choice, and it’s what we will focus on in the following sections. Yocto is the logical step forward for when Buildroot just isn’t enough.
Nuts and bolts
Before getting started with Buildroot, it’s nice to have a general overview of all that is required to have a functioning Linux system. Some pieces may vary depending on the architecture, but usually you’ll always need:
- a bootloader, which is the first program that is run by the CPU when it is powered on, and is responsible for hardware initialization and loading the operating system
- the Linux kernel, the core of the operating system, which deals with resource management, task scheduling, hardware access, and all the low-level operations which the programmer doesn’t care about
- a root filesystem, containing all the system programs, utilities, configurations and user data (basically the content of you
C:\drive on Windows)
Once you have these three components, the final step is putting them together and finally load them, in a way that depends on your custom board. To give you a better grasp on these concepts, we will use Raspberry Pi as our example target board throughout this post.
Preparing the build environment
To get started, we will need a machine running Linux, which is the only OS officially supported by Buildroot. Any Linux flavor will do (within reason) as long as you can install all the mandatory tools required for Buildroot to run.
Next, we will need the Buildroot source code. You have two options here: getting a tarball or checking out the
gitrepository. I recommend cloning the repository: you get version control for free and you can easily undo any breaking changes while experimenting. It basically boils down to:
~ $ git clone git://git.buildroot.net/buildroot
~ $ cd buildroot/
It’s also good practice to start developing from a stable tag, in order to always be able to backtrace to a known point in history:
~/buildroot $ git checkout 2018.08
Let’s now have a peek at the contents of the
~/buildroot $ ls
arch/ docs/ toolchain/ CHANGES DEVELOPERS
board/ fs/ support/ Config.in Makefile
boot/ linux/ system/ Config.in.legacy Makefile.legacy
configs/ package/ utils/ COPYING README
There’s a lot going on in here, but we can notice a couple of things:
- there’s a
Makefilein there, which means that we will likely use the make build system to interact with Buildroot
- there’s a
configs/directory, where we can reasonably assume some kind of configuration files are stored
- finally, take notice of the
board/directory, as we will soon talk about it too
Also, Buildroot has an excellent documentation that you should check out if you want to know more about what the rest of the source code does.
As said before, these build systems need some kind of configuration in order to know what to build for which target, and we’ve just encountered a
configs/ directory while diving into the code. Turns out that this directory contains many pre-baked configurations for several products: evaluation kits, development and consumer boards, etc. Among these, there’s also a nice
raspberrypi_defconfig that we can use as a starting point for our distribution.
Buildroot configurations follow the Kconfig format, used also by the Linux kernel. If you want to know more about this format, you can start from here. Also, try not to start from scratch when developing support for your own custom hardware. It’s almost always better to start from a configuration for a similar evaluation kit or development board and work your way up from there.
In Buildroot, you can apply a configuration file by running
make <config-file-name>, provided that the file name ends in
_defconfig and is located in the
configs/ directory. In our case this will do the trick:
~/buildroot $ make raspberrypi_defconfig
# configuration written to ~/buildroot/.config
It’s now time to take a look at the configuration we’ve just applied!
Tweaking the base distribution
Although we could simply open the configuration file with a text editor and have a look around, Buildroot provides some nicer tools to view and modify the applied configuration.
For the command-line enthusiasts, the
make menuconfig command will open an interactive curses-based configurator which you can navigate to modify your active configuration. This is extremely useful for when you are logged on your build machine remotely. For the more GUI-inclined, the same result can be achieved using
make xconfig or
make gconfig to open a Qt or GTK configurator, respectively.
In any case, you will be presented with a list of menus:
Target options --->
Build options --->
System configuration --->
Target packages --->
Filesystem images --->
Host utilities --->
Legacy config options --->
The menus are pretty self-explanatory, and the best way to learn is by delving into them and read the docs about the different options (which you can do by pressing
? on most of them). Some major pointers:
- Target options includes everything related to the target architecture
- Toolchain allows you to choose between Buildroot building you a toolchain from scratch or using a pre-built one that you provide
- System configuration is where you can change some system-level stuff such as hostname, root password and so on
- Kernel and Bootloaders let you specify several options about these two components (source code location, versions, etc.)
- Target packages is where you select which software you want to be installed on your final distribution, and it’s where most tweaking is done
Let’s say that we want to change our custom distribution hostname to
rpi and install an OpenSSH server. We will also increase the filesystem size to
100MB in order to accommodate for this.
The default hostname (
buildroot) can be changed using the System hostname option:
System configuration --->
(rpi) System hostname
OpenSSH can be installed by navigating the Target packages menus and selecting openssh:
Target packages --->
Networking applications --->
Finally, resizing the final root filesystem image can be achieved by modifying the exact size option in the Filesystem images section (by default
Filesystem images --->
(100M) exact size
That’s it. You can just save and exit the configurator to apply the changes (just press
ESC a bunch of times and select
<Yes> when prompted to save if you are using
In Buildroot, all configuration changes are stored locally in a
.config file in the root directory. This file is temporary, and it’s not meant to be version-controlled. To permanently store configuration changes and share them with other people on your team, you can run the
make savedefconfig command. This will overwrite the configuration file that you originally used to generate the configuration (in our case
raspberrypi_defconfig) with the newly applied changes, ready to be committed to version control.
Now, it’s finally time to build the distribution!
Building the system image
Depending on the number of selected packages, and whether or not we are building toolchain, kernel and bootloaders from scratch, the build time can vary from several minutes to several hours. A build can be initiated simply by issuing:
~/buildroot $ make
Do note that Buildroot does not support top-level parallel make using
-jN, so it should never be used as to avoid strange issues.
Once it’s done, you will find that two new directories have been created:
dl/, which will serve as a cache for all the source code downloaded during the build process;
output/, which contains all the build artifacts.
We are particularly interested in the contents of the
output/images/ directory, where all the final build outputs will be placed. Its content mostly depends on the target architecture and some configuration options, but you will usually always find:
- the kernel image (something like
Image) and its associated device trees
- the root filesystem image, in several formats that can be specified using the Filesystem images configuration menu
In some cases, Buildroot will also produce for you an SD card or USB image, already formatted and partitioned in such a way that the processor is able to boot from it. This is the case for the RPi: you can find the bootable SD card image under the name
Deploying to the board
# WARNING: THIS IS DANGEROUS AND CAN DESTROY YOUR DISK, USE WISELY
~/buildroot $ sudo dd if=output/images/sdcard.img of=<sd-device>
<sd-device> is the device under
/dev corresponding to your SD card (depending on your machine, this could be something in the
Once this step is over, you can insert the SD card into your RPi and power it up. After the initial boot phase, you should see a login prompt on the serial port: just put in
root as username to log into your brand new system!
Further tweaking and tuning
board/ directory we introduced but never actually used? Turns out it can be used to store anything and everything specific to any of the boards supported by Buildroot.
As an example, if we venture into
board/raspberrypi/ we can notice some interesting stuff:
.cfgfiles which specify, for different RPi revisions, the format and content of the generated SD card image, in order to make it suitable to boot from
post-image.shscripts that are run after each respective phase to further tweak and tune the Linux image for the RPi
readme.txtspecific to RPi containing instructions on how to build and deploy the images just built
As you can see, it’s all stuff related to RPi which wouldn’t fit anywhere else in the source code, so it’s all grouped in the
Further uses for a board directory are storing kernel or bootloader configurations and/or patches, pre-built assets, and so on. The sky is the limit! For additional info about project-specific customizations, you can refer to the dedicated section in the docs.
That’s it! Buildroot makes it extremely easy to build a completely customized Linux distribution without any knowledge of what’s going on under the hood.
And since the best way to learn is by doing, go ahead and try to further modify this base distribution, or even start your own brand new RPi project by applying everything that you’ve learned!