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 struct
33import pickle
34from distutils.version import LooseVersion
35
36import elftools
37from elftools.elf.elffile import ELFFile
38from elftools.elf.sections import SymbolTableSection
39import elftools.elf.enums
40
41# This is needed to load edt.pickle files.
42sys.path.append(os.path.join(os.path.dirname(__file__),
43                             'dts', 'python-devicetree', 'src'))
44from devicetree import edtlib  # pylint: disable=unused-import
45
46if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
47    sys.exit("pyelftools is out of date, need version 0.24 or later")
48
49scr = os.path.basename(sys.argv[0])
50
51def debug(text):
52    if not args.verbose:
53        return
54    sys.stdout.write(scr + ": " + text + "\n")
55
56def parse_args():
57    global args
58
59    parser = argparse.ArgumentParser(
60        description=__doc__,
61        formatter_class=argparse.RawDescriptionHelpFormatter)
62
63    parser.add_argument("-k", "--kernel", required=True,
64                        help="Input zephyr ELF binary")
65    parser.add_argument("-o", "--output-source", required=True,
66            help="Output source file")
67
68    parser.add_argument("-v", "--verbose", action="store_true",
69                        help="Print extra debugging information")
70
71    parser.add_argument("-z", "--zephyr-base",
72                        help="Path to current Zephyr base. If this argument \
73                        is not provided the environment will be checked for \
74                        the ZEPHYR_BASE environment variable.")
75
76    parser.add_argument("-s", "--start-symbol", required=True,
77                        help="Symbol name of the section which contains the \
78                        devices. The symbol name must point to the first \
79                        device in that section.")
80
81    args = parser.parse_args()
82    if "VERBOSE" in os.environ:
83        args.verbose = 1
84
85    ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE")
86
87    if ZEPHYR_BASE is None:
88        sys.exit("-z / --zephyr-base not provided. Please provide "
89                 "--zephyr-base or set ZEPHYR_BASE in environment")
90
91    sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts"))
92
93
94def symbol_data(elf, sym):
95    addr = sym.entry.st_value
96    len = sym.entry.st_size
97    for section in elf.iter_sections():
98        start = section['sh_addr']
99        end = start + section['sh_size']
100
101        if (start <= addr) and (addr + len) <= end:
102            offset = addr - section['sh_addr']
103            return bytes(section.data()[offset:offset + len])
104
105def symbol_handle_data(elf, sym):
106    data = symbol_data(elf, sym)
107    if data:
108        format = "<" if elf.little_endian else ">"
109        format += "%uh" % (len(data) / 2)
110        return struct.unpack(format, data)
111
112# These match the corresponding constants in <device.h>
113DEVICE_HANDLE_SEP = -32768
114DEVICE_HANDLE_ENDS = 32767
115def handle_name(hdl):
116    if hdl == DEVICE_HANDLE_SEP:
117        return "DEVICE_HANDLE_SEP"
118    if hdl == DEVICE_HANDLE_ENDS:
119        return "DEVICE_HANDLE_ENDS"
120    if hdl == 0:
121        return "DEVICE_HANDLE_NULL"
122    return str(int(hdl))
123
124class Device:
125    """
126    Represents information about a device object and its references to other objects.
127    """
128    def __init__(self, elf, ld_constants, sym, addr):
129        self.elf = elf
130        self.ld_constants = ld_constants
131        self.sym = sym
132        self.addr = addr
133        # Point to the handles instance associated with the device;
134        # assigned by correlating the device struct handles pointer
135        # value with the addr of a Handles instance.
136        self.__handles = None
137
138    @property
139    def obj_handles(self):
140        """
141        Returns the value from the device struct handles field, pointing to the
142        array of handles for devices this device depends on.
143        """
144        if self.__handles is None:
145            data = symbol_data(self.elf, self.sym)
146            format = "<" if self.elf.little_endian else ">"
147            if self.elf.elfclass == 32:
148                format += "I"
149                size = 4
150            else:
151                format += "Q"
152                size = 8
153            offset = self.ld_constants["_DEVICE_STRUCT_HANDLES_OFFSET"]
154            self.__handles = struct.unpack(format, data[offset:offset + size])[0]
155        return self.__handles
156
157class Handles:
158    def __init__(self, sym, addr, handles, node):
159        self.sym = sym
160        self.addr = addr
161        self.handles = handles
162        self.node = node
163        self.dep_ord = None
164        self.dev_deps = None
165        self.ext_deps = None
166
167def main():
168    parse_args()
169
170    assert args.kernel, "--kernel ELF required to extract data"
171    elf = ELFFile(open(args.kernel, "rb"))
172
173    edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle")
174    with open(edtser, 'rb') as f:
175        edt = pickle.load(f)
176
177    devices = []
178    handles = []
179    # Leading _ are stripped from the stored constant key
180
181    want_constants = set([args.start_symbol,
182                          "_DEVICE_STRUCT_SIZEOF",
183                          "_DEVICE_STRUCT_HANDLES_OFFSET"])
184    ld_constants = dict()
185
186    for section in elf.iter_sections():
187        if isinstance(section, SymbolTableSection):
188            for sym in section.iter_symbols():
189                if sym.name in want_constants:
190                    ld_constants[sym.name] = sym.entry.st_value
191                    continue
192                if sym.entry.st_info.type != 'STT_OBJECT':
193                    continue
194                if sym.name.startswith("__device"):
195                    addr = sym.entry.st_value
196                    if sym.name.startswith("__device_"):
197                        devices.append(Device(elf, ld_constants, sym, addr))
198                        debug("device %s" % (sym.name,))
199                    elif sym.name.startswith("__devicehdl_"):
200                        hdls = symbol_handle_data(elf, sym)
201
202                        # The first element of the hdls array is the dependency
203                        # ordinal of the device, which identifies the devicetree
204                        # node.
205                        node = edt.dep_ord2node[hdls[0]] if (hdls and hdls[0] != 0) else None
206                        handles.append(Handles(sym, addr, hdls, node))
207                        debug("handles %s %d %s" % (sym.name, hdls[0] if hdls else -1, node))
208
209    assert len(want_constants) == len(ld_constants), "linker map data incomplete"
210
211    devices = sorted(devices, key = lambda k: k.sym.entry.st_value)
212
213    device_start_addr = ld_constants[args.start_symbol]
214    device_size = 0
215
216    assert len(devices) == len(handles), 'mismatch devices and handles'
217
218    used_nodes = set()
219    for handle in handles:
220        handle.device = None
221        for device in devices:
222            if handle.addr == device.obj_handles:
223                handle.device = device
224                break
225        device = handle.device
226        assert device, 'no device for %s' % (handle.sym.name,)
227
228        device.handle = handle
229
230        if device_size == 0:
231            device_size = device.sym.entry.st_size
232
233        # The device handle is one plus the ordinal of this device in
234        # the device table.
235        device.dev_handle = 1 + int((device.sym.entry.st_value - device_start_addr) / device_size)
236        debug("%s dev ordinal %d" % (device.sym.name, device.dev_handle))
237
238        n = handle.node
239        if n is not None:
240            debug("%s dev ordinal %d\n\t%s" % (n.path, device.dev_handle, ' ; '.join(str(_) for _ in handle.handles)))
241            used_nodes.add(n)
242            n.__device = device
243        else:
244            debug("orphan %d" % (device.dev_handle,))
245        hv = handle.handles
246        hvi = 1
247        handle.dev_deps = []
248        handle.ext_deps = []
249        deps = handle.dev_deps
250        while hvi < len(hv):
251            h = hv[hvi]
252            if h == DEVICE_HANDLE_ENDS:
253                break
254            if h == DEVICE_HANDLE_SEP:
255                deps = handle.ext_deps
256            else:
257                deps.append(h)
258                n = edt
259            hvi += 1
260
261    # Compute the dependency graph induced from the full graph restricted to the
262    # the nodes that exist in the application.  Note that the edges in the
263    # induced graph correspond to paths in the full graph.
264    root = edt.dep_ord2node[0]
265    assert root not in used_nodes
266
267    for sn in used_nodes:
268        # Where we're storing the final set of nodes: these are all used
269        sn.__depends = set()
270
271        deps = set(sn.depends_on)
272        debug("\nNode: %s\nOrig deps:\n\t%s" % (sn.path, "\n\t".join([dn.path for dn in deps])))
273        while len(deps) > 0:
274            dn = deps.pop()
275            if dn in used_nodes:
276                # this is used
277                sn.__depends.add(dn)
278            elif dn != root:
279                # forward the dependency up one level
280                for ddn in dn.depends_on:
281                    deps.add(ddn)
282        debug("final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in sn.__depends])))
283
284    with open(args.output_source, "w") as fp:
285        fp.write('#include <device.h>\n')
286        fp.write('#include <toolchain.h>\n')
287
288        for dev in devices:
289            hs = dev.handle
290            assert hs, "no hs for %s" % (dev.sym.name,)
291            dep_paths = []
292            ext_paths = []
293            hdls = []
294
295            sn = hs.node
296            if sn:
297                hdls.extend(dn.__device.dev_handle for dn in sn.__depends)
298                for dn in sn.depends_on:
299                    if dn in sn.__depends:
300                        dep_paths.append(dn.path)
301                    else:
302                        dep_paths.append('(%s)' % dn.path)
303            if len(hs.ext_deps) > 0:
304                # TODO: map these to something smaller?
305                ext_paths.extend(map(str, hs.ext_deps))
306                hdls.append(DEVICE_HANDLE_SEP)
307                hdls.extend(hs.ext_deps)
308
309            # When CONFIG_USERSPACE is enabled the pre-built elf is
310            # also used to get hashes that identify kernel objects by
311            # address. We can't allow the size of any object in the
312            # final elf to change. We also must make sure at least one
313            # DEVICE_HANDLE_ENDS is inserted.
314            padding = len(hs.handles) - len(hdls)
315            assert padding > 0, \
316                (f"device {dev.sym.name}: "
317                 "linker pass 1 left no room to insert DEVICE_HANDLE_ENDS. "
318                 "To work around, increase CONFIG_DEVICE_HANDLE_PADDING by " +
319                 str(1 + (-padding)))
320            while padding > 0:
321                hdls.append(DEVICE_HANDLE_ENDS)
322                padding -= 1
323            assert len(hdls) == len(hs.handles), "%s handle overflow" % (dev.sym.name,)
324
325            lines = [
326                '',
327                '/* %d : %s:' % (dev.dev_handle, (sn and sn.path) or "sysinit"),
328            ]
329
330            if len(dep_paths) > 0:
331                lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
332            if len(ext_paths) > 0:
333                lines.append(' * + %s' % ('\n * + '.join(ext_paths)))
334
335            lines.extend([
336                ' */',
337                'const device_handle_t __aligned(2) __attribute__((__section__(".__device_handles_pass2")))',
338                '%s[] = { %s };' % (hs.sym.name, ', '.join([handle_name(_h) for _h in hdls])),
339                '',
340            ])
341
342            fp.write('\n'.join(lines))
343
344if __name__ == "__main__":
345    main()
346