From 6794f765a0da7e48f03191c0f1795ed6865ab343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Mon, 27 Feb 2023 14:14:07 -0800 Subject: [PATCH 01/16] cmake: extensions: fix comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The name of each commented section should match the name in the "table of contents", for consistency and so people can jump from contents to implementations more easily with their editors' search functions. Signed-off-by: Martí Bolívar --- cmake/modules/extensions.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 3ba47fc2baf7..b870f8e682e4 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -3426,7 +3426,7 @@ function(target_sources_if_dt_node path target scope item) endfunction() ######################################################## -# 5. Zephyr linker function +# 5. Zephyr linker functions ######################################################## # 5.1. zephyr_linker* # From 072283db87af6bcb68a2d9b8265ea1ceef482ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Mon, 27 Feb 2023 14:27:28 -0800 Subject: [PATCH 02/16] cmake: modules: add generated_file_directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Due to Hyrum's Law, there are users out in the wild depending on this directory existing to place their own generated files, so it'd break things unnecessarily to not do this if we don't have a DTS, as explained in a source code comment. However, this doesn't really have anything to do with DTS processing, so split it into its own module to keep things clean. This also paves the way for inserting another module in between the generated_file_directories and dts modules that itself depends on these directories existing. Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 8 ------- .../modules/generated_file_directories.cmake | 24 +++++++++++++++++++ cmake/modules/zephyr_default.cmake | 1 + 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 cmake/modules/generated_file_directories.cmake diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 09886800a4a3..0510a9d0e7ed 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -39,14 +39,6 @@ find_package(Dtc 1.4.6) # run the dtc tool if it is found, in order to catch any additional # warnings or errors it generates. -# We will place some generated include files in here. -set(BINARY_DIR_INCLUDE ${PROJECT_BINARY_DIR}/include) -set(BINARY_DIR_INCLUDE_GENERATED ${BINARY_DIR_INCLUDE}/generated) -# Unconditionally create it, even if we don't have DTS support. This -# is a historical artifact, and users expect this directory to exist -# to put their own generated content inside. -file(MAKE_DIRECTORY ${BINARY_DIR_INCLUDE_GENERATED}) - # The directory containing devicetree related scripts. set(DT_SCRIPTS ${ZEPHYR_BASE}/scripts/dts) diff --git a/cmake/modules/generated_file_directories.cmake b/cmake/modules/generated_file_directories.cmake new file mode 100644 index 000000000000..aac3b39a6f9b --- /dev/null +++ b/cmake/modules/generated_file_directories.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: Apache-2.0 + +include_guard(GLOBAL) + +# This file creates locations in the build directory +# for placing generated files. +# +# Outcome: +# - BINARY_DIR_INCLUDE is set to ${PROJECT_BINARY_DIR}/include +# - BINARY_DIR_INCLUDE_GENERATED is set to ${BINARY_DIR_INCLUDE}/generated +# - BINARY_DIR_INCLUDE_GENERATED is a directory +# +# Required variables: +# None +# +# Optional variables: +# None +# +# Optional environment variables: +# None + +set(BINARY_DIR_INCLUDE ${PROJECT_BINARY_DIR}/include/generated) +set(BINARY_DIR_INCLUDE_GENERATED ${PROJECT_BINARY_DIR}/include/generated) +file(MAKE_DIRECTORY ${BINARY_DIR_INCLUDE_GENERATED}) diff --git a/cmake/modules/zephyr_default.cmake b/cmake/modules/zephyr_default.cmake index 908d03fceb97..01aedc198ce4 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -83,6 +83,7 @@ list(APPEND zephyr_cmake_modules boards) list(APPEND zephyr_cmake_modules shields) list(APPEND zephyr_cmake_modules arch) list(APPEND zephyr_cmake_modules configuration_files) +list(APPEND zephyr_cmake_modules generated_file_directories) # Include board specific device-tree flags before parsing. set(pre_dt_board "\${BOARD_DIR}/pre_dt_board.cmake" OPTIONAL) From 82990caea8867fe6abed9063571e6aec2c0738fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 28 Feb 2023 11:56:45 -0800 Subject: [PATCH 03/16] cmake: modules: add pre_dt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a separate module that sets up all our devicetree handling, by setting up common variables that would apply to any and all DT processing. This is then included in the regular dts module that we include in zephyr_default.cmake. The separation of this code from dts.cmake is a useful cleanup, and is also groundwork for enabling system devicetree in Zephyr, which will need the same definitions included into its scope. Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 58 +----------------------- cmake/modules/pre_dt.cmake | 90 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 57 deletions(-) create mode 100644 cmake/modules/pre_dt.cmake diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 0510a9d0e7ed..0a1e9618fced 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -5,6 +5,7 @@ include_guard(GLOBAL) include(extensions) include(python) include(boards) +include(pre_dt) find_package(HostTools) find_package(Dtc 1.4.6) @@ -73,9 +74,6 @@ set(DTS_CMAKE ${PROJECT_BINARY_DIR}/dts.cmake) # modules. set(VENDOR_PREFIXES dts/bindings/vendor-prefixes.txt) -# The C preprocessor to use. -set_ifndef(CMAKE_DTS_PREPROCESSOR ${CMAKE_C_COMPILER}) - # # Halt execution early if there is no devicetree. # @@ -95,43 +93,6 @@ else() return() endif() -# -# Finalize the value of DTS_ROOT, so we know where all our -# DTS files, bindings, and vendor prefixes are. -# - -# Convert relative paths to absolute paths relative to the application -# source directory. -zephyr_file(APPLICATION_ROOT DTS_ROOT) - -# DTS_ROOT always includes the application directory, the board -# directory, shield directories, and ZEPHYR_BASE. -list(APPEND - DTS_ROOT - ${APPLICATION_SOURCE_DIR} - ${BOARD_DIR} - ${SHIELD_DIRS} - ${ZEPHYR_BASE} - ) - -# Convert the directories in DTS_ROOT to absolute paths without -# symlinks. -# -# DTS directories can come from multiple places. Some places, like a -# user's CMakeLists.txt can preserve symbolic links. Others, like -# scripts/zephyr_module.py --settings-out resolve them. -unset(real_dts_root) -foreach(dts_dir ${DTS_ROOT}) - file(REAL_PATH ${dts_dir} real_dts_dir) - list(APPEND real_dts_root ${real_dts_dir}) -endforeach() -set(DTS_ROOT ${real_dts_root}) - -# Finally, de-duplicate the list. -list(REMOVE_DUPLICATES - DTS_ROOT - ) - # # Find all the DTS files we need to concatenate and preprocess, as # well as all the devicetree bindings and vendor prefixes associated @@ -172,25 +133,8 @@ foreach(dts_file ${dts_files}) math(EXPR i "${i}+1") endforeach() -unset(DTS_ROOT_SYSTEM_INCLUDE_DIRS) unset(DTS_ROOT_BINDINGS) foreach(dts_root ${DTS_ROOT}) - foreach(dts_root_path - include - include/zephyr - dts/common - dts/${ARCH} - dts - ) - get_filename_component(full_path ${dts_root}/${dts_root_path} REALPATH) - if(EXISTS ${full_path}) - list(APPEND - DTS_ROOT_SYSTEM_INCLUDE_DIRS - -isystem ${full_path} - ) - endif() - endforeach() - set(bindings_path ${dts_root}/dts/bindings) if(EXISTS ${bindings_path}) list(APPEND diff --git a/cmake/modules/pre_dt.cmake b/cmake/modules/pre_dt.cmake new file mode 100644 index 000000000000..38ce53d2b904 --- /dev/null +++ b/cmake/modules/pre_dt.cmake @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2021, 2023 Nordic Semiconductor ASA + +include_guard(GLOBAL) +include(extensions) + +# Finalize the value of DTS_ROOT, so we know where all our +# DTS files, bindings, and vendor prefixes are. +# +# Outcome: +# The following variables will be defined when this CMake module completes: +# +# - CMAKE_DTS_PREPROCESSOR: the path to the preprocessor to use +# for devicetree 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 "-isystem PATH1 -isystem PATH2 ...", +# with one path per potential location where C preprocessor #includes +# may be found for devicetree files +# +# Required variables: +# None. +# +# Optional variables: +# - APPLICATION_SOURCE_DIR: path to app (added to DTS_ROOT) +# - BOARD_DIR: directory containing the board definition (added to DTS_ROOT) +# - DTS_ROOT: initial contents may be populated here +# - ZEPHYR_BASE: path to zephyr repository (added to DTS_ROOT) +# - SHIELD_DIRS: paths to shield definitions (added to DTS_ROOT) + +# Using a function avoids polluting the parent scope unnecessarily. +function(pre_dt_module_run) + # Convert relative paths to absolute paths relative to the application + # source directory. + zephyr_file(APPLICATION_ROOT DTS_ROOT) + + # DTS_ROOT always includes the application directory, the board + # directory, shield directories, and ZEPHYR_BASE. + list(APPEND + DTS_ROOT + ${APPLICATION_SOURCE_DIR} + ${BOARD_DIR} + ${SHIELD_DIRS} + ${ZEPHYR_BASE} + ) + + # Convert the directories in DTS_ROOT to absolute paths without + # symlinks. + # + # DTS directories can come from multiple places. Some places, like a + # user's CMakeLists.txt can preserve symbolic links. Others, like + # scripts/zephyr_module.py --settings-out resolve them. + unset(real_dts_root) + foreach(dts_dir ${DTS_ROOT}) + file(REAL_PATH ${dts_dir} real_dts_dir) + list(APPEND real_dts_root ${real_dts_dir}) + endforeach() + set(DTS_ROOT ${real_dts_root}) + + # Finalize DTS_ROOT. + list(REMOVE_DUPLICATES DTS_ROOT) + + # Finalize DTS_ROOT_SYSTEM_INCLUDE_DIRS. + set(DTS_ROOT_SYSTEM_INCLUDE_DIRS) + foreach(dts_root ${DTS_ROOT}) + foreach(dts_root_path + include + include/zephyr + dts/common + dts/${ARCH} + dts + ) + get_filename_component(full_path ${dts_root}/${dts_root_path} REALPATH) + if(EXISTS ${full_path}) + list(APPEND + DTS_ROOT_SYSTEM_INCLUDE_DIRS + -isystem ${full_path} + ) + endif() + endforeach() + endforeach() + + # Set output variables. + set(DTS_ROOT ${DTS_ROOT} PARENT_SCOPE) + set(DTS_ROOT_SYSTEM_INCLUDE_DIRS ${DTS_ROOT_SYSTEM_INCLUDE_DIRS} PARENT_SCOPE) +endfunction() + +pre_dt_module_run() From 6ec97b2759035c083e634ab1be49492955109c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 28 Feb 2023 12:31:57 -0800 Subject: [PATCH 04/16] cmake: modules: dts: document outcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the build system was being split up into modules under cmake/modules, most of the resulting cmake modules had their inputs and outputs documented in top-of-file comments. The dts module is an exception, which makes it harder to use since its contracts aren't defined. Fix this by adding a contract. Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 0a1e9618fced..39c9b8d7ec10 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -39,6 +39,53 @@ find_package(Dtc 1.4.6) # 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 +# - the build system will be regenerated if any devicetree files +# used in this build change, including transitive includes +# - 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 +# - KCONFIG_BINARY_DIR: where to put generated Kconfig files +# +# Optional variables: +# - BOARD: board name ot use when looking for DTS_SOURCE +# - BOARD_DIR: board directory to use when looking for DTS_SOURCE +# - BOARD_REVISION_STRING: used when looking for a board revision's +# devicetree overlay file in BOARD_DIR +# - 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_DIR}/${BOARD.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) From 67c6f1bf4e5edb848919af0cc4a207657cd738b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Fri, 24 Feb 2023 14:40:44 -0800 Subject: [PATCH 05/16] cmake: modules: dts: extract overlay helper function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This procedure will be useful elsewhere, so prep for not repeating ourselves. Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 8 +------- cmake/modules/extensions.cmake | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 39c9b8d7ec10..63b0380008e9 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -152,13 +152,7 @@ set(dts_files ) if(DTC_OVERLAY_FILE) - # Convert from space-separated files into file list - string(CONFIGURE "${DTC_OVERLAY_FILE}" DTC_OVERLAY_FILE_EXPANDED) - string(REPLACE " " ";" DTC_OVERLAY_FILE_RAW_LIST "${DTC_OVERLAY_FILE_EXPANDED}") - foreach(file ${DTC_OVERLAY_FILE_RAW_LIST}) - file(TO_CMAKE_PATH "${file}" cmake_path_file) - list(APPEND DTC_OVERLAY_FILE_AS_LIST ${cmake_path_file}) - endforeach() + zephyr_dt_normalize_overlay_list(DTC_OVERLAY_FILE DTC_OVERLAY_FILE_AS_LIST) list(APPEND dts_files ${DTC_OVERLAY_FILE_AS_LIST} diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index b870f8e682e4..753146c9bd98 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -30,6 +30,7 @@ include(CheckCXXCompilerFlag) # 4. Devicetree extensions # 4.1 dt_* # 4.2. *_if_dt_node +# 4.3. zephyr_dt_* # 5. Zephyr linker functions # 5.1. zephyr_linker* @@ -3425,6 +3426,38 @@ function(target_sources_if_dt_node path target scope item) endif() endfunction() +######################################################## +# 4.3 zephyr_dt_* +# +# The following methods are common code for dealing +# with devicetree related files in CMake. +# +# Note that functions related to accessing the +# *contents* of the devicetree belong in section 4.1. +# This section is just for DT file processing at +# configuration time. +######################################################## + +# Usage: +# zephyr_dt_normalize_overlay_list(FOO_OVERLAY_FILE FOO_OVERLAY_FILE_AS_LIST) +# +# Converts the argument to a ;-list with CMake path names, after +# passing its contents through a configure_file() transformation. +# +# : Input variable name to normalize +# : Output variable that will be set to the final list. +function(zephyr_dt_normalize_overlay_list var out_var) + set(ret) + set(input ${${var}}) + string(CONFIGURE "${input}" input_expanded) + string(REPLACE " " ";" input_raw_list "${input_expanded}") + foreach(file ${input_raw_list}) + file(TO_CMAKE_PATH "${file}" cmake_path_file) + list(APPEND ret ${cmake_path_file}) + endforeach() + set(${out_var} ${ret} PARENT_SCOPE) +endfunction() + ######################################################## # 5. Zephyr linker functions ######################################################## From 668a1bc2fe99881e4bf5293a24b009cc2cb00fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 28 Feb 2023 13:07:46 -0800 Subject: [PATCH 06/16] cmake: modules: dts: extract preprocessing helper extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten up the interface boundaries by adding an extension function that separates *how* we preprocess the DTS from *what* DTS files we are preprocessing. This involves a functional change to the pre_dt module. This is useful cleanup but is also groundwork for relying on this helper function when adding system devicetree support. Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 38 +++++---------- cmake/modules/extensions.cmake | 89 ++++++++++++++++++++++++++++++++++ cmake/modules/pre_dt.cmake | 7 +-- 3 files changed, 104 insertions(+), 30 deletions(-) diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 63b0380008e9..b304b4938da4 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -160,11 +160,7 @@ if(DTC_OVERLAY_FILE) endif() set(i 0) -unset(DTC_INCLUDE_FLAG_FOR_DTS) foreach(dts_file ${dts_files}) - list(APPEND DTC_INCLUDE_FLAG_FOR_DTS - -include ${dts_file}) - if(i EQUAL 0) message(STATUS "Found BOARD.dts: ${dts_file}") else() @@ -204,29 +200,21 @@ set(CACHED_DTS_ROOT_BINDINGS ${DTS_ROOT_BINDINGS} CACHE INTERNAL # 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. We are leaving -# linemarker directives enabled on purpose. This tells dtlib where -# each line actually came from, which improves error reporting. -execute_process( - COMMAND ${CMAKE_DTS_PREPROCESSOR} - -x assembler-with-cpp - -nostdinc - ${DTS_ROOT_SYSTEM_INCLUDE_DIRS} - ${DTC_INCLUDE_FLAG_FOR_DTS} # include the DTS source and overlays - ${NOSYSDEF_CFLAG} - -D__DTS__ - ${DTS_EXTRA_CPPFLAGS} - -E # Stop after preprocessing - -MD # Generate a dependency file as a side-effect - -MF ${DTS_DEPS} - -o ${DTS_POST_CPP} - ${ZEPHYR_BASE}/misc/empty_file.c +# 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} - RESULT_VARIABLE ret ) -if(NOT "${ret}" STREQUAL "0") - message(FATAL_ERROR "command failed with return code: ${ret}") -endif() # # Make sure we re-run CMake if any devicetree sources or transitive diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 753146c9bd98..2f1b35722a48 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -3458,6 +3458,95 @@ function(zephyr_dt_normalize_overlay_list var out_var) set(${out_var} ${ret} PARENT_SCOPE) endfunction() +# Usage: +# zephyr_dt_preprocess(CPP +# SOURCE_FILES +# OUT_FILE +# [DEPS_FILE ] +# [EXTRA_CPPFLAGS ] +# [INCLUDE_DIRECTORIES ] +# [WORKING_DIRECTORY ] +# +# Preprocess one or more devicetree source files. The preprocessor +# symbol __DTS__ will be defined. If the preprocessor command fails, a +# fatal error occurs. CMAKE_DTS_PREPROCESSOR is used as the +# preprocessor. +# +# Mandatory arguments: +# +# CPP : path to C preprocessor +# +# SOURCE_FILES : The source files to run the preprocessor on. +# These will, in effect, be concatenated in order +# and used as the preprocessor input. +# +# OUT_FILE : Where to store the preprocessor output. +# +# Optional arguments: +# +# DEPS_FILE : If set, generate a dependency file here. +# +# EXTRA_CPPFLAGS : Additional flags to pass the preprocessor. +# +# INCLUDE_DIRECTORIES : Additional directories containing #included +# files. +# +# WORKING_DIRECTORY : where to run the preprocessor. +function(zephyr_dt_preprocess) + set(req_single_args "CPP;OUT_FILE") + set(single_args "DEPS_FILE;WORKING_DIRECTORY") + set(multi_args "SOURCE_FILES;EXTRA_CPPFLAGS;INCLUDE_DIRECTORIES") + cmake_parse_arguments(DT_PREPROCESS "" "${req_single_args};${single_args}" "${multi_args}" ${ARGN}) + + foreach(arg ${req_single_args} SOURCE_FILES) + if(NOT DEFINED DT_PREPROCESS_${arg}) + message(FATAL_ERROR "dt_preprocess() missing required argument: ${arg}") + endif() + endforeach() + + set(include_opts) + foreach(dir ${DT_PREPROCESS_INCLUDE_DIRECTORIES}) + list(APPEND include_opts -isystem ${dir}) + endforeach() + + set(source_opts) + foreach(file ${DT_PREPROCESS_SOURCE_FILES}) + list(APPEND source_opts -include ${file}) + endforeach() + + set(deps_opts) + if(DEFINED DT_PREPROCESS_DEPS_FILE) + list(APPEND deps_opts -MD -MF ${DT_PREPROCESS_DEPS_FILE}) + endif() + + set(workdir_opts) + if(DEFINED DT_PREPROCESS_WORKING_DIRECTORY) + list(APPEND workdir_opts WORKING_DIRECTORY ${DT_PREPROCESS_WORKING_DIRECTORY}) + endif() + + # We are leaving linemarker directives enabled on purpose. This tells + # dtlib where each line actually came from, which improves error + # reporting. + set(preprocess_cmd ${DT_PREPROCESS_CPP} + -x assembler-with-cpp + -nostdinc + ${include_opts} + ${source_opts} + ${NOSYSDEF_CFLAG} + -D__DTS__ + ${DT_PREPROCESS_EXTRA_CPPFLAGS} + -E # Stop after preprocessing + ${deps_opts} + -o ${DT_PREPROCESS_OUT_FILE} + ${ZEPHYR_BASE}/misc/empty_file.c + ${workdir_opts}) + + execute_process(COMMAND ${preprocess_cmd} RESULT_VARIABLE ret) + if(NOT "${ret}" STREQUAL "0") + message(FATAL_ERROR "failed to preprocess devicetree files (error code ${ret}): ${DT_PREPROCESS_SOURCE_FILES}") + endif() +endfunction() + ######################################################## # 5. Zephyr linker functions ######################################################## diff --git a/cmake/modules/pre_dt.cmake b/cmake/modules/pre_dt.cmake index 38ce53d2b904..b19fd7f7fb57 100644 --- a/cmake/modules/pre_dt.cmake +++ b/cmake/modules/pre_dt.cmake @@ -16,7 +16,7 @@ include(extensions) # - 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 "-isystem PATH1 -isystem PATH2 ...", +# - 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 # @@ -74,10 +74,7 @@ function(pre_dt_module_run) ) get_filename_component(full_path ${dts_root}/${dts_root_path} REALPATH) if(EXISTS ${full_path}) - list(APPEND - DTS_ROOT_SYSTEM_INCLUDE_DIRS - -isystem ${full_path} - ) + list(APPEND DTS_ROOT_SYSTEM_INCLUDE_DIRS ${full_path}) endif() endforeach() endforeach() From 55792b2d40724980a34e83fdb920cd95392b8861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 28 Feb 2023 13:47:08 -0800 Subject: [PATCH 07/16] cmake: modules: dts: improve message() generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If DTS_SOURCE is already set, then it might not be a BOARD.dts, even though that is true basically always today. Make the output generation a bit more generic so that we can handle a pre-set DTS_SOURCE better, even though this is currently an edge case. (It won't be an edge case anymore when we have system devicetree support.) Signed-off-by: Martí Bolívar --- cmake/modules/dts.cmake | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index b304b4938da4..9f1c8e5f423d 100644 --- a/cmake/modules/dts.cmake +++ b/cmake/modules/dts.cmake @@ -127,17 +127,20 @@ set(VENDOR_PREFIXES dts/bindings/vendor-prefixes.txt) # TODO: What to do about non-posix platforms where NOT CONFIG_HAS_DTS (xtensa)? # Drop support for NOT CONFIG_HAS_DTS perhaps? -set_ifndef(DTS_SOURCE ${BOARD_DIR}/${BOARD}.dts) -if(EXISTS ${DTS_SOURCE}) - # We found a devicetree. Check for a board revision overlay. - if(BOARD_REVISION AND EXISTS ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay) - list(APPEND DTS_SOURCE ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay) +if(NOT DEFINED DTS_SOURCE) + if(EXISTS ${BOARD_DIR}/${BOARD}.dts) + set(DTS_SOURCE ${BOARD_DIR}/${BOARD}.dts) + message(STATUS "Found BOARD.dts: ${DTS_SOURCE}") + # We found a devicetree. Check for a board revision overlay. + if(BOARD_REVISION AND EXISTS ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay) + list(APPEND DTS_SOURCE ${BOARD_DIR}/${BOARD}_${BOARD_REVISION_STRING}.overlay) + endif() + else() + # If we don't have a devicetree after all, there's not much to do. + set(header_template ${ZEPHYR_BASE}/misc/generated/generated_header.template) + zephyr_file_copy(${header_template} ${DEVICETREE_GENERATED_H} ONLY_IF_DIFFERENT) + return() endif() -else() - # If we don't have a devicetree after all, there's not much to do. - set(header_template ${ZEPHYR_BASE}/misc/generated/generated_header.template) - zephyr_file_copy(${header_template} ${DEVICETREE_GENERATED_H} ONLY_IF_DIFFERENT) - return() endif() # @@ -159,15 +162,10 @@ if(DTC_OVERLAY_FILE) ) endif() -set(i 0) foreach(dts_file ${dts_files}) - if(i EQUAL 0) - message(STATUS "Found BOARD.dts: ${dts_file}") - else() + if(dts_file MATCHES ".*[.]overlay$") message(STATUS "Found devicetree overlay: ${dts_file}") endif() - - math(EXPR i "${i}+1") endforeach() unset(DTS_ROOT_BINDINGS) From a8a849727a4046b2bcd7af2ef0a3b960f681f7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 7 Feb 2023 12:40:22 -0800 Subject: [PATCH 08/16] python-devicetree: re-work reg and ranges handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The change to edtlib.Node.unit_addr does break backwards compatibility, but there are no affected in-tree users Move these into dtlib from edtlib. Preserve edtlib-side APIs for this. Just like there are corresponding dtlib.Node and edtlib.Node classes, add new dtlib.Register and dtlib.Range classes to correspond to existing edtlib.Register and edtlib.Range classes, and move the parsing of these properties into dtlib. Nothing significant in the semantics for reg or ranges depends on devicetree bindings. These basic properties are defined in the devicetree specification and are central to the way addressing works. Their initialization therefore makes more sense in dtlib than edtlib. Putting it there will make it possible to use this data in dtlib clients as well as edtlib clients. There is a longstanding TODO comment in here (which dates back to the original introduction of the library) questioning whether we really ought to have edtlib.Node.unit_addr()'s value be an int, instead of a string. There does indeed seem little point to translating the unit address of a node recursively through the address maps of its parents. Make the return types match to keep things simple and expose the actual unit address, rather than this strange value. This keeps this conversion simpler by not duplicating address translation logic in two places, at the cost of breaking backwards compatibility. Signed-off-by: Martí Bolívar --- .../python-devicetree/src/devicetree/dtlib.py | 301 +++++++++++++++++- .../src/devicetree/edtlib.py | 260 +++------------ .../python-devicetree/tests/test_edtlib.py | 117 +++++-- 3 files changed, 426 insertions(+), 252 deletions(-) diff --git a/scripts/dts/python-devicetree/src/devicetree/dtlib.py b/scripts/dts/python-devicetree/src/devicetree/dtlib.py index be015e7a7605..c774a870f36a 100644 --- a/scripts/dts/python-devicetree/src/devicetree/dtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/dtlib.py @@ -15,14 +15,18 @@ import collections import enum import errno +import logging import os import re import string import sys import textwrap +from dataclasses import dataclass from typing import Any, Dict, Iterable, List, \ NamedTuple, NoReturn, Optional, Set, Tuple, TYPE_CHECKING, Union +from devicetree._private import _slice_helper + # NOTE: tests/test_dtlib.py is the test suite for this library. class DTError(Exception): @@ -44,6 +48,28 @@ class Node: Note that this is a string. Run int(node.unit_addr, 16) to get an integer. + regs: + A list of Register objects for the node's registers. + + num_address_cells: + The number of cells that should be used to encode an + address in the node's 'reg' property. + + This is *NOT* the same thing as the value of the node's + '#address-cells' property. See Devicetree Specification + section 2.3.5 for details. + + num_size_cells: + The number of cells that should be used to encode a + register block size in the node's 'reg' property. + + This is *NOT* the same thing as the value of the node's + '#size-cells' property. See Devicetree Specification + section 2.3.5 for details. + + ranges: + A list of Range objects for the node's address ranges. + props: A dict that maps the properties defined on the node to their values. 'props' is indexed by property name (a string), and values @@ -91,6 +117,7 @@ def __init__(self, name: str, parent: Optional['Node'], dt: 'DT'): # Remember to update DT.__deepcopy__() if you change this. self._name = name + self.ranges: List['Range'] = [] self.props: Dict[str, 'Property'] = {} self.nodes: Dict[str, 'Node'] = {} self.labels: List[str] = [] @@ -124,6 +151,50 @@ def unit_addr(self) -> str: """ return self.name.partition("@")[2] + @property + def regs(self) -> List['Register']: + """ + See the class documentation. + """ + if 'reg' not in self.props: + return [] + + address_cells = _address_cells(self) + size_cells = _size_cells(self) + res = [] + for raw_reg in _slice(self, "reg", 4*(address_cells + size_cells), + f"4*(<#address-cells> (= {address_cells}) + " + f"<#size-cells> (= {size_cells}))"): + if address_cells == 0: + addr = None + else: + addr = _translate(to_num(raw_reg[:4*address_cells]), self) + if size_cells == 0: + size = None + else: + size = to_num(raw_reg[4*address_cells:]) + if size_cells != 0 and size == 0: + _err(f"zero-sized 'reg' in {self!r} seems meaningless " + "(maybe you want a size of one or #size-cells = 0 " + "instead)") + + res.append(Register(node=self, addr=addr, size=size)) + return res + + @property + def num_address_cells(self) -> int: + """ + See the class documentation. + """ + return _address_cells(self) + + @property + def num_size_cells(self) -> int: + """ + See the class documentation. + """ + return _size_cells(self) + @property def path(self) -> str: """ @@ -195,6 +266,69 @@ def __repr__(self): """ return f"" +@dataclass +class Range: + """ + Represents a translation range on a node as described by the 'ranges' property. + + These attributes are available on Range objects: + + node: + The Node instance this range is from + + child_bus_cells: + Is the number of cells (4-bytes wide words) describing the child bus address. + + child_bus_addr: + Is a physical address within the child bus address space, or None if the + child address-cells is equal 0. + + parent_bus_cells: + Is the number of cells (4-bytes wide words) describing the parent bus address. + + parent_bus_addr: + Is a physical address within the parent bus address space, or None if the + parent address-cells is equal 0. + + length_cells: + Is the number of cells (4-bytes wide words) describing the size of range in + the child address space. + + length: + Specifies the size of the range in the child address space, or None if the + child size-cells is equal 0. + """ + + node: Node + child_bus_cells: int + child_bus_addr: Optional[int] + parent_bus_cells: int + parent_bus_addr: Optional[int] + length_cells: int + length: Optional[int] + +@dataclass +class Register: + """ + Represents a node's registers, as determined from its reg property. + + These attributes are available on Register objects: + + node: + The Node instance this register is from + + addr: + The starting address of the register, in the parent address space, or None + if #address-cells is zero. Any 'ranges' properties are taken into account. + + size: + The length of the register in bytes, or None if #size-cells is zero. + """ + + node: Node + addr: Optional[int] + size: Optional[int] + # See Property.type class Type(enum.IntEnum): EMPTY = 0 @@ -732,7 +866,8 @@ class DT: # def __init__(self, filename: Optional[str], include_path: Iterable[str] = (), - force: bool = False): + force: bool = False, + warn_reg_unit_address_mismatch: bool = True): """ Parses a DTS file to create a DT instance. Raises OSError if 'filename' can't be opened, and DTError for any parse errors. @@ -749,10 +884,17 @@ def __init__(self, filename: Optional[str], include_path: Iterable[str] = (), force: Try not to raise DTError even if the input tree has errors. For experimental use; results not guaranteed. + + warn_reg_unit_address_mismatch (default: True): + If True, a warning is logged if a node has a 'reg' property where + the address of the first entry does not match the unit address of the + node """ # Remember to update __deepcopy__() if you change this. self._root: Optional[Node] = None + self._warn_reg_unit_address_mismatch: bool = \ + warn_reg_unit_address_mismatch self.alias2node: Dict[str, Node] = {} self.label2node: Dict[str, Node] = {} self.label2prop: Dict[str, Property] = {} @@ -1035,6 +1177,8 @@ def _parse_file(self, filename: str, include_path: Iterable[str]): self._register_aliases() self._remove_unreferenced() self._register_labels() + self._check_regs() + self._init_ranges() def _parse_header(self): # Parses /dts-v1/ (expected) and /plugin/ (unsupported) at the start of @@ -1930,6 +2074,91 @@ def _register_labels(self): _err(f"Label '{label}' appears " + " and ".join(strings)) + def _check_regs(self): + if not self._warn_reg_unit_address_mismatch: + return + for node in self.root.node_iter(): + self._check_regs_for(node) + + def _check_regs_for(self, node: Node): + # This warning matches the simple_bus_reg warning in dtc + try: + raw_unit_addr = int(node.unit_addr, 16) + except ValueError: + raw_unit_addr = None + + if raw_unit_addr is not None: + unit_addr = _translate(raw_unit_addr, node) + else: + unit_addr = None + + regs = node.regs + if regs and regs[0].addr != unit_addr: + _LOG.warning("unit address and first address in 'reg' " + f"(0x{regs[0].addr:x}) don't match for " + f"{node.path}") + + def _init_ranges(self): + for node in self.root.node_iter(): + self._init_ranges_for(node) + + def _init_ranges_for(self, node: Node): + if "ranges" not in node.props: + return + + parent_address_cells = _address_cells(node) + if '#address-cells' not in node.props: + child_address_cells: int = 2 # Default value per DT spec. + else: + child_address_cells = node.props['#address-cells'].to_num() + if '#size-cells' not in node.props: + child_size_cells: int = 1 # Default value per DT spec. + else: + child_size_cells = node.props['#size-cells'].to_num() + + # Number of cells for one translation 3-tuple in 'ranges' + entry_cells = child_address_cells + parent_address_cells + child_size_cells + + if entry_cells == 0: + if len(node.props["ranges"].value) == 0: + return + else: + _err(f"'ranges' should be empty in {node.path} since " + f"<#address-cells> = {child_address_cells}, " + f"<#address-cells for parent> = {parent_address_cells} and " + f"<#size-cells> = {child_size_cells}") + + for raw_range in _slice(node, "ranges", 4*entry_cells, + f"4*(<#address-cells> (= {child_address_cells}) + " + "<#address-cells for parent> " + f"(= {parent_address_cells}) + " + f"<#size-cells> (= {child_size_cells}))"): + if child_address_cells == 0: + child_bus_addr = None + else: + child_bus_addr = to_num(raw_range[:4*child_address_cells]) + if parent_address_cells == 0: + parent_bus_addr = None + else: + parent_bus_addr = to_num( + raw_range[(4*child_address_cells):\ + (4*child_address_cells + 4*parent_address_cells)]) + if child_size_cells == 0: + length = None + else: + length = to_num(raw_range[(4*child_address_cells + \ + 4*parent_address_cells):]) + + node.ranges.append(Range( + node=node, + child_bus_cells=child_address_cells, + child_bus_addr=child_bus_addr, + parent_bus_cells=parent_address_cells, + parent_bus_addr=parent_bus_addr, + length_cells=child_size_cells, + length=length, + )) + # # Misc. @@ -2085,9 +2314,79 @@ def _root_and_path_to_node(cur, path, fullpath): return cur +def _address_cells(node): + # Returns the #address-cells setting for 'node', giving the number of + # cells used to encode the address in the 'reg' property + + if node.parent and "#address-cells" in node.parent.props: + return node.parent.props["#address-cells"].to_num() + return 2 # Default value per DT spec. + + +def _size_cells(node): + # Returns the #size-cells setting for 'node', giving the number of + # cells used to encode the size in the 'reg' property + + if node.parent and "#size-cells" in node.parent.props: + return node.parent.props["#size-cells"].to_num() + return 1 # Default value per DT spec. + +def _translate(addr: int, node: Node): + # Recursively translates 'addr' on 'node' to the address space(s) of its + # parent(s), by looking at 'ranges' properties. Returns the translated + # address. + + if not node.parent or "ranges" not in node.parent.props: + # No translation + return addr + + if not node.parent.props["ranges"].value: + # DT spec.: "If the property is defined with an value, it + # specifies that the parent and child address space is identical, and + # no address translation is required." + # + # Treat this the same as a 'range' that explicitly does a one-to-one + # mapping, as opposed to there not being any translation. + return _translate(addr, node.parent) + + # Gives the size of each component in a translation 3-tuple in 'ranges' + child_address_cells = _address_cells(node) + parent_address_cells = _address_cells(node.parent) + child_size_cells = _size_cells(node) + + # Number of cells for one translation 3-tuple in 'ranges' + entry_cells = child_address_cells + parent_address_cells + child_size_cells + + for raw_range in _slice(node.parent, "ranges", 4*entry_cells, + f"4*(<#address-cells> (= {child_address_cells}) + " + "<#address-cells for parent> " + f"(= {parent_address_cells}) + " + f"<#size-cells> (= {child_size_cells}))"): + child_addr = to_num(raw_range[:4*child_address_cells]) + raw_range = raw_range[4*child_address_cells:] + + parent_addr = to_num(raw_range[:4*parent_address_cells]) + raw_range = raw_range[4*parent_address_cells:] + + child_len = to_num(raw_range) + + if child_addr <= addr < child_addr + child_len: + # 'addr' is within range of a translation in 'ranges'. Recursively + # translate it and return the result. + return _translate(parent_addr + addr - child_addr, node.parent) + + # 'addr' is not within range of any translation in 'ranges' + return addr + def _err(msg) -> NoReturn: raise DTError(msg) +def _slice(node, prop_name, size, size_hint): + return _slice_helper(node, prop_name, size, size_hint, DTError) + +# Logging object +_LOG = logging.getLogger(__name__) + _escape_table = str.maketrans({ "\\": "\\\\", '"': '\\"', diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 7af179c9d892..996d4d52e77a 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -69,6 +69,8 @@ from collections import defaultdict from copy import deepcopy +from dataclasses import asdict, dataclass +from typing import Optional import logging import os import re @@ -196,7 +198,6 @@ def __init__(self, dts, bindings_dirs, errors out if 'dts' has any deprecated properties set, or an unknown vendor prefix is used. """ - self._warn_reg_unit_address_mismatch = warn_reg_unit_address_mismatch self._default_prop_types = default_prop_types self._fixed_partitions_no_bus = support_fixed_partitions_on_any_bus self._infer_binding_for_paths = set(infer_binding_for_paths or []) @@ -207,7 +208,10 @@ def __init__(self, dts, bindings_dirs, self.bindings_dirs = bindings_dirs try: - self._dt = DT(dts) + self._dt = DT( + dts, + warn_reg_unit_address_mismatch=warn_reg_unit_address_mismatch, + ) except DTError as e: raise EDTError(e) from e _check_dt(self._dt) @@ -441,14 +445,6 @@ def _init_nodes(self): node._init_interrupts() node._init_pinctrls() - if self._warn_reg_unit_address_mismatch: - # This warning matches the simple_bus_reg warning in dtc - for node in self.nodes: - if node.regs and node.regs[0].addr != node.unit_addr: - _LOG.warning("unit address and first address in 'reg' " - f"(0x{node.regs[0].addr:x}) don't match for " - f"{node.path}") - def _init_luts(self): # Initialize node lookup tables (LUTs). @@ -557,9 +553,11 @@ class Node: The name of the node unit_addr: - An integer with the ...@ portion of the node name, - translated through any 'ranges' properties on parent nodes, or None if - the node name has no unit-address portion + The portion after the '@' in the node's name, or the empty string if the + name has no '@' in it. + + Note that this is a string. Run int(node.unit_addr, 16) to get an + integer. description: The description string from the binding for the node, or None if the node @@ -685,18 +683,7 @@ def name(self): @property def unit_addr(self): "See the class docstring" - - # TODO: Return a plain string here later, like dtlib.Node.unit_addr? - - if "@" not in self.name: - return None - - try: - addr = int(self.name.split("@", 1)[1], 16) - except ValueError: - _err(f"{self!r} has non-hex unit address") - - return _translate(addr, self._node) + return self._node.unit_addr @property def description(self): @@ -1207,99 +1194,20 @@ def _check_undeclared_props(self): def _init_ranges(self): # Initializes self.ranges - node = self._node - self.ranges = [] - - if "ranges" not in node.props: - return - - child_address_cells = node.props.get("#address-cells") - parent_address_cells = _address_cells(node) - if child_address_cells is None: - child_address_cells = 2 # Default value per DT spec. - else: - child_address_cells = child_address_cells.to_num() - child_size_cells = node.props.get("#size-cells") - if child_size_cells is None: - child_size_cells = 1 # Default value per DT spec. - else: - child_size_cells = child_size_cells.to_num() - - # Number of cells for one translation 3-tuple in 'ranges' - entry_cells = child_address_cells + parent_address_cells + child_size_cells - - if entry_cells == 0: - if len(node.props["ranges"].value) == 0: - return - else: - _err(f"'ranges' should be empty in {self._node.path} since " - f"<#address-cells> = {child_address_cells}, " - f"<#address-cells for parent> = {parent_address_cells} and " - f"<#size-cells> = {child_size_cells}") - - for raw_range in _slice(node, "ranges", 4*entry_cells, - f"4*(<#address-cells> (= {child_address_cells}) + " - "<#address-cells for parent> " - f"(= {parent_address_cells}) + " - f"<#size-cells> (= {child_size_cells}))"): - - range = Range() - range.node = self - range.child_bus_cells = child_address_cells - if child_address_cells == 0: - range.child_bus_addr = None - else: - range.child_bus_addr = to_num(raw_range[:4*child_address_cells]) - range.parent_bus_cells = parent_address_cells - if parent_address_cells == 0: - range.parent_bus_addr = None - else: - range.parent_bus_addr = to_num(raw_range[(4*child_address_cells):\ - (4*child_address_cells + 4*parent_address_cells)]) - range.length_cells = child_size_cells - if child_size_cells == 0: - range.length = None - else: - range.length = to_num(raw_range[(4*child_address_cells + \ - 4*parent_address_cells):]) - - self.ranges.append(range) + for range in self._node.ranges: + range_dict = asdict(range) + range_dict['node'] = self + self.ranges.append(Range(**range_dict)) def _init_regs(self): # Initializes self.regs - node = self._node - - self.regs = [] - - if "reg" not in node.props: - return - - address_cells = _address_cells(node) - size_cells = _size_cells(node) - - for raw_reg in _slice(node, "reg", 4*(address_cells + size_cells), - f"4*(<#address-cells> (= {address_cells}) + " - f"<#size-cells> (= {size_cells}))"): - reg = Register() - reg.node = self - if address_cells == 0: - reg.addr = None - else: - reg.addr = _translate(to_num(raw_reg[:4*address_cells]), node) - if size_cells == 0: - reg.size = None - else: - reg.size = to_num(raw_reg[4*address_cells:]) - if size_cells != 0 and reg.size == 0: - _err(f"zero-sized 'reg' in {self._node!r} seems meaningless " - "(maybe you want a size of one or #size-cells = 0 " - "instead)") - - self.regs.append(reg) - - _add_names(node, "reg", self.regs) + self.regs = [ + Register(node=self, addr=reg.addr, size=reg.size, name=None) + for reg in self._node.regs + ] + _add_names(self._node, "reg", self.regs) def _init_pinctrls(self): # Initializes self.pinctrls from any pinctrl- properties @@ -1448,6 +1356,7 @@ def _named_cells(self, controller, data, basename): return dict(zip(cell_names, data_list)) +@dataclass class Range: """ Represents a translation range on a node as described by the 'ranges' property. @@ -1479,24 +1388,17 @@ class Range: Specifies the size of the range in the child address space, or None if the child size-cells is equal 0. """ - def __repr__(self): - fields = [] - if self.child_bus_cells is not None: - fields.append("child-bus-cells: " + hex(self.child_bus_cells)) - if self.child_bus_addr is not None: - fields.append("child-bus-addr: " + hex(self.child_bus_addr)) - if self.parent_bus_cells is not None: - fields.append("parent-bus-cells: " + hex(self.parent_bus_cells)) - if self.parent_bus_addr is not None: - fields.append("parent-bus-addr: " + hex(self.parent_bus_addr)) - if self.length_cells is not None: - fields.append("length-cells " + hex(self.length_cells)) - if self.length is not None: - fields.append("length " + hex(self.length)) - - return "".format(", ".join(fields)) + node: Node + child_bus_cells: int + child_bus_addr: Optional[int] + parent_bus_cells: int + parent_bus_addr: Optional[int] + length_cells: int + length: Optional[int] + +@dataclass class Register: """ Represents a register on a node. @@ -1507,27 +1409,21 @@ class Register: The Node instance this register is from name: - The name of the register as given in the 'reg-names' property, or None if - there is no 'reg-names' property + The name of the register as given in the node's 'reg-names' property, + or None if there is no 'reg-names' property addr: The starting address of the register, in the parent address space, or None if #address-cells is zero. Any 'ranges' properties are taken into account. size: - The length of the register in bytes + The length of the register in bytes, or None if #size-cells is zero. """ - def __repr__(self): - fields = [] - - if self.name is not None: - fields.append("name: " + self.name) - if self.addr is not None: - fields.append("addr: " + hex(self.addr)) - if self.size is not None: - fields.append("size: " + hex(self.size)) - return "".format(", ".join(fields)) + node: Node + name: Optional[str] + addr: Optional[int] + size: Optional[int] class ControllerAndData: @@ -2541,58 +2437,6 @@ def ok_default(): f"in 'properties:' in {binding_path}, " f"which has type {prop_type}") - -def _translate(addr, node): - # Recursively translates 'addr' on 'node' to the address space(s) of its - # parent(s), by looking at 'ranges' properties. Returns the translated - # address. - # - # node: - # dtlib.Node instance - - if not node.parent or "ranges" not in node.parent.props: - # No translation - return addr - - if not node.parent.props["ranges"].value: - # DT spec.: "If the property is defined with an value, it - # specifies that the parent and child address space is identical, and - # no address translation is required." - # - # Treat this the same as a 'range' that explicitly does a one-to-one - # mapping, as opposed to there not being any translation. - return _translate(addr, node.parent) - - # Gives the size of each component in a translation 3-tuple in 'ranges' - child_address_cells = _address_cells(node) - parent_address_cells = _address_cells(node.parent) - child_size_cells = _size_cells(node) - - # Number of cells for one translation 3-tuple in 'ranges' - entry_cells = child_address_cells + parent_address_cells + child_size_cells - - for raw_range in _slice(node.parent, "ranges", 4*entry_cells, - f"4*(<#address-cells> (= {child_address_cells}) + " - "<#address-cells for parent> " - f"(= {parent_address_cells}) + " - f"<#size-cells> (= {child_size_cells}))"): - child_addr = to_num(raw_range[:4*child_address_cells]) - raw_range = raw_range[4*child_address_cells:] - - parent_addr = to_num(raw_range[:4*parent_address_cells]) - raw_range = raw_range[4*parent_address_cells:] - - child_len = to_num(raw_range) - - if child_addr <= addr < child_addr + child_len: - # 'addr' is within range of a translation in 'ranges'. Recursively - # translate it and return the result. - return _translate(parent_addr + addr - child_addr, node.parent) - - # 'addr' is not within range of any translation in 'ranges' - return addr - - def _add_names(node, names_ident, objs): # Helper for registering names from -names properties. # @@ -2675,9 +2519,9 @@ def _map_interrupt(child, parent, child_spec): return (parent, child_spec) def own_address_cells(node): - # Used for parents pointed at by 'interrupt-map'. We can't use - # _address_cells(), because it's the #address-cells property on 'node' - # itself that matters. + # Used for parents pointed at by 'interrupt-map'. + # The #address-cells property on 'node' itself is what matters, + # so we can't rely on information from dtlib here. address_cells = node.props.get("#address-cells") if not address_cells: @@ -2686,8 +2530,6 @@ def own_address_cells(node): return address_cells.to_num() def spec_len_fn(node): - # Can't use _address_cells() here, because it's the #address-cells - # property on 'node' itself that matters return own_address_cells(node) + _interrupt_cells(node) parent, raw_spec = _map( @@ -2840,7 +2682,7 @@ def _raw_unit_addr(node): _err(f"{node!r} lacks 'reg' property " "(needed for 'interrupt-map' unit address lookup)") - addr_len = 4*_address_cells(node) + addr_len = 4 * node.num_address_cells if len(node.props['reg'].value) < addr_len: _err(f"{node!r} has too short 'reg' property " @@ -2926,24 +2768,6 @@ def _phandle_val_list(prop, n_cells_name): return res -def _address_cells(node): - # Returns the #address-cells setting for 'node', giving the number of - # cells used to encode the address in the 'reg' property - - if "#address-cells" in node.parent.props: - return node.parent.props["#address-cells"].to_num() - return 2 # Default value per DT spec. - - -def _size_cells(node): - # Returns the #size-cells setting for 'node', giving the number of - # cells used to encode the size in the 'reg' property - - if "#size-cells" in node.parent.props: - return node.parent.props["#size-cells"].to_num() - return 1 # Default value per DT spec. - - def _interrupt_cells(node): # Returns the #interrupt-cells property value on 'node', erroring out if # 'node' has no #interrupt-cells property diff --git a/scripts/dts/python-devicetree/tests/test_edtlib.py b/scripts/dts/python-devicetree/tests/test_edtlib.py index 0653eef78ba0..1e35b571a630 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib.py @@ -3,13 +3,13 @@ import contextlib import io -from logging import WARNING import os from pathlib import Path import pytest from devicetree import edtlib +from devicetree.edtlib import Range, Register # Test suite for edtlib.py. # @@ -46,15 +46,15 @@ def test_warnings(caplog): enums_hpath = hpath('test-bindings/enums.yaml') expected_warnings = [ - f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.", "unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node", "unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node", "unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node", + f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.", f"compatible 'enums' in binding '{enums_hpath}' has non-tokenizable enum for property 'string-enum': 'foo bar', 'foo_bar'", f"compatible 'enums' in binding '{enums_hpath}' has enum for property 'tokenizable-lower-enum' that is only tokenizable in lowercase: 'bar', 'BAR'", ] - assert caplog.record_tuples == [('devicetree.edtlib', WARNING, warning_message) - for warning_message in expected_warnings] + for actual_tuple, expected in zip(caplog.record_tuples, expected_warnings): + assert actual_tuple[2] == expected def test_interrupts(): '''Tests for the interrupts property.''' @@ -83,54 +83,105 @@ def test_ranges(): with from_here(): edt = edtlib.EDT("test.dts", ["test-bindings"]) - assert str(edt.get_node("/reg-ranges/parent").ranges) == \ - "[, , ]" + node = edt.get_node("/reg-ranges/parent") + assert node.ranges == [ + Range(node=node, child_bus_cells=1, child_bus_addr=1, parent_bus_cells=2, parent_bus_addr=0xa0000000b, length_cells=1, length=1), + Range(node=node, child_bus_cells=1, child_bus_addr=2, parent_bus_cells=2, parent_bus_addr=0xc0000000d, length_cells=1, length=2), + Range(node=node, child_bus_cells=1, child_bus_addr=4, parent_bus_cells=2, parent_bus_addr=0xe0000000f, length_cells=1, length=1) + ] - assert str(edt.get_node("/reg-nested-ranges/grandparent").ranges) == \ - "[]" + node = edt.get_node("/reg-nested-ranges/grandparent") + assert node.ranges == [ + Range(node, 2, 0, 3, 0x30000000000000000, 2, 0x200000002) + ] - assert str(edt.get_node("/reg-nested-ranges/grandparent/parent").ranges) == \ - "[]" + node = edt.get_node("/reg-nested-ranges/grandparent/parent") + assert node.ranges == [ + Range(node, 1, 0, 2, 0x200000000, 1, 2) + ] - assert str(edt.get_node("/ranges-zero-cells/node").ranges) == "[]" + assert edt.get_node("/ranges-zero-cells/node").ranges == [] - assert str(edt.get_node("/ranges-zero-parent-cells/node").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-zero-parent-cells/node") + assert node.ranges == [ + Range(node=node, child_bus_cells=1, child_bus_addr=0xa, parent_bus_cells=0, parent_bus_addr=None, length_cells=0, length=None), + Range(node=node, child_bus_cells=1, child_bus_addr=0x1a, parent_bus_cells=0, parent_bus_addr=None, length_cells=0, length=None), + Range(node=node, child_bus_cells=1, child_bus_addr=0x2a, parent_bus_cells=0, parent_bus_addr=None, length_cells=0, length=None) + ] - assert str(edt.get_node("/ranges-one-address-cells/node").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-one-address-cells/node") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0xb), + Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x1b), + Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x2b) + ] - assert str(edt.get_node("/ranges-one-address-two-size-cells/node").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-one-address-two-size-cells/node") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0xb0000000c), + Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x1b0000001c), + Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x2b0000002c) + ] - assert str(edt.get_node("/ranges-two-address-cells/node@1").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-two-address-cells/node@1") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x1, length=0xd), + Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x1, length=0x1d), + Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x1, length=0x2d) + ] - assert str(edt.get_node("/ranges-two-address-two-size-cells/node@1").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-two-address-two-size-cells/node@1") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x2, length=0xd0000000e), + Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x2, length=0x1d0000001e), + Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x2, length=0x2d0000001d) + ] - assert str(edt.get_node("/ranges-three-address-cells/node@1").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-three-address-cells/node@1") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x1, length=0xf), + Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x1, length=0x1f), + Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x1, length=0x2f) + ] - assert str(edt.get_node("/ranges-three-address-two-size-cells/node@1").ranges) == \ - "[, , ]" + node = edt.get_node("/ranges-three-address-two-size-cells/node@1") + assert node.ranges == [ + Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x2, length=0xf00000010), + Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x2, length=0x1f00000110), + Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x2, length=0x2f00000210) + ] def test_reg(): '''Tests for the regs property''' with from_here(): edt = edtlib.EDT("test.dts", ["test-bindings"]) - assert str(edt.get_node("/reg-zero-address-cells/node").regs) == \ - "[, ]" + node = edt.get_node("/reg-zero-address-cells/node") + assert node.regs == [ + Register(node=node, name=None, addr=None, size=1), + Register(node=node, name=None, addr=None, size=2), + ] - assert str(edt.get_node("/reg-zero-size-cells/node").regs) == \ - "[, ]" + node = edt.get_node("/reg-zero-size-cells/node") + assert node.regs == [ + Register(node=node, name=None, addr=1, size=None), + Register(node=node, name=None, addr=2, size=None), + ] - assert str(edt.get_node("/reg-ranges/parent/node").regs) == \ - "[, , , , , ]" + node = edt.get_node("/reg-ranges/parent/node") + assert node.regs == [ + Register(node=node, name=None, addr=5, size=1), + Register(node=node, name=None, addr=0xe0000000f, size=1), + Register(node=node, name=None, addr=0xc0000000e, size=1), + Register(node=node, name=None, addr=0xc0000000d, size=1), + Register(node=node, name=None, addr=0xa0000000b, size=1), + Register(node=node, name=None, addr=0, size=1) + ] - assert str(edt.get_node("/reg-nested-ranges/grandparent/parent/node").regs) == \ - "[]" + node = edt.get_node("/reg-nested-ranges/grandparent/parent/node") + assert node.regs == [ + Register(node=node, name=None, addr=0x30000000200000001, size=1) + ] def test_pinctrl(): '''Test 'pinctrl-'.''' From 67480ae12dcd37890524051ab64bef743cdaab5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 7 Feb 2023 13:22:34 -0800 Subject: [PATCH 09/16] [WIP] devicetree: add sysdtlib module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: Various limitations and edge cases see NotImplementedError() locations for details. For more information about system devicetree, please see: https://github.com/devicetree-org/lopper/ This module implements basic support for some system devicetree concepts documented there. Specifically, add basic support for: - cpu clusters - indirect buses - execution domains This is done through a method that takes some target information gives you back a "regular" DT object for that target. You can then use the regular DT however you want in the zephyr build system. As an extension to the system DT specification, sysdtlib supports an 'address-map-secure' property that can be selected for Arm v8-M CPUs with TrustZone support. This is a separate address map that can be used for the different peripheral addressing that is typically found on such CPUs when the CPU is in the secure state. This extension is backwards compatible with the existing system DT spec in the sense that existing system devicetrees with a single address map property will not need to change to adopt this extension. We'll work on upstreaming this into the system DT spec in parallel. Signed-off-by: Martí Bolívar --- .../src/devicetree/__init__.py | 2 +- .../src/devicetree/sysdtlib.py | 640 ++++++++++++++++++ 2 files changed, 641 insertions(+), 1 deletion(-) create mode 100644 scripts/dts/python-devicetree/src/devicetree/sysdtlib.py diff --git a/scripts/dts/python-devicetree/src/devicetree/__init__.py b/scripts/dts/python-devicetree/src/devicetree/__init__.py index e9a568330b68..01fef5e4971a 100644 --- a/scripts/dts/python-devicetree/src/devicetree/__init__.py +++ b/scripts/dts/python-devicetree/src/devicetree/__init__.py @@ -1,4 +1,4 @@ # Copyright (c) 2021 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 -__all__ = ['edtlib', 'dtlib'] +__all__ = ['edtlib', 'dtlib', 'sysdtlib'] diff --git a/scripts/dts/python-devicetree/src/devicetree/sysdtlib.py b/scripts/dts/python-devicetree/src/devicetree/sysdtlib.py new file mode 100644 index 000000000000..82d6da2c67c3 --- /dev/null +++ b/scripts/dts/python-devicetree/src/devicetree/sysdtlib.py @@ -0,0 +1,640 @@ +# Copyright (c) 2022-2023, Nordic Semiconductor ASA +# SPDX-License-Identifier: BSD-3-Clause + +""" +A library for extracting regular devicetrees from system devicetree (SDT) +files. + +See the specification in the lopper repository for more information on +system devicetree: https://github.com/devicetree-org/lopper +""" + +from __future__ import annotations + +from copy import deepcopy +from dataclasses import dataclass +from typing import Iterable, NoReturn, Optional, TYPE_CHECKING +import logging + +from devicetree.dtlib import DT, DTError, Node, Property, Register, to_num +from devicetree.dtlib import Type as PropType +from devicetree._private import _slice_helper + +class SysDTError(Exception): + "Exception raised for system devicetree-related errors" + +class SysDT(DT): + """ + Represents a system devicetree. + + The main user-facing API is the convert_to_dt() method. + """ + + def __init__(self, *args, **kwargs): + # TODO: support /omit-if-no-ref/ "correctly" + # + # It's not clear what the semantics should be. Options: + # + # 1. Propagate /omit-if-no-ref/ annotations + # to the result, so the final DTS can add more? + # + # 2. Apply /omit-if-no-ref/ twice: once + # at system DT level, and again at DT level? + # + # 3. Do something else? + # + # Defer this decision until we have more use cases analyzed. + super().__init__(*args, **kwargs) + + def convert_to_dt(self, domain_path: str) -> DT: + """ + Convert this system devicetree to a regular DT object by + selecting an execution domain of interest. The CPU cluster, + its address map, and the CPU mask are inferred from the cpus + property in the execution domain node. + + domain_path: + Path to the execution domain node. Its compatible property + must include the value "openamp,domain-v1". + """ + # TODO + # + # - support or explicitly disallow the following standard + # properties: + # + # - #memory-flags-cells + # - memory + # - #sram-flags-cells + # - sram + # - #memory-implicit-default-cells + # - memory-implicit-default + # - #sram-implicit-default-cells + # - sram-implicit-default + + dt = deepcopy(self) + + domain = Domain(dt.get_node(domain_path)) + self._setup_cpus_for(dt, domain) + self._restrict_dt_to(dt, domain) + self._clean_up(domain) + + return dt + + def _setup_cpus_for(self, dt: DT, domain: Domain): + # Destructively modify the devicetree to just the CPUs requested + # for the given domain: + # + # - mask out any CPUs the domain doesn't use + # - move the domain's CPU cluster to /cpus + + # We don't expect a default cluster to exist. (We're about to + # move the selected cpu cluster to /cpus, which will error out + # if it already exists). If we need to make this more robust + # later, we can. + if dt.has_node('/cpus'): + _err("a /cpus node already exists: default CPU clusters " + "are currently unsupported") + + # Delete any masked-out CPUs from the tree; the final + # devicetree should not know about them. + domain_cpu_set = set(domain.cpus) + for cpu in domain.cpu_cluster.cpus: + if cpu not in domain_cpu_set: + self._delete_node(cpu) + + # Move the remaining nodes in the domain's CPU cluster to + # /cpus, where they are expected in a standard devicetree. + dt.move_node(domain.cpu_cluster.node, '/cpus') + + def _restrict_dt_to(self, dt: DT, domain: Domain): + # Destructively modify the devicetree to remove hardware + # resources that aren't visible to the given domain: + # + # - convert indirect-bus nodes to simple-bus nodes + # (since the domain's CPU cluster is already /cpus) + # - rewrite resource addresses to match the domain's + # address map + + mapped_nodes: set[Node] = set() + for entry in domain.address_map: + _apply_map_entry(domain, entry, mapped_nodes) + + if not mapped_nodes: + _err(f"domain '{domain.node.path}' has an empty address map") + + # Delete any indirect-bus nodes that we didn't end up referencing. + # Delete every simple-bus node, because we can't reference those + # unless they are explicitly mapped in. + # list() avoids errors from modification during iteration. + referenced_buses = [entry.ref_node for entry in domain.address_map] + for bus_node in referenced_buses: + if 'indirect-bus' not in _compatible(bus_node): + raise NotImplementedError('non-indirect-bus nodes in an address map') # TODO + for node in list(dt.node_iter()): + compats = set(_compatible(node)) + if 'indirect-bus' in compats and node not in referenced_buses: + self._delete_node(node) + elif 'simple-bus' in compats: + self._delete_node(node) + + # Delete any children of an indirect-bus that we didn't end up + # referencing. + for bus_node in referenced_buses: + for child_node in bus_node.nodes.values(): + if child_node.nodes: + raise NotImplementedError('non-leaf nodes underneath indirect buses') # TODO + if child_node not in mapped_nodes: + self._delete_node(child_node) + + # Move the domain's chosen and move reserved-memory to top level. + domain_nodes = domain.node.nodes + if 'chosen' in domain_nodes: + dt.move_node(domain_nodes['chosen'], '/chosen') + if 'reserved-memory' in domain_nodes: + dt.move_node(domain_nodes['reserved-memory'], '/reserved-memory') + + # Convert each indirect-bus to a simple-bus now that all the + # nodes inside have their correct addresses. + for bus_node in referenced_buses: + compatible = bus_node.props['compatible'] + if compatible.offset_labels: + # There doesn't seem to be a use case for an internal label + # within this property value. Forbid it so that the following + # reset to the compatible value doesn't drop any labels. + _err(f"indirect bus node '{bus_node.path}' compatible " + 'property value may not contain internal labels') + compatible.value = compatible.value.replace(b'indirect-bus', + b'simple-bus') + + @staticmethod + def _delete_node(node): + # TODO add and use a new dtlib public API for deletion + # instead of using this private node method. + # + # - ensure we handle the subtree rooted at 'node' + # - make sure we error out on dangling phandles + node._del() + + @staticmethod + def _clean_up(domain: Domain) -> None: + # Any address-map properties are a system devicetree + # extension. Remove them before emitting the DT in case + # another address map referred to resources that are + # no longer visible to us. + cluster = domain.cpu_cluster.node + for prop in list(cluster.props): + if prop.startswith('address-map-') or prop == 'address-map': + del cluster.props[prop] + +class CpuCluster: + '''Represents a CPU cluster in the system devicetree. + + These attributes are available on CpuCluster instances: + + node: + The dtlib.Node (with compatible "cpus,cluster") representing + the cluster itself. + + cpus: + A list of dtlib.Node, one for each CPU in the cluster. + A node is considered a CPU if its name starts with 'cpu@' + or if it has a device_type property set to 'cpu'. + + ranges_address_cells: + The value of the #ranges-address-cells property of the cluster, + as an int. + + ranges_size_cells: + The value of the #ranges-size-cells property of the cluster, + as an int. + ''' + + def __init__(self, node: Node): + self._check_node(node) + self.node = node + self.ranges_address_cells = \ + node.props['#ranges-address-cells'].to_num() + self.ranges_size_cells = \ + node.props['#ranges-size-cells'].to_num() + + @property + def cpus(self): + ''' + See the class documentation. + ''' + return [child for child in self.node.nodes.values() if + self._is_cpu_node(child)] + + @staticmethod + def _check_node(node: Node): + # Verify the node complies with the system DT spec for + # CPU cluster nodes. + + if 'cpus,cluster' not in _compatible(node): + _err(f"CPU cluster '{node.path}' must have a compatible " + 'property with "cpus,cluster" in the value') + if ('#address-cells' not in node.props or + node.props['#address-cells'].to_num() != 1): + _err(f"CPU cluster '{node.path}' must have a " + '#address-cells property set to <1>') + if ('#size-cells' not in node.props or + node.props['#size-cells'].to_num() != 0): + _err(f"CPU cluster '{node.path}' must have a " + '#size-cells property set to <0>') + if '#ranges-address-cells' not in node.props: + _err(f"CPU cluster '{node.path}' must have a " + '#ranges-address-cells property') + if '#ranges-size-cells' not in node.props: + _err(f"CPU cluster '{node.path}' must have a " + '#ranges-size-cells property') + + @staticmethod + def _is_cpu_node(node: Node): + # Does the given node look like a CPU? + + return (_to_string(node, 'device_type') == 'cpu' or + node.name.startswith('cpu@')) + + +class Domain: + '''Represents an execution domain in the system devicetree. + + These attributes are available on Domain instances: + + node: + The dtlib.Node underneath /domains corresponding to the domain. + + cpu_cluster: + A CpuCluster instance representing the CPU cluster that this + domain node's 'cpus' property points to in its cpu-cluster cell. + + cpu_mask: + The cpu-mask cell in the domain node's cpus property, as an int. + + execution_level: + The execution-level cell in the domain node's cpus property, + as an int. + + cpus: + A list of dtlib.Node instances representing the CPU nodes in + 'cpu_cluster' which are selected by 'cpu_mask'. + + address_map: + A list[AddressMapEntry] for this domain. This is normally determined + by the 'address-map' property in the domain's CPU cluster. + + As a Zephyr-specific extension to the system DT specification, we + use the 'address-map-secure' property instead on Arm v8-M processors + if execution_level has bit 31 set, indicating the domain + runs secure world firmware. (Using bit 31 in this way is analogous to + the system DT spec's binding for Cortex-R5 and Cortex-A53 CPUs.) + ''' + + # TODO: + # - finalize execution-level semantics for CPU clusters in + # architectures we are interested in + + # To handle the Zephyr extension for Arm v8-M CPU address maps. + _EXECUTION_LEVEL_ARM_V8M_SECURE = (1 << 31) + + def __init__(self, node: Node): + self._check_node(node) + self.node = node + + cpu_cluster, cpu_mask, execution_level = ( + to_num(field) for field in _slice( + node, 'cpus', 4, + '&cpu-cluster (=4 bytes) cpu-mask (=4 bytes) ' + 'execution-level (=4 bytes)')) + self.cpu_cluster = CpuCluster(node.dt.phandle2node[cpu_cluster]) + self.cpu_mask = cpu_mask + self.execution_level = execution_level + + self.cpus = [cpu for cpu in self.cpu_cluster.cpus + if self.cpu_mask & (1 << _unit_addr(cpu))] + if not self.cpus: + _err(f"domain '{node.path}' has no CPUs; " + f"check domain cpu-mask '{hex(self.cpu_mask)}' against " + f"CPU nodes in CPU cluster '{node.path}'") + + self.address_map = self._decoded_address_map() + + @staticmethod + def _check_node(node): + # Do some basic validation on the devicetree node we are being + # created from. + + # Verify requirements. + if not node.path.startswith('/domains/'): + _err(f"domain '{node.path}' must be in the subtree " + 'rooted at /domains') + + if 'openamp,domain-v1' not in _compatible(node): + _err(f"domain '{node.path}' must have a compatible " + 'property with "openamp,domain-v1" in the value') + + cpus = node.props.get('cpus') + if cpus is None: + _err(f"domain '{node.path}' is missing required property 'cpus'") + if cpus.type is not PropType.PHANDLES_AND_NUMS: + _err(f"domain '{node.path}' property 'cpus' should " + 'have the form <&cpu-cluster cpu-mask execution-level>') + if 'id' not in node.props: + err(f"domain '{node.path}' is missing required property 'id'") + + # Error out on unsupported specification features. + for prop in ['#access-flags-cells', + 'access', + '#access-implicit-default-cells', + 'access-implicit-default']: + if prop in node.props: + _err(f"domain '{node.path}' contains unsupported " + f'property {prop}') + + def _decoded_address_map(self) -> list[AddressMapEntry]: + # Decode the domain's 'address-map' property. + + dt = self.node.dt + root = dt.get_node('/') + if '#address-cells' not in root.props: + _err('the root node must have a #address-cells property set') + cluster = self.cpu_cluster + + # Number of bytes in each node-address field. + node_address_size = 4 * cluster.ranges_address_cells + # Number of bytes in each root-node-address field. + root_node_address_size = 4 * root.props['#address-cells'].to_num() + # Number of bytes in each length field. + length_size = 4 * cluster.ranges_size_cells + + # Split the address map property into quartets. + entry_size = (node_address_size + + 4 + # ref-node is a phandle + root_node_address_size + + length_size) + entries = _slice(self.cpu_cluster.node, + self._address_map_prop, + entry_size, + f'''\ +4 * #ranges-address-cells (= {node_address_size}) + \ +4 (ref-node phandle) + \ +4 * root #address-cells (= {root_node_address_size}) + \ +4 * #ranges-size-cells (= {length_size})''') + + # Enumerate the quartets + ref_node_offset = node_address_size + root_node_address_offset = ref_node_offset + 4 + length_offset = root_node_address_offset + root_node_address_size + ret = [] + for entry in entries: + ret.append(AddressMapEntry( + node_address=to_num(entry[:node_address_size]), + ref_node=dt.phandle2node[to_num(entry[ref_node_offset: + ref_node_offset + 4])], + root_node_address=to_num(entry[root_node_address_offset: + root_node_address_offset + + root_node_address_size]), + length=to_num(entry[length_offset: + length_offset + length_size]))) + + + return ret + + @property + def _address_map_prop(self): + # Get the name of the address map property to use for the + # given domain, handling Arm v8-M designs as a special case + # that may have 'address-map-secure' properties. + + first_cpu_compat = set(_compatible(self.cpus[0])) + if not all(set(_compatible(cpu)) == first_cpu_compat + for cpu in self.cpus): + _LOG.warning(f"domain '{self.node.path}' CPUs have differing " + 'compatible properties; unexpected address map ' + 'behavior may result') + + def arm_v8m_address_map(): + if self.execution_level & self._EXECUTION_LEVEL_ARM_V8M_SECURE: + return 'address-map-secure' + return 'address-map' + + # A map from CPU compatibles to a selector function for the + # appropriate cluster-level address map property to use, in + # cases where we want to be able to override the default. + # + # We're making this extensible in case other CPU families have + # similar problems with the system DT spec as it stands. + _compat2address_map_sel = { + 'arm,cortex-m23': arm_v8m_address_map, + 'arm,cortex-m33': arm_v8m_address_map, + 'arm,cortex-m33f': arm_v8m_address_map, + } + + for compat in first_cpu_compat: + if compat in _compat2address_map_sel: + return _compat2address_map_sel[compat]() + + return 'address-map' + + +@dataclass +class AddressMapEntry: + ''' + Represents an entry in a CPU cluster's address-map property + within the system devicetree. + + The following attributes are available on AddressMapEntry instances: + + node_address: + The physical address within the CPU cluster's address space + that the resources described by the entry appear in, as an int. + + ref_node: + The dtlib.Node whose resources are mapped into the CPU cluster's + address space by this entry. + + root_node_address: + The physical starting address, within the root node's address space, + of the resources whose addresses are being mapped in, as an int + + length: + The size of the address range being mapped into the CPU cluster's + address space, in bytes, as an int. + ''' + + node_address: int + ref_node: Node + root_node_address: int + length: int + + +def _err(msg: str) -> NoReturn: + raise SysDTError(msg) + + +def _slice(node: Node, prop_name: str, size: int, size_hint: str): + return _slice_helper(node, prop_name, size, size_hint, SysDTError) + + +def _to_string(node: Node, propname: str) -> Optional[str]: + prop = node.props.get(propname) + if prop is None: + return None + return prop.to_string() + + +def _to_strings(node: Node, propname: str) -> list[str]: + prop = node.props.get(propname) + if not prop: + return [] + return prop.to_strings() + + +def _compatible(node: Node) -> list[str]: + return _to_strings(node, 'compatible') + + +def _unit_addr(node: Node) -> int: + unit_addr = node.unit_addr + if not unit_addr: + _err(f"node '{node.path}' must have a unit address " + "(the name should look like 'foo@abcd1234', not 'foo')'") + + try: + return int(unit_addr, 16) + except ValueError: + _err(f"node '{node.path}' has unit address '{unit_addr}': expected " + 'a hexadecimal number without a leading 0x') + +def _apply_map_entry(domain: Domain, + entry: AddressMapEntry, + mapped_nodes: set[Node]): + # Helper for _restrict_dt_to(). Applies an address map entry to a + # devicetree node by rewriting its reg property as needed. If the + # node was in fact mapped in, it will be added to mapped_nodes. + # (If the node is already in mapped_nodes, an error is raised as + # it seems incorrect to map the same device in at two addressses + # explicitly.) + + bus_node = entry.ref_node + dt = bus_node.dt + base, length = entry.root_node_address, entry.length + end = base + length + + # TODO verify this is correct for the new regs. + cluster = domain.cpu_cluster + new_address_cells = cluster.ranges_address_cells + new_size_cells = cluster.ranges_size_cells + + if base != 0: + # The spec semantics aren't clear to me right now + raise NotImplementedError('nonzero root-node-address') # TODO + + _verify_is_indirect_bus(bus_node) + _verify_has_no_ranges(bus_node) + + def reg_has_overlap(reg: Register) -> bool: + # Checks if a register overlaps the address range + # specified by 'entry'. + if TYPE_CHECKING: + assert reg.addr is not None + return base <= reg.addr < end + + def new_address(reg: Register) -> int: + if TYPE_CHECKING: + assert reg.addr is not None + return entry.node_address + base + reg.addr + + def new_size(reg: Register) -> int: + # Clamp a register block's size to fit within + # the span given by 'entry'. + if TYPE_CHECKING: + assert reg.addr is not None and reg.size is not None + if reg.addr + reg.size >= end: # check depends on base==0! + # The specification says these are the semantics, + # but it's probably a mistake on the DT author's part. + _LOG.warning( + f"node '{reg.node.path}' register blocks " + 'were clamped to fit in the CPU cluster address map; ' + 'this may be a misconfigured address map') + return length - reg.addr + return reg.size + + # Keeping track of what to move and doing it later prevents + # errors due to modifying the tree while iterating over it. + movements: dict[Node, str] = {} + for child_node in bus_node.node_iter(): + if child_node is bus_node: # skip the bus node itself + continue + _verify_has_no_ranges(child_node) + if child_node.parent is not bus_node: + raise NotImplementedError('non-leaf nodes underneath indirect buses') # TODO + + if 'reg' not in child_node.props: + # TODO verify address-map semantics for nodes without 'reg' + # + # It seems important to be able to map them in for + # things like fixed-clock nodes, so just assume + # they are all part of the address map for now. + mapped_nodes.add(child_node) + continue + + # Convert reg properties and unit addresses in the + # indirect bus to the domain's address map. + # TODO: support multiple register blocks per node + + regs = child_node.regs + + for reg in regs: + if reg.addr is None or reg.size is None: + # This will affect reg_has_overlap(), new_address(), + # new_size() above. + raise NotImplementedError('how to handle this?') # TODO + + new_regs_ints = [(new_address(reg), new_size(reg)) + for reg in regs if reg_has_overlap(reg)] + if not new_regs_ints: + # Node's registers don't fit in the mapped entry. + # We'll delete it later if we don't find another + # entry that does map it in. + continue + + if child_node in mapped_nodes: + # It seems doubtful that we need to specify concrete + # semantics here, so let's just error out for now. + # We can always handle this case later if needed. + _err(f"node '{child_node.path}' appears in multiple " + "address map entries for CPU cluster " + f"'{cluster.node.path}', which " + "was selected by execution domain " + f"'{domain.node.path}'") + + child_node.props['reg'].value = b''.join( + addr.to_bytes(new_address_cells*4, 'big') + + size.to_bytes(new_size_cells*4, 'big') + for (addr, size) in new_regs_ints + ) + new_reg = child_node.regs[0] + if TYPE_CHECKING: + assert new_reg.addr is not None + + basename = child_node.name.partition('@')[0] + new_unit_addr = hex(new_reg.addr)[2:] + new_name = f'{basename}@{new_unit_addr}' + movements[child_node] = f'{child_node.parent.path}/{new_name}' + mapped_nodes.add(child_node) + + for node, new_path in movements.items(): + dt.move_node(node, new_path) + +def _verify_is_indirect_bus(node: Node): + # Need support for mapping in simple-bus and device nodes directly. + if 'indirect-bus' not in _compatible(node): + raise NotImplementedError('non-indirect-bus nodes in an address map') # TODO + +def _verify_has_no_ranges(node: Node): + if 'ranges' in node.props: + raise NotImplementedError("'ranges' within indirect-bus node hierarchies") # TODO + +# Logging object +_LOG = logging.getLogger(__name__) From 1201e2c3c64c3ec373753610d70f61f545d619aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Wed, 28 Sep 2022 01:11:03 -0700 Subject: [PATCH 10/16] scripts: dts: add gen_dtsi_from_sysdts.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a tiny glue script between the devicetree.sysdtlib Python module and the build system's dts.cmake CMake module. It can be used to convert a system devicetree (.sysdts) to a standard devicetree (.dts) file for use by the pre-existing "standard" devicetree tooling. The generated .dts file can be used as "normal". Signed-off-by: Martí Bolívar --- scripts/dts/gen_dtsi_from_sysdts.py | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 scripts/dts/gen_dtsi_from_sysdts.py diff --git a/scripts/dts/gen_dtsi_from_sysdts.py b/scripts/dts/gen_dtsi_from_sysdts.py new file mode 100755 index 000000000000..6e3dc73d520c --- /dev/null +++ b/scripts/dts/gen_dtsi_from_sysdts.py @@ -0,0 +1,59 @@ +# Copyright (c) 2022, 2023 Nordic Semiconductor ASA + +''' +Helper script that converts a system DT to a +'regular' devicetree include file using devicetree.sysdtlib. +''' + +from pathlib import Path +import argparse +import os +import subprocess +import sys +from pathlib import Path +import pickle + +HERE = Path(__file__).parent + +sys.path.insert(0, str(HERE / 'python-devicetree' / 'src')) + +from devicetree import sysdtlib +import yaml + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument('--sysdts', required=True, + help='path to the system devicetree file') + parser.add_argument('--domain', required=True, + help='''path to the execution domain in the + system devicetree which selects the CPUs, + etc that should appear in the generated + devicetree''') + parser.add_argument('--sysdt-pickle-out', dest='sysdt_pickle', + help='''if given, saves the sysdtlib.SysDT to this + file in pickle format''') + parser.add_argument('--dts-out', dest='dts_out', required=True, + help='path to the system devicetree file') + return parser.parse_args() + +def main(): + args = parse_args() + sysdt = sysdtlib.SysDT(args.sysdts) + + if args.sysdt_pickle: + with open(args.sysdt_pickle, 'wb') as f: + # Pickle protocol version 4 is the default as of Python + # 3.8, so it is both available and recommended on all + # versions of Python that Zephyr supports. + # + # Using a common protocol version here will hopefully avoid + # reproducibility issues in different Python installations. + pickle.dump(sysdt, f, protocol=4) + + dt = sysdt.convert_to_dt(args.domain) + + with open(args.dts_out, 'w') as f: + f.write(f"{dt}") + +if __name__ == '__main__': + main() From 5c5f2150dd323e3503ec8d9b33fd19561cd6187f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Fri, 3 Feb 2023 15:38:53 -0800 Subject: [PATCH 11/16] include: dt-bindings: add sys/armv8-m.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This contains system devicetree definitions for Arm v8-M CPUs. Signed-off-by: Martí Bolívar --- include/zephyr/dt-bindings/sys/armv8-m.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 include/zephyr/dt-bindings/sys/armv8-m.h diff --git a/include/zephyr/dt-bindings/sys/armv8-m.h b/include/zephyr/dt-bindings/sys/armv8-m.h new file mode 100644 index 000000000000..1716db1a8479 --- /dev/null +++ b/include/zephyr/dt-bindings/sys/armv8-m.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* System devicetree definitions for Arm v8-M CPUs. */ + +#ifndef ZEPHYR_INCLUDE_DT_BINDINGS_SYS_ARMV8_M_H_ +#define ZEPHYR_INCLUDE_DT_BINDINGS_SYS_ARMV8_M_H_ + +/** + * Execution domain execution-level cell + * for the CPU's secure state + */ +#define EXECUTION_LEVEL_SECURE (1U << 31) + +/** + * Execution domain execution-level cell + * for the CPU's non-secure state + */ +#define EXECUTION_LEVEL_NONSECURE (0U << 31) + +#endif From 335d290f304963a67b3e99746ae8e3193b7368bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 15 Nov 2022 14:58:33 -0800 Subject: [PATCH 12/16] [WIP] dts: add system DTSI for Arm MPS2 AN521 SoC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: add interrupts-extended to map nodes to NVICs This extracts all of the common information in boards/arm/mps2_an521 into a new separate system devicetree include file (.sysdtsi) for the SoC. The intention here is that any "board" using this SoC should include this .sysdtsi file in that board's system devicetree file, i.e. .sysdts should look like this: #include /* board-specific system DTS contents go here ... */ A board definition using this system devicetree is in a separate commit. Signed-off-by: Martí Bolívar --- dts/sys/arm/mps2_an521.sysdtsi | 362 +++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 dts/sys/arm/mps2_an521.sysdtsi diff --git a/dts/sys/arm/mps2_an521.sysdtsi b/dts/sys/arm/mps2_an521.sysdtsi new file mode 100644 index 000000000000..d7b22fdf1688 --- /dev/null +++ b/dts/sys/arm/mps2_an521.sysdtsi @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2018-2019 Linaro Limited + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This is a system devicetree for the soft macro model SoC documented here: + * + * https://developer.arm.com/-/media/Arm%20Developer%20Community/PDF/DAI0521A_example_sse200_subsystem_for_v2m_mps2.pdf?revision=5e9e0f9b-d4b0-41d7-9ac6-5d0eee58003e&hash=29C404128810F8031E740EE0A6ED3E97 + */ + +#include +#include + +/ { + compatible = "arm,mps2-an521"; + #address-cells = <1>; + #size-cells = <1>; + + cpu0: cpu0-cluster { + /* CPU0 / "main" CPU cluster */ + compatible = "cpus,cluster"; + #address-cells = <1>; + #size-cells = <0>; + #ranges-address-cells = <1>; + #ranges-size-cells = <1>; + + /* Non-secure address map. */ + address-map = + <0x0 &soc 0x0 0xffffffff>, + <0x0 &flash_sram 0x0 0xffffffff>, + <0x40000000 &peripherals 0x0 0x10000000>, + <0xe0000000 &cpu0_ppb 0x0 0xffffffff>; + /* Secure address map. */ + address-map-secure = + <0x0 &soc 0x0 0xffffffff>, + <0x10000000 &flash_sram 0x0 0xffffffff>, + <0x50000000 &peripherals 0x0 0x10000000>, + <0xe0000000 &cpu0_ppb 0x0 0xffffffff>; + + cpu@0 { + #address-cells = <1>; + #size-cells = <1>; + device_type = "cpu"; + compatible = "arm,cortex-m33f"; + reg = <0>; + + cpu0_mpu: mpu@e000ed90 { + compatible = "arm,armv8m-mpu"; + reg = <0xe000ed90 0x40>; + arm,num-mpu-regions = <16>; + }; + }; + }; + + cpu1: cpu1-cluster { + /* CPU1 / "remote" CPU cluster */ + compatible = "cpus,cluster"; + #address-cells = <1>; + #size-cells = <0>; + #ranges-address-cells = <1>; + #ranges-size-cells = <1>; + + address-map = + <0x0 &soc 0x0 0xffffffff>, + <0x0 &flash_sram 0x0 0xffffffff>, + <0x40000000 &peripherals 0x0 0x10000000>, + <0xe0000000 &cpu0_ppb 0x0 0xffffffff>; + + cpu@0 { + #address-cells = <1>; + #size-cells = <1>; + device_type = "cpu"; + compatible = "arm,cortex-m33f"; + reg = <0>; + + mpu: mpu@e000ed90 { + compatible = "arm,armv8m-mpu"; + reg = <0xe000ed90 0x40>; + arm,num-mpu-regions = <16>; + }; + }; + }; + + /* + * Memories + * + * The memory regions defined below are according to AN521: + * https://documentation-service.arm.com/static/5fa12fe9b1a7c5445f29017f + * + * Please see tables mentioned in individual comments below for details. + * Alternate rows mentioned in parentheses are for the secure aliases. + */ + + flash_sram: flash-sram { + compatible = "indirect-bus"; + #address-cells = <1>; + #size-cells = <1>; + + /* Table 3-2, row 1 (or row 6). */ + ssram1: sram@0 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x0 DT_SIZE_M(4)>; + zephyr,memory-region = "SSRAM1"; + }; + + /* Table 3-4, rows 8 and 9 (or 16 and 17). */ + ssram2_3: sram@28000000 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x28000000 DT_SIZE_M(4)>; + zephyr,memory-region = "SSRAM2_3"; + }; + }; + + psram: memory@80000000 { + /* Table 3-6, row 1. */ + device_type = "memory"; + reg = <0x80000000 DT_SIZE_M(16)>; + }; + + /* Peripherals */ + + soc: soc { + compatible = "arm,mps2-an521", "indirect-bus"; + #address-cells = <1>; + #size-cells = <1>; + + sysclk: system-clock { + compatible = "fixed-clock"; + clock-frequency = <25000000>; + #clock-cells = <0>; + }; + }; + + peripherals: peripherals { + compatible = "indirect-bus"; + #address-cells = <1>; + #size-cells = <1>; + + timer0: timer@0 { + compatible = "arm,cmsdk-timer"; + reg = <0x0 0x1000>; + interrupts = <3 3>; + }; + + timer1: timer@1000 { + compatible = "arm,cmsdk-timer"; + reg = <0x1000 0x1000>; + interrupts = <4 3>; + }; + + dtimer0: dtimer@2000 { + compatible = "arm,cmsdk-dtimer"; + reg = <0x2000 0x1000>; + interrupts = <5 3>; + }; + + mhu0: mhu@3000 { + compatible = "arm,mhu"; + reg = <0x3000 0x1000>; + interrupts = <6 3>; + }; + + mhu1: mhu@4000 { + compatible = "arm,mhu"; + reg = <0x4000 0x1000>; + interrupts = <7 3>; + }; + + gpio0: gpio@100000 { + compatible = "arm,cmsdk-gpio"; + reg = <0x100000 0x1000>; + interrupts = <68 3>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio1: gpio@101000 { + compatible = "arm,cmsdk-gpio"; + reg = <0x101000 0x1000>; + interrupts = <69 3>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio2: gpio@102000 { + compatible = "arm,cmsdk-gpio"; + reg = <0x102000 0x1000>; + interrupts = <70 3>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio3: gpio@103000 { + compatible = "arm,cmsdk-gpio"; + reg = <0x103000 0x1000>; + interrupts = <71 3>; + gpio-controller; + #gpio-cells = <2>; + }; + + wdog0: wdog@81000 { + compatible = "arm,cmsdk-watchdog"; + reg = <0x81000 0x1000>; + clocks = <&sysclk>; + }; + + uart0: uart@200000 { + compatible = "arm,cmsdk-uart"; + reg = <0x200000 0x1000>; + interrupts = <33 3 32 3>; + interrupt-names = "tx", "rx"; + clocks = <&sysclk>; + current-speed = <115200>; + }; + + uart1: uart@201000 { + compatible = "arm,cmsdk-uart"; + reg = <0x201000 0x1000>; + interrupts = <35 3 34 3>; + interrupt-names = "tx", "rx"; + clocks = <&sysclk>; + current-speed = <115200>; + }; + + uart2: uart@202000 { + compatible = "arm,cmsdk-uart"; + reg = <0x202000 0x1000>; + interrupts = <37 3 36 3>; + interrupt-names = "tx", "rx"; + clocks = <&sysclk>; + current-speed = <115200>; + }; + + uart3: uart@203000 { + compatible = "arm,cmsdk-uart"; + reg = <0x203000 0x1000>; + interrupts = <39 3 38 3>; + interrupt-names = "tx", "rx"; + clocks = <&sysclk>; + current-speed = <115200>; + }; + + uart4: uart@204000 { + compatible = "arm,cmsdk-uart"; + reg = <0x204000 0x1000>; + interrupts = <41 3 40 3>; + interrupt-names = "tx", "rx"; + clocks = <&sysclk>; + current-speed = <115200>; + }; + + i2c_touch: i2c@207000 { + compatible = "arm,versatile-i2c"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x207000 0x1000>; + }; + + i2c_audio_conf: i2c@208000 { + compatible = "arm,versatile-i2c"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x208000 0x1000>; + }; + + i2c_shield0: i2c@20c000 { + compatible = "arm,versatile-i2c"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x20c000 0x1000>; + }; + + i2c_shield1: i2c@20d000 { + compatible = "arm,versatile-i2c"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x20d000 0x1000>; + }; + + gpio_led0: mps2_fpgaio@302000 { + compatible = "arm,mps2-fpgaio-gpio"; + + reg = <0x302000 0x4>; + gpio-controller; + #gpio-cells = <1>; + ngpios = <2>; + }; + + gpio_button: mps2_fpgaio@302008 { + compatible = "arm,mps2-fpgaio-gpio"; + + reg = <0x302008 0x4>; + gpio-controller; + #gpio-cells = <1>; + ngpios = <2>; + }; + + gpio_misc: mps2_fpgaio@30204c { + compatible = "arm,mps2-fpgaio-gpio"; + + reg = <0x30204c 0x4>; + gpio-controller; + #gpio-cells = <1>; + ngpios = <10>; + }; + + eth0: eth@2000000 { + /* Linux has "smsc,lan9115" */ + compatible = "smsc,lan9220"; + /* Actual reg range is ~0x200 */ + reg = <0x2000000 0x100000>; + interrupts = <48 3>; + }; + }; + + cpu0_ppb: cpu0-ppb { + compatible = "indirect-bus"; + #address-cells = <1>; + #size-cells = <1>; + + cpu0_nvic: interrupt-controller@e100 { + #address-cells = <1>; + compatible = "arm,v8m-nvic"; + reg = <0xe100 0xc00>; + interrupt-controller; + #interrupt-cells = <2>; + arm,num-irq-priority-bits = <3>; + }; + + cpu0_systick: timer@e010 { + compatible = "arm,armv8m-systick"; + reg = <0xe010 0x10>; + }; + }; + + cpu1_ppb: cpu1-ppb { + compatible = "indirect-bus"; + #address-cells = <1>; + #size-cells = <1>; + + cpu1_nvic: interrupt-controller@e100 { + #address-cells = <1>; + compatible = "arm,v8m-nvic"; + reg = <0xe100 0xc00>; + interrupt-controller; + #interrupt-cells = <2>; + arm,num-irq-priority-bits = <3>; + }; + + cpu1_systick: timer@e010 { + compatible = "arm,armv8m-systick"; + reg = <0xe010 0x10>; + }; + }; +}; From 71a0f0a38d559143493c423b244f88310b60c59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Tue, 15 Nov 2022 14:58:47 -0800 Subject: [PATCH 13/16] [WIP] boards: arm: add sysdt_mps2_an521 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: - add TF-M, mcuboot domains - boot on cpu0 ns once TF-M domain is available - boot on cpu1 This is a system devicetree-based board for the Arm MPS2 AN521, which should be equivalent to the various files in boards/arm/mps2_an521. Signed-off-by: Martí Bolívar --- boards/arm/sysdt_mps2_an521/Kconfig.board | 21 ++ boards/arm/sysdt_mps2_an521/Kconfig.defconfig | 32 ++++ boards/arm/sysdt_mps2_an521/board.cmake | 29 +++ .../domains/domain_cpu0.overlay | 16 ++ .../domains/domain_cpu0_defconfig | 24 +++ .../domains/domain_cpu0_ns_defconfig | 22 +++ .../domains/domain_cpu1_defconfig | 21 ++ .../sysdt_mps2_an521/sysdt_mps2_an521.sysdts | 181 ++++++++++++++++++ .../sysdt_mps2_an521/sysdt_mps2_an521.yaml | 5 + 9 files changed, 351 insertions(+) create mode 100644 boards/arm/sysdt_mps2_an521/Kconfig.board create mode 100644 boards/arm/sysdt_mps2_an521/Kconfig.defconfig create mode 100644 boards/arm/sysdt_mps2_an521/board.cmake create mode 100644 boards/arm/sysdt_mps2_an521/domains/domain_cpu0.overlay create mode 100644 boards/arm/sysdt_mps2_an521/domains/domain_cpu0_defconfig create mode 100644 boards/arm/sysdt_mps2_an521/domains/domain_cpu0_ns_defconfig create mode 100644 boards/arm/sysdt_mps2_an521/domains/domain_cpu1_defconfig create mode 100644 boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.sysdts create mode 100644 boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.yaml diff --git a/boards/arm/sysdt_mps2_an521/Kconfig.board b/boards/arm/sysdt_mps2_an521/Kconfig.board new file mode 100644 index 000000000000..532f76489c02 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/Kconfig.board @@ -0,0 +1,21 @@ +# Copyright (c) 2018-2019 Linaro Limited +# Copyright (c) 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BOARD_SYSDT_MPS2_AN521_CPU0 + bool "ARM Cortex-M33 SMM on V2M-MPS2 (AN521) (CPU0)" + depends on SOC_MPS2_AN521_CPU0 + select QEMU_TARGET + select HAS_COVERAGE_SUPPORT + +config BOARD_SYSDT_MPS2_AN521_CPU0_NS + bool "ARM Cortex-M33 SMM on V2M-MPS2 (AN521) (CPU0 Non-Secure)" + depends on SOC_MPS2_AN521_CPU0 + select QEMU_TARGET + select HAS_COVERAGE_SUPPORT + +config BOARD_SYSDT_MPS2_AN521_CPU1 + bool "ARM Cortex-M33 SMM on V2M-MPS2 (AN521) CPU1" + depends on SOC_MPS2_AN521_CPU1 + select QEMU_TARGET + select HAS_COVERAGE_SUPPORT diff --git a/boards/arm/sysdt_mps2_an521/Kconfig.defconfig b/boards/arm/sysdt_mps2_an521/Kconfig.defconfig new file mode 100644 index 000000000000..db2b20c0d489 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/Kconfig.defconfig @@ -0,0 +1,32 @@ +# Copyright (c) 2018-2019 Linaro Limited +# Copyright (c) 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if BOARD_SYSDT_MPS2_AN521_CPU0 || BOARD_SYSDT_MPS2_AN521_CPU0_NS || BOARD_SYSDT_MPS2_AN521_CPU1 + +# MPU-based null-pointer dereferencing detection cannot +# be applied as the (0x0 - 0x400) is unmapped but QEMU +# will still permit bus access. +choice NULL_POINTER_EXCEPTION_DETECTION + bool + default NULL_POINTER_EXCEPTION_DETECTION_NONE if QEMU_TARGET +endchoice + +config BOARD + default "mps2_an521_ns" if TRUSTED_EXECUTION_NONSECURE + default "mps2_an521_remote" if BOARD_SYSDT_MPS2_AN521_CPU1 + default "mps2_an521" + +# By default, if we build for a Non-Secure version of the board, +# force building with TF-M as the Secure Execution Environment. +config BUILD_WITH_TFM + default y if TRUSTED_EXECUTION_NONSECURE + +if SERIAL + +config UART_INTERRUPT_DRIVEN + default y + +endif # SERIAL + +endif diff --git a/boards/arm/sysdt_mps2_an521/board.cmake b/boards/arm/sysdt_mps2_an521/board.cmake new file mode 100644 index 000000000000..95ba03bd35e7 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/board.cmake @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(SUPPORTED_EMU_PLATFORMS qemu) + +set(QEMU_CPU_TYPE_${ARCH} cortex-m33) +set(QEMU_FLAGS_${ARCH} + -cpu ${QEMU_CPU_TYPE_${ARCH}} + -machine mps2-an521 + -nographic + -m 16 + -vga none + ) +board_set_debugger_ifnset(qemu) + + # To enable a host tty switch between serial and pty + # -chardev serial,path=/dev/ttyS0,id=hostS0 +list(APPEND QEMU_EXTRA_FLAGS -chardev pty,id=hostS0 -serial chardev:hostS0) + +if (CONFIG_BUILD_WITH_TFM) + # Override the binary used by qemu, to use the combined + # TF-M (Secure) & Zephyr (Non Secure) image (when running + # in-tree tests). + set(QEMU_KERNEL_OPTION "-device;loader,file=${CMAKE_BINARY_DIR}/tfm_merged.hex") + +elseif (CONFIG_SOC_MPS2_AN521_CPU1) + set(CPU0_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/zephyr/boards/arm/sysdt_mps2_an521/empty_cpu0-prefix/src/empty_cpu0-build/zephyr) + set(QEMU_KERNEL_OPTION "-device;loader,file=${CPU0_BINARY_DIR}/zephyr.elf") + list(APPEND QEMU_EXTRA_FLAGS "-device;loader,file=${PROJECT_BINARY_DIR}/${KERNEL_ELF_NAME}") +endif() diff --git a/boards/arm/sysdt_mps2_an521/domains/domain_cpu0.overlay b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0.overlay new file mode 100644 index 000000000000..20820970c584 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0.overlay @@ -0,0 +1,16 @@ +/* HACK + * + * This hack is just in place until we have better system devicetree + * support for the interrupts-extended property, so that we can + * filter out interrupt parents which aren't relevant after converting + * to a regular devicetree. + * + * We add an overlay to fix up the interrupt-parent as needed for + * interrupt generating nodes. + */ + +/ { + peripherals { + interrupt-parent = <&cpu0_nvic>; + }; +}; diff --git a/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_defconfig b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_defconfig new file mode 100644 index 000000000000..0f5e2d8b05e1 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_defconfig @@ -0,0 +1,24 @@ +# +# Copyright (c) 2018-2019 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_SOC_SERIES_MPS2=y +CONFIG_SOC_MPS2_AN521_CPU0=y +CONFIG_BOARD_SYSDT_MPS2_AN521_CPU0=y +CONFIG_RUNTIME_NMI=y +CONFIG_ARM_TRUSTZONE_M=y +CONFIG_ARM_MPU=y +CONFIG_QEMU_ICOUNT_SHIFT=7 + +# GPIOs +CONFIG_GPIO=y + +# Serial +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y + +# Build a Secure firmware image +CONFIG_TRUSTED_EXECUTION_SECURE=y diff --git a/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_ns_defconfig b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_ns_defconfig new file mode 100644 index 000000000000..e5e958eb4194 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/domains/domain_cpu0_ns_defconfig @@ -0,0 +1,22 @@ +# +# Copyright (c) 2018-2019 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_SOC_SERIES_MPS2=y +CONFIG_SOC_MPS2_AN521_CPU0=y +CONFIG_BOARD_SYSDT_MPS2_AN521_CPU0_NS=y +CONFIG_ARM_TRUSTZONE_M=y +CONFIG_RUNTIME_NMI=y +CONFIG_TRUSTED_EXECUTION_NONSECURE=y +CONFIG_ARM_MPU=y +CONFIG_QEMU_ICOUNT_SHIFT=6 + +# GPIOs +CONFIG_GPIO=y + +# Serial +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y diff --git a/boards/arm/sysdt_mps2_an521/domains/domain_cpu1_defconfig b/boards/arm/sysdt_mps2_an521/domains/domain_cpu1_defconfig new file mode 100644 index 000000000000..b0f679193026 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/domains/domain_cpu1_defconfig @@ -0,0 +1,21 @@ +# +# Copyright (c) 2018-2019 Linaro Limited +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_SOC_SERIES_MPS2=y +CONFIG_SOC_MPS2_AN521_CPU1=y +CONFIG_BOARD_SYSDT_MPS2_AN521_CPU1=y +CONFIG_RUNTIME_NMI=y +CONFIG_ARM_MPU=y +CONFIG_QEMU_ICOUNT_SHIFT=7 + +# GPIOs +CONFIG_GPIO=y + +# Serial +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y diff --git a/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.sysdts b/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.sysdts new file mode 100644 index 000000000000..ca0ac15d9cd0 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.sysdts @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018-2019 Linaro Limited + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file is a system devicetree for the Arm MPS2 AN521 board. + * It pulls in the system devicetree include file (.sysdtsi) for + * the SoC, and provides board-level details that are common to + * all CPU clusters. + */ + +/dts-v1/; + +#include +#include + +/ { + aliases { + led0 = &led_0; + led1 = &led_1; + sw0 = &button_0; + sw1 = &button_1; + }; + + leds { + compatible = "gpio-leds"; + led_0: led-0 { + gpios = <&gpio_led0 0>; + label = "USERLED0"; + }; + led_1: led-1 { + gpios = <&gpio_led0 1>; + label = "USERLED1"; + }; + }; + + buttons { + compatible = "gpio-keys"; + button_0: button-0 { + label = "USERPB0"; + gpios = <&gpio_button 0>; + }; + button_1: button-1 { + label = "USERPB1"; + gpios = <&gpio_button 1>; + }; + }; + + domains { + /* TODO: + * + * - MCUboot + * - TF-M + */ + + /* Notes: + * + * - 'id' properties are currently required by + * the system DT spec, but zephyr isn't using them, + * so just ignore them. It's not clear what they're + * for, anyway. + * + * - 'memory', 'sram', etc properties are optional + * and not how zephyr is set up to configure application + * memory regions, so we don't use them. + * + * - For now, the defconfig for each domain is hard-coded + * to domains_{domain_name}_defconfig in the board + * directory. + * + * For example, domain 'cpu0_ns' has defconfig + * 'domain_cpu0_ns_defconfig'. Applying this to domains + * like MCUboot and TF-M will take some more build system + * engineering, but it's enough for a demo. + */ + + domain_cpu0 { + compatible = "openamp,domain-v1"; + cpus = <&cpu0 0x1 EXECUTION_LEVEL_SECURE>; + id = <1>; + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,flash = &cpu0_flash; + zephyr,sram = &cpu0_sram; + }; + }; + domain_cpu0_ns { + compatible = "openamp,domain-v1"; + cpus = <&cpu0 0x1 EXECUTION_LEVEL_NONSECURE>; + id = <2>; + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,flash = &cpu0_ns_flash; + zephyr,sram = &cpu0_ns_sram; + }; + }; + domain_cpu1 { + compatible = "openamp,domain-v1"; + cpus = <&cpu1 0x1 EXECUTION_LEVEL_NONSECURE>; + id = <3>; + chosen { + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,flash = &cpu1_flash; + zephyr,sram = &cpu1_sram; + }; + }; + }; + + memory-partitions { + /* + * The default memory partitions for different firmware + * configurations for this board are all defined in child nodes. + * Individual domains can refer to them as needed. + * + * Note: we're not bothering to define a compatible for + * this node, because our devicetree python tooling + * will generate the appropriate C macros for + * properties like 'reg' that are defined in the + * DT specification, and it seems likely that we'll + * need to revisit this approach at some point. + */ + #address-cells = <1>; + #size-cells = <1>; + + /* + * cpu0 secure firmware + * + * If building firmware for the secure world, + * dedicate all of the RAM to it. Be careful + * if you're also going to use cpu1 ("remote"); + * conflicts are possible. + */ + cpu0_flash: memory-partition@10000000 { + reg = <0x10000000 DT_SIZE_M(4)>; + }; + + cpu0_sram: memory-partition@38000000 { + reg = <0x38000000 DT_SIZE_M(4)>; + }; + + /* + * cpu0 non-secure firmware when running under TF-M. + * + * The memory regions defined below must match what the TF-M + * project has defined for that board - a single image boot is + * assumed. Please see the memory layout in: + * + * https://git.trustedfirmware.org/TF-M/trusted-firmware-m.git/tree/platform/ext/target/mps2/an521/partition/flash_layout.h + */ + + cpu0_ns_flash: memory-partition@100000 { + reg = <0x00100000 DT_SIZE_K(512)>; + }; + + cpu0_ns_sram: memory-partition@28100000 { + reg = <0x28100000 DT_SIZE_K(512)>; + }; + + /* + * cpu1 firmware + * + * The memory regions defined below are selected to remain + * compatible with what TF-M has defined for a single boot image, + * where 468 KB memory at the bottom of the 4 MB code region is + * marked as 'Unused'. + */ + cpu1_flash: memory-partition@38b000 { + reg = <0x0038b000 DT_SIZE_K(468)>; + }; + + cpu1_sram: memory-partition@28180000 { + reg = <0x28180000 DT_SIZE_K(512)>; + }; + }; +}; diff --git a/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.yaml b/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.yaml new file mode 100644 index 000000000000..3d3a0a4b3f45 --- /dev/null +++ b/boards/arm/sysdt_mps2_an521/sysdt_mps2_an521.yaml @@ -0,0 +1,5 @@ +identifier: sysdt_mps2_an521 +name: ARM V2M MPS2-AN521 +simulation: qemu +toolchain: + - zephyr From 1718bffcd76e8c46cb283b3afe252d518a0a9b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Fri, 24 Feb 2023 13:47:16 -0800 Subject: [PATCH 14/16] [HACK] edtlib: silence some system-DT related warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These aren't allowed vendor prefixes, but system DT is using them as such. Work around it for now. Signed-off-by: Martí Bolívar --- scripts/dts/python-devicetree/src/devicetree/edtlib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 996d4d52e77a..675a74403805 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -2898,3 +2898,7 @@ def _raw_default_property_for(name): "pl022", "pxa-mmc", "rcar_sound", "rotary-encoder", "s5m8767", "sdhci", "simple-audio-card", "st-plgpio", "st-spics", "ts", ]) + +# HACK +_VENDOR_PREFIX_ALLOWED.add('cpus') +_VENDOR_PREFIX_ALLOWED.add('openamp') From 8a65fefe3afde5a0627896ca54ea374b44f62083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Mon, 27 Feb 2023 14:34:52 -0800 Subject: [PATCH 15/16] [HACK] cmake: boards: support boards with system devicetrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The intent of this commit is to make any directory with ${BOARD}.sysdts suitable as the value of the BOARD_DIR variable. I'm unsure about the consequences of doing it this way for hardware model v1, and it's unclear to me that we even want to support system devicetree in hardware model v1, which is why I'm calling it a hack. It works for prototyping system DT support in HWMv1, though. Signed-off-by: Martí Bolívar --- cmake/modules/boards.cmake | 19 ++++++++++++++++--- scripts/list_boards.py | 12 ++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/cmake/modules/boards.cmake b/cmake/modules/boards.cmake index c9c86030693e..a2bf52775e41 100644 --- a/cmake/modules/boards.cmake +++ b/cmake/modules/boards.cmake @@ -92,11 +92,22 @@ Hints: - if in doubt, use absolute paths") endif() + # HACK: file names that signal a board implementation directory + # for BOARD. + # + # - The defconfig is the "traditional" file name; this is the + # way the build system has done it since before Zephyr used + # devicetree. Keep it for compatibility + # + # - Use the .sysdts file just to get the system DT work off the + # ground; this probably needs to be revisited in hardware model v2 + set(BOARD_FILE_NAMES ${BOARD}_defconfig ${BOARD}.sysdts) + # NB: find_path will return immediately if the output variable is # already set if (BOARD_ALIAS) find_path(BOARD_HIDDEN_DIR - NAMES ${BOARD_ALIAS}_defconfig + NAMES ${BOARD_ALIAS}_defconfig ${BOARD_ALIAS}.sysdts PATHS ${root}/boards/*/* NO_DEFAULT_PATH ) @@ -104,14 +115,16 @@ Hints: message("Board alias ${BOARD_ALIAS} is hiding the real board of same name") endif() endif() - if(BOARD_DIR AND NOT EXISTS ${BOARD_DIR}/${BOARD}_defconfig) + if(BOARD_DIR AND NOT + (EXISTS ${BOARD_DIR}/${BOARD}_defconfig OR + EXISTS ${BOARD_DIR}/${BOARD}.sysdts)) message(WARNING "BOARD_DIR: ${BOARD_DIR} has been moved or deleted. " "Trying to find new location." ) set(BOARD_DIR BOARD_DIR-NOTFOUND CACHE PATH "Path to a file." FORCE) endif() find_path(BOARD_DIR - NAMES ${BOARD}_defconfig + NAMES ${BOARD_FILE_NAMES} PATHS ${root}/boards/*/* NO_DEFAULT_PATH ) diff --git a/scripts/list_boards.py b/scripts/list_boards.py index 4c7c87cad452..8254ba2830dc 100755 --- a/scripts/list_boards.py +++ b/scripts/list_boards.py @@ -79,6 +79,18 @@ def find_arch2board_set_in(root, arches): for maybe_board in (boards / arch).iterdir(): if not maybe_board.is_dir(): continue + + # boards/ARCH/foo/foo.sysdts is a system devicetree for + # board 'foo'. In this case, the system devicetree is the + # signal that a board is being defined. We can't use a + # defconfig because that applies to a sub-target within + # the board. + if (maybe_board / f'{maybe_board.name}.sysdts').is_file(): + ret[arch].add(Board(maybe_board.name, arch, maybe_board)) + continue + + # If there is no system devicetree, try to find a board + # in the regular way, from a defconfig. for maybe_defconfig in maybe_board.iterdir(): file_name = maybe_defconfig.name if file_name.endswith('_defconfig'): From ef2c39b4a5748330016adcfc38eed9f133c697b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Bol=C3=ADvar?= Date: Mon, 27 Feb 2023 14:38:39 -0800 Subject: [PATCH 16/16] [WIP] cmake: modules: add sysdts support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This module adds build-system support for boards with system devicetrees. This module runs before the dts module, and is responsible for converting a system devicetree to a regular devicetree suitable for consumption by the dts module if that is necessary. This prepares the dts module for its work. The DTS module in turn shouldn't have to do anything differently than normal, validating that system DT is possible to fit into an existing build system in an unobtrusive way. HACKS: It also is currently setting up the board defconfig based on a way to put domain-specific defconfigs inside the board directory, and doing similarly for a domain-specific overlay. This clearly won't scale to other situations, but it's enough for prototyping. Signed-off-by: Martí Bolívar --- cmake/modules/sysdts.cmake | 166 +++++++++++++++++++++++++++++ cmake/modules/zephyr_default.cmake | 6 +- 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/sysdts.cmake diff --git a/cmake/modules/sysdts.cmake b/cmake/modules/sysdts.cmake new file mode 100644 index 000000000000..c96411a27a44 --- /dev/null +++ b/cmake/modules/sysdts.cmake @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: Apache-2.0 + +include_guard(GLOBAL) + +include(extensions) +include(python) +include(pre_dt) +find_package(HostTools) + +# This file is used for processing a system devicetree (.sysdts) +# into a regular devicetree (.dts) for use by the rest of the build +# system. +# +# Outcome: +# +# The pre_dt module will have been included; see its outcome comment +# for additional configuration options. +# +# The following variables will or may be set by this module: +# +# - SYSDTS_FOUND: will be set to TRUE or FALSE depending on +# a system devicetree was found. +# - DTS_SOURCE: if SYSDTS_FOUND is TRUE, set to the path +# to the generated .dts file or files to use in the place of +# BOARD.dts +# - BOARD_DEFCONFIG: may be set to a defconfig file to use +# for the current execution domain, instead of the usual +# ${BOARD}_defconfig file (since such files don't make sense +# semantically in a system DT world where there are notionally +# multiple "default" configurations per board, at least one per +# CPU cluster). +# +# Required variables: +# - SYSDT_EXECUTION_DOMAIN: may be provided via any means +# that zephyr_get(... SYSBUILD LOCAL) can find. The full +# path to the system DT node that defines the execution domain +# to use to generate the regular devicetree. The path should +# look like "/domains/foo". +# +# Optional variables: +# +# - DTS_EXTRA_CPPFLAGS: extra command line options to pass to the +# C preprocessor when generating the devicetree from DTS_SOURCE +# - SYSDT_OVERLAY_FILE: a whitespace- or semicolon-separated +# list of system devicetree files (usually with .sysoverlay +# extensions) to append to the board's system devicetree (.sysdts) + +# Any system devicetree overlays are specified here for now. +zephyr_get(SYSDT_OVERLAY_FILE SYSBUILD LOCAL) + +# The directory containing devicetree related scripts. +set(DT_SCRIPTS ${ZEPHYR_BASE}/scripts/dts) +# This converts system devicetree to a generated "regular" devicetree include file. +set(GEN_DTSI_FROM_SYSDTS_SCRIPT ${DT_SCRIPTS}/gen_dtsi_from_sysdts.py) +# The generated regular devicetree include file which is created from +# a system devicetree. +set(SYSDT_OUTPUT_DTS ${BINARY_DIR_INCLUDE_GENERATED}/sysdt_output.dts) +# Generated build system internals. +set(SYSDT_POST_CPP ${PROJECT_BINARY_DIR}/zephyr.sysdts.pre) +set(SYSDT_DEPS ${PROJECT_BINARY_DIR}/zephyr.sysdts.d) +# The sysdtlib.SysDT object in pickle format. +set(SYSDT_PICKLE ${PROJECT_BINARY_DIR}/sysdt.pickle) +# Where we would expect to find a system devicetree. +# TODO: what about board revisions? +set(BOARD_SYSDTS ${BOARD_DIR}/${BOARD}.sysdts) + +# See if we have a system devicetree, and bail if we don't. +if (EXISTS ${BOARD_SYSDTS}) + set(SYSDTS_FOUND TRUE) + message(STATUS "Found BOARD.sysdts: ${BOARD_SYSDTS}") +else() + set(SYSDTS_FOUND FALSE) + return() +endif() + +# +# Validate requirements. +# + +zephyr_get(SYSDT_EXECUTION_DOMAIN SYSBUILD LOCAL) +if(NOT ("${SYSDT_EXECUTION_DOMAIN}" MATCHES "^/domains/")) + message(FATAL_ERROR "Found system devicetree file ${BOARD_SYSDTS}, but SYSDT_EXECUTION_DOMAIN=\"${SYSDT_EXECUTION_DOMAIN}\" is not set to the path to a node in /domains. Set this variable to the path to the execution domain in your system devicetree that you wish to use.") +else() + message(STATUS "System DT execution domain: ${SYSDT_EXECUTION_DOMAIN}") +endif() + +zephyr_get(DTS_SOURCE SYSBUILD LOCAL) +if(DEFINED DTS_SOURCE) + message(FATAL_ERROR "do not define DTS_SOURCE; we have a system devicetree") +endif() + +# +# Generate SYSDT_OUTPUT_DTS. +# + +set(SYSDT_INCLUDE_ARGS -include ${BOARD_SYSDTS}) # Include BOARD.sysdts. +if(SYSDT_OVERLAY_FILE) + zephyr_dts_normalize_overlay_list(SYSDT_OVERLAY_FILE SYSDT_OVERLAY_FILE_AS_LIST) + foreach(sysdt_overlay_file ${SYSDT_OVERLAY_FILE_AS_LIST}) + if(NOT sysdt_overlay_file MATCHES ".*[.]sysoverlay$") + # People are probably going to get confused about file extensions, + # so try to be helpful by establishing conventions. + message(WARNING "System devicetree overlay does not have .sysoverlay extension: ${sysdt_overlay_file}") + endif() + if(EXISTS "${sysdt_overlay_file}") + message(STATUS "Found system devicetree overlay: ${sysdt_overlay_file}") + else() + message(FATAL_ERROR "System devicetree overlay not found: ${sysdt_overlay_file}") + endif() + endforeach() +endif() + +if(DEFINED CMAKE_DTS_PREPROCESSOR) + set(dts_preprocessor ${CMAKE_DTS_PREPROCESSOR}) +else() + set(dts_preprocessor ${CMAKE_C_COMPILER}) +endif() +set(source_files ${BOARD_SYSDTS} ${SYSDT_OVERLAY_FILE_AS_LIST}) +zephyr_dt_preprocess( + CPP ${dts_preprocessor} + SOURCE_FILES ${source_files} + OUT_FILE ${SYSDT_POST_CPP} + DEPS_FILE ${SYSDT_DEPS} + EXTRA_CPPFLAGS ${DTS_EXTRA_CPPFLAGS} + INCLUDE_DIRECTORIES ${DTS_ROOT_SYSTEM_INCLUDE_DIRS} + WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR} + ) + +# +# Generate sysdt_output.dts. +# + +execute_process(COMMAND ${PYTHON_EXECUTABLE} ${GEN_DTSI_FROM_SYSDTS_SCRIPT} + --sysdts "${SYSDT_POST_CPP}" + --domain "${SYSDT_EXECUTION_DOMAIN}" + --sysdt-pickle-out "${SYSDT_PICKLE}" + --dts-out "${SYSDT_OUTPUT_DTS}" + RESULT_VARIABLE ret + ) +if(NOT ret EQUAL 0) + message(FATAL_ERROR "Failed to generate .dts from system devicetree file(s) (error code ${ret}): ${source_files}") +endif() +message(STATUS "Generated DTS from BOARD.sysdts: ${SYSDT_OUTPUT_DTS}") + +# +# Make sure we re-run CMake if anything we know we depend on +# has changed. +# + +unset(sysdt_deps) +toolchain_parse_make_rule(${SYSDT_DEPS} sysdt_deps) +set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS + ${sysdt_deps} + ${GEN_DTSI_FROM_SYSDTS_SCRIPT} + ) + +# HACK: what's a more scalable way to find a domain defconfig? +set(BOARD_DEFCONFIG ${BOARD_DIR}${SYSDT_EXECUTION_DOMAIN}_defconfig) + +# HACK: what's a more scalable way to establish domain overlays? +set(DTS_SOURCE ${SYSDT_OUTPUT_DTS}) +set(domain_overlay ${BOARD_DIR}${SYSDT_EXECUTION_DOMAIN}.overlay) +if(EXISTS ${domain_overlay}) + list(APPEND DTS_SOURCE ${domain_overlay}) +endif() diff --git a/cmake/modules/zephyr_default.cmake b/cmake/modules/zephyr_default.cmake index 01aedc198ce4..57fdc0b38d20 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -89,8 +89,12 @@ list(APPEND zephyr_cmake_modules generated_file_directories) set(pre_dt_board "\${BOARD_DIR}/pre_dt_board.cmake" OPTIONAL) list(APPEND zephyr_cmake_modules "\${pre_dt_board}") -# DTS should be close to kconfig because CONFIG_ variables from +# sysdts has to happen first, since it might generate a +# devicetree file for dts to process. +# +# dts should be close to kconfig because CONFIG_ variables from # kconfig and dts should be available at the same time. +list(APPEND zephyr_cmake_modules sysdts) list(APPEND zephyr_cmake_modules dts) list(APPEND zephyr_cmake_modules kconfig) list(APPEND zephyr_cmake_modules soc)