1# SPDX-License-Identifier: Apache-2.0
2
3include_guard(GLOBAL)
4
5include(extensions)
6include(python)
7include(boards)
8include(pre_dt)
9find_package(HostTools)
10find_package(Dtc 1.4.6)
11
12# This module makes information from the devicetree available to
13# various build stages, as well as to other arbitrary Python scripts:
14#
15#   - To Zephyr and application source code files, as a C macro API
16#     defined in <zephyr/devicetree.h>
17#
18#   - To other arbitrary Python scripts (like twister) using a
19#     serialized edtlib.EDT object in Python's pickle format
20#     (https://docs.python.org/3/library/pickle.html)
21#
22#   - To users as a final devicetree source (DTS) file which can
23#     be used for debugging
24#
25#   - To CMake files, after this module has finished running, using
26#     devicetree extensions defined in cmake/modules/extensions.cmake
27#
28#   - To Kconfig files, both using some Kconfig symbols we generate
29#     here as well as the extension functions defined in
30#     scripts/kconfig/kconfigfunctions.py
31#
32# See the specific API documentation for each of these cases for more
33# information on what is currently available to you.
34#
35# We rely on the C preprocessor, the devicetree python package, and
36# files in scripts/dts to make all this work. We also optionally will
37# run the dtc tool if it is found, in order to catch any additional
38# warnings or errors it generates.
39#
40# Outcome:
41#
42# 1. The following has happened:
43#
44#    - The pre_dt module has been included; refer to its outcome
45#      section for more information on the consequences
46#    - DTS_SOURCE: set to the path to the devicetree file which
47#      was used, if one was provided or found
48#    - ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h exists
49#
50# 2. The following has happened if a devicetree was found and
51#    no errors occurred:
52#
53#    - CACHED_DTS_ROOT_BINDINGS is set in the cache to the
54#      value of DTS_ROOT_BINDINGS
55#    - DTS_ROOT_BINDINGS is set to a ;-list of locations where DT
56#      bindings were found
57#    - ${PROJECT_BINARY_DIR}/zephyr.dts exists
58#    - ${PROJECT_BINARY_DIR}/edt.pickle exists
59#    - ${KCONFIG_BINARY_DIR}/Kconfig.dts exists
60#    - DTS_INCLUDE_FILES is set to a ;-list of all devicetree files
61#      used in this build, including transitive includes (the build
62#      system will be regenerated if any of those files change)
63#    - the devicetree extensions in the extensions.cmake module
64#      will be ready for use in other CMake list files that run
65#      after this module
66#
67# Required variables:
68# - BINARY_DIR_INCLUDE_GENERATED: where to put generated include files
69# - DTS_ROOT: a deduplicated list of places where devicetree
70#   implementation files (like bindings, vendor prefixes, etc.) are
71#   found
72# - DTS_ROOT_SYSTEM_INCLUDE_DIRS: set to "PATH1 PATH2 ...",
73#   with one path per potential location where C preprocessor #includes
74#   may be found for devicetree files
75# - KCONFIG_BINARY_DIR: where to put generated Kconfig files
76#
77# Optional variables:
78# - BOARD: board name to use when looking for DTS_SOURCE
79# - BOARD_DIRECTORIES: list of board directories to use when looking for DTS_SOURCE
80# - BOARD_REVISION_STRING: used when looking for a board revision's
81#   devicetree overlay file in one of the BOARD_DIRECTORIES
82# - CMAKE_DTS_PREPROCESSOR: the path to the preprocessor to use
83#   for devicetree files
84# - DTC_OVERLAY_FILE: list of devicetree overlay files which will be
85#   used to modify or extend the base devicetree.
86# - EXTRA_DTC_OVERLAY_FILE: list of extra devicetree overlay files.
87#   This variable is similar to DTC_OVERLAY_FILE but the files in
88#   EXTRA_DTC_OVERLAY_FILE will be applied after DTC_OVERLAY_FILE and
89#   thus files specified by EXTRA_DTC_OVERLAY_FILE have higher precedence.
90# - EXTRA_DTC_FLAGS: list of extra command line options to pass to
91#   dtc when using it to check for additional errors and warnings;
92#   invalid flags are automatically filtered out of the list
93# - DTS_EXTRA_CPPFLAGS: extra command line options to pass to the
94#   C preprocessor when generating the devicetree from DTS_SOURCE
95# - DTS_SOURCE: the devicetree source file to use may be pre-set
96#   with this variable; otherwise, it defaults to
97#   ${BOARD_DIRECTORIES}/<normalized_board_target>.dts
98#
99# Variables set by this module and not mentioned above are for internal
100# use only, and may be removed, renamed, or re-purposed without prior notice.
101
102# The directory containing devicetree related scripts.
103set(DT_SCRIPTS                  ${ZEPHYR_BASE}/scripts/dts)
104
105# This parses and collects the DT information
106set(GEN_EDT_SCRIPT              ${DT_SCRIPTS}/gen_edt.py)
107# This generates DT information needed by the C macro APIs,
108# along with a few other things.
109set(GEN_DEFINES_SCRIPT          ${DT_SCRIPTS}/gen_defines.py)
110# The edtlib.EDT object in pickle format.
111set(EDT_PICKLE                  ${PROJECT_BINARY_DIR}/edt.pickle)
112# The generated file containing the final DTS, for debugging.
113set(ZEPHYR_DTS                  ${PROJECT_BINARY_DIR}/zephyr.dts)
114# The generated C header needed by <zephyr/devicetree.h>
115set(DEVICETREE_GENERATED_H      ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h)
116# Generated build system internals.
117set(DTS_POST_CPP                ${PROJECT_BINARY_DIR}/zephyr.dts.pre)
118set(DTS_DEPS                    ${PROJECT_BINARY_DIR}/zephyr.dts.d)
119
120# This generates DT information needed by the Kconfig APIs.
121set(GEN_DRIVER_KCONFIG_SCRIPT   ${DT_SCRIPTS}/gen_driver_kconfig_dts.py)
122# Generated Kconfig symbols go here.
123set(DTS_KCONFIG                 ${KCONFIG_BINARY_DIR}/Kconfig.dts)
124
125# This generates DT information needed by the CMake APIs.
126set(GEN_DTS_CMAKE_SCRIPT        ${DT_SCRIPTS}/gen_dts_cmake.py)
127# The generated information itself, which we include() after
128# creating it.
129set(DTS_CMAKE                   ${PROJECT_BINARY_DIR}/dts.cmake)
130
131# The location of a file containing known vendor prefixes, relative to
132# each element of DTS_ROOT. Users can define their own in their own
133# modules.
134set(VENDOR_PREFIXES             dts/bindings/vendor-prefixes.txt)
135
136if(NOT DEFINED DTS_SOURCE)
137  zephyr_build_string(board_string SHORT shortened_board_string
138                      BOARD ${BOARD} BOARD_QUALIFIERS ${BOARD_QUALIFIERS}
139  )
140  foreach(dir ${BOARD_DIRECTORIES})
141    if(EXISTS ${dir}/${shortened_board_string}.dts AND NOT BOARD_${BOARD}_SINGLE_SOC)
142      message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name "
143              "(${shortened_board_string}.dts) not allowed, use '<board>_<soc>.dts' naming"
144      )
145    elseif(EXISTS ${dir}/${board_string}.dts AND EXISTS ${dir}/${shortened_board_string}.dts)
146      message(FATAL_ERROR "Conflicting file names discovered. Cannot use both "
147              "${board_string}.dts and ${shortened_board_string}.dts. "
148              "Please choose one naming style, ${board_string}.dts is recommended."
149      )
150    elseif(EXISTS ${dir}/${board_string}.dts)
151      set(DTS_SOURCE ${dir}/${board_string}.dts)
152    elseif(EXISTS ${dir}/${shortened_board_string}.dts)
153      set(DTS_SOURCE ${dir}/${shortened_board_string}.dts)
154    endif()
155  endforeach()
156endif()
157
158if(EXISTS ${DTS_SOURCE})
159  # We found a devicetree. Append all relevant dts overlays we can find...
160  zephyr_file(CONF_FILES ${BOARD_DIRECTORIES} DTS DTS_SOURCE)
161
162  zephyr_file(
163    CONF_FILES ${BOARD_DIRECTORIES}
164    DTS no_rev_suffix_dts_board_overlays
165    BOARD ${BOARD}
166    BOARD_QUALIFIERS ${BOARD_QUALIFIERS}
167  )
168
169  # ...but remove the ones that do not include the revision suffix
170  list(REMOVE_ITEM DTS_SOURCE ${no_rev_suffix_dts_board_overlays})
171else()
172  # If we don't have a devicetree, provide an empty stub
173  set(DTS_SOURCE ${ZEPHYR_BASE}/boards/common/stub.dts)
174endif()
175
176#
177# Find all the DTS files we need to concatenate and preprocess, as
178# well as all the devicetree bindings and vendor prefixes associated
179# with them.
180#
181
182zephyr_file(CONF_FILES ${BOARD_EXTENSION_DIRS} DTS board_extension_dts_files)
183
184set(dts_files
185  ${DTS_SOURCE}
186  ${board_extension_dts_files}
187  ${shield_dts_files}
188  )
189
190if(DTC_OVERLAY_FILE)
191  zephyr_list(TRANSFORM DTC_OVERLAY_FILE NORMALIZE_PATHS
192              OUTPUT_VARIABLE DTC_OVERLAY_FILE_AS_LIST)
193  build_info(devicetree user-files PATH ${DTC_OVERLAY_FILE_AS_LIST})
194  list(APPEND
195    dts_files
196    ${DTC_OVERLAY_FILE_AS_LIST}
197    )
198endif()
199
200if(EXTRA_DTC_OVERLAY_FILE)
201  zephyr_list(TRANSFORM EXTRA_DTC_OVERLAY_FILE NORMALIZE_PATHS
202              OUTPUT_VARIABLE EXTRA_DTC_OVERLAY_FILE_AS_LIST)
203  build_info(devicetree extra-user-files PATH ${EXTRA_DTC_OVERLAY_FILE_AS_LIST})
204  list(APPEND
205    dts_files
206    ${EXTRA_DTC_OVERLAY_FILE_AS_LIST}
207    )
208endif()
209
210set(i 0)
211foreach(dts_file ${dts_files})
212  if(i EQUAL 0)
213    message(STATUS "Found BOARD.dts: ${dts_file}")
214  else()
215    message(STATUS "Found devicetree overlay: ${dts_file}")
216  endif()
217
218  math(EXPR i "${i}+1")
219endforeach()
220
221unset(DTS_ROOT_BINDINGS)
222foreach(dts_root ${DTS_ROOT})
223  set(bindings_path ${dts_root}/dts/bindings)
224  if(EXISTS ${bindings_path})
225    list(APPEND
226      DTS_ROOT_BINDINGS
227      ${bindings_path}
228      )
229  endif()
230
231  set(vendor_prefixes ${dts_root}/${VENDOR_PREFIXES})
232  if(EXISTS ${vendor_prefixes})
233    list(APPEND EXTRA_GEN_EDT_ARGS --vendor-prefixes ${vendor_prefixes})
234  endif()
235endforeach()
236
237# Cache the location of the root bindings so they can be used by
238# scripts which use the build directory.
239set(CACHED_DTS_ROOT_BINDINGS ${DTS_ROOT_BINDINGS} CACHE INTERNAL
240  "DT bindings root directories")
241
242#
243# Run the C preprocessor on the devicetree source, so we can parse it
244# (using the Python devicetree package) in later steps.
245#
246
247# TODO: Cut down on CMake configuration time by avoiding
248# regeneration of devicetree_generated.h on every configure. How
249# challenging is this? Can we cache the dts dependencies?
250
251# Run the preprocessor on the DTS input files.
252if(DEFINED CMAKE_DTS_PREPROCESSOR)
253  set(dts_preprocessor ${CMAKE_DTS_PREPROCESSOR})
254else()
255  set(dts_preprocessor ${CMAKE_C_COMPILER})
256endif()
257zephyr_dt_preprocess(
258  CPP ${dts_preprocessor}
259  SOURCE_FILES ${dts_files}
260  OUT_FILE ${DTS_POST_CPP}
261  DEPS_FILE ${DTS_DEPS}
262  EXTRA_CPPFLAGS ${DTS_EXTRA_CPPFLAGS}
263  INCLUDE_DIRECTORIES ${DTS_ROOT_SYSTEM_INCLUDE_DIRS}
264  WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
265  )
266
267#
268# Make sure we re-run CMake if any devicetree sources or transitive
269# includes change.
270#
271
272# Parse the generated dependency file to find the DT sources that
273# were included, including any transitive includes.
274toolchain_parse_make_rule(${DTS_DEPS}
275  DTS_INCLUDE_FILES # Output parameter
276  )
277
278# Add the results to the list of files that, when change, force the
279# build system to re-run CMake.
280set_property(DIRECTORY APPEND PROPERTY
281  CMAKE_CONFIGURE_DEPENDS
282  ${DTS_INCLUDE_FILES}
283  ${GEN_EDT_SCRIPT}
284  ${GEN_DEFINES_SCRIPT}
285  ${GEN_DRIVER_KCONFIG_SCRIPT}
286  ${GEN_DTS_CMAKE_SCRIPT}
287  )
288
289#
290# Run GEN_EDT_SCRIPT.
291#
292
293string(REPLACE ";" " " EXTRA_DTC_FLAGS_RAW "${EXTRA_DTC_FLAGS}")
294set(CMD_GEN_EDT ${PYTHON_EXECUTABLE} ${GEN_EDT_SCRIPT}
295--dts ${DTS_POST_CPP}
296--dtc-flags '${EXTRA_DTC_FLAGS_RAW}'
297--bindings-dirs ${DTS_ROOT_BINDINGS}
298--dts-out ${ZEPHYR_DTS}.new # for debugging and dtc
299--edt-pickle-out ${EDT_PICKLE}.new
300${EXTRA_GEN_EDT_ARGS}
301)
302
303execute_process(
304  COMMAND ${CMD_GEN_EDT}
305  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
306  COMMAND_ERROR_IS_FATAL ANY
307  )
308zephyr_file_copy(${ZEPHYR_DTS}.new ${ZEPHYR_DTS} ONLY_IF_DIFFERENT)
309zephyr_file_copy(${EDT_PICKLE}.new ${EDT_PICKLE} ONLY_IF_DIFFERENT)
310file(REMOVE ${ZEPHYR_DTS}.new ${EDT_PICKLE}.new)
311message(STATUS "Generated zephyr.dts: ${ZEPHYR_DTS}")
312message(STATUS "Generated pickled edt: ${EDT_PICKLE}")
313
314#
315# Run GEN_DEFINES_SCRIPT.
316#
317
318set(CMD_GEN_DEFINES ${PYTHON_EXECUTABLE} ${GEN_DEFINES_SCRIPT}
319--header-out ${DEVICETREE_GENERATED_H}.new
320--edt-pickle ${EDT_PICKLE}
321${EXTRA_GEN_DEFINES_ARGS}
322)
323
324execute_process(
325  COMMAND ${CMD_GEN_DEFINES}
326  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
327  COMMAND_ERROR_IS_FATAL ANY
328  )
329zephyr_file_copy(${DEVICETREE_GENERATED_H}.new ${DEVICETREE_GENERATED_H} ONLY_IF_DIFFERENT)
330file(REMOVE ${ZEPHYR_DTS}.new ${DEVICETREE_GENERATED_H}.new)
331message(STATUS "Generated zephyr.dts: ${ZEPHYR_DTS}")
332message(STATUS "Generated devicetree_generated.h: ${DEVICETREE_GENERATED_H}")
333
334#
335# Run GEN_DRIVER_KCONFIG_SCRIPT.
336#
337
338execute_process(
339  COMMAND ${PYTHON_EXECUTABLE} ${GEN_DRIVER_KCONFIG_SCRIPT}
340  --kconfig-out ${DTS_KCONFIG}
341  --bindings-dirs ${DTS_ROOT_BINDINGS}
342  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
343  RESULT_VARIABLE ret
344  )
345if(NOT "${ret}" STREQUAL "0")
346  message(FATAL_ERROR "gen_driver_kconfig_dts.py failed with return code: ${ret}")
347endif()
348
349#
350# Run GEN_DTS_CMAKE_SCRIPT.
351#
352# A temporary file is copied to the original file if it differs. This prevents issue such as a
353# cycle when sysbuild is used of configuring and building multiple times due to the dts.cmake file
354# of images having a newer modification time than the sysbuild build.ninja file, despite the
355# output having not changed
356#
357set(dts_cmake_tmp ${DTS_CMAKE}.new)
358
359execute_process(
360  COMMAND ${PYTHON_EXECUTABLE} ${GEN_DTS_CMAKE_SCRIPT}
361  --edt-pickle ${EDT_PICKLE}
362  --cmake-out ${dts_cmake_tmp}
363  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
364  RESULT_VARIABLE ret
365  )
366if(NOT "${ret}" STREQUAL "0")
367  message(FATAL_ERROR "gen_dts_cmake.py failed with return code: ${ret}")
368else()
369  zephyr_file_copy(${dts_cmake_tmp} ${DTS_CMAKE} ONLY_IF_DIFFERENT)
370  file(REMOVE ${dts_cmake_tmp})
371  set(dts_cmake_tmp)
372  message(STATUS "Including generated dts.cmake file: ${DTS_CMAKE}")
373  include(${DTS_CMAKE})
374endif()
375
376#
377# Run dtc if it was found.
378#
379# This is just to generate warnings and errors; we discard the output.
380#
381
382if(DTC)
383
384set(DTC_WARN_UNIT_ADDR_IF_ENABLED "")
385check_dtc_flag("-Wunique_unit_address_if_enabled" check)
386if (check)
387  set(DTC_WARN_UNIT_ADDR_IF_ENABLED "-Wunique_unit_address_if_enabled")
388endif()
389
390set(DTC_NO_WARN_UNIT_ADDR "")
391check_dtc_flag("-Wno-unique_unit_address" check)
392if (check)
393  set(DTC_NO_WARN_UNIT_ADDR "-Wno-unique_unit_address")
394endif()
395
396set(VALID_EXTRA_DTC_FLAGS "")
397foreach(extra_opt ${EXTRA_DTC_FLAGS})
398  check_dtc_flag(${extra_opt} check)
399  if (check)
400    list(APPEND VALID_EXTRA_DTC_FLAGS ${extra_opt})
401  endif()
402endforeach()
403set(EXTRA_DTC_FLAGS ${VALID_EXTRA_DTC_FLAGS})
404
405execute_process(
406  COMMAND ${DTC}
407  -O dts
408  -o - # Write output to stdout, which we discard below
409  -b 0
410  -E unit_address_vs_reg
411  ${DTC_NO_WARN_UNIT_ADDR}
412  ${DTC_WARN_UNIT_ADDR_IF_ENABLED}
413  ${EXTRA_DTC_FLAGS} # User settable
414  ${ZEPHYR_DTS}
415  OUTPUT_QUIET # Discard stdout
416  WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
417  RESULT_VARIABLE ret
418  ERROR_VARIABLE stderr
419  )
420
421if(NOT "${ret}" STREQUAL "0")
422  message(FATAL_ERROR "dtc failed with return code: ${ret}")
423elseif(stderr)
424  # dtc printed warnings on stderr but did not fail.
425  # Display them as CMake warnings to draw attention.
426  message(WARNING "dtc raised one or more warnings:\n${stderr}")
427endif()
428endif(DTC)
429
430build_info(devicetree files PATH ${dts_files})
431build_info(devicetree include-dirs PATH ${DTS_ROOT_SYSTEM_INCLUDE_DIRS})
432build_info(devicetree bindings-dirs PATH ${DTS_ROOT_BINDINGS})
433