Skip to content

Digging (a little bit) into KConfig and device tree

Understanding program and board configurations

In order to programme a Zephyr OS application for a specific target device, you need to use different configuration tools: Kconfig and device tree. This codelab introduces these tools and explains the basic principles behind them. The goal is not to bring a in-depth understanding of all application and board configurations, but rather to enable you to configure an application for board fully supported by the Zephyr RTOS ecosystem.

What you’ll build

  • How to configure an application and compile it using the different Zephyr Development Environment and Zephyr RTOS.
  • How to add specific application and board configuration parameters.

What you’ll learn

  • The basic principles behind Kconfig and device tree.
  • How to use some tools that help with the configuration.

What you’ll need

  • Zephyr Development Environment for developing and debugging C++ code snippets.
  • The (getting started codelab)[../getting-started-zephyr.md]{target=”blank”} is a prerequisite for this codelab.

Digging into Kconfig

As you learned earlier, Zephyr RTOS applications can be configured using the application configuration file, which is named “prf.conf” by default. This file contains the definition of the symbols used to configure the build process.

The symbols for which values are defined in the “prf.conf” file are declared in Kconfig files. Kconfig is a concept borrowed from the Linux kernel configuration system. It uses a hierarchy of configuration files that ultimately results in the declaration of a hierarchy of configuration options or symbols. The build system uses these symbols to include or exclude files from the build process. It also uses the symbols in the source code itself as symbols used by the precompiler.

With Zephyr RTOS, west uses Kconfig as part of the build process.

Visualizing the Configuration Options using menuconfig or guiconfig

To configure the options of a Zephyr RTOS application, the developer must navigate through the hierarchy of Kconfig files to understand the hierarchy of configuration symbols. This is a tedious task, and west provides an interactive Kconfig interface to facilitate this task. To run the interface, you will need to:

  • Run west build -b nrf5340dk/nrf5340/cpuapp . from the application folder.
  • Run west build -t menuconfig or west build -t guiconfig. Each command provides an interface that makes configuration much easier. With the use of these interfaces the understanding of the symbol hierarchy is also made much easier. The use of each symbol is also explained for a better understanding.

The guiconfig interface is illustrated below:

guiconfig interface

Figure 1: guiconfig interface

Learning Kconfig with an Example

To explain how Kconfig works in detail, a good example is the Zephyr RTOS logging subsystem that we learned how to use earlier. Part of the logging subsystem Kconfig definition is shown below:

zephyr/subsys/logging/Kconfig
menu "Logging"

config LOG
    bool "Logging"
    select PRINTK if USERSPACE
    help
      Global switch for the logger, when turned off log calls will not be
      compiled in.

if LOG

config LOG_CORE_INIT_PRIORITY
    int "Log Core Initialization Priority"
    range 0 99
    default 0

rsource "Kconfig.mode"
...

This definition can be understood as follows:

  • menu is the definition of the menu name displayed when using menuconfig or guiconfig, as shown in Figure 1.

  • config LOG is the definition of the LOG symbol. In the Kconfig nomenclature, it is a menu entry.

  • A menu entry can have a number of attributes. For the config LOG menu entry:

    • A type definition: The symbol is defined as a bool and application can define the use of the symbol as CONFIG_LOG=y (to enable logging) or CONFIG_LOG=n (to disable logging).

    • A reverse dependency: select PRINTK if USERSPACE forces the value of the PRINTK symbol to the logical AND of the value of the menu symbol LOG and of the symbol USERSPACE. If both values are true, then PRINTK will be set to true.

    • help contains the explanation of the symbol as shown in Figure 1.

  • The Kconfig file above contains further menu items that are defined only if the value of the LOG menu item is true. config LOG_CORE_INIT_PRIORITY is one of these menu items.

  • The config LOG_CORE_INIT_PRIORITY contains the following attributes:

    • A type definition int that defines the menu item to be an integer.
    • A range attribute that specifies acceptable values for the menu item.
    • A default attribute that specifies the default value if it is not specified in the application prj.conf file.

    • rsource "Kconfig.mode" is a Kconfig extension defined in the Kconfiglib. It tells the build system to include the file specified with a path relative to the Kconfig file.

More details on the Kconfig language can be found here. Details about the Kconfig extensions used by west can be found here.

How are Kconfig Definitions Used?

For a better understanding of the use of the configuration parameters, it is useful to have a look at the definition of the CONFIG_PRINTK symbol, which is also used in the logging subsystem. The declaration of the printk() function depends on the definition of the CONFIG_PRINTK, as shown in the source code of the Zephyr RTOS printk() function (simplified here):

zephyr/include/zephyr/sys/log.h
...
#ifdef CONFIG_PRINTK
void printk(const char *fmt, ...);
#else
static inline void printk(const char *fmt, ...)
{
    ARG_UNUSED(fmt);
}
#endif
...
If CONFIG_PRINTK is not defined, printk() will be replaced by a dummy function and any call to printk() will be removed by the compiler. To verify this behaviour, you can

  • Add a call to printk() in the main() funcation.
  • Add the option CONFIG_PRINTK=n in the prj.conf file.
  • Build the application again with the command west build -b nrf5340dk/nrf5340/cpuapp -p.
  • Flash your board.

You would expect the printk() call to print nothing, but even though printk() is disabled, the message is still displayed! Why is this happening? From the source code, the only possible explanation is that the CONFIG_PRINTK definition ended up with CONFIG_PRINTK=y, not as configured in our prj.conf file. And the explanation lies in how the hierarchy of Kconfig files is combined to build the hierarchy of options.

How are Kconfig files combined?

To understand the printk() behaviour explained above, you can observe the following:

  • When building an application, west generates a number of output files. One of these is the file containing all the Kconfig settings (“build/zephyr/.config”). If you look for CONFIG_PRINTK in this file, you will see that it is defined as
    build/zephyr/.config
    ...
    CONFIG_PRINTK=y
    ...
    
  • If you look at the console output while building the application, you will see a warning message:
    console
    warning: PRINTK (defined at subsys/debug/Kconfig:220) was assigned the value 'n' but got the value 'y'.
    

Both of these observations confirm that the `CONFIG_PRINTK’ symbol was not set as expected from the from the prj.conf file. The reasons is two-fold:

  • In the Zephyr RTOS system, there are hundreds of Kconfig files (called fragments in the Zephyr RTOS nomenclature) that are combined at build time. The way the configuration options are finally built is explained in detail in the official Zephyr RTOS documentation and will not be repeated here. It is important to note that the prj.conf file is only a small part of how configuration parameters are built.
  • As explained earlier, Kconfig files contain menu items, each of which can have dependencies (using select or imply). These dependencies may enable some options that conflict with the options set at the application level.

The easiest way to understand how the CONFIG_PRINTK option is finally set to CONFIG_PRINTK=y is to start the guiconfig by running west build -t guiconfig. If you do this, you will see that the PRINTK option is set to y, and that the reason for this is that the BOOT_BANNER symbol has a dependency on the PRINTK symbol, and that it `selects’ it, as shown in Figure 2.

guiconfig interface

Figure 2: guiconfig interface for PRINTK option

This dependency is visible in the following Kconfig file

zephyr/kernel/Kconfig
...
config BOOT_BANNER
    bool "Boot banner"
    default y
    select PRINTK
    select EARLY_CONSOLE
    help
      This option outputs a banner to the console device during boot up.
The boot banner corresponds to the “*** Booting Zephyr OS build v4.1.0 ***” which is printed in the console at application startup. To display this message, the kernel uses printk() and therefore needs to set a dependency on the PRINTK option. If we really want to disable PRINTK, we need to add the following line to our prj.conf file.

blinky/prj.conf
CONFIG_BOOT_BANNER=n

Note that you may also need to disable all logging options to prevent any warnings at build time. If you make this change and reflash your board, you will see that the boot banner and the printk() message are no longer displayed.

Searching for Kconfig options

There exists another online tool provided by Zephyr RTOS to browse the available configuration options and understand their meaning, type and dependencies. If you search for CONFIG_PRINTK, then the search system will display the following result:

kconfig search

Figure 3: Kconfig search result for CONFIG_PRINTK option

Using Kconfig for Specifying Compiler Optimizations

When building any application written in C++, the compiler may apply different optimization rules such as -O1 or -Oz. Although this is possible, the compiler optimization options are usually not defined in the CMakeLists.txt file using the zephyr_library_compile_options definition. The preferred way to define different optimisation options and build types is to use alternative application configuration files.

If you search for CONFIG_COMPILER_OPTIMIZATIONS on the Zephyr RTOS platform, you will see the following output:

kconfig search

Figure 4: Kconfig search result for CONFIG_COMPILER_OPTIMIZATIONS option

This explains how the `CONFIG_COMPILER_OPTIMIZATIONS’ option can be set and its dependencies. If you want to use a different compiler optimisation, such as a release build type, you can copy the existing prj.conf file, rename it and add the following configuration:

blinky/prj_release.conf
CONFIG_COMPILER_OPTIMIZATIONS=CONFIG_SPEED_OPTIMIZATIONS

You can then start another build by specifying the alternate configuration file with the command west build -p -b nrf5340dk/nrf5340/cpuapp -- -DCONF_FILE=prj_release.conf. As explained in the official documentation, additional arguments can be passed to the CMake invocation performed by west build after a -- at the end of the west build command line.

Board Specific Options

Device Tree Basics

Adding Overlay Definitions

For running applications on specific platforms, you may add overlay files. The following overlay files are useful for this lecture:

boards/qemu_x86.overlay
boards/qemu_x86.overlay
#include <zephyr/dt-bindings/gpio/gpio.h>

/ {  
    gpio0: gpio_emul_0 {
        status = "okay";
        compatible = "zephyr,gpio-emul";
        rising-edge;
        falling-edge;
        high-level;
        low-level;
        gpio-controller;
        ngpios=<2>;
        #gpio-cells = <2>;
    };

    resources {
        compatible = "test-gpio-basic-api";
        out-gpios = <&gpio0 0 0>; /* Pin 0 */
        in-gpios = <&gpio0 1 0>; /* Pin 1 */
    };

  leds {
        compatible = "gpio-leds";
        led0: led_0 {
            gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
            label = "Green LED";
        };
    led1: led_1 {
            gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
            label = "Yellow LED";
        };
    };

  aliases {
    led0 = &led0;
        led1 = &led1;
    };
};