diff --git a/CMakeLists.txt b/CMakeLists.txt index 0afc945667b1a..b7a91e37f77e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1898,37 +1898,6 @@ if (CONFIG_LLEXT AND CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) endif() -if(NOT CMAKE_C_COMPILER_ID STREQUAL "ARMClang") - set(check_init_priorities_input - $,${BYPRODUCT_KERNEL_EXE_NAME},${BYPRODUCT_KERNEL_ELF_NAME}> - ) - set(check_init_priorities_command - ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py - --elf-file=${check_init_priorities_input} - ) - set(check_init_priorities_dependencies - ${logical_target_for_zephyr_elf} - $<$:native_runner_executable> - ) - - if(CONFIG_CHECK_INIT_PRIORITIES) - if(TARGET native_runner_executable) - add_custom_command(TARGET native_runner_executable POST_BUILD - COMMAND ${check_init_priorities_command} - ) - else() - list(APPEND post_build_commands COMMAND ${check_init_priorities_command}) - endif() - endif() - - add_custom_target( - initlevels - COMMAND ${check_init_priorities_command} --initlevels - DEPENDS ${check_init_priorities_dependencies} - USES_TERMINAL - ) -endif() - # Generate signed (MCUboot or other) related artifacts as needed. Priority is: # * Sysbuild (if set) # * SIGNING_SCRIPT target property (if set) diff --git a/Kconfig.zephyr b/Kconfig.zephyr index 01b5f4497e095..46b794d6b8a87 100644 --- a/Kconfig.zephyr +++ b/Kconfig.zephyr @@ -894,21 +894,6 @@ config BUILD_OUTPUT_STRIP_PATHS Debuggers usually have a path mapping feature to ensure the files are still found. -config CHECK_INIT_PRIORITIES - bool "Build time initialization priorities check" - default y - # If we are building a native_simulator target, we can only check the init priorities - # if we are building the final output but we are not assembling several images together - depends on !(NATIVE_LIBRARY && (!BUILD_OUTPUT_EXE || NATIVE_SIMULATOR_EXTRA_IMAGE_PATHS != "")) - depends on "$(ZEPHYR_TOOLCHAIN_VARIANT)" != "armclang" - help - Check the build for initialization priority issues by comparing the - initialization priority in the build with the device dependency - derived from the devicetree definition. - - Fails the build on priority errors (dependent devices, inverted - priority). - config EMIT_ALL_SYSCALLS bool "Emit all possible syscalls in the tree" help diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 4687d1282009d..367ccc51df23e 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -758,7 +758,6 @@ Device Driver Model: - include/zephyr/init.h - tests/kernel/device/ - doc/kernel/drivers/ - - tests/misc/check_init_priorities/ labels: - "area: Device Model" tests: diff --git a/arch/x86/fw_config.yaml b/arch/x86/fw_config.yaml new file mode 100644 index 0000000000000..1afbf5ab476d4 --- /dev/null +++ b/arch/x86/fw_config.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 +# +# X86 specific scripts/fwconfig file + +init: + arch_smp_init: + dependencies: + - ioapic@fec00000 + - loapic@fee00000 + + pcie_init: + dependencies: + - arch_smp_init diff --git a/boards/native/nrf_bsim/fw_config.yaml b/boards/native/nrf_bsim/fw_config.yaml new file mode 100644 index 0000000000000..7f9c93c1b62c5 --- /dev/null +++ b/boards/native/nrf_bsim/fw_config.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + sync_rtc_setup: + dependencies: + - k_sys_work_q_init + + k_sys_work_q_init: + dependencies: + - mbox diff --git a/cmake/linker_script/common/common-rom.cmake b/cmake/linker_script/common/common-rom.cmake index 2fd4fcedb4709..f9ffc1eb5c126 100644 --- a/cmake/linker_script/common/common-rom.cmake +++ b/cmake/linker_script/common/common-rom.cmake @@ -6,6 +6,7 @@ zephyr_linker_section_obj_level(SECTION init LEVEL PRE_KERNEL_1) zephyr_linker_section_obj_level(SECTION init LEVEL PRE_KERNEL_2) zephyr_linker_section_obj_level(SECTION init LEVEL POST_KERNEL) zephyr_linker_section_obj_level(SECTION init LEVEL APPLICATION) +zephyr_linker_section_obj_level(SECTION init LEVEL MANUAL) zephyr_linker_section_obj_level(SECTION init LEVEL SMP) zephyr_linker_section(NAME deferred_init_list KVMA RAM_REGION GROUP RODATA_REGION) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 9fe387e238815..2dfcd4c8009a9 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -2555,7 +2555,7 @@ endfunction() # returns an updated list of absolute paths # # Usage: -# zephyr_file(CONF_FILES [DTS ] [KCONF ] +# zephyr_file(CONF_FILES [DTS ] [KCONF ] [FWCONF ] # [BOARD [BOARD_REVISION ] | NAMES ...] # [BUILD ] [SUFFIX ] [REQUIRED] # ) @@ -2566,6 +2566,7 @@ endfunction() # - DTS: Overlay files (.overlay) # - Kconfig: Config fragments (.conf) # - defconfig: defconfig files (_defconfig) +# - firmware config: [_]fw_config.yaml files # The conf file search will return existing configuration # files for the current board. # CONF_FILES takes the following additional arguments: @@ -2584,6 +2585,7 @@ endfunction() # DTS : List to append DTS overlay files in to # KCONF : List to append Kconfig fragment files in to # DEFCONF : List to append _defconfig files in to +# FWCONF : List to append [_]fwconfig.yaml files in to # BUILD : Build type to include for search. # For example: # BUILD debug, will look for _debug.conf @@ -2593,7 +2595,7 @@ endfunction() # For example: # SUFFIX fish, will look for _fish.conf and use # if found but will use .conf if not found -# REQUIRED: Option to indicate that the specified by DTS or KCONF +# REQUIRED: Option to indicate that the specified by DTS, KCONF or FCONF # must contain at least one element, else an error will be raised. # function(zephyr_file) @@ -2607,7 +2609,7 @@ Please provide one of following: APPLICATION_ROOT, CONF_FILES") set(single_args APPLICATION_ROOT BASE_DIR) elseif(${ARGV0} STREQUAL CONF_FILES) set(options QUALIFIERS REQUIRED) - set(single_args BOARD BOARD_REVISION BOARD_QUALIFIERS DTS KCONF DEFCONFIG BUILD SUFFIX) + set(single_args BOARD BOARD_REVISION BOARD_QUALIFIERS DTS KCONF DEFCONFIG FWCONF BUILD SUFFIX) set(multi_args CONF_FILES NAMES) endif() @@ -2679,6 +2681,7 @@ Relative paths are only allowed with `-D${ARGV1}=`") if(ZFILE_NAMES) set(dts_filename_list ${ZFILE_NAMES}) set(kconf_filename_list ${ZFILE_NAMES}) + set(fwconf_filename_list ${ZFILE_NAMES}) else() if(NOT ZFILE_QUALIFIERS) zephyr_build_string(filename_list @@ -2706,6 +2709,11 @@ Relative paths are only allowed with `-D${ARGV1}=`") set(kconf_shortened_filename_list ${shortened_filename_list}) list(TRANSFORM kconf_filename_list APPEND ".conf") list(TRANSFORM kconf_shortened_filename_list APPEND ".conf") + + set(fwconf_filename_list ${filename_list}) + set(fwconf_shortened_filename_list ${shortened_filename_list}) + list(TRANSFORM fwconf_filename_list APPEND "_fw_config.yaml") + list(TRANSFORM fwconf_shortened_filename_list APPEND "_fw_config.yaml") endif() if(ZFILE_DTS) @@ -2858,6 +2866,68 @@ Relative paths are only allowed with `-D${ARGV1}=`") # This updates the provided list in parent scope (callers scope) set(${ZFILE_DEFCONFIG} ${${ZFILE_DEFCONFIG}} PARENT_SCOPE) endif() + + if(ZFILE_FWCONF) + set(found_fwconf_files) + foreach(path ${ZFILE_CONF_FILES}) + set(test_file ${path}/fw_config.yaml) + if(EXISTS ${test_file}) + list(APPEND found_fwconf_files ${test_file}) + else() + zephyr_file_suffix(test_file SUFFIX ${ZFILE_SUFFIX}) + if(EXISTS ${test_file}) + list(APPEND found_fwconf_files ${test_file}) + endif() + endif() + + foreach(filename IN ZIP_LISTS fwconf_filename_list fwconf_shortened_filename_list) + foreach(i RANGE 1) + if(NOT IS_ABSOLUTE filename_${i} AND DEFINED filename_${i}) + set(test_file_${i} ${path}/${filename_${i}}) + else() + set(test_file_${i} ${filename_${i}}) + endif() + zephyr_file_suffix(test_file_${i} SUFFIX ${ZFILE_SUFFIX}) + + + if(NOT EXISTS ${test_file_${i}}) + set(test_file_${i}) + endif() + endforeach() + + if(test_file_0 OR test_file_1) + list(APPEND found_fwconf_files ${test_file_0}) + list(APPEND found_fwconf_files ${test_file_1}) + + if(DEFINED ZFILE_BUILD) + set(deprecated_file_found y) + endif() + + if(ZFILE_NAMES) + break() + endif() + endif() + + if(test_file_1 AND NOT BOARD_${ZFILE_BOARD}_SINGLE_SOC) + message(FATAL_ERROR "Board ${ZFILE_BOARD} defines multiple SoCs.\nShortened file name " + "(${filename_1}) not allowed, use '__fwconfig.yaml' naming" + ) + endif() + + if(test_file_0 AND test_file_1) + message(FATAL_ERROR "Conflicting file names discovered. Cannot use both ${filename_0} " + "and ${filename_1}. Please choose one naming style, " + "${filename_0} is recommended." + ) + endif() + endforeach() + endforeach() + + list(APPEND ${ZFILE_FWCONF} ${found_fwconf_files}) + + # This updates the provided list in parent scope (callers scope) + set(${ZFILE_FWCONF} ${${ZFILE_FWCONF}} PARENT_SCOPE) + endif() endif() endfunction() @@ -4971,8 +5041,8 @@ endfunction() # This is useful content such as struct devices. # # For example: zephyr_linker_section_obj_level(SECTION init LEVEL PRE_KERNEL_1) -# will create an input section matching `.z_init_PRE_KERNEL_1?_` and -# `.z_init_PRE_KERNEL_1??_`. +# will create an input section matching `.z_init_PRE_KERNEL_1?_`, +# `.z_init_PRE_KERNEL_1??_` and `.z_init_PRE_KERNEL_1???_` # # SECTION
: Section in which the objects shall be placed # LEVEL : Priority level, all input sections matching the level @@ -4996,13 +5066,18 @@ function(zephyr_linker_section_obj_level) zephyr_linker_section_configure( SECTION ${OBJ_SECTION} - INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}?_*" + INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}_?_*" SYMBOLS __${OBJ_SECTION}_${OBJ_LEVEL}_start KEEP SORT NAME ) zephyr_linker_section_configure( SECTION ${OBJ_SECTION} - INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}??_*" + INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}_??_*" + KEEP SORT NAME + ) + zephyr_linker_section_configure( + SECTION ${OBJ_SECTION} + INPUT ".z_${OBJ_SECTION}_${OBJ_LEVEL}_???_*" KEEP SORT NAME ) endfunction() diff --git a/cmake/modules/fwconfig.cmake b/cmake/modules/fwconfig.cmake new file mode 100644 index 0000000000000..9dc72bb980fcd --- /dev/null +++ b/cmake/modules/fwconfig.cmake @@ -0,0 +1,79 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +include_guard(GLOBAL) + +include(extensions) +include(python) +include(arch_v1) +include(arch_v2) +include(soc_v1) +include(soc_v2) +include(boards) + +# +# ToDo DOC +# + +# The directory containing fwconfig script and files. +set(FWCONFIG_BASE ${ZEPHYR_BASE}/scripts/fwconfig) + +# Fwconfig script that will generate a header about soft/hard init nodes +# priority and - whene relevant - levels too. +set(FWCONFIG_SCRIPT ${FWCONFIG_BASE}/fwconfig) + +# The edtlib.EDT object in pickle format. +set(EDT_PICKLE ${PROJECT_BINARY_DIR}/edt.pickle) + +# The .config file generated by Kconfig +set(DOTCONFIG ${PROJECT_BINARY_DIR}/.config) + +# The generated C header needed by +set(FWCONFIG_INIT_H ${BINARY_DIR_INCLUDE_GENERATED}/zinit.h) + +# Predefined directories where to look for fw_config.yaml files +# 1 - Default: +list(APPEND fwconfig_lookup_dirs ${ZEPHYR_BASE}) + +# 2 - Arch: +list(APPEND fwconfig_lookup_dirs ${ARCH_DIR}/${ARCH}) + +# 3 - SoC: +list(APPEND fwconfig_lookup_dirs ${SOC_FULL_DIR}) + +# 4 - Modules: +zephyr_get(ZEPHYR_MODULES) +zephyr_get(EXTRA_ZEPHYR_MODULES VAR EXTRA_ZEPHYR_MODULES ZEPHYR_EXTRA_MODULES) +list(APPEND fwconfig_lookup_dirs ${ZEPHYR_MODULES} ${EXTRA_ZEPHYR_MODULES}) + +# 5 - Board: +list(APPEND fwconfig_lookup_dirs ${BOARD_DIR} ${BOARD_EXTENSION_DIRS}) + +# 6 - Application: +list(APPEND fwconfig_lookup_dirs ${APPLICATION_CONFIG_DIR} ${APPLICATION_CONFIG_DIR}/boards) + +# Now looking up for the actual files: +zephyr_file(CONF_FILES ${fwconfig_lookup_dirs} FWCONF fwconfig_files) + +message(STATUS "Found fwconfig files: ${fwconfig_files}") + +# +# Run FWCONFIG_SCRIPT +# +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${FWCONFIG_SCRIPT} + --fwconfig-files ${fwconfig_files} + - + init + --dotconfig-file ${DOTCONFIG} + --edt-pickle ${EDT_PICKLE} + --header-out ${FWCONFIG_INIT_H} + OUTPUT_QUIET # Discard stdout + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + RESULT_VARIABLE ret + ) + +if(NOT "${ret}" STREQUAL "0") + message(FATAL_ERROR "command failed with return code: ${ret}") +endif() diff --git a/cmake/modules/unittest.cmake b/cmake/modules/unittest.cmake index de5b0f3eeb618..feeda940e9442 100644 --- a/cmake/modules/unittest.cmake +++ b/cmake/modules/unittest.cmake @@ -63,6 +63,7 @@ set(INCL_GENERATED_DIR ${APPLICATION_BINARY_DIR}/zephyr/include/generated/zephyr set(INCL_GENERATED_SYSCALL_DIR ${INCL_GENERATED_DIR}/syscalls) list(APPEND INCL_GENERATED_HEADERS ${INCL_GENERATED_DIR}/devicetree_generated.h + ${INCL_GENERATED_DIR}/zinit.h ${INCL_GENERATED_DIR}/offsets.h ${INCL_GENERATED_DIR}/syscall_list.h ${INCL_GENERATED_DIR}/syscall_macros.h diff --git a/cmake/modules/zephyr_default.cmake b/cmake/modules/zephyr_default.cmake index 7472331255b81..abf49a50eee15 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -112,6 +112,7 @@ list(APPEND zephyr_cmake_modules kconfig) list(APPEND zephyr_cmake_modules arch_v2) list(APPEND zephyr_cmake_modules soc_v1) list(APPEND zephyr_cmake_modules soc_v2) +list(APPEND zephyr_cmake_modules fwconfig) foreach(component ${SUB_COMPONENTS}) if(NOT ${component} IN_LIST zephyr_cmake_modules) diff --git a/doc/build/cmake/index.rst b/doc/build/cmake/index.rst index ee9bde7206ff8..6db2bd4170e76 100644 --- a/doc/build/cmake/index.rst +++ b/doc/build/cmake/index.rst @@ -409,12 +409,3 @@ The following is a detailed description of the scripts used during the build pro .. include:: ../../../scripts/build/gen_app_partitions.py :start-after: """ :end-before: """ - -.. _check_init_priorities.py: - -:zephyr_file:`scripts/build/check_init_priorities.py` ------------------------------------------------------ - -.. include:: ../../../scripts/build/check_init_priorities.py - :start-after: """ - :end-before: """ diff --git a/fw_config.yaml b/fw_config.yaml new file mode 100644 index 0000000000000..4582c71e8aaa1 --- /dev/null +++ b/fw_config.yaml @@ -0,0 +1,40 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 +# +# Default config file for scripts/fwconfig + +init: + uart_console_init: + level: POST_KERNEL + dependencies: + - zephyr,console + if: + CONFIG_EARLY_CONSOLE + then: + level: PRE_KERNEL_1 + + enable_shell_uart: + level: POST_KERNEL + dependencies: + - zephyr,shell-uart + + posix_arch_console_init: + dependencies: + - uart_console_init + + soc: + dependencies: + - arch_smp_init + + net_init: + dependencies: + - k_sys_work_q_init + + littlefs_init: + dependencies: + - zephyr,flash + + gpio_hogs_init: + dependencies: + - zephyr,gpio-emul diff --git a/include/zephyr/device.h b/include/zephyr/device.h index 90774ef16698c..a83f4e814e74e 100644 --- a/include/zephyr/device.h +++ b/include/zephyr/device.h @@ -1046,7 +1046,7 @@ __syscall int device_init(const struct device *dev); Z_DEVICE_LEVEL_CHECK_DEPRECATED_LEVEL(level) \ \ static const Z_DECL_ALIGN(struct init_entry) __used __noasan Z_INIT_ENTRY_SECTION( \ - level, prio, Z_DEVICE_INIT_SUB_PRIO(node_id)) \ + node_id, level, ZINIT_GET_PRIORITY(node_id, dev_id), 0) \ Z_INIT_ENTRY_NAME(DEVICE_NAME_GET(dev_id)) = { \ .init_fn = {COND_CODE_1(Z_DEVICE_IS_MUTABLE(node_id), (.dev_rw), (.dev)) = \ (init_fn_)}, \ diff --git a/include/zephyr/devicetree.h b/include/zephyr/devicetree.h index 58f924e266d82..116e75ece0915 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -3209,8 +3209,10 @@ * @param status a status as one of the tokens okay or disabled, not a string * @return 1 if the node has the given status, 0 otherwise. */ -#define DT_NODE_HAS_STATUS(node_id, status) \ +#ifndef ZTEST_UNITTEST /* If ZTEST_UNITTEST, a dummy version will be defined */ +#define DT_NODE_HAS_STATUS(node_id, status) \ DT_NODE_HAS_STATUS_INTERNAL(node_id, status) +#endif /* ZTEST_UNITTEST */ /** * @brief Does the devicetree have a status `okay` node with a compatible? diff --git a/include/zephyr/init.h b/include/zephyr/init.h index 512ea9f67bea6..0ce6060151b36 100644 --- a/include/zephyr/init.h +++ b/include/zephyr/init.h @@ -13,6 +13,8 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif @@ -40,6 +42,8 @@ extern "C" { * - `APPLICATION`: Executed just before application code (`main`). * - `SMP`: Only available if @kconfig{CONFIG_SMP} is enabled, specific for * SMP. + * - `MANUAL`: Executed "manually" at runtime, unlike other levels which are + * ran one after another automatically. * * Initialization priority can take a value in the range of 0 to 99. * @@ -124,6 +128,7 @@ struct init_entry { #define Z_INIT_POST_KERNEL_POST_KERNEL 1 #define Z_INIT_APPLICATION_APPLICATION 1 #define Z_INIT_SMP_SMP 1 +#define Z_INIT_MANUAL_MANUAL 1 /* Init level ordinals */ #define Z_INIT_ORD_EARLY 0 @@ -132,6 +137,7 @@ struct init_entry { #define Z_INIT_ORD_POST_KERNEL 3 #define Z_INIT_ORD_APPLICATION 4 #define Z_INIT_ORD_SMP 5 +#define Z_INIT_ORD_MANUAL 6 /** * @brief Obtain init entry name. @@ -147,9 +153,9 @@ struct init_entry { * linker scripts to sort them according to the specified * level/priority/sub-priority. */ -#define Z_INIT_ENTRY_SECTION(level, prio, sub_prio) \ - __attribute__((__section__( \ - ".z_init_" #level STRINGIFY(prio)"_" STRINGIFY(sub_prio)"_"))) +#define Z_INIT_ENTRY_SECTION(node_id, level, prio, sub_prio) \ + __attribute__((__section__( \ + ".z_init_" STRINGIFY(ZINIT_GET_LEVEL(node_id, level)) "_" STRINGIFY(prio)"_"))) /* Designated initializers where added to C in C99. There were added to @@ -190,7 +196,7 @@ struct init_entry { * @brief Obtain the ordinal for an init level. * * @param level Init level (EARLY, PRE_KERNEL_1, PRE_KERNEL_2, POST_KERNEL, - * APPLICATION, SMP). + * APPLICATION, SMP, MANUAL). * * @return Init level ordinal. */ @@ -201,7 +207,8 @@ struct init_entry { (COND_CODE_1(Z_INIT_POST_KERNEL_##level, (Z_INIT_ORD_POST_KERNEL), \ (COND_CODE_1(Z_INIT_APPLICATION_##level, (Z_INIT_ORD_APPLICATION), \ (COND_CODE_1(Z_INIT_SMP_##level, (Z_INIT_ORD_SMP), \ - (ZERO_OR_COMPILE_ERROR(0))))))))))))) + (COND_CODE_1(Z_INIT_MANUAL_##level, (Z_INIT_ORD_MANUAL), \ + (ZERO_OR_COMPILE_ERROR(0))))))))))))))) /** * @brief Register an initialization function. @@ -211,8 +218,8 @@ struct init_entry { * * @param init_fn Initialization function. * @param level Initialization level. Allowed tokens: `EARLY`, `PRE_KERNEL_1`, - * `PRE_KERNEL_2`, `POST_KERNEL`, `APPLICATION` and `SMP` if - * @kconfig{CONFIG_SMP} is enabled. + * `PRE_KERNEL_2`, `POST_KERNEL`, `APPLICATION` , `SMP` if + * @kconfig{CONFIG_SMP} is enabled, and `MANUAL`. * @param prio Initialization priority within @p _level. Note that it must be a * decimal integer literal without leading zeroes or sign (e.g. `32`), or an * equivalent symbolic name (e.g. `#define MY_INIT_PRIO 32`); symbolic @@ -237,7 +244,8 @@ struct init_entry { */ #define SYS_INIT_NAMED(name, init_fn_, level, prio) \ static const Z_DECL_ALIGN(struct init_entry) \ - Z_INIT_ENTRY_SECTION(level, prio, 0) __used __noasan \ + Z_INIT_ENTRY_SECTION(name, level, ZINIT_GET_PRIORITY(name, name), 0) \ + __used __noasan \ Z_INIT_ENTRY_NAME(name) = {.init_fn = {.sys = (init_fn_)}, \ Z_INIT_SYS_INIT_DEV_NULL} diff --git a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld index 35ac520cc070e..d02d1746c6687 100644 --- a/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld +++ b/include/zephyr/linker/common-rom/common-rom-kernel-devices.ld @@ -17,6 +17,7 @@ CREATE_OBJ_LEVEL(init, POST_KERNEL) CREATE_OBJ_LEVEL(init, APPLICATION) CREATE_OBJ_LEVEL(init, SMP) + CREATE_OBJ_LEVEL(init, MANUAL) __init_end = .; __deferred_init_list_start = .; KEEP(*(.z_deferred_init)) diff --git a/include/zephyr/linker/linker-defs.h b/include/zephyr/linker/linker-defs.h index 8a1db6da081f5..f86aa0d41e694 100644 --- a/include/zephyr/linker/linker-defs.h +++ b/include/zephyr/linker/linker-defs.h @@ -45,8 +45,9 @@ */ #define CREATE_OBJ_LEVEL(object, level) \ __##object##_##level##_start = .; \ - KEEP(*(SORT(.z_##object##_##level?_*))); \ - KEEP(*(SORT(.z_##object##_##level??_*))); + KEEP(*(SORT(.z_##object##_##level##_?_*))); \ + KEEP(*(SORT(.z_##object##_##level##_??_*))); \ + KEEP(*(SORT(.z_##object##_##level##_???_*))); /* * link in shell initialization objects for all modules that use shell and diff --git a/include/zephyr/sys/util_init.h b/include/zephyr/sys/util_init.h new file mode 100644 index 0000000000000..98a26f946defb --- /dev/null +++ b/include/zephyr/sys/util_init.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, Tomasz Bursztyka + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_ZEPHYR_SYS_UTIL_INIT_H_ +#define ZEPHYR_INCLUDE_ZEPHYR_SYS_UTIL_INIT_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif +/** @{ */ + +#define ZINIT_EXISTS(init_id) \ + IS_ENABLED(DT_CAT3(ZINIT_, init_id, _EXISTS)) + +#define ZINIT_EXPLICIT_LEVEL_EXISTS(init_id) \ + IS_ENABLED(DT_CAT3(ZINIT_, init_id, _INIT_EXPLICIT_LEVEL_EXISTS)) + +#define ZINIT_EXPLICIT_LEVEL(init_id) \ + DT_CAT3(ZINIT_, init_id, _INIT_EXPLICIT_LEVEL) + +#define ZINIT_IMPLICIT_LEVEL_EXISTS(init_id) \ + IS_ENABLED(DT_CAT3(ZINIT_, init_id, _INIT_IMPLICIT_LEVEL_EXISTS)) + +#define ZINIT_IMPLICIT_LEVEL(init_id) \ + DT_CAT3(ZINIT_, init_id, _INIT_IMPLICIT_LEVEL) + +#define ZINIT_PRIORITY(init_id) \ + DT_CAT3(ZINIT_, init_id, _INIT_PRIORITY) + +#define ZINIT_GET_PRIORITY(node_id, dev_id) \ + COND_CODE_1(ZINIT_EXISTS(node_id), \ + (ZINIT_PRIORITY(node_id)), \ + (COND_CODE_1(ZINIT_EXISTS(dev_id), \ + (ZINIT_PRIORITY(dev_id)), \ + (0)))) + +/* Helper definition to evaluate level predominance */ +#define ZINIT_LEVEL_PREDOMINANCE_PRE_KERNEL_1_VS_EARLY 1 +#define ZINIT_LEVEL_PREDOMINANCE_PRE_KERNEL_2_VS_EARLY 1 +#define ZINIT_LEVEL_PREDOMINANCE_POST_KERNEL_VS_EARLY 1 +#define ZINIT_LEVEL_PREDOMINANCE_APPLICATION_VS_EARLY 1 +#define ZINIT_LEVEL_PREDOMINANCE_SMP_VS_EARLY 1 +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_EARLY 1 + +#define ZINIT_LEVEL_PREDOMINANCE_PRE_KERNEL_2_VS_PRE_KERNEL_1 1 +#define ZINIT_LEVEL_PREDOMINANCE_POST_KERNEL_VS_PRE_KERNEL_1 1 +#define ZINIT_LEVEL_PREDOMINANCE_APPLICATION_VS_PRE_KERNEL_1 1 +#define ZINIT_LEVEL_PREDOMINANCE_SMP_VS_PRE_KERNEL_1 1 +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_PRE_KERNEL_1 1 + +#define ZINIT_LEVEL_PREDOMINANCE_POST_KERNEL_VS_PRE_KERNEL_2 1 +#define ZINIT_LEVEL_PREDOMINANCE_APPLICATION_VS_PRE_KERNEL_2 1 +#define ZINIT_LEVEL_PREDOMINANCE_SMP_VS_PRE_KERNEL_2 1 +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_PRE_KERNEL_2 1 + +#define ZINIT_LEVEL_PREDOMINANCE_APPLICATION_VS_POST_KERNEL 1 +#define ZINIT_LEVEL_PREDOMINANCE_SMP_VS_POST_KERNEL 1 +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_POST_KERNEL 1 + +#define ZINIT_LEVEL_PREDOMINANCE_SMP_VS_APPLICATION 1 +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_APPLICATION 1 + +#define ZINIT_LEVEL_PREDOMINANCE_MANUAL_VS_SMP 1 + +#define ZINIT_LEVEL_PREDOMINANCE(default_level, level) \ + COND_CODE_1(_CONCAT_4(ZINIT_LEVEL_PREDOMINANCE_, default_level, _, level),\ + (default_level), (level)) + +#define ZINIT_GET_LEVEL(node_id, level) \ + COND_CODE_1(ZINIT_EXISTS(node_id), \ + (COND_CODE_1(ZINIT_EXPLICIT_LEVEL_EXISTS(node_id), \ + (ZINIT_EXPLICIT_LEVEL(node_id)), \ + (COND_CODE_1(ZINIT_IMPLICIT_LEVEL_EXISTS(node_id), \ + (ZINIT_LEVEL_PREDOMINANCE(level, \ + ZINIT_IMPLICIT_LEVEL(node_id))), \ + (level))))), \ + (level)) + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_ZEPHYR_SYS_UTIL_INIT_H_ */ diff --git a/kernel/init.c b/kernel/init.c index 998cb938f1622..2036d6e4243f4 100644 --- a/kernel/init.c +++ b/kernel/init.c @@ -120,6 +120,7 @@ extern const struct init_entry __init_PRE_KERNEL_1_start[]; extern const struct init_entry __init_PRE_KERNEL_2_start[]; extern const struct init_entry __init_POST_KERNEL_start[]; extern const struct init_entry __init_APPLICATION_start[]; +extern const struct init_entry __init_MANUAL_start[]; extern const struct init_entry __init_end[]; enum init_level { @@ -131,6 +132,7 @@ enum init_level { #ifdef CONFIG_SMP INIT_LEVEL_SMP, #endif /* CONFIG_SMP */ + INIT_LEVEL_MANUAL, }; #ifdef CONFIG_SMP @@ -357,6 +359,7 @@ static void z_sys_init_run_level(enum init_level level) __init_SMP_start, #endif /* CONFIG_SMP */ /* End marker */ + __init_MANUAL_start, __init_end, }; const struct init_entry *entry; diff --git a/samples/subsys/pm/device_pm/fw_config.yaml b/samples/subsys/pm/device_pm/fw_config.yaml new file mode 100644 index 0000000000000..2abc804385458 --- /dev/null +++ b/samples/subsys/pm/device_pm/fw_config.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + dummy_driver: + dependencies: + - dummy_parent diff --git a/scripts/build/check_init_priorities.py b/scripts/build/check_init_priorities.py deleted file mode 100755 index f126f2097c34d..0000000000000 --- a/scripts/build/check_init_priorities.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 Google LLC -# SPDX-License-Identifier: Apache-2.0 - -""" -Checks the initialization priorities - -This script parses a Zephyr executable file, creates a list of known devices -and their effective initialization priorities and compares that with the device -dependencies inferred from the devicetree hierarchy. - -This can be used to detect devices that are initialized in the incorrect order, -but also devices that are initialized at the same priority but depends on each -other, which can potentially break if the linking order is changed. - -Optionally, it can also produce a human readable list of the initialization -calls for the various init levels. -""" - -import argparse -import logging -import os -import pathlib -import pickle -import sys - -from elftools.elf.elffile import ELFFile -from elftools.elf.sections import SymbolTableSection - -# This is needed to load edt.pickle files. -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", - "dts", "python-devicetree", "src")) -from devicetree import edtlib # pylint: disable=unused-import - -# Prefix used for "struct device" reference initialized based on devicetree -# entries with a known ordinal. -_DEVICE_ORD_PREFIX = "__device_dts_ord_" - -# Defined init level in order of priority. -_DEVICE_INIT_LEVELS = ["EARLY", "PRE_KERNEL_1", "PRE_KERNEL_2", "POST_KERNEL", - "APPLICATION", "SMP"] - -# List of compatibles for node where the initialization priority should be the -# opposite of the device tree inferred dependency. -_INVERTED_PRIORITY_COMPATIBLES = frozenset() - -# List of compatibles for nodes where we don't check the priority. -_IGNORE_COMPATIBLES = frozenset([ - # There is no direct dependency between the CDC ACM UART and the USB - # device controller, the logical connection is established after USB - # device support is enabled. - "zephyr,cdc-acm-uart", - ]) - -class Priority: - """Parses and holds a device initialization priority. - - The object can be used for comparing levels with one another. - - Attributes: - name: the section name - """ - def __init__(self, level, priority): - for idx, level_name in enumerate(_DEVICE_INIT_LEVELS): - if level_name == level: - self._level = idx - self._priority = priority - # Tuples compare elementwise in order - self._level_priority = (self._level, self._priority) - return - - raise ValueError("Unknown level in %s" % level) - - def __repr__(self): - return "<%s %s %d>" % (self.__class__.__name__, - _DEVICE_INIT_LEVELS[self._level], self._priority) - - def __str__(self): - return "%s+%d" % (_DEVICE_INIT_LEVELS[self._level], self._priority) - - def __lt__(self, other): - return self._level_priority < other._level_priority - - def __eq__(self, other): - return self._level_priority == other._level_priority - - def __hash__(self): - return self._level_priority - - -class ZephyrInitLevels: - """Load an executable file and find the initialization calls and devices. - - Load a Zephyr executable file and scan for the list of initialization calls - and defined devices. - - The list of devices is available in the "devices" class variable in the - {ordinal: Priority} format, the list of initilevels is in the "initlevels" - class variables in the {"level name": ["call", ...]} format. - - Attributes: - file_path: path of the file to be loaded. - """ - def __init__(self, file_path): - self.file_path = file_path - self._elf = ELFFile(open(file_path, "rb")) - self._load_objects() - self._load_level_addr() - self._process_initlevels() - - def _load_objects(self): - """Initialize the object table.""" - self._objects = {} - - for section in self._elf.iter_sections(): - if not isinstance(section, SymbolTableSection): - continue - - for sym in section.iter_symbols(): - if (sym.name and - sym.entry.st_size > 0 and - sym.entry.st_info.type in ["STT_OBJECT", "STT_FUNC"]): - self._objects[sym.entry.st_value] = ( - sym.name, sym.entry.st_size, sym.entry.st_shndx) - - def _load_level_addr(self): - """Find the address associated with known init levels.""" - self._init_level_addr = {} - - for section in self._elf.iter_sections(): - if not isinstance(section, SymbolTableSection): - continue - - for sym in section.iter_symbols(): - for level in _DEVICE_INIT_LEVELS: - name = f"__init_{level}_start" - if sym.name == name: - self._init_level_addr[level] = sym.entry.st_value - elif sym.name == "__init_end": - self._init_level_end = sym.entry.st_value - - if len(self._init_level_addr) != len(_DEVICE_INIT_LEVELS): - raise ValueError(f"Missing init symbols, found: {self._init_level_addr}") - - if not self._init_level_end: - raise ValueError(f"Missing init section end symbol") - - def _device_ord_from_name(self, sym_name): - """Find a device ordinal from a symbol name.""" - if not sym_name: - return None - - if not sym_name.startswith(_DEVICE_ORD_PREFIX): - return None - - _, device_ord = sym_name.split(_DEVICE_ORD_PREFIX) - return int(device_ord) - - def _object_name(self, addr): - if not addr: - return "NULL" - elif addr in self._objects: - return self._objects[addr][0] - else: - return "unknown" - - def _initlevel_pointer(self, addr, idx, shidx): - elfclass = self._elf.elfclass - if elfclass == 32: - ptrsize = 4 - elif elfclass == 64: - ptrsize = 8 - else: - raise ValueError(f"Unknown pointer size for ELF class f{elfclass}") - - section = self._elf.get_section(shidx) - start = section.header.sh_addr - data = section.data() - - offset = addr - start - - start = offset + ptrsize * idx - stop = offset + ptrsize * (idx + 1) - - return int.from_bytes(data[start:stop], byteorder="little") - - def _process_initlevels(self): - """Process the init level and find the init functions and devices.""" - self.devices = {} - self.initlevels = {} - - for i, level in enumerate(_DEVICE_INIT_LEVELS): - start = self._init_level_addr[level] - if i + 1 == len(_DEVICE_INIT_LEVELS): - stop = self._init_level_end - else: - stop = self._init_level_addr[_DEVICE_INIT_LEVELS[i + 1]] - - self.initlevels[level] = [] - - priority = 0 - addr = start - while addr < stop: - if addr not in self._objects: - raise ValueError(f"no symbol at addr {addr:08x}") - obj, size, shidx = self._objects[addr] - - arg0_name = self._object_name(self._initlevel_pointer(addr, 0, shidx)) - arg1_name = self._object_name(self._initlevel_pointer(addr, 1, shidx)) - - self.initlevels[level].append(f"{obj}: {arg0_name}({arg1_name})") - - ordinal = self._device_ord_from_name(arg1_name) - if ordinal: - prio = Priority(level, priority) - self.devices[ordinal] = (prio, arg0_name) - - addr += size - priority += 1 - -class Validator(): - """Validates the initialization priorities. - - Scans through a build folder for object files and list all the device - initialization priorities. Then compares that against the EDT derived - dependency list and log any found priority issue. - - Attributes: - elf_file_path: path of the ELF file - edt_pickle: name of the EDT pickle file - log: a logging.Logger object - """ - def __init__(self, elf_file_path, edt_pickle, log): - self.log = log - - edt_pickle_path = pathlib.Path( - pathlib.Path(elf_file_path).parent, - edt_pickle) - with open(edt_pickle_path, "rb") as f: - edt = pickle.load(f) - - self._ord2node = edt.dep_ord2node - - self._obj = ZephyrInitLevels(elf_file_path) - - self.errors = 0 - - def _check_dep(self, dev_ord, dep_ord): - """Validate the priority between two devices.""" - if dev_ord == dep_ord: - return - - dev_node = self._ord2node[dev_ord] - dep_node = self._ord2node[dep_ord] - - if dev_node._binding: - dev_compat = dev_node._binding.compatible - if dev_compat in _IGNORE_COMPATIBLES: - self.log.info(f"Ignoring priority: {dev_node._binding.compatible}") - return - - if dev_node._binding and dep_node._binding: - dev_compat = dev_node._binding.compatible - dep_compat = dep_node._binding.compatible - if (dev_compat, dep_compat) in _INVERTED_PRIORITY_COMPATIBLES: - self.log.info(f"Swapped priority: {dev_compat}, {dep_compat}") - dev_ord, dep_ord = dep_ord, dev_ord - - dev_prio, dev_init = self._obj.devices.get(dev_ord, (None, None)) - dep_prio, dep_init = self._obj.devices.get(dep_ord, (None, None)) - - if not dev_prio or not dep_prio: - return - - if dev_prio == dep_prio: - raise ValueError(f"{dev_node.path} and {dep_node.path} have the " - f"same priority: {dev_prio}") - elif dev_prio < dep_prio: - if not self.errors: - self.log.error("Device initialization priority validation failed, " - "the sequence of initialization calls does not match " - "the devicetree dependencies.") - self.errors += 1 - self.log.error( - f"{dev_node.path} <{dev_init}> is initialized before its dependency " - f"{dep_node.path} <{dep_init}> ({dev_prio} < {dep_prio})") - else: - self.log.info( - f"{dev_node.path} <{dev_init}> {dev_prio} > " - f"{dep_node.path} <{dep_init}> {dep_prio}") - - def check_edt(self): - """Scan through all known devices and validate the init priorities.""" - for dev_ord in self._obj.devices: - dev = self._ord2node[dev_ord] - for dep in dev.depends_on: - self._check_dep(dev_ord, dep.dep_ordinal) - - def print_initlevels(self): - for level, calls in self._obj.initlevels.items(): - print(level) - for call in calls: - print(f" {call}") - -def _parse_args(argv): - """Parse the command line arguments.""" - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter, - allow_abbrev=False) - - parser.add_argument("-f", "--elf-file", default=pathlib.Path("build", "zephyr", "zephyr.elf"), - help="ELF file to use") - parser.add_argument("-v", "--verbose", action="count", - help=("enable verbose output, can be used multiple times " - "to increase verbosity level")) - parser.add_argument("--always-succeed", action="store_true", - help="always exit with a return code of 0, used for testing") - parser.add_argument("-o", "--output", - help="write the output to a file in addition to stdout") - parser.add_argument("-i", "--initlevels", action="store_true", - help="print the initlevel functions instead of checking the device dependencies") - parser.add_argument("--edt-pickle", default=pathlib.Path("edt.pickle"), - help="name of the the pickled edtlib.EDT file", - type=pathlib.Path) - - return parser.parse_args(argv) - -def _init_log(verbose, output): - """Initialize a logger object.""" - log = logging.getLogger(__file__) - - console = logging.StreamHandler() - console.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) - log.addHandler(console) - - if output: - file = logging.FileHandler(output, mode="w") - file.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) - log.addHandler(file) - - if verbose and verbose > 1: - log.setLevel(logging.DEBUG) - elif verbose and verbose > 0: - log.setLevel(logging.INFO) - else: - log.setLevel(logging.WARNING) - - return log - -def main(argv=None): - args = _parse_args(argv) - - log = _init_log(args.verbose, args.output) - - log.info(f"check_init_priorities: {args.elf_file}") - - validator = Validator(args.elf_file, args.edt_pickle, log) - if args.initlevels: - validator.print_initlevels() - else: - validator.check_edt() - - if args.always_succeed: - return 0 - - if validator.errors: - return 1 - - return 0 - -if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) diff --git a/scripts/build/check_init_priorities_test.py b/scripts/build/check_init_priorities_test.py deleted file mode 100755 index c7b2aabb25f46..0000000000000 --- a/scripts/build/check_init_priorities_test.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 Google LLC -# SPDX-License-Identifier: Apache-2.0 -""" -Tests for check_init_priorities -""" - -import mock -import pathlib -import unittest - -from elftools.elf.relocation import Section -from elftools.elf.sections import SymbolTableSection - -import check_init_priorities - -class TestPriority(unittest.TestCase): - """Tests for the Priority class.""" - - def test_priority_parsing(self): - prio1 = check_init_priorities.Priority("POST_KERNEL", 12) - self.assertEqual(prio1._level_priority, (3, 12)) - - prio1 = check_init_priorities.Priority("APPLICATION", 9999) - self.assertEqual(prio1._level_priority, (4, 9999)) - - with self.assertRaises(ValueError): - check_init_priorities.Priority("i-am-not-a-priority", 0) - check_init_priorities.Priority("_DOESNOTEXIST0_", 0) - - def test_priority_levels(self): - prios = [ - check_init_priorities.Priority("EARLY", 0), - check_init_priorities.Priority("EARLY", 1), - check_init_priorities.Priority("PRE_KERNEL_1", 0), - check_init_priorities.Priority("PRE_KERNEL_1", 1), - check_init_priorities.Priority("PRE_KERNEL_2", 0), - check_init_priorities.Priority("PRE_KERNEL_2", 1), - check_init_priorities.Priority("POST_KERNEL", 0), - check_init_priorities.Priority("POST_KERNEL", 1), - check_init_priorities.Priority("APPLICATION", 0), - check_init_priorities.Priority("APPLICATION", 1), - check_init_priorities.Priority("SMP", 0), - check_init_priorities.Priority("SMP", 1), - ] - - self.assertListEqual(prios, sorted(prios)) - - def test_priority_strings(self): - prio = check_init_priorities.Priority("POST_KERNEL", 12) - self.assertEqual(str(prio), "POST_KERNEL+12") - self.assertEqual(repr(prio), "") - -class testZephyrInitLevels(unittest.TestCase): - """Tests for the ZephyrInitLevels class.""" - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_load_objects(self, mock_zilinit): - mock_elf = mock.Mock() - - sts = mock.Mock(spec=SymbolTableSection) - rel = mock.Mock(spec=Section) - mock_elf.iter_sections.return_value = [sts, rel] - - s0 = mock.Mock() - s0.name = "a" - s0.entry.st_info.type = "STT_OBJECT" - s0.entry.st_size = 4 - s0.entry.st_value = 0xaa - s0.entry.st_shndx = 1 - - s1 = mock.Mock() - s1.name = None - - s2 = mock.Mock() - s2.name = "b" - s2.entry.st_info.type = "STT_FUNC" - s2.entry.st_size = 8 - s2.entry.st_value = 0xbb - s2.entry.st_shndx = 2 - - sts.iter_symbols.return_value = [s0, s1, s2] - - obj = check_init_priorities.ZephyrInitLevels("") - obj._elf = mock_elf - obj._load_objects() - - self.assertDictEqual(obj._objects, {0xaa: ("a", 4, 1), 0xbb: ("b", 8, 2)}) - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_load_level_addr(self, mock_zilinit): - mock_elf = mock.Mock() - - sts = mock.Mock(spec=SymbolTableSection) - rel = mock.Mock(spec=Section) - mock_elf.iter_sections.return_value = [sts, rel] - - s0 = mock.Mock() - s0.name = "__init_EARLY_start" - s0.entry.st_value = 0x00 - - s1 = mock.Mock() - s1.name = "__init_PRE_KERNEL_1_start" - s1.entry.st_value = 0x11 - - s2 = mock.Mock() - s2.name = "__init_PRE_KERNEL_2_start" - s2.entry.st_value = 0x22 - - s3 = mock.Mock() - s3.name = "__init_POST_KERNEL_start" - s3.entry.st_value = 0x33 - - s4 = mock.Mock() - s4.name = "__init_APPLICATION_start" - s4.entry.st_value = 0x44 - - s5 = mock.Mock() - s5.name = "__init_SMP_start" - s5.entry.st_value = 0x55 - - s6 = mock.Mock() - s6.name = "__init_end" - s6.entry.st_value = 0x66 - - sts.iter_symbols.return_value = [s0, s1, s2, s3, s4, s5, s6] - - obj = check_init_priorities.ZephyrInitLevels("") - obj._elf = mock_elf - obj._load_level_addr() - - self.assertDictEqual(obj._init_level_addr, { - "EARLY": 0x00, - "PRE_KERNEL_1": 0x11, - "PRE_KERNEL_2": 0x22, - "POST_KERNEL": 0x33, - "APPLICATION": 0x44, - "SMP": 0x55, - }) - self.assertEqual(obj._init_level_end, 0x66) - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_device_ord_from_name(self, mock_zilinit): - obj = check_init_priorities.ZephyrInitLevels("") - - self.assertEqual(obj._device_ord_from_name(None), None) - self.assertEqual(obj._device_ord_from_name("hey, hi!"), None) - self.assertEqual(obj._device_ord_from_name("__device_dts_ord_123"), 123) - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_object_name(self, mock_zilinit): - obj = check_init_priorities.ZephyrInitLevels("") - obj._objects = {0x123: ("name", 4)} - - self.assertEqual(obj._object_name(0), "NULL") - self.assertEqual(obj._object_name(73), "unknown") - self.assertEqual(obj._object_name(0x123), "name") - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_initlevel_pointer_32(self, mock_zilinit): - obj = check_init_priorities.ZephyrInitLevels("") - obj._elf = mock.Mock() - obj._elf.elfclass = 32 - mock_section = mock.Mock() - obj._elf.get_section.return_value = mock_section - mock_section.header.sh_addr = 0x100 - mock_section.data.return_value = (b"\x01\x00\x00\x00" - b"\x02\x00\x00\x00" - b"\x03\x00\x00\x00") - - self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1) - self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2) - self.assertEqual(obj._initlevel_pointer(0x104, 0, 0), 2) - self.assertEqual(obj._initlevel_pointer(0x104, 1, 0), 3) - - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_initlevel_pointer_64(self, mock_zilinit): - obj = check_init_priorities.ZephyrInitLevels("") - obj._elf = mock.Mock() - obj._elf.elfclass = 64 - mock_section = mock.Mock() - obj._elf.get_section.return_value = mock_section - mock_section.header.sh_addr = 0x100 - mock_section.data.return_value = (b"\x01\x00\x00\x00\x00\x00\x00\x00" - b"\x02\x00\x00\x00\x00\x00\x00\x00" - b"\x03\x00\x00\x00\x00\x00\x00\x00") - - self.assertEqual(obj._initlevel_pointer(0x100, 0, 0), 1) - self.assertEqual(obj._initlevel_pointer(0x100, 1, 0), 2) - self.assertEqual(obj._initlevel_pointer(0x108, 0, 0), 2) - self.assertEqual(obj._initlevel_pointer(0x108, 1, 0), 3) - - @mock.patch("check_init_priorities.ZephyrInitLevels._object_name") - @mock.patch("check_init_priorities.ZephyrInitLevels._initlevel_pointer") - @mock.patch("check_init_priorities.ZephyrInitLevels.__init__", return_value=None) - def test_process_initlevels(self, mock_zilinit, mock_ip, mock_on): - obj = check_init_priorities.ZephyrInitLevels("") - obj._init_level_addr = { - "EARLY": 0x00, - "PRE_KERNEL_1": 0x00, - "PRE_KERNEL_2": 0x00, - "POST_KERNEL": 0x08, - "APPLICATION": 0x0c, - "SMP": 0x0c, - } - obj._init_level_end = 0x0c - obj._objects = { - 0x00: ("a", 4, 0), - 0x04: ("b", 4, 0), - 0x08: ("c", 4, 0), - } - - mock_ip.side_effect = lambda *args: args - - def mock_obj_name(*args): - if args[0] == (0, 0, 0): - return "i0" - elif args[0] == (0, 1, 0): - return "__device_dts_ord_11" - elif args[0] == (4, 0, 0): - return "i1" - elif args[0] == (4, 1, 0): - return "__device_dts_ord_22" - return f"name_{args[0][0]}_{args[0][1]}" - mock_on.side_effect = mock_obj_name - - obj._process_initlevels() - - self.assertDictEqual(obj.initlevels, { - "EARLY": [], - "PRE_KERNEL_1": [], - "PRE_KERNEL_2": ["a: i0(__device_dts_ord_11)", "b: i1(__device_dts_ord_22)"], - "POST_KERNEL": ["c: name_8_0(name_8_1)"], - "APPLICATION": [], - "SMP": [], - }) - self.assertDictEqual(obj.devices, { - 11: (check_init_priorities.Priority("PRE_KERNEL_2", 0), "i0"), - 22: (check_init_priorities.Priority("PRE_KERNEL_2", 1), "i1"), - }) - -class testValidator(unittest.TestCase): - """Tests for the Validator class.""" - - @mock.patch("check_init_priorities.ZephyrInitLevels") - @mock.patch("pickle.load") - def test_initialize(self, mock_pl, mock_zil): - mock_log = mock.Mock() - mock_prio = mock.Mock() - mock_obj = mock.Mock() - mock_obj.defined_devices = {123: mock_prio} - mock_zil.return_value = mock_obj - - with mock.patch("builtins.open", mock.mock_open()) as mock_open: - validator = check_init_priorities.Validator("path", "pickle", mock_log) - - self.assertEqual(validator._obj, mock_obj) - mock_zil.assert_called_once_with("path") - mock_open.assert_called_once_with(pathlib.Path("pickle"), "rb") - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_dep_same_node(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - - validator._check_dep(123, 123) - - self.assertFalse(validator.log.info.called) - self.assertFalse(validator.log.warning.called) - self.assertFalse(validator.log.error.called) - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_dep_no_prio(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - validator._obj = mock.Mock() - - validator._ord2node = {1: mock.Mock(), 2: mock.Mock()} - validator._ord2node[1]._binding = None - validator._ord2node[2]._binding = None - - validator._obj.devices = {1: (10, "i1")} - validator._check_dep(1, 2) - - validator._obj.devices = {2: (20, "i2")} - validator._check_dep(1, 2) - - self.assertFalse(validator.log.info.called) - self.assertFalse(validator.log.warning.called) - self.assertFalse(validator.log.error.called) - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - validator._obj = mock.Mock() - validator.errors = 0 - - validator._ord2node = {1: mock.Mock(), 2: mock.Mock()} - validator._ord2node[1]._binding = None - validator._ord2node[1].path = "/1" - validator._ord2node[2]._binding = None - validator._ord2node[2].path = "/2" - - validator._obj.devices = {1: (10, "i1"), 2: (20, "i2")} - - validator._check_dep(2, 1) - validator._check_dep(1, 2) - - validator.log.info.assert_called_once_with("/2 20 > /1 10") - validator.log.error.assert_has_calls([ - mock.call("/1 is initialized before its dependency /2 (10 < 20)") - ]) - self.assertEqual(validator.errors, 1) - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_same_prio_assert(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - validator._obj = mock.Mock() - validator.errors = 0 - - validator._ord2node = {1: mock.Mock(), 2: mock.Mock()} - validator._ord2node[1]._binding = None - validator._ord2node[1].path = "/1" - validator._ord2node[2]._binding = None - validator._ord2node[2].path = "/2" - - validator._obj.devices = {1: (10, "i1"), 2: (10, "i2")} - - with self.assertRaises(ValueError): - validator._check_dep(1, 2) - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_swapped(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - validator._obj = mock.Mock() - validator.errors = 0 - - save_inverted_priorities = check_init_priorities._INVERTED_PRIORITY_COMPATIBLES - - check_init_priorities._INVERTED_PRIORITY_COMPATIBLES = set([("compat-3", "compat-1")]) - - validator._ord2node = {1: mock.Mock(), 3: mock.Mock()} - validator._ord2node[1]._binding.compatible = "compat-1" - validator._ord2node[1].path = "/1" - validator._ord2node[3]._binding.compatible = "compat-3" - validator._ord2node[3].path = "/3" - - validator._obj.devices = {1: (20, "i1"), 3: (10, "i3")} - - validator._check_dep(3, 1) - - self.assertListEqual(validator.log.info.call_args_list, [ - mock.call("Swapped priority: compat-3, compat-1"), - mock.call("/3 20 > /1 10"), - ]) - self.assertEqual(validator.errors, 0) - - check_init_priorities._INVERTED_PRIORITY_COMPATIBLES = save_inverted_priorities - - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_ignored(self, mock_vinit): - validator = check_init_priorities.Validator("", "", None) - validator.log = mock.Mock() - validator._obj = mock.Mock() - validator.errors = 0 - - save_ignore_compatibles = check_init_priorities._IGNORE_COMPATIBLES - - check_init_priorities._IGNORE_COMPATIBLES = set(["compat-3"]) - - validator._ord2node = {1: mock.Mock(), 3: mock.Mock()} - validator._ord2node[1]._binding.compatible = "compat-1" - validator._ord2node[1].path = "/1" - validator._ord2node[3]._binding.compatible = "compat-3" - validator._ord2node[3].path = "/3" - - validator._obj.devices = {1: 20, 3: 10} - - validator._check_dep(3, 1) - - self.assertListEqual(validator.log.info.call_args_list, [ - mock.call("Ignoring priority: compat-3"), - ]) - self.assertEqual(validator.errors, 0) - - check_init_priorities._IGNORE_COMPATIBLES = save_ignore_compatibles - - @mock.patch("check_init_priorities.Validator._check_dep") - @mock.patch("check_init_priorities.Validator.__init__", return_value=None) - def test_check_edt(self, mock_vinit, mock_cd): - d0 = mock.Mock() - d0.dep_ordinal = 1 - d1 = mock.Mock() - d1.dep_ordinal = 2 - d2 = mock.Mock() - d2.dep_ordinal = 3 - - dev0 = mock.Mock() - dev0.depends_on = [d0] - dev1 = mock.Mock() - dev1.depends_on = [d1] - dev2 = mock.Mock() - dev2.depends_on = [d2] - - validator = check_init_priorities.Validator("", "", None) - validator._ord2node = {1: dev0, 2: dev1, 3: dev2} - validator._obj = mock.Mock() - validator._obj.devices = {1: 10, 2: 10, 3: 20} - - validator.check_edt() - - self.assertListEqual(mock_cd.call_args_list, [ - mock.call(1, 1), - mock.call(2, 2), - mock.call(3, 3), - ]) - -if __name__ == "__main__": - unittest.main() diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 088ef6e09967a..1b4468baf1b43 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -1953,6 +1953,13 @@ class EDT: /chosen node which can't be converted to a Node are not included in the value. + aliases_nodes: + A dict that maps the properties defined on the devicetree's /aliases + node to their values. 'aliases' is indexed by property name (a string), + and values are converted to Node objects. Note that properties of the + /aliases node which can't be converted to a Node are not included in + the value. + dts_path: The .dts path passed to __init__() @@ -2117,6 +2124,33 @@ def chosen_node(self, name: str) -> Optional[Node]: """ return self.chosen_nodes.get(name) + @property + def aliases_nodes(self) -> Dict[str, Node]: + ret: Dict[str, Node] = {} + + try: + aliases = self._dt.get_node("/aliases") + except DTError: + return ret + + for name, prop in aliases.props.items(): + try: + node = prop.to_path() + except DTError: + # DTS value is not phandle or string, or path doesn't exist + continue + + ret[name] = self._node2enode[node] + + return ret + + def aliases_node(self, name: str) -> Optional[Node]: + """ + Returns the Node pointed at by the property named 'name' in /aliases, or + None if the property is missing + """ + return self.aliases_nodes.get(name) + @property def dts_source(self) -> str: return f"{self._dt}" diff --git a/scripts/fwconfig/__init__.py b/scripts/fwconfig/__init__.py new file mode 100644 index 0000000000000..596edfdfa9c9d --- /dev/null +++ b/scripts/fwconfig/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/scripts/fwconfig/fwconfig b/scripts/fwconfig/fwconfig new file mode 100755 index 0000000000000..10e6bd3987037 --- /dev/null +++ b/scripts/fwconfig/fwconfig @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import sys +import yaml +import argparse +import pykwalify.core +from pathlib import Path + +try: + # Use the C LibYAML parser if available, rather than the Python parser. + from yaml import CSafeLoader as SafeLoader +except ImportError: + from yaml import SafeLoader # type: ignore + +# Importing relevant modules +import modules.fwconfig_init as fwconfig_init + +def load_schemas(): + schemas = {} + p = Path(os.path.abspath(os.path.dirname(__file__)) + "/modules") + for sch_p in sorted(p.glob('*.yaml')): + sch_n = Path(sch_p).stem + if sch_n in schemas: + raise Exception(f"Duplicate schema file name {sch_n}") + + with open(sch_p, 'r', encoding='utf-8') as sch_f: + schemas[sch_n] = yaml.load(sch_f, SafeLoader) + + return schemas + +def validate_input(fwconfig_files): + schemas = load_schemas() + if len(schemas) == 0: + return + + for fw_p in fwconfig_files: + with open(fw_p, 'r', encoding="utf-8") as fw_f: + try: + content = yaml.load(fw_f, SafeLoader) + for sch in schemas: + pykwalify.core.Core(source_data=content, + schema_data=schemas[sch]).validate() + except (yaml.YAMLError, pykwalify.errors.SchemaError) as e: + sys.exit(f"ERROR: malformed fwconfig file {fw_p}") + +def main(): + parser = argparse.ArgumentParser(allow_abbrev=False) + + parser.add_argument('--debug', action='store_true', + help='Print debug messages') + parser.add_argument('--validate', action='store_true', + help='Validate the input files prior to using them') + parser.add_argument('--fwconfig-files', required=True, nargs='+', + help='files to load') + # Below is a trick to by-pass this old bug: + # https://github.com/python/cpython/issues/53584 + # or else, we would need to get all modules to have their own parameter + # for loading the common fwconfig files, becoming an hassle for the input + # validation part. + parser.add_argument('-', dest='__into_the_void', action="store_true", + help='Mandatory separation for using a module as below') + + sub_parsers = parser.add_subparsers(title='Modules') + + # 1 - Update arg parser on modules + # .add_arguments(sub_parsers) + fwconfig_init.add_arguments(sub_parsers) + + # 2 - Parse the arguments + args = parser.parse_args() + + # 3 - Should we validate the input files? + if args.validate: + validate_input(args.fwconfig_files) + + # 4 - Run the relevant module + args.func(args) + +if __name__ == "__main__": + main() diff --git a/scripts/fwconfig/modules/__init__.py b/scripts/fwconfig/modules/__init__.py new file mode 100644 index 0000000000000..596edfdfa9c9d --- /dev/null +++ b/scripts/fwconfig/modules/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/scripts/fwconfig/modules/fwconfig_init-schema.yaml b/scripts/fwconfig/modules/fwconfig_init-schema.yaml new file mode 100644 index 0000000000000..cbcb7d837324b --- /dev/null +++ b/scripts/fwconfig/modules/fwconfig_init-schema.yaml @@ -0,0 +1,43 @@ +# Schema file for init object in fw_config.yaml files +# +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +type: map +mapping: + "init": + type: map + mapping: + =: + type: map + mapping: + "level": + type: str + enum: ['EARLY', 'PRE_KERNEL_1', 'PRE_KERNEL_2', + 'POST_KERNEL', 'APPLICATION', 'SMP', 'MANUAL'] + required: false + + "dependencies": + type: seq + seq: + - type: str + required: false + + "if": + type: str + required: false + "then": + required: false + type: map + mapping: + "level": + type: str + enum: ['EARLY', 'PRE_KERNEL_1', 'PRE_KERNEL_2', + 'POST_KERNEL', 'APPLICATION', 'SMP', 'MANUAL'] + required: false + "dependencies": + type: seq + seq: + - type: str + required: false diff --git a/scripts/fwconfig/modules/fwconfig_init.py b/scripts/fwconfig/modules/fwconfig_init.py new file mode 100644 index 0000000000000..aea3d22f9c194 --- /dev/null +++ b/scripts/fwconfig/modules/fwconfig_init.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +import os +import re +import sys +import pickle +import fileinput + +import yaml +try: + # Use the C LibYAML parser if available, rather than the Python parser. + from yaml import CSafeLoader as SafeLoader +except ImportError: + from yaml import SafeLoader # type: ignore + +# We will need to access edtlib +sys.path.insert(0, os.path.join(os.path.dirname(__file__), + '../..', 'dts', 'python-devicetree', 'src')) + +from devicetree.grutils import Graph + +LEVELS = """ +EARLY: + fake: true + rank: 0 + +PRE_KERNEL_1: + fake: true + rank: 1 + dependencies: + - EARLY + +PRE_KERNEL_2: + fake: true + rank: 2 + dependencies: + - PRE_KERNEL_1 + +POST_KERNEL: + fake: true + rank: 3 + dependencies: + - PRE_KERNEL_2 + +APPLICATION: + fake: true + rank: 4 + dependencies: + - POST_KERNEL + +SMP: + fake: true + rank: 5 + dependencies: + - APPLICATION + +MANUAL: + fake: true + rank: 6 + dependencies: + - SMP +""" + +# Q&D debug enabler +global debug +debug = False + +# Zephyr Init Node +class ZInitNode: + def __init__(self, name, node = None): + self.name = name + self.fake = False + self.rank = -1 # used only on fake nodes (aka: actual level nodes) + self.level = None + self.explicit_level = False + self.hard_dependencies = [] + + # compat properties for Graph, as it is supposed to work with edt nodes + self.parent = None + self.unit_addr = None + self.dep_ordinal = -1 + + self.node = node + if node is not None: + # Let's fill in the compat properties relevantly then + self.parent = node.parent + self.unit_addr = node.unit_addr + + def __str__(self): + if self.fake: + return self.name + + s = f"ZInitNode({self.name} {self.dep_ordinal})" + if self.node is not None: + s += f" ({self.node.name})" + + return s + + def set_level(self, level): + self.level = level + self.explicit_level = True + + def add_hard_dependency(self, node): + self.hard_dependencies.append(node) + + def set_implicit_level(self, depth, bottom): + if (self.rank == 0 or + (self.level is not None and self.explicit_level)): + return + + impl_level = None + for node in self.hard_dependencies: + if node.level is None: + if depth == bottom: + raise Exception(f"Check arrived at maximum depth") + node.set_implicit_level(depth+1, bottom) + + if impl_level is None: + if node.fake: + impl_level = node + else: + impl_level = node.level + elif (node.level is not None and + node.level.rank > impl_level.rank): + impl_level = node.level + + if impl_level is not None and not self.fake and debug: + print(f"-> Setting implicit level {impl_level} on {self}") + + self.level = impl_level + + def _get_define(self): + if self.node is not None: + basename = f"DT_{self.node.z_path_id}" + else: + basename = self.name.replace('@', '_').replace('-', '_') + + return f"#define ZINIT_{basename}_" + + def _get_level_macros(self): + if self.level is None: + return None + + implicit = 'IMPLICIT' + if self.explicit_level: + implicit = 'EXPLICIT' + + level_str = f"{self._get_define()}INIT_{implicit}_LEVEL_EXISTS 1\n" + level_str += f"{self._get_define()}INIT_{implicit}_LEVEL {self.level}" + + return level_str + + def _get_priority_macro(self): + return f"{self._get_define()}INIT_PRIORITY {self.dep_ordinal}" + + def write_macros(self, header): + if self.fake: + return + + if self.node is not None: + name = self.node.name + path = self.node.path + else: + name = self.name + path = None + + header.write(f"/** Zephyr Init Node: {name} ") + if path is not None: + header.write(f"\n * DTS object path: {path}\n ") + header.write("*/\n") + + header.write(f"{self._get_define()}EXISTS 1\n") + + level = self._get_level_macros() + if level is not None: + header.write(f"{level}\n") + + header.write(f"{self._get_priority_macro()}\n\n") + + +class EDTNodeMatcher: + def __init__(self, edt_file): + self.names = {} + self.paths = {} + self.yaml_nodes = {} + self.macro_viability_rule = re.compile('[0-9a-zA-Z_]*') + + self.__load(edt_file) + + def __is_excluded(self, name): + return name in [ '/', 'aliases', 'chosen' ] + + def __load(self, edt_file): + with open(edt_file, 'rb') as f: + self.edt = pickle.load(f) + + for node_list in self.edt.scc_order: + for node in node_list: + if self.__is_excluded(node.name): + continue + + self.add(node) + self.yaml_nodes[node.path] = {} + + deps = [] + for d_node in self.edt._graph.depends_on(node): + if self.__is_excluded(d_node.name): + continue + deps.append(d_node.path) + + if len(deps) > 0: + self.yaml_nodes[node.path]['dependencies'] = deps + + def as_yaml_nodes(self): + return self.yaml_nodes + + def get_real_name(self, name): + chosen = self.edt.chosen_node(name) + if chosen is not None: + return chosen.path, True + + alias = self.edt.aliases_node(name) + if alias is not None: + return alias.path, True + + if name in self.edt.label2node: + return self.edt.label2node[name].path, False + + if name in self.edt.compat2nodes: + return None, True + + node = self.get(name) + if node is None: + return name, False + + return node.path, False + + def add(self, node): + if node.path in self.paths: + return + + if node.name not in self.names: + self.names[node.name] = [] + + self.names[node.name].append(node) + self.paths[node.path] = node + + def get(self, desc): + if desc in self.names: + if len(self.names[desc]) > 1: + raise Exception(f"Name {desc} leads to multiple nodes") + return self.names[desc][0] + + if desc in self.paths: + return self.paths[desc] + + # ToDo: desc could be a truncated path, if needed + + return None + + def check_viability(self, name=None, names=None): + if name is not None: + names = [] + names.append(name) + + results = [] + for n in names: + rn, alias = self.get_real_name(n) + if (n == rn and self.get(n) is None and + self.macro_viability_rule.fullmatch(n) is None): + if debug: + print(f"-> Skipping {n}: not EDT-based nor macro viable") + continue + + if rn is None and alias is True: + compats = self.edt.compat2nodes[n] + for node in compats: + results.append(node.path) + else: + results.append(rn) + + if len(results) == 0: + return None + + return results + +def load_dotconfig(dotconfig_file): + dotconfig = [] + with fileinput.input(files=(dotconfig_file), encoding="utf-8") as dcf: + for line in dcf: + if len(line) < 8 or line[0] == '#': + continue + + opt, val = line.split('=', 1) + val = val.strip('\r\n ') + if val != 'y': + continue + + dotconfig.append(opt) + + return dotconfig + +def update_from_yaml(yaml_nodes, updates, dotcfg, edt_nodes): + if updates is None or 'init' not in updates: + return yaml_nodes + + updates = updates['init'] + for un in list(updates): + yn_l = edt_nodes.check_viability(name=un) + if yn_l is None: + continue + + for yn in yn_l: + if yn not in yaml_nodes.keys(): + yaml_nodes[yn] = {} + + if 'level' in updates[un]: + yaml_nodes[yn]['level'] = updates[un]['level'] + + if 'dependencies' in updates[un]: + n_l = updates[un]['dependencies'] + deps = edt_nodes.check_viability(names=n_l) + if deps is not None: + if 'dependencies' in yaml_nodes[yn]: + yaml_nodes[yn]['dependencies'] += deps + else: + yaml_nodes[yn]['dependencies'] = deps + + if 'if' in updates[un] and 'then' in updates[un]: + cond = updates[un]['if'] + if cond not in dotcfg: + continue + + stmt = updates[un]['then'] + if 'level' in stmt: + yaml_nodes[yn]['level'] = stmt['level'] + if 'dependencies' in stmt: + n_l = stmt['dependencies'] + deps = edt_nodes.check_viability(names=n_l) + if deps is not None: + yaml_nodes[yn]['dependencies'] = deps + + return yaml_nodes + +def generate_zinit_nodes_graph(edt_nodes, yaml_nodes): + zinit_nodes = {} + graph = Graph() + + for yn_name in list(yaml_nodes): + z_n, alias = edt_nodes.get_real_name(yn_name) + if z_n not in zinit_nodes: + zinit_node = ZInitNode(z_n, node = edt_nodes.get(z_n)) + zinit_nodes[z_n] = zinit_node + else: + zinit_node = zinit_nodes[z_n] + + ynode = yaml_nodes[yn_name] + if ynode is None and not alias: + graph.add_node(zinit_node) + continue + + if 'level' in ynode: + if ynode['level'] not in zinit_nodes: + raise Exception("No such level as "+ynode['level']) + zinit_node.set_level(zinit_nodes[ynode['level']]) + + if 'fake' in ynode: + zinit_node.fake = ynode['fake'] + zinit_node.rank = ynode['rank'] # rank has to be provided + + if 'dependencies' in ynode: + for d in ynode['dependencies']: + d_n, _ = edt_nodes.get_real_name(d) + if d_n not in zinit_nodes: + dnode = ZInitNode(d_n, node = edt_nodes.get(d_n)) + zinit_nodes[d_n] = dnode + else: + dnode = zinit_nodes[d_n] + + zinit_node.add_hard_dependency(dnode) + graph.add_edge(zinit_node, dnode) + else: + graph.add_node(zinit_node) + + # Updating edges depending on levels + for zn in zinit_nodes: + zinit_node = zinit_nodes[zn] + if not zinit_node.explicit_level: + zinit_node.set_implicit_level(1, len(zinit_nodes)) + if zinit_node.level is not None: + graph.add_edge(zinit_node, zinit_node.level) + + return zinit_nodes, graph + +def verify_detected_loops(zinit_nodes): + found = [] + for ns in zinit_nodes: + node = zinit_nodes[ns] + if not node.fake and node.dep_ordinal == -1: + found.append(node) + + if len(found) > 0: + s = "" + for nf in found: + s.join(f"{nf} ") + + raise Exception(f"Dependency loop detected around: {s}") + +def verify_level_sanity(zinit_nodes): + def _level_check(node, level, depth, bottom): + for hd_node in node.hard_dependencies: + if (hd_node.level is not None and level.rank < hd_node.level.rank): + return hd_node + + if depth == bottom: + raise Exception("Check arrived at maximum depth") + + return _level_check(hd_node, level, depth+1, bottom) + + return None + + found = [] + for ns in zinit_nodes: + node = zinit_nodes[ns] + if node.level is None: + continue + + hd_node = _level_check(node, node.level, 0, len(zinit_nodes)) + if hd_node is not None: + found.append([node, hd_node]) + + if len(found) > 0: + s = "" + for nf in found: + s += f"{nf[0]} is on an earlier level than " + s += f"{nf[1]} but requires the later\n" + + raise Exception(f"Level sanity broken: {s}") + +def generate_header_file(header, ord_list): + with open(header, "w") as hf: + hf.write("/*\n * Generated by fwconfig's init module\n */\n\n") + + for l in ord_list: + for n in l: + n.write_macros(hf) + +def print_yaml_nodes(yaml_nodes): + if not debug: + return + print("\n# YAML nodes :") + print("init:") + for yn in list(yaml_nodes): + print(f" {yn}:") + ynode = yaml_nodes[yn] + if 'fake' in ynode: + print(f" fake: true") + print(f" rank: {ynode['rank']}") + if 'level' in ynode: + print(f" level: {ynode['level']}") + if 'dependencies' in ynode: + print(f" dependencies:") + for elt in ynode['dependencies']: + print(f" - {elt}") + print("") + +def print_edt_nodes(edt_nodes): + if not debug: + return + print("EDT original graph:") + for l in edt_nodes.edt.scc_order: + for n in l: + print(f" {n.name} -> {n.path}") + print("") + +def print_ordered(ordered): + if not debug: + return + print("\nFinal initialization graph:") + for l in ordered: + for n in l: + print(f" {n}") + print("") + +def init_run(args): + global debug + debug = args.debug + + # 1 - Loading the generated .config + dotconfig = load_dotconfig(args.dotconfig_file) + + # 2 - Loading levels, to act as fake nodes + yaml_nodes = yaml.safe_load(LEVELS) + + # 3a - Loading EDT DTS nodes (pair of name/path leading to node) + edt_nodes = EDTNodeMatcher(args.edt_pickle) + print_edt_nodes(edt_nodes) + + # 3b - Integrating EDT as yaml nodes + yaml_nodes.update(edt_nodes.as_yaml_nodes()) + + # 4 - Now loading any extra yaml nodes, and integrating them relevantly + for fwc_p in args.fwconfig_files: + with open(fwc_p, 'r', encoding="utf-8") as fwc_f: + yaml_nodes = update_from_yaml(yaml_nodes, + yaml.load(fwc_f, SafeLoader), + dotconfig, + edt_nodes) + print_yaml_nodes(yaml_nodes) + + # 5 - Generating dependency graph from the yaml_nodes + zinit_nodes, graph = generate_zinit_nodes_graph(edt_nodes, yaml_nodes) + + print_ordered(graph.scc_order()) + + # 6 - Sanity check + verify_detected_loops(zinit_nodes) + verify_level_sanity(zinit_nodes) + + # 7 - Finally, as all went well, let's generate the header + generate_header_file(args.header_out, graph.scc_order()) + +def add_arguments(parser_parent): + parser = parser_parent.add_parser( + 'init', help='Zephyr initialization nodes generator') + + parser.add_argument("--dotconfig-file", required=True, + help="Path to the general .config file") + parser.add_argument("--edt-pickle", required=True, + help="Path to the pickled edtlib.EDT object file") + parser.add_argument("--header-out", required=True, + help="Path to write generated header to") + + parser.set_defaults(func=init_run) diff --git a/scripts/pylib/twister/twisterlib/testplan.py b/scripts/pylib/twister/twisterlib/testplan.py index 83c5d6b06fdad..0251226722be9 100755 --- a/scripts/pylib/twister/twisterlib/testplan.py +++ b/scripts/pylib/twister/twisterlib/testplan.py @@ -425,7 +425,7 @@ def add_configurations(self): os.path.basename(file).startswith( re.split('[/@]', p)[0] ) for p in self.options.platform - ]): + ]) or file.endswith('fw_config.yaml'): continue try: platform = Platform() diff --git a/tests/drivers/build_all/input/app.overlay b/tests/drivers/build_all/input/app.overlay index 23bd36a30743b..2654f88de1705 100644 --- a/tests/drivers/build_all/input/app.overlay +++ b/tests/drivers/build_all/input/app.overlay @@ -145,7 +145,6 @@ }; longpress: longpress { - input = <&longpress>; compatible = "zephyr,input-longpress"; input-codes = <0>; short-codes = <0>; diff --git a/tests/drivers/ipm/fw_config.yaml b/tests/drivers/ipm/fw_config.yaml new file mode 100644 index 0000000000000..d549e6591ed92 --- /dev/null +++ b/tests/drivers/ipm/fw_config.yaml @@ -0,0 +1,12 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + ipm_console_send0: + dependencies: + - ipm_dummy0 + + ipm_console_recv0: + dependencies: + - ipm_dummy0 diff --git a/tests/kernel/device/fw_config.yaml b/tests/kernel/device/fw_config.yaml new file mode 100644 index 0000000000000..3a81216bfb6e8 --- /dev/null +++ b/tests/kernel/device/fw_config.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + my_driver_priority_2: + dependencies: + - my_driver_priority_1 + + my_driver_priority_3: + dependencies: + - my_driver_priority_2 + + my_driver_priority_4: + dependencies: + - my_driver_priority_3 diff --git a/tests/lib/devicetree/devices/fw_config.yaml b/tests/lib/devicetree/devices/fw_config.yaml new file mode 100644 index 0000000000000..dcdea8d199272 --- /dev/null +++ b/tests/lib/devicetree/devices/fw_config.yaml @@ -0,0 +1,36 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + test_dev_a: + dependencies: + - test_i2c + + test_gpiox: + dependencies: + - test_dev_a + + test_dev_b: + dependencies: + - test_gpiox + + test_dev_c: + dependencies: + - test_dev_b + + test_p0: + dependencies: + - test_dev_c + + test_gpio_injected: + dependencies: + - test_p0 + + manual_dev: + dependencies: + - test_gpio_injected + + test-i2c-dev@14: + dependencies: + - manual_dev diff --git a/tests/lib/devicetree/devices/prj.conf b/tests/lib/devicetree/devices/prj.conf index fa50f0de8c63e..5358dab3d11fe 100644 --- a/tests/lib/devicetree/devices/prj.conf +++ b/tests/lib/devicetree/devices/prj.conf @@ -1,4 +1,3 @@ CONFIG_ZTEST=y CONFIG_I2C=n CONFIG_DEVICE_DEPS=y -CONFIG_CHECK_INIT_PRIORITIES=n diff --git a/tests/lib/devicetree/devices/src/main.c b/tests/lib/devicetree/devices/src/main.c index 55eade26fa8e1..1cb6f8765c12e 100644 --- a/tests/lib/devicetree/devices/src/main.c +++ b/tests/lib/devicetree/devices/src/main.c @@ -40,7 +40,6 @@ DEVICE_DT_DEFINE(TEST_I2C, dev_init, NULL, NULL, NULL, POST_KERNEL, 10, NULL); DEVICE_DT_DEFINE(TEST_DEVA, dev_init, NULL, NULL, NULL, POST_KERNEL, 20, NULL); -/* NB: Intentional init devb before required gpiox */ DEVICE_DT_DEFINE(TEST_DEVB, dev_init, NULL, NULL, NULL, POST_KERNEL, 30, NULL); DEVICE_DT_DEFINE(TEST_GPIOX, dev_init, NULL, @@ -104,8 +103,8 @@ ZTEST(devicetree_devices, test_init_order) zassert_equal(init_order[0], DEV_HDL(TEST_GPIO)); zassert_equal(init_order[1], DEV_HDL(TEST_I2C)); zassert_equal(init_order[2], DEV_HDL(TEST_DEVA)); - zassert_equal(init_order[3], DEV_HDL(TEST_DEVB)); - zassert_equal(init_order[4], DEV_HDL(TEST_GPIOX)); + zassert_equal(init_order[3], DEV_HDL(TEST_GPIOX)); + zassert_equal(init_order[4], DEV_HDL(TEST_DEVB)); zassert_equal(init_order[5], DEV_HDL(TEST_DEVC)); zassert_equal(init_order[6], DEV_HDL(TEST_PARTITION)); zassert_equal(init_order[7], DEV_HDL(TEST_GPIO_INJECTED)); diff --git a/tests/misc/check_init_priorities/CMakeLists.txt b/tests/misc/check_init_priorities/CMakeLists.txt deleted file mode 100644 index 9a615977d5d77..0000000000000 --- a/tests/misc/check_init_priorities/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -cmake_minimum_required(VERSION 3.20.0) -find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) - -set(output_file ${PROJECT_BINARY_DIR}/check_init_priorities_output.txt) - -add_custom_command( - COMMENT "Running check_init_priorities.py" - OUTPUT ${output_file} - DEPENDS - ${logical_target_for_zephyr_elf} - $<$:native_runner_executable> - COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/build/check_init_priorities.py - --elf-file=$,${BYPRODUCT_KERNEL_EXE_NAME},${BYPRODUCT_KERNEL_ELF_NAME}> - --verbose - --output ${output_file} - --always-succeed - COMMAND ${PYTHON_EXECUTABLE} ${APPLICATION_SOURCE_DIR}/validate_check_init_priorities_output.py - ${output_file} -) - -add_custom_target(check_init_priorities_output ALL DEPENDS ${output_file}) - -project(check_init_priorities) - -target_sources(app PRIVATE src/main.c) diff --git a/tests/misc/check_init_priorities/boards/native_posix.overlay b/tests/misc/check_init_priorities/boards/native_posix.overlay deleted file mode 100644 index 1cf720283b395..0000000000000 --- a/tests/misc/check_init_priorities/boards/native_posix.overlay +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "native_sim.overlay" diff --git a/tests/misc/check_init_priorities/boards/native_posix_native_64.overlay b/tests/misc/check_init_priorities/boards/native_posix_native_64.overlay deleted file mode 100644 index 1cf720283b395..0000000000000 --- a/tests/misc/check_init_priorities/boards/native_posix_native_64.overlay +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "native_sim.overlay" diff --git a/tests/misc/check_init_priorities/boards/native_sim.overlay b/tests/misc/check_init_priorities/boards/native_sim.overlay deleted file mode 100644 index 0f32c8121a894..0000000000000 --- a/tests/misc/check_init_priorities/boards/native_sim.overlay +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/ { - test_gpio_0: gpio@ffff { - gpio-controller; - #gpio-cells = <0x2>; - compatible = "vnd,gpio-device"; - status = "okay"; - reg = <0xffff 0x1000>; - }; - - test_i2c: i2c@11112222 { - #address-cells = <1>; - #size-cells = <0>; - compatible = "vnd,i2c"; - status = "okay"; - reg = <0x11112222 0x1000>; - clock-frequency = <100000>; - - test_dev_a: test-i2c-dev@10 { - compatible = "vnd,i2c-device"; - status = "okay"; - reg = <0x10>; - supply-gpios = <&test_gpio_0 1 0>; - }; - - test_dev_b: test-i2c-dev@11 { - compatible = "vnd,i2c-device"; - status = "okay"; - reg = <0x11>; - supply-gpios = <&test_gpio_0 2 0>; - }; - }; -}; diff --git a/tests/misc/check_init_priorities/boards/native_sim_native_64.overlay b/tests/misc/check_init_priorities/boards/native_sim_native_64.overlay deleted file mode 100644 index 1cf720283b395..0000000000000 --- a/tests/misc/check_init_priorities/boards/native_sim_native_64.overlay +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "native_sim.overlay" diff --git a/tests/misc/check_init_priorities/prj.conf b/tests/misc/check_init_priorities/prj.conf deleted file mode 100644 index 6709862e8d433..0000000000000 --- a/tests/misc/check_init_priorities/prj.conf +++ /dev/null @@ -1 +0,0 @@ -CONFIG_CHECK_INIT_PRIORITIES=n diff --git a/tests/misc/check_init_priorities/src/main.c b/tests/misc/check_init_priorities/src/main.c deleted file mode 100644 index a9cf1c472ccbd..0000000000000 --- a/tests/misc/check_init_priorities/src/main.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -DEVICE_DT_DEFINE(DT_INST(0, vnd_gpio_device), NULL, NULL, NULL, NULL, - PRE_KERNEL_1, 50, NULL); -DEVICE_DT_DEFINE(DT_INST(0, vnd_i2c), NULL, NULL, NULL, NULL, - PRE_KERNEL_1, 50, NULL); - -DEVICE_DT_DEFINE(DT_INST(0, vnd_i2c_device), NULL, NULL, NULL, NULL, - PRE_KERNEL_1, 49, NULL); -DEVICE_DT_DEFINE(DT_INST(1, vnd_i2c_device), NULL, NULL, NULL, NULL, - PRE_KERNEL_1, 50, NULL); diff --git a/tests/misc/check_init_priorities/testcase.yaml b/tests/misc/check_init_priorities/testcase.yaml deleted file mode 100644 index 571cba0e1000a..0000000000000 --- a/tests/misc/check_init_priorities/testcase.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -tests: - init.check_init_priorities: - build_only: true - platform_allow: - - native_sim - - native_sim/native/64 - - native_posix - integration_platforms: - - native_sim - - native_sim diff --git a/tests/misc/check_init_priorities/validate_check_init_priorities_output.py b/tests/misc/check_init_priorities/validate_check_init_priorities_output.py deleted file mode 100755 index f5d322164c6f6..0000000000000 --- a/tests/misc/check_init_priorities/validate_check_init_priorities_output.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 Google LLC -# SPDX-License-Identifier: Apache-2.0 - -"""Validate the output of check_init_priorities against a test reference.""" - -import sys - -REFERENCE_OUTPUT = [ - "ERROR: Device initialization priority validation failed, the sequence of initialization calls does not match the devicetree dependencies.", - "ERROR: /i2c@11112222/test-i2c-dev@10 is initialized before its dependency /gpio@ffff (PRE_KERNEL_1+0 < PRE_KERNEL_1+1)", - "ERROR: /i2c@11112222/test-i2c-dev@10 is initialized before its dependency /i2c@11112222 (PRE_KERNEL_1+0 < PRE_KERNEL_1+2)", - "INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1+3 > /gpio@ffff PRE_KERNEL_1+1", - "INFO: /i2c@11112222/test-i2c-dev@11 PRE_KERNEL_1+3 > /i2c@11112222 PRE_KERNEL_1+2", -] - -if len(sys.argv) != 2: - print(f"usage: {sys.argv[0]} FILE_PATH") - sys.exit(1) - -output = [] -with open(sys.argv[1], "r") as file: - for line in file: - if line.startswith("INFO: check_init_priorities"): - continue - output.append(line.strip()) - -if sorted(REFERENCE_OUTPUT) != sorted(output): - print("Mismatched otuput") - print() - print("expected:") - print("\n".join(sorted(REFERENCE_OUTPUT))) - print() - print("got:") - print("\n".join(sorted(output))) - print("TEST FAILED") - sys.exit(1) - -print("TEST PASSED") -sys.exit(0) diff --git a/tests/net/mld/fw_config.yaml b/tests/net/mld/fw_config.yaml new file mode 100644 index 0000000000000..c71f969df2375 --- /dev/null +++ b/tests/net/mld/fw_config.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024, Tomasz Bursztyka +# +# SPDX-License-Identifier: Apache-2.0 + +init: + net_test_null_iface: + dependencies: + - net_init