Board Porting Guide

When building an application you must specify the target hardware and the exact board or model. Specifying the board name results in a binary that is suited for the target hardware by selecting the right Zephyr features and components and setting the right Zephyr configuration for that specific target hardware.

A board is defined as a special configuration of an SoC with possible additional components. For example, a board might have sensors and flash memory implemented as additional features on top of what the SoC provides. Such additional hardware is configured and referenced in the Zephyr board configuration.

The board implements at least one SoC and thus inherits all of the features that are provided by the SoC. When porting a board to Zephyr, you should first make sure the SoC is implemented in Zephyr.

Hardware Configuration Hierarchy

Hardware definitions in Zephyr follow a well-defined hierarchy of configurations and layers, below are the layers from top to bottom:

  • Board
  • SoC
  • SoC Series
  • SoC Family
  • CPU Core
  • Architecture

This design contributes to code reuse and implementation of device drivers and features at the bottom of the hierarchy making a board configuration as simple as a selection of features that are implemented by the underlying layers. The figures below shows this hierarchy with a few example of boards currently available in the source tree:

Configuration Hierarchy

Configuration Hierarchy

Hierarchy Example

Board FRDM K64F nRF52 NITROGEN nRF51XX Quark SE C1000 Devboard Arduino 101
SOC MK64F12 nRF52832 nRF51XX Quark SE C1000 Curie
SOC Series Kinetis K6x Series Nordic NRF52 Nordic NRF51 Quark SE Quark SE
SOC Family NXP Kinetis Nordic NRF5 Nordic NRF5 Quark Quark
CPU Core Cortex-M4 Cortex-M4 Cortex-M0 Lakemont Lakemont
Architecture ARM ARM ARM x86 x86

Architecture

If your CPU architecture is already supported by Zephyr, there is no architecture work involved in porting to your board. If your CPU architecture is not supported by the Zephyr kernel, you can add support by following the instructions available at Architecture Porting Guide.

CPU Core

Some OS code depends on the CPU core that your board is using. For example, a given CPU core has a specific assembly language instruction set, and may require special cross compiler or compiler settings to use the appropriate instruction set.

If your CPU architecture is already supported by Zephyr, there is no CPU core work involved in porting to your platform or board. You need only to select the appropriate CPU in your configuration and the rest will be taken care of by the configuration system in Zephyr which will select the features implemented by the corresponding CPU.

Platform

This layer implements most of the features that need porting and is split into three layers to allow for code reuse when dealing with implementations with slight differences.

SoC Family

This layer is a container of all SoCs of the same class that, for example implement one single type of CPU core but differ in peripherals and features. The base hardware will in most cases be the same across all SoCs and MCUs of this family.

SoC Series

Moving closer to the SoC, the series is derived from an SoC family. A series is defined by a feature set that serves the purpose of distinguishing different SoCs belonging to the same family.

SoC

Finally, an SoC is actual hardware component that is physically available on a board.

Board

A board implements an SoC with all its features, together with peripherals available on the board that differentiates the board with additional interfaces and features not available in the SoC.

Default board configuration

When porting Zephyr to a board, you must provide the board’s default Kconfig configuration, which is used in application builds unless explicitly overridden.

Note

See the Kconfig - Tips and Best Practices page for some best practices and tips when writing Kconfig files.

In order to provide consistency across the various boards and ease the work of users providing applications that are not board specific, the following guidelines should be followed when porting a board:

  • Provide pin and driver configuration that matches the board’s valuable components such as sensors, buttons or LEDs, and communication interfaces such as USB, Ethernet connector, or Bluetooth/WiFi chip.
  • When a well-known connector is present (such as used on an Arduino or 96board), configure pins to fit this connector.
  • Configure components that enable the use of these pins, such as configuring an SPI instance for Arduino SPI.
  • Configure an output for the console.
  • Propose and configure a default network interface.
  • Enable all GPIO ports.

Setting configuration values

Kconfig symbols can be set to their BOARD-specific values in one of two ways. The right method to use depends on whether the symbol is visible or not.

Visible and invisible Kconfig symbols

Kconfig symbols come in two varieties:

  • A Kconfig symbol defined with a prompt is visible, and can be configured from the menuconfig configuration interface.
  • A Kconfig symbol defined without a prompt is invisible. The user has no direct control over its value.

Here are some examples of visible and invisible symbols:

config NOT_VISIBLE
    bool
    default FOO

config VISIBLE_1
    string
    prompt "Foo value"

config VISIBLE_2
    # Shorthand for giving a type and a prompt at the same time. This is
    # the preferred style in Zephyr.
    bool "Enable stuff"

Configuring visible Kconfig symbols

Default BOARD-specific configuration values for visible Kconfig symbols should be given in boards/ARCHITECTURE/BOARD/BOARD_defconfig, which uses the standard Kconfig .config file syntax.

Configuring invisible Kconfig symbols

BOARD-specific configuration values for invisible Kconfig symbols must be given in boards/ARCHITECTURE/BOARD/Kconfig.defconfig, which uses Kconfig syntax.

Note

Assignments in .config files have no effect on invisible symbols, so this scheme is not just an organizational issue.

Assigning values in Kconfig.defconfig relies on being able to define a Kconfig symbol in multiple locations. As an example, say we want to set FOO_WIDTH below to 32:

config FOO_WIDTH
    int

To do this, we extend the definition of FOO_WIDTH as follows, in Kconfig.defconfig:

if BOARD_MY_BOARD

config FOO_WIDTH
    default 32

endif

Note

Since the type of the symbol (int) has already been given at the first definition location, it does not need to be repeated here.

default values in Kconfig.defconfig files have priority over default values given on the “base” definition of a symbol. Internally, this is implemented by including the Kconfig.defconfig files first. Kconfig uses the first default with a satisfied condition, where an empty condition works like if y (is always satisfied).

Note

range properties on int and hex symbols work the same way, and can also be added or overriden in Kconfig.defconfig files.

If you want a symbol to only be user-configurable on some boards, make its base definition have no prompt, and then add a prompt to it in the Kconfig.defconfig files of the boards where it should be configurable.

Note

Prompts added in Kconfig.defconfig files show up at the location of the Kconfig.defconfig file in the menuconfig interface, rather than at the location of the base definition of the symbol.

Configuring choices

There are two ways to configure a Kconfig choice:

  1. By setting one of the choice symbols to y in BOARD_defconfig.

    Note

    Setting one choice symbol to y automatically gives all other choice symbols the value n.

    If multiple choice symbols are set to y, only the last one set to y will be honored (and the rest will get the value n). This allows a choice selection from a board defconfig file to be overridden from an application prj.conf file.

  2. By changing the default of the choice in Kconfig.defconfig.

    As with symbols, changing the default for a choice is done by defining the choice in multiple locations. For this to work, the choice must have a name.

    As an example, assume that a choice has the following base definition (here, the name of the choice is FOO):

    choice FOO
        bool "Foo choice"
        default B
    
    config A
        bool "A"
    
    config B
        bool "B"
    
    endchoice
    

    To change the default symbol of FOO to A, you would add the following definition to Kconfig.defconfig:

    choice FOO
        default A
    endchoice
    

The Kconfig.defconfig method should be used when the dependencies of the choice might not be satisfied. In that case, you’re setting the default selection whenever the user makes the choice visible.

Motivation

One motivation for this configuration scheme is to avoid making fixed BOARD-specific settings configurable in the menuconfig interface. If all configuration were done via BOARD_defconfig, all symbols would have to be visible, as values given in BOARD_defconfig have no effect on invisible symbols.

Having fixed settings be user-configurable might be confusing, and would allow the user to create broken configurations.

Kconfig extensions

Zephyr uses the Kconfiglib implementation of Kconfig, which includes some Kconfig extensions.

Environment variables in source statements are expanded directly in Kconfiglib, meaning no option env="ENV_VAR" “bounce” symbols need to be defined. If you need compatibility with the C Kconfig tools for an out-of-tree Kconfig tree, you can still add such symbols, but they must have the same name as the corresponding environment variables.

Note

option env has been removed from the C tools in Linux 4.18 as well.

The recommended syntax for referencing environment variables is now $(FOO) rather than $FOO. This uses the new Kconfig preprocessor.

The following Kconfig extensions are available:

  • The source statement supports glob patterns and includes each matching file. A pattern is required to match at least one file.

    Consider the following example:

    source "foo/bar/*/Kconfig"
    

    If the pattern foo/bar/*/Kconfig matches the files foo/bar/baz/Kconfig and foo/bar/qaz/Kconfig, the statement above is equivalent to the following two source statements:

    source "foo/bar/baz/Kconfig"
    source "foo/bar/qaz/Kconfig"
    

    Note

    The wildcard patterns accepted are the same as for the Python glob module.

    If no files match the pattern, an error is generated.

    For cases where it’s okay for a pattern to match no files (or for a plain filename to not exist), a separate osource (optional source) statement is available. osource is a no-op in case of no matches.

    Note

    source and osource are analogous to include and -include in Make.

  • An rsource statement is available for including files specified with a relative path. The path is relative to the directory of the Kconfig file that contains the rsource statement.

    As an example, assume that foo/Kconfig is the top-level Kconfig file, and that foo/bar/Kconfig has the following statements:

    source "qaz/Kconfig1"
    rsource "qaz/Kconfig2"
    

    This will include the two files foo/qaz/Kconfig1 and foo/bar/qaz/Kconfig2.

    rsource can be used to create Kconfig “subtrees” that can be moved around freely.

    Note

    rsource also supports glob patterns.

  • An orsource statement, which combines osource and rsource.

    For example, the following statement will include Kconfig1 and Kconfig2 from the current directory (if they exist):

    orsource "Kconfig[12]"
    
  • def_int, def_hex, and def_string keywords, which are analogous to def_bool. These set the type and add a default at the same time.

Old Zephyr Kconfig behavior for defaults

Prior to early August 2018 (during development of Zephyr 1.13), Zephyr used a custom patch that made Kconfig prefer the last default with a satisfied condition, instead of the first one. This patch has been removed.

Consider this example:

config FOO
    string
    default "first" if n
    default "second"
    default "third" if n
    default "fourth"
    default "fifth" if n

With the old custom behavior, FOO got the value "fourth", from the last default with a satisfied condition.

With the new behavior, FOO gets the value "second", from the first default with a satisfied condition. This is standard Kconfig behavior.

There are two issues with the old behavior:

  1. It’s inconsistent with how Kconfig works in other projects, which is confusing.

  2. Due to oversights, earlier range properties were still preferred, as well as earlier default properties on choices.

    In addition to being inconsistent, this made it impossible to override range properties and default properties on choices if the base definition of the symbol/choice already had range/default properties.

Note

If you’re maintaining an external project that has symbols with multiple default properties, you will need to swap the order of the default properties to get the same behavior as before.

If your external project is modifying symbols in the base Zephyr configuration by sourcing Kconfig.zephyr and adding additional symbol definitions, you might need to move the source from before the extra symbol definitions to after them.

More Kconfig resources

The official documentation for Kconfig is kconfig-language.txt and kconfig-macro-language.txt.

The Kconfig - Tips and Best Practices page has some best practices and tips for writing Kconfig files.

The kconfiglib.py docstring (at the top of the file) goes over how symbol values are calculated in detail.