瀏覽代碼

esp_system: add a script to check the startup priorities

Ivan Grokhotkov 3 年之前
父節點
當前提交
42654927d5

+ 9 - 0
.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

+ 105 - 0
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()

+ 18 - 0
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)

+ 1 - 1
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: