1#!/usr/bin/env python3
2#
3# Copyright (c) 2017 Intel Corporation
4# Copyright (c) 2020 Nordic Semiconductor NA
5#
6# SPDX-License-Identifier: Apache-2.0
7"""Translate generic handles into ones optimized for the application.
8
9Immutable device data includes information about dependencies,
10e.g. that a particular sensor is controlled through a specific I2C bus
11and that it signals event on a pin on a specific GPIO controller.
12This information is encoded in the first-pass binary using identifiers
13derived from the devicetree.  This script extracts those identifiers
14and replaces them with ones optimized for use with the devices
15actually present.
16
17For example the sensor might have a first-pass handle defined by its
18devicetree ordinal 52, with the I2C driver having ordinal 24 and the
19GPIO controller ordinal 14.  The runtime ordinal is the index of the
20corresponding device in the static devicetree array, which might be 6,
215, and 3, respectively.
22
23The output is a C source file that provides alternative definitions
24for the array contents referenced from the immutable device objects.
25In the final link these definitions supersede the ones in the
26driver-specific object file.
27"""
28
29import sys
30import argparse
31import os
32import pickle
33
34from elf_parser import ZephyrElf
35
36# This is needed to load edt.pickle files.
37sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..',
38                                'dts', 'python-devicetree', 'src'))
39
40def parse_args():
41    global args
42
43    parser = argparse.ArgumentParser(
44        description=__doc__,
45        formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
46
47    parser.add_argument("-k", "--kernel", required=True,
48                        help="Input zephyr ELF binary")
49    parser.add_argument("--dynamic-deps", action="store_true",
50                        help="Indicates if device dependencies are dynamic")
51    parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0,
52                        type=int, help="Input number of dynamic devices allowed")
53    parser.add_argument("-o", "--output-source", required=True,
54                        help="Output source file")
55    parser.add_argument("-g", "--output-graphviz",
56                        help="Output file for graphviz dependency graph")
57    parser.add_argument("-z", "--zephyr-base",
58                        help="Path to current Zephyr base. If this argument \
59                        is not provided the environment will be checked for \
60                        the ZEPHYR_BASE environment variable.")
61    parser.add_argument("-s", "--start-symbol", required=True,
62                        help="Symbol name of the section which contains the \
63                        devices. The symbol name must point to the first \
64                        device in that section.")
65
66    args = parser.parse_args()
67
68    ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE")
69
70    if ZEPHYR_BASE is None:
71        sys.exit("-z / --zephyr-base not provided. Please provide "
72                 "--zephyr-base or set ZEPHYR_BASE in environment")
73
74    sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts"))
75
76def c_handle_comment(dev, handles):
77    def dev_path_str(dev):
78        return dev.edt_node and dev.edt_node.path or dev.sym.name
79    lines = [
80        '',
81        '/* {:d} : {:s}:'.format(dev.handle, (dev_path_str(dev))),
82    ]
83    if len(handles["depends"]) > 0:
84        lines.append(' * Direct Dependencies:')
85        for dep in handles["depends"]:
86            lines.append(' *    - {:s}'.format(dev_path_str(dep)))
87    if len(handles["injected"]) > 0:
88        lines.append(' * Injected Dependencies:')
89        for dep in handles["injected"]:
90            lines.append(' *    - {:s}'.format(dev_path_str(dep)))
91    if len(handles["supports"]) > 0:
92        lines.append(' * Supported:')
93        for sup in handles["supports"]:
94            lines.append(' *    - {:s}'.format(dev_path_str(sup)))
95    lines.append(' */')
96    return lines
97
98def c_handle_array(dev, handles, dynamic_deps, extra_support_handles=0):
99    handles = [
100        *[str(d.handle) for d in handles["depends"]],
101        'Z_DEVICE_DEPS_SEP',
102        *[str(d.handle) for d in handles["injected"]],
103        'Z_DEVICE_DEPS_SEP',
104        *[str(d.handle) for d in handles["supports"]],
105        *(extra_support_handles * ['DEVICE_HANDLE_NULL']),
106        'Z_DEVICE_DEPS_ENDS',
107    ]
108    ctype = (
109        '{:s}Z_DECL_ALIGN(device_handle_t) '
110        '__attribute__((__section__(".__device_deps_pass2")))'
111    ).format('const ' if not dynamic_deps else '')
112    return [
113        # The `extern` line pretends this was first declared in some .h
114        # file to silence "should it be static?" warnings in some
115        # compilers and static analyzers.
116        'extern {:s} {:s}[{:d}];'.format(ctype, dev.ordinals.sym.name, len(handles)),
117        ctype,
118        '{:s}[] = {{ {:s} }};'.format(dev.ordinals.sym.name, ', '.join(handles)),
119    ]
120
121def main():
122    parse_args()
123
124    edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle")
125    with open(edtser, 'rb') as f:
126        edt = pickle.load(f)
127
128    parsed_elf = ZephyrElf(args.kernel, edt, args.start_symbol)
129    if parsed_elf.relocatable:
130        # While relocatable elf files will load cleanly, the pointers pulled from
131        # the symbol table are invalid (as expected, because the structures have not
132        # yet been allocated addresses). Fixing this will require iterating over
133        # the relocation sections to find the symbols those pointers will end up
134        # referring to.
135        sys.exit('Relocatable elf files are not yet supported')
136
137    if args.output_graphviz:
138        # Try and output the dependency tree
139        try:
140            dot = parsed_elf.device_dependency_graph('Device dependency graph', args.kernel)
141            with open(args.output_graphviz, 'w') as f:
142                f.write(dot.source)
143        except ImportError:
144            pass
145
146    with open(args.output_source, "w") as fp:
147        fp.write('#include <zephyr/device.h>\n')
148        fp.write('#include <zephyr/toolchain.h>\n')
149        for dev in parsed_elf.devices:
150            # The device handle are collected up in a set, which has no
151            # specified order.  Sort each sub-category of device handle types
152            # separately, so that the generated C array is reproducible across
153            # builds.
154            sorted_handles = {
155                "depends": sorted(dev.devs_depends_on, key=lambda d: d.handle),
156                "injected": sorted(dev.devs_depends_on_injected, key=lambda d: d.handle),
157                "supports": sorted(dev.devs_supports, key=lambda d: d.handle),
158            }
159            extra_sups = args.num_dynamic_devices if dev.pm and dev.pm.is_power_domain else 0
160            lines = c_handle_comment(dev, sorted_handles)
161            lines.extend(
162                c_handle_array(dev, sorted_handles, args.dynamic_deps, extra_sups)
163            )
164            lines.extend([''])
165            fp.write('\n'.join(lines))
166
167if __name__ == "__main__":
168    main()
169