Troubleshooting devicetree

Here are some tips for fixing misbehaving devicetree related code.

See Devicetree HOWTOs for other “HOWTO” style information.

Try again with a pristine build directory

Important

Try this first, before doing anything else.

See Pristine Builds for examples, or just delete the build directory completely and retry.

This is general advice which is especially applicable to debugging devicetree issues, because the outputs are created during the CMake configuration phase, and are not always regenerated when one of their inputs changes.

Make sure <devicetree.h> is included

Unlike Kconfig symbols, the devicetree.h header must be included explicitly.

Many Zephyr header files rely on information from devicetree, so including some other API may transitively include devicetree.h, but that’s not guaranteed.

undefined reference to __device_dts_ord_<N>

This usually happens on a line like this:

const struct device *dev = DEVICE_DT_GET(NODE_ID);

where NODE_ID is a valid node identifier, but no device driver has allocated a struct device for this devicetree node. You thus get a linker error, because you’re asking for a pointer to a device that isn’t defined.

To fix it, you need to make sure that:

  1. The node is enabled: the node must have status = "okay";.

    (Recall that a missing status property means the same thing as status = "okay";; see Important properties for more information about status).

  2. A device driver responsible for allocating the struct device is enabled. That is, the Kconfig option which makes the build system compile the driver sources into your application needs to be set to y.

    (See Setting Kconfig configuration values for more information on setting Kconfig options.)

Below, <build> means your build directory.

Making sure the node is enabled:

To find the devicetree node you need to check, use the number <N> from the linker error. Look for this number in the list of nodes at the top of <build>/zephyr/include/generated/zephyr/devicetree_generated.h. For example, if <N> is 15, and your devicetree_generated.h file looks like this, the node you are interested in is /soc/i2c@deadbeef:

/*
 * Generated by gen_defines.py
 *
 * DTS input file:
 *   <build>/zephyr/zephyr.dts.pre
 *
 * Directories with bindings:
 *   $ZEPHYR_BASE/dts/bindings
 *
 * Node dependency ordering (ordinal and path):
 *   0   /
 *   1   /aliases
[...]
 *   15  /soc/i2c@deadbeef
[...]

Now look for this node in <build>/zephyr/zephyr.dts, which is the final devicetree for your application build. (See Get your devicetree and generated header for information and examples.)

If the node has status = "disabled"; in zephyr.dts, then you need to enable it by setting status = "okay";, probably by using a devicetree overlay. For example, if zephyr.dts looks like this:

i2c0: i2c@deadbeef {
        status = "disabled";
};

Then you should put this into your devicetree overlay and Try again with a pristine build directory:

&i2c0 {
        status = "okay";
};

Make sure that you see status = "okay"; in zephyr.dts after you rebuild.

Making sure the device driver is enabled:

The first step is to figure out which device driver is responsible for handling your devicetree node and allocating devices for it. To do this, you need to start with the compatible property in your devicetree node, and find the driver that allocates struct device instances for that compatible.

If you’re not familiar with how devices are allocated from devicetree nodes based on compatible properties, the ZDS 2021 talk A deep dive into the Zephyr 2.5 device model may be a useful place to start, along with the Device Driver Model pages. See Important properties and the Devicetree specification for more information about compatible.

There is currently no documentation for what device drivers exist and which devicetree compatibles they are associated with. You will have to figure this out by reading the source code:

  • Look in drivers for the appropriate subdirectory that corresponds to the API your device implements

  • Look inside that directory for relevant files until you figure out what the driver is, or realize there is no such driver.

Often, but not always, you can find the driver by looking for a file that sets the DT_DRV_COMPAT macro to match your node’s compatible property, except lowercased and with special characters converted to underscores. For example, if your node’s compatible is vnd,foo-device, look for a file with this line:

#define DT_DRV_COMPAT vnd_foo_device

Important

This does not always work since not all drivers use DT_DRV_COMPAT.

If you find a driver, you next need to make sure the Kconfig option that compiles it is enabled. (If you don’t find a driver, and you are sure the compatible property is correct, then you need to write a driver. Writing drivers is outside the scope of this documentation page.)

Continuing the above example, if your devicetree node looks like this now:

i2c0: i2c@deadbeef {
        compatible = "nordic,nrf-twim";
        status = "okay";
};

Then you would look inside of drivers/i2c for the driver file that handles the compatible nordic,nrf-twim. In this case, that is drivers/i2c/i2c_nrfx_twim.c. Notice how even in cases where DT_DRV_COMPAT is not set, you can use information like driver file names as clues.

Once you know the driver you want to enable, you need to make sure its Kconfig option is set to y. You can figure out which Kconfig option is needed by looking for a line similar to this one in the CMakeLists.txt file in the drivers subdirectory. Continuing the above example, drivers/i2c/CMakeLists.txt has a line that looks like this:

zephyr_library_sources_ifdef(CONFIG_NRFX_TWIM       i2c_nrfx_twim.c)

This means that CONFIG_NRFX_TWIM must be set to y in <build>/zephyr/.config file.

If your driver’s Kconfig is not set to y, you need to figure out what you need to do to make that happen. Often, this will happen automatically as soon as you enable the devicetree node. Otherwise, it is sometimes as simple as adding a line like this to your application’s prj.conf file and then making sure to Try again with a pristine build directory:

CONFIG_FOO=y

where CONFIG_FOO is the option that CMakeLists.txt uses to decide whether or not to compile the driver.

However, there may be other problems in your way, such as unmet Kconfig dependencies that you also have to enable before you can enable your driver.

Consult the Kconfig file that defines CONFIG_FOO (for your value of FOO) for more information.

Make sure you’re using the right names

Remember that:

  • In C/C++, devicetree names must be lowercased and special characters must be converted to underscores. Zephyr’s generated devicetree header has DTS names converted in this way into the C tokens used by the preprocessor-based <devicetree.h> API.

  • In overlays, use devicetree node and property names the same way they would appear in any DTS file. Zephyr overlays are just DTS fragments.

For example, if you’re trying to get the clock-frequency property of a node with path /soc/i2c@12340000 in a C/C++ file:

/*
 * foo.c: lowercase-and-underscores names
 */

/* Don't do this: */
#define MY_CLOCK_FREQ DT_PROP(DT_PATH(soc, i2c@1234000), clock-frequency)
/*                                           ^               ^
 *                                        @ should be _     - should be _  */

/* Do this instead: */
#define MY_CLOCK_FREQ DT_PROP(DT_PATH(soc, i2c_1234000), clock_frequency)
/*                                           ^               ^           */

And if you’re trying to set that property in a devicetree overlay:

/*
 * foo.overlay: DTS names with special characters, etc.
 */

/* Don't do this; you'll get devicetree errors. */
&{/soc/i2c_12340000/} {
     clock_frequency = <115200>;
};

/* Do this instead. Overlays are just DTS fragments. */
&{/soc/i2c@12340000/} {
     clock-frequency = <115200>;
};

Look at the preprocessor output

To save preprocessor output files, enable the CONFIG_COMPILER_SAVE_TEMPS option. For example, to build Hello World with west with this option set, use:

west build -b BOARD samples/hello_world -- -DCONFIG_COMPILER_SAVE_TEMPS=y

This will create a preprocessor output file named foo.c.i in the build directory for each source file foo.c.

You can then search for the file in the build directory to see what your devicetree macros expanded to. For example, on macOS and Linux, using find to find main.c.i:

$ find build -name main.c.i
build/CMakeFiles/app.dir/src/main.c.i

It’s usually easiest to run a style formatter on the results before opening them. For example, to use clang-format to reformat the file in place:

clang-format -i build/CMakeFiles/app.dir/src/main.c.i

You can then open the file in your favorite editor to view the final C results after preprocessing.

Do not track macro expansion

Compiler messages for devicetree errors can sometimes be very long. This typically happens when the compiler prints a message for every step of a complex macro expansion that has several intermediate expansion steps.

To prevent the compiler from doing this, you can disable the CONFIG_COMPILER_TRACK_MACRO_EXPANSION option. This typically reduces the output to one message per error.

For example, to build Hello World with west and this option disabled, use:

west build -b BOARD samples/hello_world -- -DCONFIG_COMPILER_TRACK_MACRO_EXPANSION=n

Validate properties

If you’re getting a compile error reading a node property, check your node identifier and property. For example, if you get a build error on a line that looks like this:

int baud_rate = DT_PROP(DT_NODELABEL(my_serial), current_speed);

Try checking the node by adding this to the file and recompiling:

#if !DT_NODE_EXISTS(DT_NODELABEL(my_serial))
#error "whoops"
#endif

If you see the “whoops” error message when you rebuild, the node identifier isn’t referring to a valid node. Get your devicetree and generated header and debug from there.

Some hints for what to check next if you don’t see the “whoops” error message:

Check for missing bindings

See Devicetree bindings for information about bindings, and Bindings index for information on bindings built into Zephyr.

If the build fails to Find a devicetree binding for a node, then either the node’s compatible property is not defined, or its value has no matching binding. If the property is set, check for typos in its name. In a devicetree source file, compatible should look like "vnd,some-device"Make sure you’re using the right names.

If your binding file is not under zephyr/dts, you may need to set DTS_ROOT; see Where bindings are located.

Errors with DT_INST_() APIs

If you’re using an API like DT_INST_PROP(), you must define DT_DRV_COMPAT to the lowercase-and-underscores version of the compatible you are interested in. See Option 1: create devices using instance numbers.