blob: c8354d9ec8385b70674115b7a50c060abe1ecba4 [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
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
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
Peter Bigotd554d342020-06-30 10:05:35 -050046if LooseVersion(elftools.__version__) < LooseVersion('0.24'):
47 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 Yates0c6588f2021-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 Yates0c6588f2021-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 Yates0c6588f2021-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
271 for sn in used_nodes:
272 # Where we're storing the final set of nodes: these are all used
273 sn.__depends = set()
Jordan Yates0c6588f2021-08-20 18:31:18 +1000274 sn.__supports = set()
Peter Bigotd554d342020-06-30 10:05:35 -0500275
276 deps = set(sn.depends_on)
277 debug("\nNode: %s\nOrig deps:\n\t%s" % (sn.path, "\n\t".join([dn.path for dn in deps])))
278 while len(deps) > 0:
279 dn = deps.pop()
280 if dn in used_nodes:
281 # this is used
282 sn.__depends.add(dn)
283 elif dn != root:
284 # forward the dependency up one level
285 for ddn in dn.depends_on:
286 deps.add(ddn)
Jordan Yates0c6588f2021-08-20 18:31:18 +1000287 debug("Final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in sn.__depends])))
288
289 sups = set(sn.required_by)
290 debug("\nOrig sups:\n\t%s" % ("\n\t".join([dn.path for dn in sups])))
291 while len(sups) > 0:
292 dn = sups.pop()
293 if dn in used_nodes:
294 # this is used
295 sn.__supports.add(dn)
296 debug("\nFinal sups:\n\t%s" % ("\n\t".join([dn.path for dn in sn.__supports])))
Peter Bigotd554d342020-06-30 10:05:35 -0500297
298 with open(args.output_source, "w") as fp:
299 fp.write('#include <device.h>\n')
300 fp.write('#include <toolchain.h>\n')
301
302 for dev in devices:
303 hs = dev.handle
304 assert hs, "no hs for %s" % (dev.sym.name,)
305 dep_paths = []
306 ext_paths = []
Jordan Yates0c6588f2021-08-20 18:31:18 +1000307 sup_paths = []
Peter Bigotd554d342020-06-30 10:05:35 -0500308 hdls = []
309
310 sn = hs.node
311 if sn:
312 hdls.extend(dn.__device.dev_handle for dn in sn.__depends)
313 for dn in sn.depends_on:
314 if dn in sn.__depends:
315 dep_paths.append(dn.path)
316 else:
317 dep_paths.append('(%s)' % dn.path)
Jordan Yates0c6588f2021-08-20 18:31:18 +1000318
Jordan Yatesec331c62021-08-20 18:43:38 +1000319 # Force separator to signal start of injected dependencies
320 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500321 if len(hs.ext_deps) > 0:
322 # TODO: map these to something smaller?
323 ext_paths.extend(map(str, hs.ext_deps))
Peter Bigotd554d342020-06-30 10:05:35 -0500324 hdls.extend(hs.ext_deps)
325
Jordan Yates0c6588f2021-08-20 18:31:18 +1000326 # Force separator to signal start of supported devices
327 hdls.append(DEVICE_HANDLE_SEP)
328 if len(hs.dev_sups) > 0:
329 for dn in sn.required_by:
330 if dn in sn.__supports:
331 sup_paths.append(dn.path)
332 else:
333 sup_paths.append('(%s)' % dn.path)
334 hdls.extend(dn.__device.dev_handle for dn in sn.__supports)
335
Peter Bigotd554d342020-06-30 10:05:35 -0500336 # When CONFIG_USERSPACE is enabled the pre-built elf is
337 # also used to get hashes that identify kernel objects by
Jordan Yatesb5c41402021-08-20 18:12:47 +1000338 # address. We can't allow the size of any object in the
339 # final elf to change. We also must make sure at least one
340 # DEVICE_HANDLE_ENDS is inserted.
341 assert len(hdls) < len(hs.handles), "%s no DEVICE_HANDLE_ENDS inserted" % (dev.sym.name,)
Peter Bigotd554d342020-06-30 10:05:35 -0500342 while len(hdls) < len(hs.handles):
343 hdls.append(DEVICE_HANDLE_ENDS)
344 assert len(hdls) == len(hs.handles), "%s handle overflow" % (dev.sym.name,)
345
346 lines = [
347 '',
348 '/* %d : %s:' % (dev.dev_handle, (sn and sn.path) or "sysinit"),
349 ]
350
351 if len(dep_paths) > 0:
Jordan Yates0c6588f2021-08-20 18:31:18 +1000352 lines.append(' * Direct Dependencies:')
353 lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500354 if len(ext_paths) > 0:
Jordan Yates0c6588f2021-08-20 18:31:18 +1000355 lines.append(' * Injected Dependencies:')
356 lines.append(' * - %s' % ('\n * - '.join(ext_paths)))
357 if len(sup_paths) > 0:
358 lines.append(' * Supported:')
359 lines.append(' * - %s' % ('\n * - '.join(sup_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500360
361 lines.extend([
362 ' */',
363 'const device_handle_t __aligned(2) __attribute__((__section__(".__device_handles_pass2")))',
364 '%s[] = { %s };' % (hs.sym.name, ', '.join([handle_name(_h) for _h in hdls])),
365 '',
366 ])
367
368 fp.write('\n'.join(lines))
369
370if __name__ == "__main__":
371 main()