blob: aa046a13b4db7c323fdd8500239deb4e0ce0f8fd [file] [log] [blame]
Peter Bigotd554d342020-06-30 10:05:35 -05001#!/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
Julien Massot36f116b2021-11-19 16:30:43 +010034from packaging import version
Peter Bigotd554d342020-06-30 10:05:35 -050035
36import elftools
37from elftools.elf.elffile import ELFFile
38from elftools.elf.sections import SymbolTableSection
39import elftools.elf.enums
40
Martí Bolívar53328472021-03-26 16:18:58 -070041# 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
Julien Massot36f116b2021-11-19 16:30:43 +010046if version.parse(elftools.__version__) < version.parse('0.24'):
Peter Bigotd554d342020-06-30 10:05:35 -050047 sys.exit("pyelftools is out of date, need version 0.24 or later")
48
Peter Bigotd554d342020-06-30 10:05:35 -050049scr = 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")
Torsten Rasmussenb92b5802021-03-12 15:28:54 +010070
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
Torsten Rasmussenc9804d22021-05-21 21:34:58 +020076 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
Peter Bigotd554d342020-06-30 10:05:35 -050081 args = parser.parse_args()
82 if "VERBOSE" in os.environ:
83 args.verbose = 1
84
Torsten Rasmussenb92b5802021-03-12 15:28:54 +010085 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
Peter Bigotd554d342020-06-30 10:05:35 -050093
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
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200153 offset = self.ld_constants["_DEVICE_STRUCT_HANDLES_OFFSET"]
Peter Bigotd554d342020-06-30 10:05:35 -0500154 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
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200180
181 want_constants = set([args.start_symbol,
Peter Bigotd554d342020-06-30 10:05:35 -0500182 "_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:
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200190 ld_constants[sym.name] = sym.entry.st_value
Peter Bigotd554d342020-06-30 10:05:35 -0500191 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
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200213 device_start_addr = ld_constants[args.start_symbol]
Peter Bigotd554d342020-06-30 10:05:35 -0500214 device_size = 0
215
216 assert len(devices) == len(handles), 'mismatch devices and handles'
217
218 used_nodes = set()
219 for handle in handles:
Jordan Yates371a83b2021-04-17 17:03:49 +1000220 handle.device = None
Peter Bigotd554d342020-06-30 10:05:35 -0500221 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 = []
Jordan Yates106f7102021-08-20 18:31:18 +1000249 handle.dev_sups = []
250 hdls = handle.dev_deps
Jordan Yatesb5c41402021-08-20 18:12:47 +1000251 while hvi < len(hv):
Peter Bigotd554d342020-06-30 10:05:35 -0500252 h = hv[hvi]
253 if h == DEVICE_HANDLE_ENDS:
254 break
255 if h == DEVICE_HANDLE_SEP:
Jordan Yates106f7102021-08-20 18:31:18 +1000256 if hdls == handle.dev_deps:
257 hdls = handle.ext_deps
258 else:
259 hdls = handle.dev_sups
Peter Bigotd554d342020-06-30 10:05:35 -0500260 else:
Jordan Yates106f7102021-08-20 18:31:18 +1000261 hdls.append(h)
Peter Bigotd554d342020-06-30 10:05:35 -0500262 n = edt
263 hvi += 1
264
265 # Compute the dependency graph induced from the full graph restricted to the
266 # the nodes that exist in the application. Note that the edges in the
267 # induced graph correspond to paths in the full graph.
268 root = edt.dep_ord2node[0]
269 assert root not in used_nodes
270
Jordan Yates106f7102021-08-20 18:31:18 +1000271 for n in used_nodes:
Peter Bigotd554d342020-06-30 10:05:35 -0500272 # Where we're storing the final set of nodes: these are all used
Jordan Yates106f7102021-08-20 18:31:18 +1000273 n.__depends = set()
274 n.__supports = set()
Peter Bigotd554d342020-06-30 10:05:35 -0500275
Jordan Yates106f7102021-08-20 18:31:18 +1000276 deps = set(n.depends_on)
277 debug("\nNode: %s\nOrig deps:\n\t%s" % (n.path, "\n\t".join([dn.path for dn in deps])))
Peter Bigotd554d342020-06-30 10:05:35 -0500278 while len(deps) > 0:
279 dn = deps.pop()
280 if dn in used_nodes:
281 # this is used
Jordan Yates106f7102021-08-20 18:31:18 +1000282 n.__depends.add(dn)
Peter Bigotd554d342020-06-30 10:05:35 -0500283 elif dn != root:
284 # forward the dependency up one level
285 for ddn in dn.depends_on:
286 deps.add(ddn)
Jordan Yates106f7102021-08-20 18:31:18 +1000287 debug("Final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in n.__depends])))
288
289 sups = set(n.required_by)
290 debug("\nOrig sups:\n\t%s" % ("\n\t".join([dn.path for dn in sups])))
291 while len(sups) > 0:
292 sn = sups.pop()
293 if sn in used_nodes:
294 # this is used
295 n.__supports.add(sn)
296 else:
297 # forward the support down one level
298 for ssn in sn.required_by:
299 sups.add(ssn)
300 debug("\nFinal sups:\n\t%s" % ("\n\t".join([_sn.path for _sn in n.__supports])))
Peter Bigotd554d342020-06-30 10:05:35 -0500301
302 with open(args.output_source, "w") as fp:
303 fp.write('#include <device.h>\n')
304 fp.write('#include <toolchain.h>\n')
305
306 for dev in devices:
307 hs = dev.handle
308 assert hs, "no hs for %s" % (dev.sym.name,)
309 dep_paths = []
310 ext_paths = []
Jordan Yates106f7102021-08-20 18:31:18 +1000311 sup_paths = []
Peter Bigotd554d342020-06-30 10:05:35 -0500312 hdls = []
313
314 sn = hs.node
315 if sn:
316 hdls.extend(dn.__device.dev_handle for dn in sn.__depends)
317 for dn in sn.depends_on:
318 if dn in sn.__depends:
319 dep_paths.append(dn.path)
320 else:
321 dep_paths.append('(%s)' % dn.path)
Jordan Yates106f7102021-08-20 18:31:18 +1000322 # Force separator to signal start of injected dependencies
323 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500324 if len(hs.ext_deps) > 0:
325 # TODO: map these to something smaller?
326 ext_paths.extend(map(str, hs.ext_deps))
Martí Bolívar2c991732021-09-24 12:36:56 -0700327 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500328 hdls.extend(hs.ext_deps)
329
Jordan Yates106f7102021-08-20 18:31:18 +1000330 # Force separator to signal start of supported devices
331 hdls.append(DEVICE_HANDLE_SEP)
332 if len(hs.dev_sups) > 0:
333 for dn in sn.required_by:
334 if dn in sn.__supports:
335 sup_paths.append(dn.path)
336 else:
337 sup_paths.append('(%s)' % dn.path)
338 hdls.extend(dn.__device.dev_handle for dn in sn.__supports)
339
Jordan Yates44b8a0d2021-10-20 20:29:10 +1000340 # Terminate the array with the end symbol
341 hdls.append(DEVICE_HANDLE_ENDS)
Peter Bigotd554d342020-06-30 10:05:35 -0500342
343 lines = [
344 '',
345 '/* %d : %s:' % (dev.dev_handle, (sn and sn.path) or "sysinit"),
346 ]
347
348 if len(dep_paths) > 0:
Jordan Yates106f7102021-08-20 18:31:18 +1000349 lines.append(' * Direct Dependencies:')
350 lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500351 if len(ext_paths) > 0:
Jordan Yates106f7102021-08-20 18:31:18 +1000352 lines.append(' * Injected Dependencies:')
353 lines.append(' * - %s' % ('\n * - '.join(ext_paths)))
354 if len(sup_paths) > 0:
355 lines.append(' * Supported:')
356 lines.append(' * - %s' % ('\n * - '.join(sup_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500357
358 lines.extend([
359 ' */',
360 'const device_handle_t __aligned(2) __attribute__((__section__(".__device_handles_pass2")))',
361 '%s[] = { %s };' % (hs.sym.name, ', '.join([handle_name(_h) for _h in hdls])),
362 '',
363 ])
364
365 fp.write('\n'.join(lines))
366
367if __name__ == "__main__":
368 main()