| # SPDX-License-Identifier: Apache-2.0 |
| |
| include_guard(GLOBAL) |
| |
| include(extensions) |
| include(python) |
| include(boards) |
| include(pre_dt) |
| find_package(HostTools) |
| find_package(Dtc 1.4.6) |
| |
| # This module makes information from the devicetree available to |
| # various build stages, as well as to other arbitrary Python scripts: |
| # |
| # - To Zephyr and application source code files, as a C macro API |
| # defined in <zephyr/devicetree.h> |
| # |
| # - To other arbitrary Python scripts (like twister) using a |
| # serialized edtlib.EDT object in Python's pickle format |
| # (https://docs.python.org/3/library/pickle.html) |
| # |
| # - To users as a final devicetree source (DTS) file which can |
| # be used for debugging |
| # |
| # - To CMake files, after this module has finished running, using |
| # devicetree extensions defined in cmake/modules/extensions.cmake |
| # |
| # - To Kconfig files, both using some Kconfig symbols we generate |
| # here as well as the extension functions defined in |
| # scripts/kconfig/kconfigfunctions.py |
| # |
| # See the specific API documentation for each of these cases for more |
| # information on what is currently available to you. |
| # |
| # We rely on the C preprocessor, the devicetree python package, and |
| # files in scripts/dts to make all this work. We also optionally will |
| # run the dtc tool if it is found, in order to catch any additional |
| # warnings or errors it generates. |
| # |
| # Outcome: |
| # |
| # 1. The following has happened: |
| # |
| # - The pre_dt module has been included; refer to its outcome |
| # section for more information on the consequences |
| # - DTS_SOURCE: set to the path to the devicetree file which |
| # was used, if one was provided or found |
| # - ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h exists |
| # |
| # 2. The following has happened if a devicetree was found and |
| # no errors occurred: |
| # |
| # - CACHED_DTS_ROOT_BINDINGS is set in the cache to the |
| # value of DTS_ROOT_BINDINGS |
| # - DTS_ROOT_BINDINGS is set to a ;-list of locations where DT |
| # bindings were found |
| # - ${PROJECT_BINARY_DIR}/zephyr.dts exists |
| # - ${PROJECT_BINARY_DIR}/edt.pickle exists |
| # - ${KCONFIG_BINARY_DIR}/Kconfig.dts exists |
| # - DTS_INCLUDE_FILES is set to a ;-list of all devicetree files |
| # used in this build, including transitive includes (the build |
| # system will be regenerated if any of those files change) |
| # - the devicetree extensions in the extensions.cmake module |
| # will be ready for use in other CMake list files that run |
| # after this module |
| # |
| # Required variables: |
| # - BINARY_DIR_INCLUDE_GENERATED: where to put generated include files |
| # - DTS_ROOT: a deduplicated list of places where devicetree |
| # implementation files (like bindings, vendor prefixes, etc.) are |
| # found |
| # - DTS_ROOT_SYSTEM_INCLUDE_DIRS: set to "PATH1 PATH2 ...", |
| # with one path per potential location where C preprocessor #includes |
| # may be found for devicetree files |
| # - KCONFIG_BINARY_DIR: where to put generated Kconfig files |
| # |
| # Optional variables: |
| # - BOARD: board name to use when looking for DTS_SOURCE |
| # - BOARD_DIRECTORIES: list of board directories to use when looking for DTS_SOURCE |
| # - BOARD_REVISION_STRING: used when looking for a board revision's |
| # devicetree overlay file in one of the BOARD_DIRECTORIES |
| # - CMAKE_DTS_PREPROCESSOR: the path to the preprocessor to use |
| # for devicetree files |
| # - DTC_OVERLAY_FILE: list of devicetree overlay files which will be |
| # used to modify or extend the base devicetree. |
| # - EXTRA_DTC_OVERLAY_FILE: list of extra devicetree overlay files. |
| # This variable is similar to DTC_OVERLAY_FILE but the files in |
| # EXTRA_DTC_OVERLAY_FILE will be applied after DTC_OVERLAY_FILE and |
| # thus files specified by EXTRA_DTC_OVERLAY_FILE have higher precedence. |
| # - EXTRA_DTC_FLAGS: list of extra command line options to pass to |
| # dtc when using it to check for additional errors and warnings; |
| # invalid flags are automatically filtered out of the list |
| # - DTS_EXTRA_CPPFLAGS: extra command line options to pass to the |
| # C preprocessor when generating the devicetree from DTS_SOURCE |
| # - DTS_SOURCE: the devicetree source file to use may be pre-set |
| # with this variable; otherwise, it defaults to |
| # ${BOARD_DIRECTORIES}/<normalized_board_target>.dts |
| # |
| # Variables set by this module and not mentioned above are for internal |
| # use only, and may be removed, renamed, or re-purposed without prior notice. |
| |
| # The directory containing devicetree related scripts. |
| set(DT_SCRIPTS ${ZEPHYR_BASE}/scripts/dts) |
| |
| # This parses and collects the DT information |
| set(GEN_EDT_SCRIPT ${DT_SCRIPTS}/gen_edt.py) |
| # This generates DT information needed by the C macro APIs, |
| # along with a few other things. |
| set(GEN_DEFINES_SCRIPT ${DT_SCRIPTS}/gen_defines.py) |
| # The edtlib.EDT object in pickle format. |
| set(EDT_PICKLE ${PROJECT_BINARY_DIR}/edt.pickle) |
| # The generated file containing the final DTS, for debugging. |
| set(ZEPHYR_DTS ${PROJECT_BINARY_DIR}/zephyr.dts) |
| # The generated C header needed by <zephyr/devicetree.h> |
| set(DEVICETREE_GENERATED_H ${BINARY_DIR_INCLUDE_GENERATED}/devicetree_generated.h) |
| # Generated build system internals. |
| set(DTS_POST_CPP ${PROJECT_BINARY_DIR}/zephyr.dts.pre) |
| set(DTS_DEPS ${PROJECT_BINARY_DIR}/zephyr.dts.d) |
| |
| # This generates DT information needed by the Kconfig APIs. |
| set(GEN_DRIVER_KCONFIG_SCRIPT ${DT_SCRIPTS}/gen_driver_kconfig_dts.py) |
| # Generated Kconfig symbols go here. |
| set(DTS_KCONFIG ${KCONFIG_BINARY_DIR}/Kconfig.dts) |
| |
| # This generates DT information needed by the CMake APIs. |
| set(GEN_DTS_CMAKE_SCRIPT ${DT_SCRIPTS}/gen_dts_cmake.py) |
| # The generated information itself, which we include() after |
| # creating it. |
| set(DTS_CMAKE ${PROJECT_BINARY_DIR}/dts.cmake) |
| |
| # The location of a file containing known vendor prefixes, relative to |
| # each element of DTS_ROOT. Users can define their own in their own |
| # modules. |
| set(VENDOR_PREFIXES dts/bindings/vendor-prefixes.txt) |
| |
| if(NOT DEFINED DTS_SOURCE) |
| zephyr_build_string(board_string SHORT shortened_board_string |
| BOARD ${BOARD} BOARD_QUALIFIERS ${BOARD_QUALIFIERS} |
| ) |
| foreach(dir ${BOARD_DIRECTORIES}) |
| if(EXISTS ${dir}/${shortened_board_string}.dts AND NOT BOARD_${BOARD}_SINGLE_SOC) |
| message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " |
| "(${shortened_board_string}.dts) not allowed, use '<board>_<soc>.dts' naming" |
| ) |
| elseif(EXISTS ${dir}/${board_string}.dts AND EXISTS ${dir}/${shortened_board_string}.dts) |
| message(FATAL_ERROR "Conflicting file names discovered. Cannot use both " |
| "${board_string}.dts and ${shortened_board_string}.dts. " |
| "Please choose one naming style, ${board_string}.dts is recommended." |
| ) |
| elseif(EXISTS ${dir}/${board_string}.dts) |
| set(DTS_SOURCE ${dir}/${board_string}.dts) |
| elseif(EXISTS ${dir}/${shortened_board_string}.dts) |
| set(DTS_SOURCE ${dir}/${shortened_board_string}.dts) |
| endif() |
| endforeach() |
| endif() |
| |
| if(EXISTS ${DTS_SOURCE}) |
| # We found a devicetree. Append all relevant dts overlays we can find... |
| zephyr_file(CONF_FILES ${BOARD_DIRECTORIES} DTS DTS_SOURCE) |
| |
| zephyr_file( |
| CONF_FILES ${BOARD_DIRECTORIES} |
| DTS no_rev_suffix_dts_board_overlays |
| BOARD ${BOARD} |
| BOARD_QUALIFIERS ${BOARD_QUALIFIERS} |
| ) |
| |
| # ...but remove the ones that do not include the revision suffix |
| list(REMOVE_ITEM DTS_SOURCE ${no_rev_suffix_dts_board_overlays}) |
| else() |
| # If we don't have a devicetree, provide an empty stub |
| set(DTS_SOURCE ${ZEPHYR_BASE}/boards/common/stub.dts) |
| endif() |
| |
| # |
| # Find all the DTS files we need to concatenate and preprocess, as |
| # well as all the devicetree bindings and vendor prefixes associated |
| # with them. |
| # |
| |
| zephyr_file(CONF_FILES ${BOARD_EXTENSION_DIRS} DTS board_extension_dts_files) |
| |
| set(dts_files |
| ${DTS_SOURCE} |
| ${board_extension_dts_files} |
| ${shield_dts_files} |
| ) |
| |
| if(DTC_OVERLAY_FILE) |
| zephyr_list(TRANSFORM DTC_OVERLAY_FILE NORMALIZE_PATHS |
| OUTPUT_VARIABLE DTC_OVERLAY_FILE_AS_LIST) |
| build_info(devicetree user-files PATH ${DTC_OVERLAY_FILE_AS_LIST}) |
| list(APPEND |
| dts_files |
| ${DTC_OVERLAY_FILE_AS_LIST} |
| ) |
| endif() |
| |
| if(EXTRA_DTC_OVERLAY_FILE) |
| zephyr_list(TRANSFORM EXTRA_DTC_OVERLAY_FILE NORMALIZE_PATHS |
| OUTPUT_VARIABLE EXTRA_DTC_OVERLAY_FILE_AS_LIST) |
| build_info(devicetree extra-user-files PATH ${EXTRA_DTC_OVERLAY_FILE_AS_LIST}) |
| list(APPEND |
| dts_files |
| ${EXTRA_DTC_OVERLAY_FILE_AS_LIST} |
| ) |
| endif() |
| |
| set(i 0) |
| foreach(dts_file ${dts_files}) |
| if(i EQUAL 0) |
| message(STATUS "Found BOARD.dts: ${dts_file}") |
| else() |
| message(STATUS "Found devicetree overlay: ${dts_file}") |
| endif() |
| |
| math(EXPR i "${i}+1") |
| endforeach() |
| |
| unset(DTS_ROOT_BINDINGS) |
| foreach(dts_root ${DTS_ROOT}) |
| set(bindings_path ${dts_root}/dts/bindings) |
| if(EXISTS ${bindings_path}) |
| list(APPEND |
| DTS_ROOT_BINDINGS |
| ${bindings_path} |
| ) |
| endif() |
| |
| set(vendor_prefixes ${dts_root}/${VENDOR_PREFIXES}) |
| if(EXISTS ${vendor_prefixes}) |
| list(APPEND EXTRA_GEN_EDT_ARGS --vendor-prefixes ${vendor_prefixes}) |
| endif() |
| endforeach() |
| |
| # Cache the location of the root bindings so they can be used by |
| # scripts which use the build directory. |
| set(CACHED_DTS_ROOT_BINDINGS ${DTS_ROOT_BINDINGS} CACHE INTERNAL |
| "DT bindings root directories") |
| |
| # |
| # Run the C preprocessor on the devicetree source, so we can parse it |
| # (using the Python devicetree package) in later steps. |
| # |
| |
| # TODO: Cut down on CMake configuration time by avoiding |
| # regeneration of devicetree_generated.h on every configure. How |
| # challenging is this? Can we cache the dts dependencies? |
| |
| # Run the preprocessor on the DTS input files. |
| if(DEFINED CMAKE_DTS_PREPROCESSOR) |
| set(dts_preprocessor ${CMAKE_DTS_PREPROCESSOR}) |
| else() |
| set(dts_preprocessor ${CMAKE_C_COMPILER}) |
| endif() |
| zephyr_dt_preprocess( |
| CPP ${dts_preprocessor} |
| SOURCE_FILES ${dts_files} |
| OUT_FILE ${DTS_POST_CPP} |
| DEPS_FILE ${DTS_DEPS} |
| EXTRA_CPPFLAGS ${DTS_EXTRA_CPPFLAGS} |
| INCLUDE_DIRECTORIES ${DTS_ROOT_SYSTEM_INCLUDE_DIRS} |
| WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR} |
| ) |
| |
| # |
| # Make sure we re-run CMake if any devicetree sources or transitive |
| # includes change. |
| # |
| |
| # Parse the generated dependency file to find the DT sources that |
| # were included, including any transitive includes. |
| toolchain_parse_make_rule(${DTS_DEPS} |
| DTS_INCLUDE_FILES # Output parameter |
| ) |
| |
| # Add the results to the list of files that, when change, force the |
| # build system to re-run CMake. |
| set_property(DIRECTORY APPEND PROPERTY |
| CMAKE_CONFIGURE_DEPENDS |
| ${DTS_INCLUDE_FILES} |
| ${GEN_EDT_SCRIPT} |
| ${GEN_DEFINES_SCRIPT} |
| ${GEN_DRIVER_KCONFIG_SCRIPT} |
| ${GEN_DTS_CMAKE_SCRIPT} |
| ) |
| |
| # |
| # Run GEN_EDT_SCRIPT. |
| # |
| |
| string(REPLACE ";" " " EXTRA_DTC_FLAGS_RAW "${EXTRA_DTC_FLAGS}") |
| set(CMD_GEN_EDT ${PYTHON_EXECUTABLE} ${GEN_EDT_SCRIPT} |
| --dts ${DTS_POST_CPP} |
| --dtc-flags '${EXTRA_DTC_FLAGS_RAW}' |
| --bindings-dirs ${DTS_ROOT_BINDINGS} |
| --dts-out ${ZEPHYR_DTS}.new # for debugging and dtc |
| --edt-pickle-out ${EDT_PICKLE}.new |
| ${EXTRA_GEN_EDT_ARGS} |
| ) |
| |
| execute_process( |
| COMMAND ${CMD_GEN_EDT} |
| WORKING_DIRECTORY ${PROJECT_BINARY_DIR} |
| COMMAND_ERROR_IS_FATAL ANY |
| ) |
| zephyr_file_copy(${ZEPHYR_DTS}.new ${ZEPHYR_DTS} ONLY_IF_DIFFERENT) |
| zephyr_file_copy(${EDT_PICKLE}.new ${EDT_PICKLE} ONLY_IF_DIFFERENT) |
| file(REMOVE ${ZEPHYR_DTS}.new ${EDT_PICKLE}.new) |
| message(STATUS "Generated zephyr.dts: ${ZEPHYR_DTS}") |
| message(STATUS "Generated pickled edt: ${EDT_PICKLE}") |
| |
| # |
| # Run GEN_DEFINES_SCRIPT. |
| # |
| |
| set(CMD_GEN_DEFINES ${PYTHON_EXECUTABLE} ${GEN_DEFINES_SCRIPT} |
| --header-out ${DEVICETREE_GENERATED_H}.new |
| --edt-pickle ${EDT_PICKLE} |
| ${EXTRA_GEN_DEFINES_ARGS} |
| ) |
| |
| execute_process( |
| COMMAND ${CMD_GEN_DEFINES} |
| WORKING_DIRECTORY ${PROJECT_BINARY_DIR} |
| COMMAND_ERROR_IS_FATAL ANY |
| ) |
| zephyr_file_copy(${DEVICETREE_GENERATED_H}.new ${DEVICETREE_GENERATED_H} ONLY_IF_DIFFERENT) |
| file(REMOVE ${ZEPHYR_DTS}.new ${DEVICETREE_GENERATED_H}.new) |
| message(STATUS "Generated zephyr.dts: ${ZEPHYR_DTS}") |
| message(STATUS "Generated devicetree_generated.h: ${DEVICETREE_GENERATED_H}") |
| |
| # |
| # Run GEN_DRIVER_KCONFIG_SCRIPT. |
| # |
| |
| execute_process( |
| COMMAND ${PYTHON_EXECUTABLE} ${GEN_DRIVER_KCONFIG_SCRIPT} |
| --kconfig-out ${DTS_KCONFIG} |
| --bindings-dirs ${DTS_ROOT_BINDINGS} |
| WORKING_DIRECTORY ${PROJECT_BINARY_DIR} |
| RESULT_VARIABLE ret |
| ) |
| if(NOT "${ret}" STREQUAL "0") |
| message(FATAL_ERROR "gen_driver_kconfig_dts.py failed with return code: ${ret}") |
| endif() |
| |
| # |
| # Run GEN_DTS_CMAKE_SCRIPT. |
| # |
| # A temporary file is copied to the original file if it differs. This prevents issue such as a |
| # cycle when sysbuild is used of configuring and building multiple times due to the dts.cmake file |
| # of images having a newer modification time than the sysbuild build.ninja file, despite the |
| # output having not changed |
| # |
| set(dts_cmake_tmp ${DTS_CMAKE}.new) |
| |
| execute_process( |
| COMMAND ${PYTHON_EXECUTABLE} ${GEN_DTS_CMAKE_SCRIPT} |
| --edt-pickle ${EDT_PICKLE} |
| --cmake-out ${dts_cmake_tmp} |
| WORKING_DIRECTORY ${PROJECT_BINARY_DIR} |
| RESULT_VARIABLE ret |
| ) |
| if(NOT "${ret}" STREQUAL "0") |
| message(FATAL_ERROR "gen_dts_cmake.py failed with return code: ${ret}") |
| else() |
| zephyr_file_copy(${dts_cmake_tmp} ${DTS_CMAKE} ONLY_IF_DIFFERENT) |
| file(REMOVE ${dts_cmake_tmp}) |
| set(dts_cmake_tmp) |
| message(STATUS "Including generated dts.cmake file: ${DTS_CMAKE}") |
| include(${DTS_CMAKE}) |
| endif() |
| |
| # |
| # Run dtc if it was found. |
| # |
| # This is just to generate warnings and errors; we discard the output. |
| # |
| |
| if(DTC) |
| |
| set(DTC_WARN_UNIT_ADDR_IF_ENABLED "") |
| check_dtc_flag("-Wunique_unit_address_if_enabled" check) |
| if (check) |
| set(DTC_WARN_UNIT_ADDR_IF_ENABLED "-Wunique_unit_address_if_enabled") |
| endif() |
| |
| set(DTC_NO_WARN_UNIT_ADDR "") |
| check_dtc_flag("-Wno-unique_unit_address" check) |
| if (check) |
| set(DTC_NO_WARN_UNIT_ADDR "-Wno-unique_unit_address") |
| endif() |
| |
| set(VALID_EXTRA_DTC_FLAGS "") |
| foreach(extra_opt ${EXTRA_DTC_FLAGS}) |
| check_dtc_flag(${extra_opt} check) |
| if (check) |
| list(APPEND VALID_EXTRA_DTC_FLAGS ${extra_opt}) |
| endif() |
| endforeach() |
| set(EXTRA_DTC_FLAGS ${VALID_EXTRA_DTC_FLAGS}) |
| |
| execute_process( |
| COMMAND ${DTC} |
| -O dts |
| -o - # Write output to stdout, which we discard below |
| -b 0 |
| -E unit_address_vs_reg |
| ${DTC_NO_WARN_UNIT_ADDR} |
| ${DTC_WARN_UNIT_ADDR_IF_ENABLED} |
| ${EXTRA_DTC_FLAGS} # User settable |
| ${ZEPHYR_DTS} |
| OUTPUT_QUIET # Discard stdout |
| WORKING_DIRECTORY ${PROJECT_BINARY_DIR} |
| RESULT_VARIABLE ret |
| ERROR_VARIABLE stderr |
| ) |
| |
| if(NOT "${ret}" STREQUAL "0") |
| message(FATAL_ERROR "dtc failed with return code: ${ret}") |
| elseif(stderr) |
| # dtc printed warnings on stderr but did not fail. |
| # Display them as CMake warnings to draw attention. |
| message(WARNING "dtc raised one or more warnings:\n${stderr}") |
| endif() |
| endif(DTC) |
| |
| build_info(devicetree files PATH ${dts_files}) |
| build_info(devicetree include-dirs PATH ${DTS_ROOT_SYSTEM_INCLUDE_DIRS}) |
| build_info(devicetree bindings-dirs PATH ${DTS_ROOT_BINDINGS}) |