3. User Guide
This chapter is the day-to-day reference: how to get a build environment, how to configure the target, and how to build, deploy and run a system. The concepts behind the build system (layers, recipes, tasks, patchsets) are described in the build system chapter.
Everything below assumes an x86_64 Linux host. Ubuntu 24.04 is what the build is validated on, but the container makes the host distribution mostly irrelevant.
3.1. Getting a build environment
There are two ways to get one, and they are interchangeable: the tree is built at its own absolute path in both cases, so you can switch back and forth without invalidating BitBake stamps or CMake caches.
3.1.1. Using the container (recommended)
The container carries the environment — cross toolchains, host packages, Python — and no project source. The repository stays on the host, bind-mounted at its own path, and the build runs as your own user, so nothing comes back root-owned.
$ scripts/dbuild.sh --build # build the image, once
$ scripts/dbuild.sh build.sh bsp-linux # run a build inside it
$ scripts/dbuild.sh # interactive shell, env.sh sourced
See the container chapter for the details, including what cannot be done from inside (running the emulator’s GUI, mounts outliving the command).
Note
The container also solves a class of host problems for good: it carries its
own sudo configuration, so a host whose PAM stack interferes with
sudo -v (a fingerprint reader, for instance) can still run
build.sh bsp-linux and deploy.sh.
3.1.2. Building on the host
The build requires the bash shell.
Warning
On Ubuntu, /bin/sh points at dash, which does not share bash’s
syntax. If you invoke the scripts through sh, see
this procedure.
The authoritative list of host packages is
docker/build-env/packages.txt — the same file the container image installs,
one package per line with comments explaining why each group is there. Install it
with:
$ sed -e 's/#.*//' -e '/^[[:space:]]*$/d' docker/build-env/packages.txt \
| xargs sudo apt install -y
That list covers BitBake, the boot images, the storage tooling, the rootfs pipeline, CMake for the user space, QEMU (building and running it) and Sphinx for this documentation.
3.1.2.1. Toolchains
The 32-bit cross toolchain comes from apt and is in the list above
(gcc-arm-linux-gnueabihf, matching IB_TOOLCHAIN:arm). The 64-bit one is
not interchangeable with Ubuntu’s: the recipes pin the prefix
aarch64-none-linux-gnu-, while gcc-aarch64-linux-gnu installs
aarch64-linux-gnu-. Install the official Arm toolchain (12.3.rel1 is the
version the build is validated with, and the one the container ships):
$ sudo mkdir -p /opt/toolchains && cd /opt/toolchains
$ sudo curl -fSLO "https://developer.arm.com/-/media/Files/downloads/gnu/12.3.rel1/binrel/arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz"
$ sudo tar xf arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
$ sudo mv arm-gnu-toolchain-12.3.rel1-x86_64-aarch64-none-linux-gnu aarch64-none-linux-gnu
$ echo 'export PATH="${PATH}:/opt/toolchains/aarch64-none-linux-gnu/bin"' \
| sudo tee /etc/profile.d/02-toolchains.sh
If the toolchain lives somewhere else, point IB_TOOLCHAIN_PATH at its
bin/ directory instead; env.sh appends it to PATH.
3.2. The environment — env.sh
Sourcing env.sh from the top of the tree is required before anything else:
$ . ./env.sh
It exports IB_ROOT_DIR and BUILDDIR, and puts scripts/ and the
bundled bitbake on your PATH — which is what lets you invoke a
standard script from anywhere inside the tree. It also removes the
previous tree’s entries, so moving between two checkouts leaves no stale paths.
Every script prints a banner on stderr naming the tree and the platform it is about to act on:
[infrabase] bsp-linux root=/home/user/infrabase platform=virt64
It is worth a glance: the scripts resolve the target tree from the current directory (falling back to their own location), so running a script from another checkout prompts you before switching, and refuses outright when non-interactive.
3.3. Configuration
The project configuration lives in build/conf/local.conf. All project
variables use the IB_ prefix, and BitBake’s override syntax
VAR:<override> scopes a value to a platform or a component. Read the comments
in the file — they carry the reasoning behind each default.
A second, untracked file is read after it: build/conf/site.conf. That is
where a single machine deviates from the tracked defaults, without dirtying the
repository. It is absent on most machines.
3.3.1. Platforms
IB_PLATFORM selects the target platform (BitBake calls it the machine):
Name |
Platform |
|---|---|
virt64 |
QEMU |
virt32 |
QEMU |
rpi4_64 |
Raspberry Pi 4 in 64-bit mode |
rpi4 |
Raspberry Pi 4 in 32-bit mode |
x86-qemu |
QEMU x86 platform |
verdin-imx8mp |
Toradex Verdin iMX8M Plus, installed over the network with TEZI |
Note
After changing IB_PLATFORM, rebuild and redeploy. Component trees are
attached in place, so a stale kernel or buildroot .config from the previous
platform can survive; build.sh -c <recipe> forces a clean re-attach.
3.3.2. What gets built: the boot chain
Infrabase builds four components — ATF, OP-TEE, AVZ and Linux.
Which of them end up in an image is decided by one variable in
build/conf/local.conf, IB_BOOT_CHAIN: the stages that run before the
payload, in the order they run, joined by +:
Stage |
Role |
|---|---|
|
ARM Trusted Firmware (BL1/BL2/BL31). |
|
OP-TEE as BL32, a secure world. Needs |
|
U-Boot. |
|
The AVZ hypervisor at EL2, entered by U-Boot, with Linux as its guest.
Needs |
|
MCUboot, which picks an image out of a flash slot and runs it. |
So uboot is Linux on a bare U-Boot, atf+optee+uboot adds a secure world,
uboot+avz runs Linux as an AVZ guest with no secure world (QEMU’s
virtualization=on gives EL2 without one), and atf+optee+uboot+avz has
both. The order is checked, not just the spelling — atf+avz+uboot is
refused — and a chain must contain uboot or mcuboot, or nothing can
enter the payload.
Everything else about the boot shape is derived from that one value by
ib_normalize_boot_axes (meta/classes/base.bbclass): IB_HYPERVISOR
(avz when the chain carries the stage, none otherwise), the
IB_CHAIN_HAS_<STAGE> flags the recipes gate on, and which ITS is assembled.
Nothing else has to be set, and nothing can disagree with it.
What each platform can run is declared as a set of stages,
IB_BOOT_STAGES_SUPPORTED:<platform> (plus IB_BOOT_STAGES_REQUIRED where a
stage is mandatory):
Platform |
Supported stages |
Required |
|---|---|---|
virt64 |
|
— |
verdin-imx8mp |
|
|
rpi4_64 |
|
— |
rpi4, virt32, x86-qemu |
|
— |
A missing stage is a hardware or upstream limit, not an omission, and each one
is explained next to IB_BOOT_STAGES_SUPPORTED in build/conf/local.conf.
In short: the i.MX8MP boot ROM always installs BL31, so there is no chain
without atf on the Verdin; TF-A’s rpi4 port is AArch64-only, so the
32-bit Pi has no ATF; OP-TEE has no plat-rpi4 upstream and the BCM2711 has
no secure memory controller, so a secure world on either Pi could never be a
real TEE; and AVZ ships aarch64 defconfigs only, so the 32-bit platforms are
standalone-Linux only.
Asking for a stage the platform cannot run is refused at parse time, naming what it does support:
ERROR: Platform "rpi4_64" cannot run boot stage "optee" (IB_BOOT_CHAIN="atf+optee+uboot").
Supported on this platform: atf uboot avz.
See IB_BOOT_STAGES_SUPPORTED in build/conf/local.conf for why.
Note
IB_TARGET_ITS is derived from the chain — <plat> without avz,
<plat>_avz with it — so the boot image always matches the requested
shape. Set it explicitly only for a hand-written ITS.
Note
IB_BOOT_CHAIN = "full" is still accepted as a legacy alias for
atf+optee+uboot+avz (the edge-m1 capsule chain), so a tree aligning onto
this one keeps building unchanged.
3.3.3. Key variables
Variable |
Meaning |
|---|---|
|
Target platform, see above. |
|
Kernel defconfig, e.g. |
|
ITS basename used to build the FIT image ( |
|
The ordered boot stages, e.g. |
|
Derived from |
|
AVZ defconfig, e.g. |
|
Which cpio becomes the embedded ramfs: |
|
Where a deploy writes: |
|
Device for |
|
|
|
Storage image size, default |
|
Partition holding the rootfs ( |
|
Architecture and cross-compiler prefix per platform. |
|
Set to |
|
Where an |
|
Port |
|
|
3.4. Building
Components are built with the build.sh standard script:
build.sh [-h] [-l] [-c] [-v] [-x] <recipe>
The recipe name is a positional argument. The -x flag is an optional,
no-op marker kept for explicitness — build.sh bsp-linux and
build.sh -x bsp-linux are equivalent. Options come before the recipe.
Option |
Effect |
|---|---|
|
List all available recipes (BSPs and components). |
|
Clean the recipe first ( |
|
Verbose build logs ( |
|
Optional “build this recipe” marker (recipe stays positional). |
|
Print help. |
A BSP recipe (bsp-linux) pulls its whole dependency tree; a component
recipe (uboot, linux, rootfs-linux, buildroot, usr-linux,
qemu, filesystem, atf, optee) builds just itself.
$ build.sh bsp-linux # full Linux BSP (kernel + rootfs + user space + FIT)
$ build.sh linux # rebuild just the kernel
$ build.sh -c uboot # clean + rebuild u-boot
$ build.sh -v -c bsp-linux # clean + rebuild everything, verbose
$ build.sh -l # list all recipes
On the QEMU platforms (virt32, virt64), a BSP build also builds the
emulator when its binary is missing, so a fresh tree is runnable with st.sh
right away. This is a bootstrap only: an already-built emulator is left
untouched, and the QEMU-hacking loop stays the explicit build.sh qemu. Set
IB_BUILD_QEMU = "0" in local.conf to skip it — useful in CI, which builds
and deploys but never runs the emulator.
Note
bitbake itself runs unprivileged. The build steps that need root
(filesystem creating the image, bsp-linux loop-mounting the rootfs)
escalate individual commands via sudo -n against a timestamp the script
opens once — you are prompted for your password at most once per invocation.
3.4.1. Running a task by hand
Tasks normally run through the dependency graph, but any single one can be
invoked from the build/ directory, without the do_ prefix:
$ bitbake linux -c patch
$ bitbake linux -c updiff # regenerate the patchset, see the build system chapter
3.5. Storage
IB_STORAGE_MODE decides where a deploy writes: a loopback image
(soft, the default on virt64), a real block device (hard), or an HTTP
feed for a network install (http).
The storage image is filesystem/sdcard.img.<platform>. A deploy creates it
when it is missing, so the explicit step below is rarely needed:
$ init_storage.sh # partition + mkfs the storage image/device
To inspect or edit its contents, mount the two partitions — boot (FAT) as
filesystem/p1, rootfs (ext4) as filesystem/p2:
$ mount.sh # mount p1 + p2
$ umount.sh # unmount them
Both wrap the filesystem recipe tasks (fs_mount / fs_umount); the
losetup/mount calls escalate via sudo -n.
Warning
Unmount before redeploying — deploy.sh reuses an existing mount
instead of making a fresh one. And with IB_STORAGE_MODE = "hard" the
target is a real block device named by IB_STORAGE_DEVICE: double-check
local.conf.
3.5.1. Editing an archive instead of the storage
The same two scripts also open the cpio archives, which is what you want when the target is the initrd rather than the SD card:
$ mount.sh -i # extract board/<plat>/initrd.cpio into filesystem/p1
$ umount.sh -i # repack it
A cpio archive is not a block image, so there is nothing to loop-mount:
“mounting” it means extracting it into a tree you can edit, and unmounting means
repacking. Both run under fakeroot, so the archive keeps its root ownership
and modes without sudo and without bitbake — the fake-ownership database is
saved on mount and replayed on umount.
For the cases that need real root (cpio -id restoring device nodes) the
bitbake path is still there, unpacking into the recipe’s own workdir:
$ mount.sh ramfs # rootfs-linux's initrd.cpio
$ mount.sh rootfs rootfs-linux # ... or its rootfs.cpio
$ umount.sh ramfs # repack with the matching call
mount.sh -h and umount.sh -h spell out all of it.
3.6. Deployment
deploy.sh [-h] [-l] [-v] [-x] <recipe>
Like build.sh, the recipe is a positional argument (-x optional and
no-op), and deploy.sh inherits the state left by the prior build.sh.
Deploying the BSP recipe writes the full image:
$ deploy.sh bsp-linux # boot chain + FIT to p1, rootfs to p2
It creates the storage image if needed, mounts it, then copies the bootloader and
the .itb to the boot partition and the root filesystem to the second one.
Deployment can also be done per component — after rebuilding only the user space,
redeploy just that:
$ deploy.sh usr-linux
deploy.sh -l lists the recipes that define a do_deploy task (it is a
little slow: it queries bitbake per recipe).
Note
As with the build, bitbake runs unprivileged and the privileged
operations (mount, losetup, mkfs, parted, …) escalate via
sudo -n against a timestamp opened once at the start of the deploy.
3.6.1. Network install (http mode)
On a platform whose IB_STORAGE_MODE is http, the deploy does not write a
storage device at all: it publishes a file set the board downloads and installs
over the network. The files land in IB_HTTP_DEPLOY_PATH, which defaults
inside the tree (build/deploy/tezi/<platform>) — no web-server document
root to arrange, no root privilege, and nothing written outside the tree.
deploy.sh then starts a server for it, so a deploy leaves a working feed
behind with no manual step:
$ tezi-feed-serve.sh # serve in the foreground
$ tezi-feed-serve.sh --status # is it running, and on which URL?
$ tezi-feed-serve.sh --stop # stop a detached server
It is a plain python3 -m http.server on IB_HTTP_FEED_PORT (8080 by
default, so it runs as your user), rooted at the feed directory. --status
prints the URL to hand to the board. To publish somewhere else on one machine —
a real web server’s document root, say — override IB_HTTP_DEPLOY_PATH in
build/conf/site.conf rather than in the tracked local.conf.
3.7. Running the emulated system
st.sh launches the freshly deployed image in the patched QEMU built by the
qemu recipe:
$ st.sh # headless: serial multiplexed on stdio, no display
$ st.sh -d # graphical: adds virtio-gpu/keyboard/mouse and an SDL window
It handles virt64 and virt32, picking the emulator binary from
IB_PLATFORM (qemu-system-aarch64 / qemu-system-arm). The serial
console stays on stdio in graphical mode too, so a run with -d remains
scriptable. Any argument other than -d and -h is passed through to
QEMU — st.sh -S freezes the machine at reset, waiting for a debugger.
The boot mode is picked from filesystem/flash0.img: present means the ATF
chain (-M virt,virtualization=on,secure=on plus the pflash image), absent
means bare U-Boot loaded with -kernel u-boot/u-boot at EL1.
Networking is user-mode (slirp): the guest gets its address immediately, needs no
sudo and no tap device, and guest SSH is forwarded to host port 2222
(ssh -p 2222 root@localhost). A GDB stub listens on tcp::1234, offset by
the number of emulators already running so two instances never collide.
3.8. User space applications
Custom applications and out-of-tree kernel modules live in linux/usr and are
built with CMake by the usr-linux recipe. See
Linux user applications for how they are installed into the
root filesystem.
For a tight edit-compile loop, makeusr.sh reproduces what the recipe does in
do_build — CMake plus make, and the kernel modules — without going
through bitbake. Run it from inside linux/usr:
$ cd linux/usr && makeusr.sh
$ makeusr.sh -M # skip the kernel modules
$ makeusr.sh -C # remove build/ and exit
$ makeusr.sh -h # all options
It needs the buildroot toolchain to have been built once (it takes the CMake
toolchain file from linux/rootfs/host), and it deploys locally into
linux/usr/build/deploy. A full deploy.sh usr-linux is still what puts the
result into the target rootfs.