zephyr/scripts/build/gen_device_deps.py

169 lines
6.9 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
# Copyright (c) 2020 Nordic Semiconductor NA
#
# SPDX-License-Identifier: Apache-2.0
"""Translate generic handles into ones optimized for the application.
Immutable device data includes information about dependencies,
e.g. that a particular sensor is controlled through a specific I2C bus
and that it signals event on a pin on a specific GPIO controller.
This information is encoded in the first-pass binary using identifiers
derived from the devicetree. This script extracts those identifiers
and replaces them with ones optimized for use with the devices
actually present.
For example the sensor might have a first-pass handle defined by its
devicetree ordinal 52, with the I2C driver having ordinal 24 and the
GPIO controller ordinal 14. The runtime ordinal is the index of the
corresponding device in the static devicetree array, which might be 6,
5, and 3, respectively.
The output is a C source file that provides alternative definitions
for the array contents referenced from the immutable device objects.
In the final link these definitions supersede the ones in the
driver-specific object file.
"""
import sys
import argparse
import os
import pickle
from elf_parser import ZephyrElf
# This is needed to load edt.pickle files.
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..',
'dts', 'python-devicetree', 'src'))
def parse_args():
global args
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
parser.add_argument("-k", "--kernel", required=True,
help="Input zephyr ELF binary")
parser.add_argument("--dynamic-deps", action="store_true",
help="Indicates if device dependencies are dynamic")
parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0,
type=int, help="Input number of dynamic devices allowed")
parser.add_argument("-o", "--output-source", required=True,
help="Output source file")
parser.add_argument("-g", "--output-graphviz",
help="Output file for graphviz dependency graph")
parser.add_argument("-z", "--zephyr-base",
help="Path to current Zephyr base. If this argument \
is not provided the environment will be checked for \
the ZEPHYR_BASE environment variable.")
parser.add_argument("-s", "--start-symbol", required=True,
help="Symbol name of the section which contains the \
devices. The symbol name must point to the first \
device in that section.")
args = parser.parse_args()
ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE")
if ZEPHYR_BASE is None:
sys.exit("-z / --zephyr-base not provided. Please provide "
"--zephyr-base or set ZEPHYR_BASE in environment")
sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts"))
def c_handle_comment(dev, handles):
def dev_path_str(dev):
return dev.edt_node and dev.edt_node.path or dev.sym.name
lines = [
'',
'/* {:d} : {:s}:'.format(dev.handle, (dev_path_str(dev))),
]
if len(handles["depends"]) > 0:
lines.append(' * Direct Dependencies:')
for dep in handles["depends"]:
lines.append(' * - {:s}'.format(dev_path_str(dep)))
if len(handles["injected"]) > 0:
lines.append(' * Injected Dependencies:')
for dep in handles["injected"]:
lines.append(' * - {:s}'.format(dev_path_str(dep)))
if len(handles["supports"]) > 0:
lines.append(' * Supported:')
for sup in handles["supports"]:
lines.append(' * - {:s}'.format(dev_path_str(sup)))
lines.append(' */')
return lines
def c_handle_array(dev, handles, dynamic_deps, extra_support_handles=0):
handles = [
*[str(d.handle) for d in handles["depends"]],
'Z_DEVICE_DEPS_SEP',
*[str(d.handle) for d in handles["injected"]],
'Z_DEVICE_DEPS_SEP',
*[str(d.handle) for d in handles["supports"]],
*(extra_support_handles * ['DEVICE_HANDLE_NULL']),
'Z_DEVICE_DEPS_ENDS',
]
ctype = (
'{:s}Z_DECL_ALIGN(device_handle_t) '
'__attribute__((__section__(".__device_deps_pass2")))'
).format('const ' if not dynamic_deps else '')
return [
# The `extern` line pretends this was first declared in some .h
# file to silence "should it be static?" warnings in some
# compilers and static analyzers.
'extern {:s} {:s}[{:d}];'.format(ctype, dev.ordinals.sym.name, len(handles)),
ctype,
'{:s}[] = {{ {:s} }};'.format(dev.ordinals.sym.name, ', '.join(handles)),
]
def main():
parse_args()
edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle")
with open(edtser, 'rb') as f:
edt = pickle.load(f)
parsed_elf = ZephyrElf(args.kernel, edt, args.start_symbol)
if parsed_elf.relocatable:
# While relocatable elf files will load cleanly, the pointers pulled from
# the symbol table are invalid (as expected, because the structures have not
# yet been allocated addresses). Fixing this will require iterating over
# the relocation sections to find the symbols those pointers will end up
# referring to.
sys.exit('Relocatable elf files are not yet supported')
if args.output_graphviz:
# Try and output the dependency tree
try:
dot = parsed_elf.device_dependency_graph('Device dependency graph', args.kernel)
with open(args.output_graphviz, 'w') as f:
f.write(dot.source)
except ImportError:
pass
with open(args.output_source, "w") as fp:
fp.write('#include <zephyr/device.h>\n')
fp.write('#include <zephyr/toolchain.h>\n')
for dev in parsed_elf.devices:
# The device handle are collected up in a set, which has no
# specified order. Sort each sub-category of device handle types
# separately, so that the generated C array is reproducible across
# builds.
sorted_handles = {
"depends": sorted(dev.devs_depends_on, key=lambda d: d.handle),
"injected": sorted(dev.devs_depends_on_injected, key=lambda d: d.handle),
"supports": sorted(dev.devs_supports, key=lambda d: d.handle),
}
extra_sups = args.num_dynamic_devices if dev.pm and dev.pm.is_power_domain else 0
lines = c_handle_comment(dev, sorted_handles)
lines.extend(
c_handle_array(dev, sorted_handles, args.dynamic_deps, extra_sups)
)
lines.extend([''])
fp.write('\n'.join(lines))
if __name__ == "__main__":
main()