Skip to main content

Buildroot minimal system

ST's own SDK uses Yocto (see Distribution Package). Buildroot is a lighter alternative that builds a complete but minimal system from source, and it is a good way to understand every piece of the boot chain because there is so little of it. The example targets the STM32MP157-DK2.

Distribution versus build system

To assemble a Linux system there are two broad approaches:

  • A binary distribution — Debian, Ubuntu, Fedora — several of which support ARMv7. Easy and familiar, with a package manager and pre-compiled packages, so a system comes up fast. But it is hard to customise (components are prebuilt) and hard to shrink in footprint or boot time.
  • A build system — Buildroot, or Yocto/OpenEmbedded. These compile the whole system from source, so it can be trimmed and tuned to exactly what the product needs, at the cost of build time and a steeper setup.

A minimal system is four parts: a bootloader (U-Boot), the kernel, user-space libraries and tools from the community, and your own application code.

Build

Host packages Buildroot needs: which, sed, make (≥ 3.81), binutils, build-essential (Debian), diffutils, gcc/g++ (≥ 4.8), bash, patch, gzip, bzip2, perl (≥ 5.8.7), tar, cpio, unzip, rsync, file (at /usr/bin/file), bc, findutils, wget.

git clone git://git.buildroot.net/buildroot
cd buildroot

# A community configuration for the STM32MP157-DK2
git remote add tpetazzoni https://github.com/tpetazzoni/buildroot.git
git fetch tpetazzoni
git checkout -b stm32mp157-dk2 tpetazzoni/2019.02/stm32mp157-dk

make stm32mp157_dk_defconfig

# menuconfig needs libncurses-dev / ncurses-devel
make menuconfig

In menuconfig, under Toolchain, change Toolchain type from Buildroot toolchain to External toolchain — this reuses a prebuilt cross-compiler and saves a large chunk of build time.

make 2>&1 | tee build.log

Build output

Everything lands in output/, with the important results in output/images/:

FileWhat it is
zImageThe Linux kernel image
stm32mp157c-dk2.dtbDevice Tree Blob — describes the hardware to the kernel
rootfs.ext4The root filesystem image (ext4)
u-boot-spl.stm32First stage bootloader
u-boot.imgSecond stage bootloader
sdcard.imgA complete, ready-to-flash SD card image built from the above

Flash and boot

sdcard.img is a full-disk image — write it to the whole device, not a partition:

# Destroys everything on the target device. Confirm /dev/mmcblk0 is the SD card.
sudo dd if=output/images/sdcard.img of=/dev/mmcblk0 bs=1M conv=fdatasync status=progress

Insert the card, connect a USB cable to ST-LINK CN11 (this creates /dev/ttyACM0 on the host), and open a serial terminal:

picocom -b 115200 /dev/ttyACM0

Power the board through PWR_IN CN6. It boots to Buildroot login: — log in as root, no password.

Toggle a board LED to confirm user space works:

echo 255 > /sys/class/leds/heartbeat/brightness
echo 0 > /sys/class/leds/heartbeat/brightness

Reading the boot log

The serial log narrates the boot chain:

U-Boot SPL 2018.11-stm32mp-r2.1 ... # first stage (u-boot-spl.stm32),
# loaded by the SoC ROM into internal SRAM
U-Boot 2018.11-stm32mp-r2.1 ... # second stage (u-boot.img), loaded into
# external RAM by the first stage
Retrieving file: /boot/zImage # second stage loads the kernel
Retrieving file: /boot/stm32mp157c-dk2.dtb # ... and the device tree
Starting kernel ... # U-Boot's last message
[ 0.000000] Booting Linux ... # kernel takes over
[ 3.248315] VFS: Mounted root (ext4 filesystem) ... # rootfs mounted
Welcome to Buildroot
buildroot login: # first user-space process reached init

The first stage has to fit in the SoC's small internal memory, which is why it does the bare minimum and hands off to the second stage in external RAM.

Key configuration choices

What the stm32mp157_dk_defconfig sets, and why:

  • Target — ARM Little Endian, Cortex-A7 variant; the whole Linux system runs on the Cortex-A7 cores.
  • Toolchain — external, to skip building a compiler.
  • System configuration — a rootfs overlay directory (board/stmicroelectronics/stm32mp157-dk/overlay/) whose contents are copied into the rootfs at the end of the build, and a post-image script (support/scripts/genimage.sh -c .../genimage.cfg) that assembles the final SD card image.
  • Kernel — fetched from the STMicroelectronics GitHub at tag v4.19-stm32mp-r1.2, configured with board/stmicroelectronics/stm32mp157-dk/linux.config, building the stm32mp157c-dk2 DTB and installing the image to /boot in the target.
  • Target packages — only BusyBox, which supplies the shell and most common command-line tools in one binary. The minimal system is essentially BusyBox.
  • Filesystem — ext4, the de-facto standard for SD/eMMC block storage.
  • Bootloaders — U-Boot from the ST GitHub at v2018.11-stm32mp-r2.1, base config stm32mp15_basic. A fragment disables the STM32 watchdog, because the minimal user space has no watchdog daemon to service it and the board would otherwise reset constantly (re-enable and handle it properly for a real product). The build produces u-boot.img (second stage) and spl/u-boot-spl.stm32 (first stage), and passes DEVICE_TREE=stm32mp157c-dk2.
  • Host utilitieshost genimage, used to build the SD card image.

The SD card image layout

genimage.cfg describes the partitions of sdcard.img:

image sdcard.img {
hdimage { gpt = "true" }

partition fsbl1 { image = "u-boot-spl.stm32" }
partition fsbl2 { image = "u-boot-spl.stm32" }
partition uboot { image = "u-boot.img" }
partition rootfs {
image = "rootfs.ext4"
partition-type = 0x83
bootable = "yes"
size = 256M
}
}
  • A GPT table is required — the STM32MP157 ROM code needs it to locate the first stage bootloader.
  • fsbl1 and fsbl2 hold the raw first stage bootloader (no filesystem). The ROM code is hard-wired to search the first two partitions whose names begin with fsbl.
  • uboot holds the raw second stage. The first stage is configured to load it from the third partition.
  • rootfs holds the ext4 filesystem and is marked bootable.

The bootable flag matters because U-Boot uses the Generic Distro Concept: at boot it finds the bootable partition and reads /boot/extlinux/extlinux.conf from it to learn how to start the system.

extlinux.conf, delivered through the rootfs overlay:

label stm32mp15-buildroot
kernel /boot/zImage
devicetree /boot/stm32mp157c-dk2.dtb
append root=/dev/mmcblk0p4 rootwait

Boot process, end to end

  1. The SoC ROM finds an fsbl* GPT partition, loads it into internal memory and runs it — the first stage bootloader.
  2. The first stage initialises external RAM, loads the second stage from the third partition into that RAM, and runs it.
  3. The second stage finds the bootable partition, reads /boot/extlinux/extlinux.conf, loads the kernel and device tree it names, and starts the kernel with the given arguments.
  4. The kernel mounts the root filesystem indicated by root=/dev/mmcblk0p4, then starts the first user-space process.
  5. That process is /sbin/init (BusyBox), which starts a few services and the login prompt.

The dual-copy variant of this layout, for safe field updates, is on the Firmware updates page.


Adapted from Bootlin's "Building a Linux system for the STM32MP1" blog series. The explanations are rewritten; the configuration snippets and boot logs are from running the steps.