Setting up Yocto for efficient embedded Linux systems development

In this blog post, I will explain how I’ve set up my workstation for building custom Linux image for STM32MP157D-DK1 and demonstrate a basic build in this setup.

Table of Contents

Introduction

Similarly to my previous posts, this writeup is based on Bootlin’s Yocto labs. And as always, thanks to Bootlin for making their materials free and open source!

Before starting with Yocto, make sure you check all the required boxes for working with Yocto as described in Yocto’s system requirements reference manual.

This especially goes out to all Arch-based distro users as it doesn’t support natively!

Even though it is possible to work with Yocto on an Arch-based distribution, the build environment must be at least containerized which many users prefer and I would argue makes the builds more easily reproducible and transferable. If one opts no to use podman or docker to do do and still work with Yocto on Arch or similar non-supported distros, get ready to jump through some hoops because there will be quite few of them.

Also, make sure your home directory is not encrypted with eCryptFS since OpenEmbedded cannot be used on top of it due to limitation in file name lengths.

If you are using Ubuntu: Ubuntu 24.04 added an apparmor policy preventing usage of unprivileged user namespace restrictions to improve security. This prevents bitbake from working, because it uses namespaces to forbid untracked downloads outside of the do_fetch task (we will get to terminology and technicalities later, don’t worry if you don’t understand these terms). Ironically, bitbake does this to improve security. This results in operation not permitted error which can be disabled by running:

echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns

You will need to run this command time you reboot your machine or look at the Ubuntu 24.04 Release Notes to see how to disable this restriction permanently.

First Yocto build

SIDE NOTE: For a quick build, you can check out Yocto Project Quick Build.

The first prerequisite to building with a Yocto image for SMT32MP157D-DK1 is to install required packages and to download data which we will use for our board.

First, install the required packages:

$ sudo apt install gawk wget git diffstat unzip texinfo gcc build-essential \
chrpath socat cpio python3 python3-pip python3-pexpect xz-utils debianutils \
iputils-ping python3-git python3-jinja2 python3-subunit zstd liblz4-tool \
file locales libacl1

Then, obtain data we will use for working with target board in this setup:

cd
$ wget https://bootlin.com/doc/training/yocto-stm32/yocto-stm32-labs.tar.xz
$ tar xvf yocto-stm32-labs.tar.xz

After that, update your distribution:

$ sudo apt update
$ sudo apt dist-upgrade

Next, download Yocto (I used the scarthgap version of Poky reference distribution):

$ git clone https://git.yoctoproject.org/git/poky
$ cd $HOME/yocto-stm32-labs/poky
$ git checkout -b scarthgap-5.0.1 scarthgap-5.0.1

Then, return to project root directory and download the OpenEmbedded and STM32MP layers:

$ cd $HOME/yocto-stm32-labs/
$ git clone -b scarthgap https://git.openembedded.org/meta-openembedded
$ git clone https://github.com/STMicroelectronics/meta-st-stm32mp
$ cd meta-st-stm32mp
$ git checkout b820cf3a1a855d2bd95969251e6465e281502759

You now just need to set up the build environment:

$ cd $HOME/yocto-stm32-labs
$ source poky/oe-init-build-env

and you are now ready to use the bitbake which is Yocto’s main build engine.

To build specifically for SMT32MP157D-DK1, you must specify the target machine in the conf/local.conf configuration file.

To save disk space on your computer, you can add INHERIT += "rm_work" at the end of the same file which will remove package work directory once a package is built.

Also, the build configuration needs to be aware of the OpenEmbedded and STM32MP layers. This is done by editing the $BUILDDIR/conf/bblayers.conf configuration file by adding full paths to layers to the BBLAYERS variable.

If not done already, make sure to configure your git username and email as some recipe builds can fail without it:

$ git config --global user.name "Your Name"
$ git config --global user.email "your@email.com"

To test image build with set configuration, run the following from the sourced environment:

$ bitbake core-image-minimal

This build will initially last for quite some time. Mine was building for an hour if I recall correctly. Anyway, the build might fail for various unexpected reasons. One might be due to RAM limitations where you can build the failing recipe individually will all the RAM available for it. Simply rerunning bitbake core-image-minimal sometimes did the trick for me because bitake caches various stages of the build so it doesn’t have to rebuild the same recipes over again when rebuilding the full image.

In that case, make sure to search though the internet for your specific build issue if one occurs.

Once the build is finished, you can observe the output image under $BUILDDIR/tmp/deploy/images/stm32mp1.

Now, the easiest way to transfer the newly built image onto the board is by using an SD card. Insert your SD you plan to use to store the bootloader, kernel and root filesystem files into the SD card reader of your computer.

To generate the final image for the board run the following script located in $BUILDDIR/tmp/deploy/images/stm32mp1/scripts:

./create_sdcard_from_flashlayout.sh \
../flashlayout_core-image-minimal/extensible/FlashLayout_sdcard_\
stm32mp157d-dk1-extensible.tsv

Now, flash the SD card with image:

WARNING: make sure you are 100% sure you know which partition are you writing to because overwriting other paritions might cause irreversible changes!

$ umount /dev/mmcblk0p*
$ sudo dd if=../FlashLayout_sdcard_stm32mp157d-dk1-extensible.raw of=/dev/mmcblk0 bs=8M \
conv=fdatasync status=progress

To observe the booting process of our custom Linux distribution we have places onto the SD card, we first need to set up serial communication with SMT32MP157D-DK1. Plug the USB-A to micro USB-B cable on the Discovery board to micro USB port (CN11) which is actually ST-LINK. This is a debug interface and exposes multiple debugging interfaces including a serial interface. When you plug it in your computer, a serial device should appear. The exact name of it can be observed by looking ath the output of dmesg utility. Once we have identified the device, we can pass it to picocom in order to start the serial communication with the board:

picocom -b 115200 /dev/ttyACM0

You can now insert the SD card into the dedicated slot on the STM32MP157D-DK1 and then power up the baord by connecting the USB-C cable to the board (CN6). You should now see boot messages in picocom. Wait until the login prompt and then enter root as username and root as password.

Great! The board has booted and now we have access to the shell!

Build process and basic Yocto lexicon

in principle, you can think of Yocto as a build system that takes custom built and open-source components as an input and builds binary packages (i.e. distributions) as its output.

Yocto lexicon

Advanced Yocto configuration

Setting up Ethernet communication and NFS on the board

It’s very impractical to to reflash the root filesystem onto the SD card every time Yocto rebuilds an image. This is why it is very useful to set up networking between the development workstation (host) and the target machine because the target machine can then access workstation files using NFS:

We first need to set the kernel boot arguments U-Boot will pass to the Linux kenrel on the target at boot time. To do that, modify the mmc0_extlinux/extlinux.conf configuration file and cahnge the APPEND file:

APPEND root=/dev/nfs rw console=ttySTM0,115200 nfsroot=192.168.0.1:/nfs,vers=3,tcp ip=192.168.0.100

Setting up Ethernet communication on the workstation

Now with a network cable, connect the Ethernet port of the board to the computer over the USB Ethernet adapter if your computer already has a wired connection to the network. A new network interface should appear on your computer that you can check by running ip a.

The network interface name is enxxx.

You now need to make sure you set the static IP of that port to be in a different subnet of where your computer is connected. You can do that over the command line:

nmcli con add type ethernet ifname en... ip4 192.168.0.1/24

Change en... and 192.160.1/24 to fit your network configuaration.

Setting up the NFS server on the workstation

First install the NFS server on the workstation by running the following:

$ sudo apt install nfs-kernel-server
$ sudo mkdir -m 777 /nfs

Then make sure this directory is used and exported by the NFS server by adding the following line to the /etc/exports file:

/nfs *(rw,sync,no_root_squash,subtree_check) # NOTE: modify the last parameter to no_subtree_check if needed!

Finally, make the NFS server use the new configuration:

$ sudo exportfs -r

Adding SSH support

We will now add a package called dropbear to support connecting to the board over SSH by appending the IMAGE_INSTALL variable $BUILDDIR/conf/local.conf.

The Dropbear SSH server is now enabled and runs as a service on the STM32MP157D-DK1 board.

We first need to put the rootfs under the NFS root directory so that it is accessible by NFS clients. To do taht, uncompress the archived output image in the previously created /nfs directory:

sudo tar xpf $BUILDDIR/tmp/deploy/images/stm32mp1/\
core-image-minimal-stm32mp1.rootfs.tar.xz -C /nfs

Now boot the board and open a separate terminal to access it using ssh:

ssh root@192.168.2.100

Setting up TFTP

Setting up TFTP is extremely useful since it lets us bypass having to reflash the SD card for every test.

We first need to install a TFTP server on our host machine:

$ sudo apt install tftp-hpa

We then need tp copy the Linux kernel image and the device tree to the TFTP server home directory (which is /srv/tftp in my case as defined in /etc/default/tftpd-hpa) so that they are reachable by the TFTP server.

We then need to boot the board and interrupt the booting process to enter in the U-Boot shell and change the bootcmd variable to load the kernel image and the device tree over TFTP:

setenv ipaddr <your-client-ip>
setenv serverip <your-server-ip>
setenv bootcmd 'tftp 0xc2000000 zImage; tftp 0xc4000000 dtb; bootz 0xc2000000 - 0xc4000000'

And finally, set the bootargs variable as follows:

setenv bootargs root=/dev/nfs rw console=ttySTM0,115200 nfsroot=<your-server-ip>:/nfs,vers=3,tcp ip=<your-client-ip>
saveenv

For more information on how to set up TFTP, make sure you check out section on this link.

And that’s it. You now have a well configured and hassle-free setup for developing with Yocto!