scripts: dts: Refactor gen_driver_kconfig_dts

The motivation for this patch was to improve the script's performance,
but some stylistic changes and cleanups are included as well.

The main optimization concerns the use of PyYAML, as it offers multiple
functions for interpreting YAML. The commonly used `load`/`safe_load`
converts a YAML stream to a dictionary. There are also `scan`, `parse`,
and `compose`, which return intermediate representations, the last one
being a graph. [1]

Since `gen_driver_kconfig_dts` scans DT bindings for compatible strings,
it only needs to look through top level keys in YAML. The intermediate
PyYAML graph is sufficient for this, and using it reduces the script's
execution time by about 30%, without making the code too complicated.

[1] - https://pyyaml.org/wiki/PyYAMLDocumentation

Signed-off-by: Grzegorz Swiderski <grzegorz.swiderski@nordicsemi.no>
This commit is contained in:
Grzegorz Swiderski 2025-01-13 08:34:52 +01:00 committed by Benjamin Cabé
commit 15488be6af

View file

@ -1,13 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# Copyright (c) 2022 Kumar Gala <galak@kernel.org> # Copyright (c) 2022 Kumar Gala <galak@kernel.org>
# Copyright (c) 2025 Nordic Semiconductor ASA
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import argparse import argparse
import os import os
import sys
import re
import yaml import yaml
try: try:
@ -16,22 +15,33 @@ try:
except ImportError: except ImportError:
from yaml import SafeLoader # type: ignore from yaml import SafeLoader # type: ignore
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python-devicetree',
'src')) HEADER = """\
# Generated devicetree Kconfig
#
# SPDX-License-Identifier: Apache-2.0"""
KCONFIG_TEMPLATE = """
DT_COMPAT_{COMPAT} := {compat}
config DT_HAS_{COMPAT}_ENABLED
\tdef_bool $(dt_compat_enabled,$(DT_COMPAT_{COMPAT}))"""
# Character translation table used to derive Kconfig symbol names
TO_UNDERSCORES = str.maketrans("-,.@/+", "______")
def binding_paths(bindings_dirs): def binding_paths(bindings_dirs):
# Returns a list with the paths to all bindings (.yaml files) in # Yields paths to all bindings (.yaml files) in 'bindings_dirs'
# 'bindings_dirs'
binding_paths = []
for bindings_dir in bindings_dirs: for bindings_dir in bindings_dirs:
for root, _, filenames in os.walk(bindings_dir): for root, _, filenames in os.walk(bindings_dir):
for filename in filenames: for filename in filenames:
if filename.endswith(".yaml") or filename.endswith(".yml"): if filename.endswith((".yaml", ".yml")):
binding_paths.append(os.path.join(root, filename)) yield os.path.join(root, filename)
return binding_paths
def parse_args(): def parse_args():
# Returns parsed command-line arguments # Returns parsed command-line arguments
@ -45,56 +55,38 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def printfile(s):
print(s, file=kconfig_file)
def str2ident(s):
# Converts 's' to a form suitable for (part of) an identifier
return re.sub('[-,.@/+]', '_', s.upper())
def compat2kconfig(compat):
compat_ident = str2ident(compat)
printfile(f'')
printfile(f'DT_COMPAT_{compat_ident} := {compat}')
printfile(f'')
printfile(f'config DT_HAS_{compat_ident}_ENABLED')
printfile(f'\tdef_bool $(dt_compat_enabled,$(DT_COMPAT_{compat_ident}))')
def main(): def main():
global kconfig_file
args = parse_args() args = parse_args()
compat_list = [] compats = set()
for binding_path in binding_paths(args.bindings_dirs): for binding_path in binding_paths(args.bindings_dirs):
with open(binding_path, encoding="utf-8") as f: with open(binding_path, encoding="utf-8") as f:
contents = f.read()
try: try:
# Parsed PyYAML output (Python lists/dictionaries/strings/etc., # Parsed PyYAML representation graph. For our purpose,
# representing the file) # we don't need the whole file converted into a dict.
raw = yaml.load(contents, Loader=SafeLoader) root = yaml.compose(f, Loader=SafeLoader)
except yaml.YAMLError as e: except yaml.YAMLError as e:
print(f"WARNING: '{binding_path}' appears in binding " print(f"WARNING: '{binding_path}' appears in binding "
f"directories but isn't valid YAML: {e}") f"directories but isn't valid YAML: {e}")
continue continue
if raw is None or 'compatible' not in raw:
if not isinstance(root, yaml.MappingNode):
continue continue
for key, node in root.value:
compat_list.append(raw['compatible']) if key.value == "compatible" and isinstance(node, yaml.ScalarNode):
compats.add(node.value)
# Remove any duplicates and sort the list break
compat_list = sorted(set(compat_list))
with open(args.kconfig_out, "w", encoding="utf-8") as kconfig_file: with open(args.kconfig_out, "w", encoding="utf-8") as kconfig_file:
printfile(f'# Generated devicetree Kconfig') print(HEADER, file=kconfig_file)
printfile(f'#')
printfile(f'# SPDX-License-Identifier: Apache-2.0')
for c in compat_list: for c in sorted(compats):
compat2kconfig(c) out = KCONFIG_TEMPLATE.format(
compat=c, COMPAT=c.upper().translate(TO_UNDERSCORES)
)
print(out, file=kconfig_file)
if __name__ == "__main__": if __name__ == "__main__":