This guide is intended for those who are building a custom firmware image for a particular [sub-]target or device wish to tweak the openwrt linux kernel to optimize it for that specific [sub-]target or device. Tweaking the kernel configuration in openwrt is hard to do right...due to the way the buildsystem is setup it is easy to tweak things and then either
- The kernel wont build at all due to missing dependencies, or
- The kernel builds but is broken and the resulting firmware image is unstable (to some degree) due to using a broken kernel
This is particularly frustrating since its often not immediately obvious why this is happening if you dont have a thorough understanding of how this part of the build process actually works. This guide analyses the buildsystem's kernel configuration process, describing why these outcomes often occur when you tweak the kernel configuration and offers a couple of ways to avoid this problem and produce custom kernels that actually work correctly.
Note: to clarify, this is not a guide about what tweaks you should make to optimize the kernel for a given device. It is a guide on how to work with the openwrt buuildsystem to allow you to tweak the kernel as you would like and not have it break the kernel. The buildsystem kernel configuration is currently optimized for setting up kernels on a variety of targets/subtargets/devices using layered config templates. If you want to optimize the kernel for a wide range of devices use the build system as-is. If you only care about a specific device or [sub-]target and wont be using this buildroot to build for anything other than that, then read on, since make kernel_menuconfig
probably doesnt work how you think it does...
TL;DR
To build firmware with a custom kernel, dont just run
./scripts/feeds update -a
./scripts/feeds install -a
make menuconfig kernel_menuconfig prepare
make
At the bare minimum, run
./scripts/feeds update -a
./scripts/feeds install -a
make menuconfig kernel_menuconfig target/linux/clean prepare
# ## ^^ADD THIS^^ ##
make
Ideally, go a step further and follow "Solution #2" near the bottom of this guide (a full build script is provided at the bottom of this guide)
Note: make prepare
builds the build tools / toolchain and builds the kernel, but doesn't build target openwrt packages (including kmods).
Note: its not a bad idea to change make menuconfig
to make menuconfig download check
and to add -j$(nproc) V=sc
to the make
commands. Building will utilize multiple CPUs and will be a good bit faster this way.
How make kernel_menuconfig
works
The kernel configuration uses a single kernel .config
file in the kernrl build_dir, but generates this .config from multiple sources. It does this because the buildsystem is optimized to build firmware for a wide range of targets/devices, and this makes it possible to automate the kernel's configuration over a wide range of targets/devices by using a series of progressively more "device-specific" configuration options. Assuming the router runs linux, it pulls config info from a few sources:
- The "generic" kernel config for linux-based routers (found in
target/linux/generic/
) - The kernel config for that target (found in
target/linux/$TARGET/
) - The kernel config for that subtarget (found in
target/linux/$TARGET/$SUBTARGET/
) - Dynamicly-generated config options based on what you chose when running
make menuconfig
(Im not sure exactly the process of how these get added, but they do somehow)
Note: these aren't full expanded .config
's, but rather just enable / disable a few specific options.
The configs dictated by these 4 sources get layered together, combining them into a single config template (or a "sparse .config
), and then something like make defconfig
is run on this combined sparse .config
, expanding it to the full .config
that is actually used in the kernel build.
The Problem
The problem is that when you run make kernel_menuconfig
you dont see the full .config...rather it only pulls the config from one of these sources (you can choose which using make kernel_menuconfig CONFIG_TARGET=<...>
). Furthermore, regardless of which CONFIG_TARGET you use (or dont use), these dont seem to include the dynamically generated config options based on your make menuconfig
selections). You then tweak it with the kernel_menuconfig, which expands it to a full .config when you save it.
Furthermore, if the .config
file exists in the kernel's build_dir build root (at something like build_dir/target_$GCC_TARGET/linux-$TARGET/linux-$KERNEL_VER/.config
) then that is what gets used to build the kernel. Which means that if you run
make menuconfig kernel_menuconfig prepare
It builds the generic kernel with your modifications, but without the TARGET / SUBTARGET configs and without the dynamic configs generated from your make menuconfig
picks. Which, in particular, if you did a lot of tweaking during your make menuconfig
, tends to produce the sort of errors described at the top of the post that are so easy to run into.
Solution #1 - quick and easy, but imperfect
THE SOLUTION
Its important to note that running make kernel_menuconfig
doesnt just save the .config file in the kernel; build_dir, but also updates the config templates in the target/linux/<...>
directory. Which leads to an easy (though imperfect) solution: remove the kernel .config after you run kernel_menuconfig but before you build the kernel. i.e., run
make menuconfig kernel_menuconfig target/linux/clean prepare
- the
menuconfig
sets up the build and configures the "dynamically added kernel configuration based on menuconfig selections" kernel configuration templates - the
kernel_menuconfig
generates the.config
in the build_dir and updates thetarget/linux/*
config templates, - the
target/linux/clean
removes the kernel build_dir (and the generated.config
), and - the
prepare
builds the kernel, using the proper config layering with the modified config templates that were updated bykernel_menuconfig
WHY IT IS "IMPERFECT"
This mostly fixes the problems that you describe, but leaves the possibility that things can get screwed up when updating the config template overrides some other essential config option. This might be due to a couple of things:
Problem #1: Your kernel_menuconfig choices.
You change something essential that is set in one of the other config templates or in the dynamically generated configs added based on your make menuconfig
choices (meaning that you dont see it in the make kernel_menuconfig
menu), overriding it.
Problem #2: The automatic update of the kernel config templates
When kernel_menuconfig
exits and updates the config templates, it replaces them with a partially-expanded config, sort of like what running ./scripts/diffconfig
produces for the standard )(non-kernel) config. i.e., not just the exact config options you selected / deselected, but also any config options that (due to the automatic kconfig dependency rules) were unlocked based on your config change. These automatic dependencies can, for example, add "default" config options for a given item. These unlocked configs (that you didnt explicitly choose) are more often than not are wayyy deep in submenus in the kernel_menuconfig
.
So, if you re-enable some config that is enabled by a config template but that isnt shown in the kernel_menuconfig
, all the unlocked config options will be assigned default values, which will get saved in the config template and can override other (essential) configs pulled in from one of the other config template sources. Furthermore, because part of the complete config is missing, diffconfig can expand things in such a way that just running make kernel_menuconfig
and immediately exiting (with or without saving ) can/will update the config template in a way that overrides some of the other configs templates, and ultimately gives a different final kernel .config
configuration and a different (and possibly broken) compiled kernel than if you didnt run make kernel_menuconfig
at all and just started with make prepare
.
Solution #2: a better (but more involved) way to configure the kernel
So, the idea here is to (before running make kernel_menuconfig
) first run make prepare
and get the unmodified but complete kernel .config
from the kernel's build directory and incorporate that info into the $TARGET config template, so that when you run make kernel_menuconfig
it shows the full (properly layered and including dynamic modifications from make menuconfig
choices) kernel configuration.
Unfortunately, we cant just replace the $TARGET config template with the full .config
, since that would break the automatic dependency system that is used in the kernel config (automatic dependencies wont override used-selected config options, and using the full .config makes it think everything is user selected). We want/need this dependency system working to produce working kernels all the time except when it would override an essential config option listed in one of the config template sources.
The code for the solution is below, but it more-or-less involves
- building an unmodified kernel (
make prepare
) and saving the full unmodified .config and then remove it from the kernel build dir - getting the partial unmodified .config (
make kernel_menuconfig
--> save (without making changes) to .config --> exit), then saving that .config and then remove it from the kernel build dir - running a
diff
between the full and partial unmodified .configs, and adding all the stuff that is present in the full .config but missing from the partial .config into the $TARGET config template - run
make_kernel menuconfig
again (which should now basically be using thediffconfig
version of the full kernal.config
configuration) and actually tweak the kernel as desired, then save it to.config
- (optional "sanity check") save this .config somewhere, remove it from the kernel build_dir, then re-run
make kernel_menuconfig
and save to .config without changing anything. This .config should be identical to the .config you just saved outside of the kernel build directory, meaning that everything in the $TARGET config template includes (and isnt getting overwritten by) everything that the other config template sources would have done to the .config. - run
make prepare
to build the kernel, thenmake
to finish building the firmware.
FULL BUILD SCRIPT FOR ROBUSTLY BUILDING FIRMWARE WITH A CUSTOM KERNEL
#!/usr/bin/env bash
# clone openwrt repo and cd to openwrt buildroot
git clone https://github.com/openwrt/openwrt.git
cd openwrt
# setup package feeds
./scripts/feeds update -a
./scripts/feeds install -a
./scripts/feeds install -a
# OPTIONAL: copy a generic .config for your device to serve as a "starting point". save it is `.config`
# configure openwrt settings
make menuconfig
# downloiad/check sources, then build unmodified kernel
make -j$(nproc) V=sc download check prepare
# save important paths in variables
target_board="$(grep -F CONFIG_TARGET_BOARD <.config | sed -E 's/^[^"]*"//;s/".*$//')"
target_kconfig="$(ls target/linux/${target_board}/config-*)"
builddir_kconfig="$(ls build_dir/target*/linux-${target_board}*/linux-*/.config)"
# copy full default config of the (just built) default kernel
\mv -f "${builddir_kconfig}" .config.kernel
# generate the partial .config (for the kernel that kernel_menuconfig by default shows) in the kernel build dir
# NOTE: ***DONT CHANGE ANYTHING*** in this kernel_menuconfig - immediately save it (to .config) and exit.
make kernel_menuconfig
# diff and add to target config to get target diffconfig of full default kernel config
diff .config.kernel "${builddir_kconfig}" | grep '<' | sed -E s/'^< '// >> "${target_kconfig}"
# remove incomplete .config out of kernel build dir
rm "${builddir_kconfig}"
# regenerate .config (which gives the full complete kernel .config) and actually custom configure the kernel this time -- save as .config when done
make kernel_menuconfig
# diff and add to target config to get diffconfig of full custom kernel config,
# then diff that with the target config template (that make kernel_menuconfig
# *should* have updated) and add anything that is missing to target .config.
# note: diff arguments of inner diff are flipped from previous diff command
echo "$(diff <(diff "${builddir_kconfig}" .config.kernel | grep '<' | sed -E s/'^< '//) "${target_kconfig}" | grep '<' | sed -E s/'^< '//)" >> "${target_kconfig}"
# save full .config and fix up target config.
# dont change anything in kernel_menuconfig - immediately save it (to .config) and exit
\mv -f "${builddir_kconfig}" .config.kernel
make kernel_menuconfig
# check that re-generating the kernel .config doesnt change anything
# the following command should indicate there are no changes
diff .config.kernel "${builddir_kconfig}"
# make kernel
make -j$(nproc) V=sc prepare
# make the rest of the build
make -j$(nproc) -k V=sc