diff --git a/.gitlab/ci/pre_check.yml b/.gitlab/ci/pre_check.yml index 4a3dfe3649..7718af3ec3 100644 --- a/.gitlab/ci/pre_check.yml +++ b/.gitlab/ci/pre_check.yml @@ -148,6 +148,15 @@ check_esp_err_to_name: - ./gen_esp_err_to_name.py - git diff --exit-code -- ../components/esp_common/src/esp_err_to_name.c || { echo 'Differences found. Please run gen_esp_err_to_name.py and commit the changes.'; exit 1; } +check_esp_system: + extends: + - .pre_check_base_template + - .rules:build + tags: + - build + script: + - python components/esp_system/check_system_init_priorities.py + scan_tests: extends: - .pre_check_base_template diff --git a/components/esp_system/check_system_init_priorities.py b/components/esp_system/check_system_init_priorities.py new file mode 100644 index 0000000000..4bb1741c08 --- /dev/null +++ b/components/esp_system/check_system_init_priorities.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# +# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +# +# This file is used to check the order of execution of ESP_SYSTEM_INIT_FN functions. +# It compares the priorities found in .c source files to the contents of system_init_fn.txt +# In case of an inconsistency, the script prints the differences found and returns with a +# non-zero exit code. + +import difflib +import glob +import itertools +import os +import re +import sys +import typing + +ESP_SYSTEM_INIT_FN_STR = r'ESP_SYSTEM_INIT_FN' +ESP_SYSTEM_INIT_FN_REGEX_SIMPLE = re.compile(r'ESP_SYSTEM_INIT_FN') +ESP_SYSTEM_INIT_FN_REGEX = re.compile(r'ESP_SYSTEM_INIT_FN\(([a-zA-Z0-9_]+)\s*,\s*([a-zA-Z\ _0-9\(\)|]+)\s*,\s*([0-9]+)\)') +STARTUP_ENTRIES_FILE = 'components/esp_system/system_init_fn.txt' + + +class StartupEntry: + def __init__(self, filename: str, func: str, affinity: str, priority: int) -> None: + self.filename = filename + self.func = func + self.affinity = affinity + self.priority = priority + + def __str__(self) -> str: + return f'{self.priority:3d}: {self.func} in {self.filename} on {self.affinity}' + + +def main() -> None: + try: + idf_path = os.environ['IDF_PATH'] + except KeyError: + raise SystemExit('IDF_PATH must be set before running this script') + + has_errors = False + startup_entries = [] # type: typing.List[StartupEntry] + + # + # 1. Iterate over all .c and .cpp source files and find ESP_SYSTEM_INIT_FN definitions + # + source_files_iters = [] + for extension in ('c', 'cpp'): + glob_iter = glob.glob(os.path.join(idf_path, 'components', '**', f'*.{extension}'), recursive=True) + source_files_iters.append(glob_iter) + for filename in itertools.chain(*source_files_iters): + with open(filename, 'r') as f_obj: + file_contents = f_obj.read() + if ESP_SYSTEM_INIT_FN_STR not in file_contents: + continue + count_expected = len(ESP_SYSTEM_INIT_FN_REGEX_SIMPLE.findall(file_contents)) + found = ESP_SYSTEM_INIT_FN_REGEX.findall(file_contents) + if len(found) != count_expected: + print((f'error: In {filename}, found ESP_SYSTEM_INIT_FN {count_expected} time(s), ' + f'but regular expression matched {len(found)} time(s)'), file=sys.stderr) + has_errors = True + + for match in found: + entry = StartupEntry( + filename=os.path.relpath(filename, idf_path), + func=match[0], + affinity=match[1], + priority=int(match[2]) + ) + startup_entries.append(entry) + + # + # 2. Sort the ESP_SYSTEM_INIT_FN functions in C source files by priority + # + startup_entries = list(sorted(startup_entries, key=lambda e: e.priority)) + startup_entries_lines = [str(entry) for entry in startup_entries] + + # + # 3. Load startup entries list from STARTUP_ENTRIES_FILE, removing comments and empty lines + # + startup_entries_expected_lines = [] + with open(os.path.join(idf_path, STARTUP_ENTRIES_FILE), 'r') as startup_entries_expected_file: + for line in startup_entries_expected_file: + if line.startswith('#') or len(line.strip()) == 0: + continue + startup_entries_expected_lines.append(line.rstrip()) + + # + # 4. Print the list of differences, if any + # + diff_lines = list(difflib.unified_diff(startup_entries_expected_lines, startup_entries_lines, lineterm='')) + if len(diff_lines) > 0: + print(('error: startup order doesn\'t match the reference file. ' + f'please update {STARTUP_ENTRIES_FILE} to match the actual startup order:'), file=sys.stderr) + for line in diff_lines: + print(f'{line}', file=sys.stderr) + has_errors = True + + if has_errors: + raise SystemExit(1) + + +if __name__ == '__main__': + main() diff --git a/components/esp_system/system_init_fn.txt b/components/esp_system/system_init_fn.txt new file mode 100644 index 0000000000..426f99eb95 --- /dev/null +++ b/components/esp_system/system_init_fn.txt @@ -0,0 +1,18 @@ +# This file documents the expected order of execution of ESP_SYSTEM_INIT_FN functions. +# +# When adding new ESP_SYSTEM_INIT_FN functions or changing init priorities of existing functions, +# keep this file up to date. This is checked in CI. +# When adding new functions or changing the priorities, please read the comments and see if +# they need to be updated to be consistent with the changes you are making. +# +# Entries are ordered by the order of execution (i.e. from low priority values to high ones). +# Each line has the following format: +# prio: function_name in path/to/source_file on affinity_expression +# Where: +# prio: priority value (higher value means function is executed later) +# affinity_expression: bit map of cores the function is executed on + + +# the rest of the components which are initialized from startup.c +# [refactor-todo]: move init calls into respective components +200: init_components0 in components/esp_system/startup.c on BIT(0) diff --git a/docs/en/api-guides/startup.rst b/docs/en/api-guides/startup.rst index af8013e319..26c1340b2e 100644 --- a/docs/en/api-guides/startup.rst +++ b/docs/en/api-guides/startup.rst @@ -142,7 +142,7 @@ The primary system initialization stage includes: - Initialize SPI flash API support. - Call global C++ constructors and any C functions marked with ``__attribute__((constructor))``. -Secondary system initialization allows individual components to be initialized. If a component has an initialization function annotated with the ``ESP_SYSTEM_INIT_FN`` macro, it will be called as part of secondary initialization. +Secondary system initialization allows individual components to be initialized. If a component has an initialization function annotated with the ``ESP_SYSTEM_INIT_FN`` macro, it will be called as part of secondary initialization. Component initialization functions have priorities assigned to them to ensure the desired initialization order. The priorities are documented in :component_file:`esp_system/system_init_fn.txt` and ``ESP_SYSTEM_INIT_FN`` definition in source code are checked against this file. .. _app-main-task: