Lesson
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_URIadds files, patches, and configuration fragmentsFILESEXTRAPATHStells BitBake where to find files added from your layer.bbappendfiles 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.bbappendcurrently being parsed${PN}is the recipe name, in this case normallylinux-myvendor:prependputs 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
.dtbfiles - a U-Boot binary or SPL
- a
wicdisk 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
.bbappendfiles 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-getvarandbitbake-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
FILESEXTRAPATHSandSRC_URIexplain 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.
Check your understanding
Quick quiz: kernel and boot
A quick review of low-level platform customisation patterns.