Engineering /Embedded Linux & Yocto
What a Yocto BSP hand-over should actually contain
TL;DR
A usable Yocto BSP hand-over: a pinned manifest, vendored layers, a documented DISTRO/MACHINE, a reproducible build, a signed image with its SPDX SBOM, and the rationale for every kernel and U-Boot config choice.
A Yocto BSP hand-over fails in a predictable way. Six months after the last
invoice, someone runs the build on a fresh machine and it breaks: a layer moved,
meta-openembedded advanced a branch, an SRC_URI 404s, the host GCC is now too
new for a fetched tarball. The image that shipped is fine. The ability to
rebuild it is gone. That is the thing a hand-over has to protect, and most
hand-overs do not.
This page is the checklist we hold our own BSP deliveries to. It is deliberately about artefacts and their properties, not about a particular board.
The one property that matters: bit-for-bit rebuildability
Everything below is in service of a single test. Take the delivery, put it on a machine that has never seen the project, follow the written steps, and get an image whose package manifest matches the one that shipped. If that test passes, the BSP is maintainable. If it does not, you have bought a binary with source attached, not a BSP.
Yocto gives you the machinery for this — BB_HASHSERVE, hash-equivalence,
buildhistory, reproducible-builds class — but none of it is on by a default
that survives a vendor change. It has to be configured, and the configuration is
part of the deliverable.
1. A pinned, self-contained source manifest
- A
repomanifest or a kas file that pins every layer to a commit SHA, not a branch.honister,kirkstone,scarthgap— a branch name is a moving target.kirkstonetoday is notkirkstonefrom the release date. - The BitBake and OE-Core revision pinned in the same file.
- No layer fetched from a URL the client does not control. If a layer lives only on a vendor’s GitLab, it is a single point of failure with someone else’s uptime. Vendored into the client’s own Git, with the upstream URL and SHA recorded in the commit message so the provenance is not lost.
- The
conf/bblayers.confBBLAYERSorder documented, because layer priority changes which.bbappendwins.
The test: kas checkout (or repo sync) on an air-gapped mirror reproduces the
exact tree. No “then update meta-freescale to the tip”.
2. Your own layer, and only your changes in it
There should be exactly one layer that contains the project’s work —
meta-<project> — and it should be the only layer with local commits. Every
change to a BSP or upstream recipe lives there as a .bbappend or a versioned
recipe copy, never as an edit to meta-ti, meta-freescale, or poky.
Why this is a hand-over issue and not a style preference: when the client later
moves from kirkstone to the next LTS, the migration work is reviewing one
layer. If changes are smeared across five vendor layers, the migration is
archaeology, and the estimate for it triples.
What belongs in meta-<project>:
- The machine
.conf(or a.bbappendto the vendor’s) with every deviation commented — whyPREFERRED_VERSION_linux-*is pinned, why aMACHINE_FEATUREwas removed. - The image recipe(s). One production image, one development image, and the
delta between them stated explicitly (dev adds
debug-tweaks,openssh-sftp,gdbserver— and production must be verified not to). - The distro config if the project defines its own
DISTRO. It usually should: inheritingpokyand overriding twelve variables inlocal.confmeans the config only exists on the machine that has thatlocal.conf.
3. local.conf is not part of the delivery — the distro config is
local.conf is per-developer scratch. Anything load-bearing that lives there
will be lost. The hand-over must move every meaningful setting into version
control:
DISTRO_FEATURES/DISTRO_FEATURES_remove— particularlysystemdvssysvinit,wayland/x11,pam,usrmerge.IMAGE_FSTYPESand how they map to what the factory actually flashes (wic.gz,wic.bmap, a.swufor SWUpdate).EXTRA_IMAGE_FEATURES,IMAGE_INSTALL:append.- The
PACKAGE_CLASSESchoice (package_rpm/ipk/deb) — this affects the on-target update mechanism and cannot be changed casually later. - Any
PREMIRRORS/SSTATE_MIRRORSpointing at internal infrastructure.
4. The kernel: a defconfig fragment and a reason for every line
A kernel handed over as a 6,000-line .config is not maintainable, because
nobody can tell an essential setting from an accident of make oldconfig.
defconfigplus fragments, wired throughKERNEL_CONFIG_FRAGMENTSor alinux-*.bbappend. The fragment is small and every line is intentional.- A written rationale for the non-obvious ones: which
CONFIG_enables the Ethernet PHY, which sets the RT behaviour, which was needed for a USB gadget mode, which disables an unused subsystem to cut attack surface and boot time. - The kernel provenance: mainline version, the vendor SoC tree it is based on, the patch stack applied on top — as a quilt series or Git history, not a single squashed diff. When a CVE lands, someone needs to know whether the tree already carries the fix.
- Out-of-tree modules identified, with their licence and their source. An out-of-tree Wi-Fi driver with no upstream is a maintenance liability that should be named in the hand-over, not discovered later.
5. Device tree: the board’s hardware description, reviewed
The device tree is where “the BSP works” and “the BSP is correct” diverge. A node can be missing and the board still boots.
- The board
.dtsinmeta-<project>, including via.dtsi, not patched into the vendor’s file in place. - Every pinmux group traceable to the schematic net name. A comment linking
MX8MM_IOMUXC_SD2_CD_B_GPIO2_IO12to the actual card-detect net saves the next engineer an afternoon with a multimeter. - Regulators modelled properly —
regulator-always-on,regulator-boot-onand the supply chain to each peripheral. Half of “the peripheral randomly does not enumerate” bugs are a missing or lazy regulator description. - Overlays, if used, with the base-plus-overlay combination that the running system actually uses documented. Applied by U-Boot or by the kernel — state which.
6. U-Boot: the config, the environment, and the boot flow
- The U-Boot defconfig and any board patches, same discipline as the kernel.
- The boot environment as a file, not as whatever is currently in the SPI
flash of the one golden board. The
boot.cmd/boot.scror the extlinux config, in version control. - The boot flow written out: ROM → SPL → U-Boot proper → kernel → init, with where each stage lives (eMMC boot partition, offset, GPT) and what the fallback path is if the primary kernel does not come up.
- If secure boot is in play: which keys sign what, where the public keys are fused or stored, and — critically — a documented recovery path for a board whose signature check fails. A secure-boot BSP with no recovery story is a brick generator.
7. The signed image and its bill of materials
- The exact image that was released, plus its signature and the public key to verify it.
buildhistoryoutput committed for that build: the package list with versions, image size, dependency graph, and the diff against the previous release. This is what makes “what changed between v1.2 and v1.3” answerable in minutes.- An SBOM — Yocto emits SPDX 2.2 JSON via
create-spdx— covering every package in the image with its version and licence. Increasingly this is a contractual and regulatory requirement (the EU Cyber Resilience Act among them), and it is far cheaper to generate at build time than to reconstruct. - The licence manifest (
license.manifest,deploy/licenses/) and the sources for anything under a copyleft licence, or a workingbitbake -c archiverconfiguration that regenerates them.
8. The build environment itself
“Works on my machine” is not a hand-over. Pin the host too:
- A container (the CROPS base or a project Dockerfile) that fixes the host distro, the essential host packages, Python, and locale. Yocto is sensitive to host GCC and host Python; a 2024 build host and a 2027 build host are not equivalent.
- The exact
bitbakeinvocation, target names, and anyBB_ENV_PASSTHROUGHadditions. - Expected build resources: disk for
TMPDIRandSSTATE_DIR, RAM, rough wall time on a stated machine — so the client can size CI. - A populated sstate and downloads mirror, or instructions to build one.
Without it the first rebuild pulls hundreds of source archives from the
internet, and some of those URLs will be dead. An offline
DL_DIRarchive is the single most valuable non-obvious artefact in the box.
9. Documentation that is task-shaped
Not a wiki dump. Five documents, each answering a question someone will actually ask:
- Build it — from empty machine to flashable image, copy-paste commands.
- Flash it — factory path and field path, including the recovery/USB-download procedure for a bricked board.
- Change the kernel config / add a package / bump a recipe — the routine maintenance loop.
- Cut a release — versioning, signing, what gets tagged, what gets archived.
- Known issues and deferred work — the honest list. Every BSP has one; a hand-over without it just means the client finds them the hard way.
What this costs
Producing this is real work — on the order of one to two engineer-weeks on top of the BSP itself, most of it in items 1, 7, and 8. It is worth being explicit about that in a statement of work rather than discovering it at the end. The payoff is entirely deferred: it shows up the first time the client rebuilds without the original team, and it is the difference between an afternoon and a re-engagement.
The trade-off we make deliberately: we do not hand over a build that tracks upstream branches, even though that would look more “up to date” on delivery day. A pinned build is frozen and will drift out of security currency until someone deliberately bumps it — that is the cost. It is the right cost, because a build that silently changes under the client is not a build they own.
Talk to an engineer
Ask about this directly — the person who wrote it answers, not a sales desk.