---
title: "What a Yocto BSP hand-over should actually contain"
tldr: "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."
type: "reference"
hub: "Embedded Linux & Yocto"
published: "2026-09-03"
updated: "2026-09-03"
canonical: "https://exubits.com/engineering/yocto-bsp-handover"
author: "Exubits Engineering"
---

# What a Yocto BSP hand-over should actually contain

> 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 `repo` manifest 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. `kirkstone` today is not `kirkstone` from 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.conf` `BBLAYERS` order documented, because layer priority
  changes which `.bbappend` wins.

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 `.bbappend` to the vendor's) with every deviation
  commented — why `PREFERRED_VERSION_linux-*` is pinned, why a `MACHINE_FEATURE`
  was 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:
  inheriting `poky` and overriding twelve variables in `local.conf` means the
  config only exists on the machine that has that `local.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` — particularly `systemd` vs
  `sysvinit`, `wayland`/`x11`, `pam`, `usrmerge`.
- `IMAGE_FSTYPES` and how they map to what the factory actually flashes
  (`wic.gz`, `wic.bmap`, a `.swu` for SWUpdate).
- `EXTRA_IMAGE_FEATURES`, `IMAGE_INSTALL:append`.
- The `PACKAGE_CLASSES` choice (`package_rpm`/`ipk`/`deb`) — this affects the
  on-target update mechanism and cannot be changed casually later.
- Any `PREMIRRORS` / `SSTATE_MIRRORS` pointing 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`.

- **`defconfig` plus fragments**, wired through `KERNEL_CONFIG_FRAGMENTS` or a
  `linux-*.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 `.dts` in `meta-<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_IO12` to the actual card-detect net saves the next
  engineer an afternoon with a multimeter.
- Regulators modelled properly — `regulator-always-on`, `regulator-boot-on` and
  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.scr` or 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.
- **`buildhistory` output** 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 working `bitbake -c
  archiver` configuration 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](https://github.com/crops/poky-container) 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 `bitbake` invocation, target names, and any `BB_ENV_PASSTHROUGH`
  additions.
- Expected build resources: disk for `TMPDIR` and `SSTATE_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_DIR` archive 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:

1. **Build it** — from empty machine to flashable image, copy-paste commands.
2. **Flash it** — factory path and field path, including the recovery/USB-download
  procedure for a bricked board.
3. **Change the kernel config / add a package / bump a recipe** — the routine
  maintenance loop.
4. **Cut a release** — versioning, signing, what gets tagged, what gets archived.
5. **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.
