Kernel and Boot Customisation

How to customise the Linux kernel, configuration fragments, and boot-related behaviour in Yocto

Once a project moves beyond QEMU or an unmodified evaluation board, the kernel and boot chain usually need attention.

That might mean selecting a vendor kernel, enabling a driver, adding a small patch, choosing the right device tree, or telling U-Boot which board configuration to build. These are not application decisions. They are normally part of the hardware support for the platform, so they belong close to the BSP and machine metadata we looked at in the previous lesson.

The useful mental model is:

MACHINE
  -> BSP metadata
  -> kernel provider and bootloader provider
  -> kernel configuration, patches and device tree
  -> deployable boot artifacts
  -> image that can actually boot on the board

The kernel can build perfectly and still not boot your board. That is mildly annoying the first time and completely normal thereafter. Treat the build result and the boot result as related, but separate, questions.

What Belongs Here

Kernel and boot customisation is usually hardware work.

It often includes:

  • selecting the kernel recipe for a machine
  • selecting the U-Boot configuration for a machine
  • enabling kernel options required by the board
  • applying board-specific kernel or bootloader patches
  • selecting one or more device trees
  • choosing kernel image and boot image formats
  • making sure the boot artifacts end up where the image generation step expects them

The boundary matters. Choosing the board device tree is BSP work. Deciding whether the product image should include SSH is not. Keeping that separation clear makes it much easier to reuse the same BSP with more than one product.

The Kernel Is Still a Recipe

The Linux kernel feels special because it produces boot-critical artifacts, but Yocto still builds it through a BitBake recipe.

The normal recipe flow still applies:

do_fetch
  -> do_unpack
  -> do_patch
  -> do_kernel_configme / do_configure
  -> do_compile
  -> do_install
  -> do_deploy

You do not need to memorise every kernel task at this point. The important point is that the same mechanisms we have already used for recipes still matter:

  • SRC_URI adds files, patches, and configuration fragments
  • FILESEXTRAPATHS tells BitBake where to find files added from your layer
  • .bbappend files let you extend a vendor or upstream recipe without editing it
  • machine metadata selects the provider and board-specific settings

The kernel-specific part is what those files and settings mean.

Selecting the Kernel Provider

Most machines depend on virtual/kernel, not a hard-coded kernel recipe name. That lets the machine decide which recipe provides the kernel.

In a machine configuration such as meta-my-bsp/conf/machine/myboard.conf, that may look like this:

PREFERRED_PROVIDER_virtual/kernel ??= "linux-myvendor"

This says: for this machine, use the linux-myvendor recipe whenever something depends on virtual/kernel.

The ??= form makes this a weak default. A product or development build can still override it deliberately, but the board carries the normal answer.

I normally keep this kind of selection in machine or BSP metadata because it is a hardware decision. A distro usually should not need to know whether this board uses linux-myvendor, linux-yocto, or another vendor kernel.

You can check what BitBake selected with:

bitbake-getvar -r virtual/kernel PREFERRED_PROVIDER_virtual/kernel
bitbake-getvar -r virtual/kernel PN

The first command shows the final provider preference and where it came from. The second confirms the recipe name BitBake resolved for virtual/kernel.

Configure or Patch?

Before changing the kernel, decide what kind of problem you actually have.

Use kernel configuration when:

  • the driver or feature already exists in the kernel tree
  • you only need to enable, disable, or adjust a Kconfig option
  • the required change should survive kernel source updates cleanly

Use a patch when:

  • you need to change kernel source code
  • you need to add or fix a device tree source file
  • you need to add board support that is not already in the kernel tree
  • you need to fix a bug in the kernel version you are using

If the kernel already knows how to do the thing, prefer configuration. Patch the source only when the behaviour or source tree actually needs to change. This one habit avoids a surprising amount of unnecessary kernel forking.

Adding Kernel Configuration Fragments

A kernel configuration fragment is a small file containing only the kernel options you want to add or change.

For example, suppose our board exposes a CAN interface and needs the kernel to include raw CAN socket support. The kernel already has the code. We just need to enable the options.

Create this file in your BSP layer:

meta-my-bsp/
  recipes-kernel/
    linux/
      linux-myvendor/
        myboard-can.cfg
      linux-myvendor_%.bbappend

myboard-can.cfg:

CONFIG_CAN=y
CONFIG_CAN_RAW=y
CONFIG_CAN_DEV=y

The append file adds the fragment to the kernel recipe:

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI:append = " file://myboard-can.cfg"

There are a few important details in that small example.

FILESEXTRAPATHS

The original kernel recipe lives in another layer, so BitBake will not automatically search the directory beside your .bbappend.

This line:

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

adds your append’s recipe-specific directory to the front of the file search path.

  • ${THISDIR} is the directory containing the .bbappend currently being parsed
  • ${PN} is the recipe name, in this case normally linux-myvendor
  • :prepend puts your path before the paths from the original recipe
  • := expands ${THISDIR} immediately while BitBake is parsing this append
  • the trailing : keeps the value as a colon-separated path list

So for:

meta-my-bsp/recipes-kernel/linux/linux-myvendor_%.bbappend

BitBake is told to search:

meta-my-bsp/recipes-kernel/linux/linux-myvendor/

when resolving file://myboard-can.cfg.

SRC_URI

SRC_URI lists the recipe inputs. For a kernel recipe, that can include the kernel source, patches, configuration fragments, and other metadata.

By appending:

SRC_URI:append = " file://myboard-can.cfg"

we tell BitBake that the config fragment is part of the kernel recipe input. Kernel recipes using the standard Yocto kernel tooling then include it when preparing the final .config.

Adding a Kernel Patch

Now suppose the vendor kernel has almost everything we need, but one UART quirk for our board has not made it upstream yet. That is a source change, so this is a patch.

Use the same append directory:

meta-my-bsp/
  recipes-kernel/
    linux/
      linux-myvendor/
        myboard-can.cfg
        0001-arm64-dts-myboard-fix-uart-clock.patch
      linux-myvendor_%.bbappend

Update the append:

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI:append = " \
    file://myboard-can.cfg \
    file://0001-arm64-dts-myboard-fix-uart-clock.patch \
"

Patch files listed in SRC_URI are applied during do_patch. When patches have an ordering requirement, list them in the order they should be applied. I still name them with numeric prefixes, because humans have to review this stuff too:

0001-arm64-dts-myboard-fix-uart-clock.patch
0002-net-myboard-adjust-phy-reset-delay.patch

If the patch does not apply, run the patch task and inspect the log:

bitbake -c patch virtual/kernel

The useful files are normally under the kernel work directory:

tmp/work/<machine>-<distro>-linux/<kernel-recipe>/<version>/temp/

Look for:

log.do_patch
run.do_patch

The log tells you why the patch failed. The run script shows what BitBake tried to execute.

Selecting Device Trees

For many embedded platforms, the device tree is where the board description meets the kernel.

The kernel source may contain many .dts files, but the machine has to select which compiled .dtb files should be built and deployed.

In meta-my-bsp/conf/machine/myboard.conf:

KERNEL_DEVICETREE = "vendor/myboard.dtb"

For multiple board revisions:

KERNEL_DEVICETREE = " \
    vendor/myboard-rev-a.dtb \
    vendor/myboard-rev-b.dtb \
"

KERNEL_DEVICETREE names device tree blobs from the kernel build, not arbitrary files from your layer. If your board needs a new device tree source file, add it to the kernel source through a patch or another kernel-supported mechanism, then select the resulting .dtb here.

This is a common place to get a false sense of success. The kernel recipe can build successfully with the wrong device tree selected. The board may then boot with missing devices, broken pin control, no console, or no storage. None of those are improved by staring angrily at the image recipe.

After building the kernel, check the deployed artifacts:

bitbake virtual/kernel
ls tmp/deploy/images/myboard/

You should expect to see the kernel image and the selected .dtb files for the machine.

Defconfig and Configuration Fragments

Some kernel workflows start from an in-tree defconfig. Others start from a defconfig supplied by the recipe or BSP layer. Fragments then adjust the final configuration.

You may see:

KBUILD_DEFCONFIG = "myboard_defconfig"

or a machine-specific form in some kernel recipes. This tells the kernel build which base defconfig to use.

For training and for many product BSPs, I normally prefer small fragments for board deltas rather than maintaining a full copied .config. A full defconfig has its place, especially when bringing up a new platform, but it becomes harder to review over time. A fragment that says:

CONFIG_CAN=y
CONFIG_CAN_RAW=y

is much easier to understand than a 6,000-line config file where two important lines changed.

To check whether the final kernel configuration contains your option, inspect the configured kernel build directory after the configure task:

bitbake -c configure virtual/kernel

Then search the generated .config under the kernel work directory. The exact path varies by kernel recipe, but it will be under:

tmp/work/<machine>-<distro>-linux/<kernel-recipe>/<version>/

For linux-yocto style kernels, and vendor kernels that use the same kernel tooling, you can also ask BitBake to audit the configuration:

bitbake -c kernel_configcheck -f virtual/kernel

That task is useful when a fragment requests an option but Kconfig drops it because a dependency is missing.

Bootloader Configuration

The kernel does not boot itself. On many embedded systems, U-Boot is responsible for loading the kernel, loading the device tree, and passing the command line.

The machine usually selects the U-Boot configuration:

UBOOT_MACHINE = "myboard_defconfig"

This belongs in machine metadata because it describes how this board’s bootloader should be built.

A minimal machine file might therefore contain:

require conf/machine/include/arm/armv8a/tune-cortexa53.inc

MACHINE_FEATURES = "usbhost usbgadget ethernet wifi"

PREFERRED_PROVIDER_virtual/kernel ??= "linux-myvendor"
PREFERRED_PROVIDER_virtual/bootloader ??= "u-boot-myvendor"

KERNEL_DEVICETREE = "vendor/myboard.dtb"
UBOOT_MACHINE = "myboard_defconfig"

KERNEL_IMAGETYPE = "Image"
IMAGE_FSTYPES += "wic.gz"

This example is deliberately small, but it shows the kind of decisions that belong close to the board:

  • which CPU tuning to use
  • which kernel and bootloader providers to use
  • which device tree to deploy
  • which U-Boot defconfig to build
  • which kernel image type and image format the board expects

If you need to patch U-Boot, use the same pattern as the kernel: keep the vendor recipe as the base and add a .bbappend in your BSP layer.

meta-my-bsp/
  recipes-bsp/
    u-boot/
      u-boot-myvendor/
        0001-myboard-set-default-bootcmd.patch
      u-boot-myvendor_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI:append = " file://0001-myboard-set-default-bootcmd.patch"

Do not edit the vendor U-Boot recipe directly. It works right up until the day you update the vendor layer and have to rediscover your local changes.

Boot Arguments and Root Filesystem Hand-Off

Boot failures often sit between components rather than inside one component.

The boot chain has to agree on:

  • which kernel image is loaded
  • which device tree is loaded
  • what kernel command line is passed
  • where the root filesystem lives
  • whether the drivers needed to mount that root filesystem are built in early enough

Some machines set kernel command-line additions with APPEND:

APPEND += "console=ttyS0,115200 rootwait"

APPEND contributes to the kernel command line used by many boot flows. The exact hand-off still depends on the bootloader and image class used by the machine, so treat this as board metadata to verify, not a universal magic string.

For example, if the root filesystem is on MMC but the MMC host driver is built as a module, the kernel may not be able to mount the root filesystem early in boot. The image can be correct, the kernel can be built, and the board can still fail before userspace starts.

That is why kernel configuration, bootloader configuration, device tree, and Wic layout need to be considered together.

Image Formats and Deployment Artifacts

Boot customisation often affects the artifacts Yocto should emit.

A board may need:

  • Image, zImage, or another kernel image type
  • one or more .dtb files
  • a U-Boot binary or SPL
  • a wic disk image with a boot partition
  • firmware files in a specific partition

Machine metadata often selects the low-level artifact shape:

KERNEL_IMAGETYPE = "Image"
IMAGE_FSTYPES += "wic.gz"
WKS_FILE = "myboard.wks"

KERNEL_IMAGETYPE controls the kernel image form. IMAGE_FSTYPES asks the image build to produce a particular output format. WKS_FILE selects the Wic kickstart file used to assemble the disk layout.

The Wic details are covered separately, but the connection matters here: the boot partition layout has to match what the bootloader expects to load.

Complete BSP Example

Suppose our board needs:

  • the vendor kernel selected as virtual/kernel
  • CAN support enabled in the kernel
  • one device tree fix applied as a patch
  • a specific device tree deployed
  • a U-Boot defconfig selected
  • a compressed Wic image emitted for SD card testing

A tidy BSP layer might look like this:

conf/machine/myboard.conf:

require conf/machine/include/arm/armv8a/tune-cortexa53.inc

MACHINE_FEATURES = "usbhost usbgadget ethernet"

PREFERRED_PROVIDER_virtual/kernel ??= "linux-myvendor"
PREFERRED_PROVIDER_virtual/bootloader ??= "u-boot-myvendor"

KERNEL_DEVICETREE = "vendor/myboard.dtb"
KERNEL_IMAGETYPE = "Image"

UBOOT_MACHINE = "myboard_defconfig"

IMAGE_FSTYPES += "wic.gz"
WKS_FILE = "myboard.wks"

recipes-kernel/linux/linux-myvendor_%.bbappend:

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI:append = " \
    file://myboard-can.cfg \
    file://0001-arm64-dts-myboard-fix-uart-clock.patch \
"

recipes-bsp/u-boot/u-boot-myvendor_%.bbappend:

FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"

SRC_URI:append = " file://0001-myboard-set-default-bootcmd.patch"

This keeps the vendor kernel and bootloader recipes intact while making the project-specific hardware changes visible in your own BSP layer.

Build the kernel first:

bitbake virtual/kernel

Then check the deploy directory:

ls tmp/deploy/images/myboard/

You should expect to see the kernel image and device tree artifacts selected by the machine. Build your image after that:

bitbake my-image

The final deploy directory should contain the image artifacts requested by IMAGE_FSTYPES, such as a .wic.gz image.

Debugging Kernel and Boot Problems

When something fails, split the problem into stages.

If the build fails, ask:

  • did BitBake find the .bbappend?
  • did BitBake find the files listed in SRC_URI?
  • did the patch apply during do_patch?
  • did the kernel configuration accept the requested options?
  • did the compile fail in the kernel source itself?

Useful commands:

bitbake-layers show-appends linux-myvendor
bitbake-getvar -r linux-myvendor SRC_URI
bitbake -c patch virtual/kernel

show-appends confirms that your append is being applied. bitbake-getvar shows the final SRC_URI after all appends and overrides. The task-specific commands force the stages where patching and configuration issues usually appear.

For linux-yocto style kernels, also use:

bitbake -c kernel_configcheck -f virtual/kernel

That checks whether requested configuration values survived Kconfig dependency resolution.

If the build succeeds but the board does not boot, ask a different set of questions:

  • did the deploy directory contain the kernel image the bootloader expects?
  • did it contain the selected .dtb?
  • is the bootloader loading the same files that Yocto deployed?
  • does the kernel command line point at the right root filesystem?
  • are storage, console, and interrupt-controller drivers enabled early enough?
  • does the Wic layout match the bootloader’s expectations?

This is the part where serial console output is worth more than optimism. Capture the boot log, then work backwards through the chain.

Good Habits

Kernel and boot work stays manageable when the changes are small and traceable.

In practice:

  • keep board-specific kernel and boot choices in the BSP layer
  • use .bbappend files instead of editing vendor recipes
  • use configuration fragments when the kernel already supports the feature
  • use patches only when the source tree must change
  • keep device tree selection explicit in machine metadata
  • verify what BitBake selected with bitbake-getvar and bitbake-layers
  • check tmp/deploy/images/<machine>/ before assuming the boot media is correct

The aim is not to make the BSP clever. The aim is to make it obvious.

Summary

Kernel and boot customisation in Yocto is mostly about selecting and extending the right low-level metadata.

The important points are:

  • machine metadata selects the kernel provider, bootloader configuration, device tree, and boot artifact shape
  • kernel config fragments are the right tool when the kernel already supports the feature
  • patches are for source changes, including device tree source changes
  • FILESEXTRAPATHS and SRC_URI explain how BitBake finds the files added by your .bbappend
  • build success and boot success are not the same thing

Keep the changes hardware-focused, visible in your BSP layer, and easy to inspect. That gives you a fighting chance when the board does something educational at 03:00.

Quick quiz: kernel and boot

A quick review of low-level platform customisation patterns.

Question 1A driver is already in the kernel source, but your board image still needs one configuration option enabled. What is the least invasive fix?
Question 2Which of these belongs closest to BSP or machine metadata rather than distro or product policy?
Question 3Your kernel recipe builds successfully, but the board still does not boot. What is the best next conclusion?