Part 1: Setting Up the Foundation with Kas and Yocto
Introduction
Building custom Linux distributions for the Raspberry Pi 4 offers significant advantages over pre-built solutions like Raspberry Pi OS. These include reduced image size, faster boot times, improved security through minimal attack surface, and precise control over included packages. For professional embedded systems, this approach allows creating purpose-built distributions that contain exactly what's needed—nothing more, nothing less.
The Yocto Project provides powerful tools for this customization, but its complexity can be intimidating. This series demonstrates how to use Kas to simplify Yocto builds, making custom distribution creation more accessible and manageable.
Target Audience
This series is aimed at:
-
Embedded systems engineers looking to create production-ready images
-
IoT developers needing customized, minimal Linux distributions
-
Raspberry Pi enthusiasts who want deeper control than standard distributions offer
Prerequisites: Installing Yocto Dependencies
Before we begin, we need to set up our development environment with all the necessary tools. The Yocto Project requires several packages to function correctly. Refer to https://docs.yoctoproject.org/5.0.9/singleindex.html#yocto-project-quick-build to install the dependencies based on your distribution.
Understanding the Yocto Project Workflow
The Yocto Project uses a task-oriented approach to building Linux images. Here's a simplified overview of the workflow:
-
Meta layers: These are collections of recipes, configuration files, and other components that define what goes into your Linux distribution.
-
BitBake: The task execution engine that parses recipes and configuration files to build the system.
-
Recipes: Individual instructions for building packages, system components, or the entire image.
-
Configuration files: Define system-wide settings, machine-specific settings, and distribution properties.
When you decide to build a Yocto distribution, you will in general follow this workflow:
-
Downloading the Poky reference distribution
-
Adding additional meta-layers for your specific hardware (e.g., meta-raspberrypi)
-
Configuring your build through local.conf and other configuration files
-
Running BitBake to generate the image
This process can be complex, especially for newcomers. That's where Kas comes in.
Configuring Kas to Simplify Yocto Builds
Kas is a tool developed by Siemens that simplifies Yocto builds through YAML configuration files. It handles the setup of the build environment, downloading of repositories, and configuration of layers automatically. For reference, the project is here: https://github.com/siemens/kas
Installing kas is straightforward, refer to https://kas.readthedocs.io/en/latest/userguide/getting-started.html#usage
The version used in this article is:
$> kas --version
kas 4.7 (configuration format version 18, earliest compatible version 1)
Creating a Kas Configuration File
Now, let's prepare a directory to store our YAML file for kas:
export PROJECT=${HOME}/Projects/yoseli
mkdir -p ${PROJECT}/kas/include
cd ${PROJECT}/kas
And create a first simple file in this directory named project.yml:
header:
version: 18
includes:
- include/raspberrypi.yml
machine: raspberrypi4-64
distro: poky
target:
- core-image-minimal
repos:
poky:
url: "https://git.yoctoproject.org/git/poky"
branch: scarthgap
commit: 9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34
layers:
meta:
meta-poky:
meta-yocto-bsp:
meta-openembedded:
url: https://git.openembedded.org/meta-openembedded
branch: scarthgap
commit: e92d0173a80ea7592c866618ef5293203c50544c
layers:
meta-oe:
local_conf_header:
local_dirs: |
DL_DIR = "/home/yocto/Projects/dl"
SSTATE_DIR = "/home/yocto/Projects/sstate-cache"
A few things must be noticed:
-
There is an include file, which will be created a bit later
-
The YAML file specifies the machine and the image to generate.
-
The local_conf_header part is updating the local.conf file, we will see that later too.
Now, in our kas directory, let's create an include/raspberrypi.yml
file:
header:
version: 18
repos:
meta-raspberrypi:
url: https://github.com/agherzan/meta-raspberrypi.git
branch: scarthgap
commit: 6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17
meta-lts-mixins:
url: https://git.yoctoproject.org/meta-lts-mixins
commit: 66ceeebd047d7fdfc8668b300319a76da8ae257d
local_conf_header:
raspberrypi: |
RPI_USE_U_BOOT = "1"
ENABLE_UART = "1"
IMAGE_INSTALL:append = " kernel-image kernel-devicetree"
IMAGE_FSTYPES:remove = " rpi-sdimg"
The meta-lts-mixins uses a specific version for u-boot for the scarthgap release. For more details, refer to https://docs.yoctoproject.org/5.0.9/singleindex.html#long-term-support-releases
Generating a base image for the Raspberry Pi4
Now, we need somewhere to store the layers and everything the build will generate:
cd ${PROJECT}
mkdir rpi4-test
cd rpi4-test
Before launching a full build, let's dive into a shell for the build environment:
$> kas shell ../kas/project.yml
2025-05-06 17:44:35 - INFO - kas 4.7 started
2025-05-06 17:44:36 - INFO - Cloning repository meta-raspberrypi
2025-05-06 17:44:36 - INFO - Cloning repository meta-lts-mixins
2025-05-06 17:44:36 - INFO - Cloning repository poky
2025-05-06 17:44:36 - INFO - Cloning repository meta-openembedded
2025-05-06 17:44:36 - INFO - Repository meta-lts-mixins already contains 66ceeebd047d7fdfc8668b300319a76da8ae257d as
commit
2025-05-06 17:44:38 - INFO - Repository meta-raspberrypi already contains 6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17
as commit
2025-05-06 17:44:40 - INFO - Repository meta-openembedded already contains e92d0173a80ea7592c866618ef5293203c50544c
as commit
2025-05-06 17:44:48 - INFO - Repository poky already contains 9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34 as commit
2025-05-06 17:44:48 - INFO - Repository meta-raspberrypi checked out to 6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17
2025-05-06 17:44:48 - INFO - Repository meta-lts-mixins checked out to 66ceeebd047d7fdfc8668b300319a76da8ae257d
2025-05-06 17:44:48 - INFO - Repository poky checked out to 9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34
2025-05-06 17:44:49 - INFO - Repository meta-openembedded checked out to e92d0173a80ea7592c866618ef5293203c50544c
2025-05-06 17:44:49 - INFO - To start the default build, run: bitbake -c build core-image-minimal
We can see all the layers included, the commit id used for each of them, and we also have all the open embedded environment variables sourced. We are now in the
build
directory of our project. We can examine the local.conf
file:
$ cat conf/local.conf
# local_dirs
DL_DIR = "/home/yocto/Projects/dl"
SSTATE_DIR = "/home/yocto/Projects/sstate-cache"
# raspberrypi
RPI_USE_U_BOOT = "1"
ENABLE_UART = "1"
IMAGE_INSTALL:append = " kernel-image kernel-devicetree"
IMAGE_FSTYPES:remove = " rpi-sdimg"
MACHINE ??= "raspberrypi4-64"
DISTRO ??= "poky"
BBMULTICONFIG ?= ""
Notice that each lines of the YAML configuration files is there.
This is how we can modify it:
-
add a local_conf_header
-
use a unique id
-
set the variables we want.
We can also, in this shell, call some bitbake commands, for instance, to display the layers used for the build:
$ bitbake-layers show-layers
NOTE: Starting bitbake server...
layer path priority
========================================================================================================
lts-u-boot-mixin /home/yocto/Projects/yoseli/rpi4-test/build/../meta-lts-mixins 6
openembedded-layer /home/yocto/Projects/yoseli/rpi4-test/build/../meta-openembedded/meta-oe 5
raspberrypi /home/yocto/Projects/yoseli/rpi4-test/build/../meta-raspberrypi 9
core /home/yocto/Projects/yoseli/rpi4-test/build/../poky/meta 5
yocto /home/yocto/Projects/yoseli/rpi4-test/build/../poky/meta-poky 5
yoctobsp /home/yocto/Projects/yoseli/rpi4-test/build/../poky/meta-yocto-bsp 5
We can now exit the shell (with CTRL-D) and launch a build:
$ kas build ../kas/project.yml
2025-05-06 17:50:51 - INFO - kas 4.7 started
2025-05-06 17:50:51 - INFO - Repository meta-raspberrypi already contains 6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17
as commit
2025-05-06 17:50:51 - INFO - Repository meta-lts-mixins already contains 66ceeebd047d7fdfc8668b300319a76da8ae257d as
commit
2025-05-06 17:50:51 - INFO - Repository poky already contains 9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34 as commit
2025-05-06 17:50:51 - INFO - Repository meta-openembedded already contains e92d0173a80ea7592c866618ef5293203c50544c
as commit
2025-05-06 17:50:51 - INFO - Repository meta-raspberrypi checked out to 6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17
2025-05-06 17:50:51 - INFO - Repository meta-lts-mixins checked out to 66ceeebd047d7fdfc8668b300319a76da8ae257d
2025-05-06 17:50:51 - INFO - Repository poky checked out to 9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34
2025-05-06 17:50:51 - INFO - Repository meta-openembedded checked out to e92d0173a80ea7592c866618ef5293203c50544c
2025-05-06 17:50:51 - INFO - /home/yocto/Projects/yoseli/rpi4-test/build$
/home/yocto/Projects/yoseli/rpi4-test/poky/bitbake/bin/bitbake -c build core-image-minimal
Loading cache: 100% |
| ETA: --:--:--
Loaded 0 entries from dependency cache.
Parsing recipes: 100%
|######################################################################################################################################################################################################|
Time: 0:00:19
Parsing of 1915 .bb files complete (0 cached, 1915 parsed). 3261 targets, 129 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
Build Configuration:
BB_VERSION = "2.8.0"
BUILD_SYS = "x86_64-linux"
NATIVELSBSTRING = "debian-12"
TARGET_SYS = "aarch64-poky-linux"
MACHINE = "raspberrypi4-64"
DISTRO = "poky"
DISTRO_VERSION = "5.0.9"
TUNE_FEATURES = "aarch64 crc cortexa72"
TARGET_FPU = ""
meta-lts-mixins = "HEAD:66ceeebd047d7fdfc8668b300319a76da8ae257d"
meta-oe = "scarthgap:e92d0173a80ea7592c866618ef5293203c50544c"
meta-raspberrypi = "scarthgap:6df7e028a2b7b2d8cab0745dc0ed2eebc3742a17"
meta
meta-poky
meta-yocto-bsp = "scarthgap:9c63e0c9646c61663e8cfc6b4c75865cd0cd3b34"
Sstate summary: Wanted 1769 Local 560 Mirrors 0 Missed 1209 Current 0 (31% match, 0% complete)########################################################################################################### | ETA: 0:00:00
Initialising tasks: 100% |###################################################################################################################################################################################################| Time: 0:00:01
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 3774 tasks of which 1145 didn't need to be rerun and all succeeded.
The first build can take a long time (maybe hours). The next ones will be very fast, as the DL and SSTATE directories are kept in the local.conf. On my machine, this build
with an already existing DL
and SSTATE
took less than 2 minutes.
Once the build is finished, the deployed files are in build/tmp/deploy/images/${MACHINE}
. We are interested by the image file to write it down on our SD card, let's find those:
$ find build/tmp/deploy/images/raspberrypi4-64/ -maxdepth 1 -type l -ls |grep core-image*
25858892 4 lrwxrwxrwx 2 yocto yocto 61 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.ext3 -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.ext3
25858882 4 lrwxrwxrwx 2 yocto yocto 70 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.testdata.json -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.testdata.json
25858887 4 lrwxrwxrwx 2 yocto yocto 69 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.spdx.tar.zst -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.spdx.tar.zst
25858889 4 lrwxrwxrwx 2 yocto yocto 64 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.tar.bz2 -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.tar.bz2
25858884 4 lrwxrwxrwx 2 yocto yocto 65 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.manifest -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.manifest
25858896 4 lrwxrwxrwx 2 yocto yocto 65 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.wic.bmap -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.wic.bmap
25858895 4 lrwxrwxrwx 2 yocto yocto 64 mai 6 18:28 build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.wic.bz2 -> core-image-minimal-raspberrypi4-64.rootfs-20250506155112.wic.bz2
Image Flashing with bmaptool
After building your image, you'll need to flash it to an SD card. The bmaptool offers significant speed improvements over traditional tools. Notice in the generated files that
we have a wic.bz2
and a wic.bmap
file. Those can be given to the bmaptool to flash the SD card. For more information, refer to https://github.com/yoctoproject/bmaptool.
Now, we can copy the rootfs to the SD card (mine is /dev/sda
):
$ apt install bmap-tools
$ bmaptool copy --bmap build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.wic.bmap build/tmp/deploy/images/raspberrypi4-64/core-image-minimal-raspberrypi4-64.rootfs.wic.bz2 /dev/sda
bmaptool: info: block map format version 2.0
bmaptool: info: 54784 blocks of size 4096 (214.0 MiB), mapped 23093 blocks (90.2 MiB or 42.2%)
bmaptool: info: copying image 'core-image-minimal-raspberrypi4-64.rootfs.wic.bz2' to block device '/dev/sda' using bmap file 'core-image-minimal-raspberrypi4-64.rootfs.wic.bmap'
bmaptool: info: 100% copied
bmaptool: info: synchronizing '/dev/sda'
bmaptool: info: copying time: 2.2s, copying speed 40.9 MiB/sec
Now, let's boot the board, after connecting an UART console (refer to https://raspberrypi.stackexchange.com/questions/82946/pi-3-model-b-wont-boot/82992#82992 if needed):
Now, The system is up and running, but we can't do a lot: we don't have any SSH server, no default user, etc.
Next post of this series will focus on making this image a bit more useful, adding connectivity, and starting a simple A/B setup with Mender.
You can find the code for this part on my github: https://github.com/YoseliSAS/yocto-blog-posts/releases/tag/part1