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 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/cmake/modules/dts.cmake b/cmake/modules/dts.cmake index 09886800a4a3..9f1c8e5f423d 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) @@ -38,14 +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. - -# 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}) +# +# 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) @@ -81,65 +121,28 @@ 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. # # 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() -# -# 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 @@ -152,53 +155,21 @@ 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} ) 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() + if(dts_file MATCHES ".*[.]overlay$") message(STATUS "Found devicetree overlay: ${dts_file}") endif() - - 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 @@ -227,29 +198,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 3ba47fc2baf7..2f1b35722a48 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* @@ -3426,7 +3427,128 @@ function(target_sources_if_dt_node path target scope item) endfunction() ######################################################## -# 5. Zephyr linker function +# 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() + +# 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 ######################################################## # 5.1. zephyr_linker* # 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/pre_dt.cmake b/cmake/modules/pre_dt.cmake new file mode 100644 index 000000000000..b19fd7f7fb57 --- /dev/null +++ b/cmake/modules/pre_dt.cmake @@ -0,0 +1,87 @@ +# 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 "PATH1 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 ${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() 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 908d03fceb97..57fdc0b38d20 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -83,13 +83,18 @@ 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) 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) 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>; + }; + }; +}; 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 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() 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/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..675a74403805 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 @@ -3074,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') 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__) 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-'.''' 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'):