← Zephyr Documents build/dts/troubleshooting.html · build/dts/troubleshooting.rst 공식 원문 ↗

Zephyr 3.7.0 · Build · Devicetree

Devicetree 문제 해결

최종 zephyr.dts에서 driver와 macro expansion까지 순서대로 좁혀 가는 진단 절차입니다.

Official pathbuild/dts/troubleshooting.html
Source filebuild/dts/troubleshooting.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

Part 1

요약·해설

가장 먼저 pristine build를 수행하고 node 상태, compatible driver, Kconfig, 이름 변환, binding을 차례로 확인합니다.

First

Pristine build

Linker

__device_dts_ord_N

Driver

compatible · Kconfig

Macro

Preprocessor output

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 DEFA26AB52720114F04FA9E5DE408D4BC78A1EF66D6A397E933BE3390AB6941E
.. _dt-trouble:

Troubleshooting devicetree
##########################

Here are some tips for fixing misbehaving devicetree related code.

See :ref:`dt-howtos` for other "HOWTO" style information.

.. _dt-trouble-try-pristine:

Try again with a pristine build directory
*****************************************

.. important:: Try this first, before doing anything else.

See :ref:`west-building-pristine` 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 :file:`devicetree.h` header must be included
explicitly.

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

undefined reference to ``__device_dts_ord_<N>``
***********************************************

This usually happens on a line like this:

.. code-block:: c

   const struct device *dev = DEVICE_DT_GET(NODE_ID);

where ``NODE_ID`` is a valid :ref:`node identifier <dt-node-identifiers>`, 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 :ref:`dt-important-props` 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 :ref:`setting_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
:file:`<build>/zephyr/include/generated/zephyr/devicetree_generated.h`. For example, if
``<N>`` is 15, and your :file:`devicetree_generated.h` file looks like this,
the node you are interested in is ``/soc/i2c@deadbeef``:

.. code-block:: none

   /*
    * 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 :file:`<build>/zephyr/zephyr.dts`, which is the final
devicetree for your application build. (See :ref:`get-devicetree-outputs` for
information and examples.)

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

.. code-block:: DTS

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

Then you should put this into your devicetree overlay and
:ref:`dt-trouble-try-pristine`:

.. code-block:: DTS

   &i2c0 {
           status = "okay";
   };

Make sure that you see ``status = "okay";`` in :file:`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
:ref:`device_model_api` pages. See :ref:`dt-important-props` and the Devicetree
specification for more information about ``compatible``.

.. _A deep dive into the Zephyr 2.5 device model:
   https://www.youtube.com/watch?v=sWaxQyIgEBY

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 :zephyr_file:`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:

.. code-block:: C

   #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:

.. code-block:: DTS

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

Then you would look inside of :zephyr_file:`drivers/i2c` for the driver file
that handles the compatible ``nordic,nrf-twim``. In this case, that is
:zephyr_file:`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 :file:`CMakeLists.txt` file in
the drivers subdirectory. Continuing the above example,
:zephyr_file:`drivers/i2c/CMakeLists.txt` has a line that looks like this:

.. code-block:: cmake

   zephyr_library_sources_ifdef(CONFIG_NRFX_TWIM       i2c_nrfx_twim.c)

This means that :kconfig:option:`CONFIG_NRFX_TWIM` must be set to ``y`` in
:file:`<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 :file:`prj.conf` file and then
making sure to :ref:`dt-trouble-try-pristine`:

.. code-block:: cfg

   CONFIG_FOO=y

where ``CONFIG_FOO`` is the option that :file:`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.

.. _dt-use-the-right-names:

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:

.. code-block:: c

   /*
    * 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:

.. code-block:: none

   /*
    * 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
:kconfig:option:`CONFIG_COMPILER_SAVE_TEMPS` option. For example, to build
:ref:`hello_world` with west with this option set, use:

.. code-block:: sh

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

This will create a preprocessor output file named :file:`foo.c.i` in the build
directory for each source file :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 :file:`main.c.i`:

.. code-block:: sh

   $ 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:

.. code-block:: sh

   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
:kconfig:option:`CONFIG_COMPILER_TRACK_MACRO_EXPANSION` option. This typically
reduces the output to one message per error.

For example, to build :ref:`hello_world` with west and this option disabled,
use:

.. code-block:: sh

   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:

.. code-block:: c

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

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

.. code-block:: c

   #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. :ref:`get-devicetree-outputs` and debug from
there.

Some hints for what to check next if you don't see the "whoops" error message:

- did you :ref:`dt-use-the-right-names`?
- does the :ref:`property exist <dt-checking-property-exists>`?
- does the node have a :ref:`matching binding <dt-bindings>`?
- does the binding define the property?

.. _missing-dt-binding:

Check for missing bindings
**************************

See :ref:`dt-bindings` for information about bindings, and
:ref:`devicetree_binding_index` for information on bindings built into Zephyr.

If the build fails to :ref:`dts-find-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"`` --
:ref:`dt-use-the-right-names`.

If your binding file is not under :file:`zephyr/dts`, you may need to set
:ref:`DTS_ROOT <dts_root>`; see :ref:`dt-where-bindings-are-located`.

Errors with DT_INST_() APIs
***************************

If you're using an API like :c:func:`DT_INST_PROP`, you must define
``DT_DRV_COMPAT`` to the lowercase-and-underscores version of the compatible
you are interested in. See :ref:`dt-create-devices-inst`.

Part 3

한국어 전문 번역

Devicetree 문제 해결의 출발점

Devicetree 관련 code가 예상대로 동작하지 않을 때 확인할 항목을 순서대로 정리합니다.

Devicetree 출력은 CMake configuration 단계에서 만들어지고 입력이 바뀌어도 항상 재생성되는 것은 아니므로 clean rebuild가 특히 중요합니다.

<devicetree.h> include 확인

Kconfig symbol과 달리 devicetree.h header는 명시적으로 include해야 합니다. 다른 Zephyr API header가 간접 include할 때도 있지만 보장되지 않습니다.

undefined reference to __device_dts_ord_<N>

다음처럼 유효한 node identifier를 DEVICE_DT_GET에 넘겼지만 그 devicetree node에 struct device를 할당한 driver가 없을 때 주로 발생합니다. 정의되지 않은 device pointer를 요구했으므로 linker가 실패한 것입니다.

const struct device *dev = DEVICE_DT_GET(NODE_ID);
  1. Node가 enable되어야 합니다. status = "okay";이거나 status property가 없어야 합니다.
  2. 그 node의 struct device를 만드는 device driver가 enable되어야 합니다. Driver source를 application에 compile하는 Kconfig option이 y여야 합니다.

Node가 enable되었는지 확인

오류의 <N><build>/zephyr/include/generated/zephyr/devicetree_generated.h 첫 부분의 node dependency ordering에서 찾습니다. 예를 들어 15라면 아래에서는 /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
[...]

그 node를 최종 tree인 <build>/zephyr/zephyr.dts에서 찾습니다. Disabled라면 overlay에서 enable해야 합니다.

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

Overlay에는 다음을 넣고 pristine build를 다시 수행합니다.

&i2c0 {
        status = "okay";
};

재build 후 zephyr.dts에서 실제로 status = "okay";가 되었는지 확인합니다.

Device driver가 enable되었는지 확인

먼저 node의 compatible을 처리하고 struct device instance를 할당하는 driver를 찾아야 합니다. Device model의 compatible 기반 할당 방식을 모르면 Zephyr device model 문서와 ZDS 2021 발표를 참고할 수 있습니다.

현재는 모든 driver와 compatible의 완전한 대응 문서가 없으므로 source를 확인해야 합니다. drivers에서 장치 API에 맞는 하위 directory를 찾고 관련 file을 조사합니다.

흔히 compatible을 소문자로 바꾸고 특수문자를 underscore로 바꾼 값이 DT_DRV_COMPAT에 지정됩니다. vnd,foo-device라면 다음 줄을 찾습니다.

#define DT_DRV_COMPAT vnd_foo_device

Driver를 찾지 못했고 compatible이 확실히 맞다면 driver를 새로 작성해야 합니다. 다음 Nordic 예제를 계속 보겠습니다.

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

drivers/i2c에서 nordic,nrf-twim을 처리하는 drivers/i2c/i2c_nrfx_twim.c를 찾습니다. DT_DRV_COMPAT이 없어도 file 이름이 단서가 될 수 있습니다.

다음으로 driver directory의 CMakeLists.txt에서 compile 조건을 찾습니다.

zephyr_library_sources_ifdef(CONFIG_NRFX_TWIM       i2c_nrfx_twim.c)

이는 <build>/zephyr/.config에서 CONFIG_NRFX_TWIM=y여야 함을 뜻합니다. Node를 enable하면 자동으로 켜질 때도 있고, application의 prj.conf에 직접 다음처럼 추가해야 할 수도 있습니다.

CONFIG_FOO=y

CONFIG_FOO는 CMake가 driver compile 여부에 사용하는 실제 option으로 바꿉니다. 이 option 자체의 dependency가 충족되지 않았을 수도 있으므로 정의한 Kconfig file도 확인해야 합니다.

올바른 이름 사용

  • C/C++에서는 devicetree 이름을 소문자로 쓰고 특수문자를 underscore로 바꿉니다. 생성 header가 DTS 이름을 preprocessor token으로 이 방식으로 변환합니다.
  • Overlay에서는 일반 DTS와 동일한 node·property 이름을 그대로 씁니다. Overlay는 DTS fragment입니다.

C/C++에서 /soc/i2c@12340000 node의 clock-frequency를 읽는 예입니다.

/*
 * 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)
/*                                           ^               ^           */

반대로 overlay에서 property를 설정할 때는 DTS의 @와 hyphen을 유지합니다.

/*
 * 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>;
};

Preprocessor 출력 확인

CONFIG_COMPILER_SAVE_TEMPS를 enable하면 source foo.c마다 build directory에 foo.c.i preprocessor 출력이 생깁니다.

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

macOS와 Linux에서는 다음처럼 main.c.i를 찾을 수 있습니다.

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

열기 전에 formatter로 정리하면 읽기 쉽습니다.

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

정리한 file을 열면 devicetree macro가 preprocessing 뒤 최종 C code로 어떻게 펼쳐졌는지 확인할 수 있습니다.

Macro expansion 추적 끄기

복잡한 macro의 중간 expansion 단계마다 compiler message가 출력되면 오류가 지나치게 길어집니다. CONFIG_COMPILER_TRACK_MACRO_EXPANSION을 disable하면 보통 오류 하나당 message 하나로 줄어듭니다.

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

Property 검증

Node property를 읽는 줄에서 compile error가 나면 node identifier와 property를 각각 확인합니다.

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

먼저 다음 검사를 임시로 넣고 다시 compile합니다.

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

whoops error가 보이면 node identifier가 유효한 node를 가리키지 않습니다. 최종 devicetree 출력을 확인해 거기서부터 추적합니다. Error가 없다면 다음을 검사합니다.

  • C/C++용 이름 변환이 맞는가
  • Property가 실제로 존재하는가
  • Node에 matching binding이 있는가
  • Binding이 그 property를 정의하는가

누락된 binding 확인

Node의 binding을 찾지 못하면 compatible이 없거나 값과 일치하는 binding이 없는 것입니다. Property가 있다면 "vnd,some-device" 형식과 철자를 확인합니다. Binding file이 zephyr/dts 밖에 있으면 DTS_ROOT 설정이 필요할 수 있습니다.

DT_INST_() API 오류

DT_INST_PROP 같은 API를 사용하려면 관심 있는 compatible을 소문자와 underscore 형식으로 바꾼 값을 DT_DRV_COMPAT으로 정의해야 합니다.

Source

출처

원문 파일의 단락, directive, 표, 코드, symbol, 경로는 영어 원문 영역에 그대로 보존했습니다.