blob: a55436ff716f1436e0d1dc3c174cdff2b79fa1fd [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")
Flavio Ceolin0b13b442022-01-05 17:19:53 -080065 parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0,
66 type=int, help="Input number of dynamic devices allowed")
Peter Bigotd554d342020-06-30 10:05:35 -050067 parser.add_argument("-o", "--output-source", required=True,
68 help="Output source file")
69
70 parser.add_argument("-v", "--verbose", action="store_true",
71 help="Print extra debugging information")
Torsten Rasmussenb92b5802021-03-12 15:28:54 +010072
73 parser.add_argument("-z", "--zephyr-base",
74 help="Path to current Zephyr base. If this argument \
75 is not provided the environment will be checked for \
76 the ZEPHYR_BASE environment variable.")
77
Torsten Rasmussenc9804d22021-05-21 21:34:58 +020078 parser.add_argument("-s", "--start-symbol", required=True,
79 help="Symbol name of the section which contains the \
80 devices. The symbol name must point to the first \
81 device in that section.")
82
Peter Bigotd554d342020-06-30 10:05:35 -050083 args = parser.parse_args()
84 if "VERBOSE" in os.environ:
85 args.verbose = 1
86
Torsten Rasmussenb92b5802021-03-12 15:28:54 +010087 ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE")
88
89 if ZEPHYR_BASE is None:
90 sys.exit("-z / --zephyr-base not provided. Please provide "
91 "--zephyr-base or set ZEPHYR_BASE in environment")
92
93 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts"))
94
Peter Bigotd554d342020-06-30 10:05:35 -050095
96def symbol_data(elf, sym):
97 addr = sym.entry.st_value
98 len = sym.entry.st_size
99 for section in elf.iter_sections():
100 start = section['sh_addr']
101 end = start + section['sh_size']
102
103 if (start <= addr) and (addr + len) <= end:
104 offset = addr - section['sh_addr']
105 return bytes(section.data()[offset:offset + len])
106
107def symbol_handle_data(elf, sym):
108 data = symbol_data(elf, sym)
109 if data:
110 format = "<" if elf.little_endian else ">"
111 format += "%uh" % (len(data) / 2)
112 return struct.unpack(format, data)
113
114# These match the corresponding constants in <device.h>
115DEVICE_HANDLE_SEP = -32768
116DEVICE_HANDLE_ENDS = 32767
Flavio Ceolin0b13b442022-01-05 17:19:53 -0800117DEVICE_HANDLE_NULL = 0
Peter Bigotd554d342020-06-30 10:05:35 -0500118def handle_name(hdl):
119 if hdl == DEVICE_HANDLE_SEP:
120 return "DEVICE_HANDLE_SEP"
121 if hdl == DEVICE_HANDLE_ENDS:
122 return "DEVICE_HANDLE_ENDS"
123 if hdl == 0:
124 return "DEVICE_HANDLE_NULL"
125 return str(int(hdl))
126
127class Device:
128 """
129 Represents information about a device object and its references to other objects.
130 """
131 def __init__(self, elf, ld_constants, sym, addr):
132 self.elf = elf
133 self.ld_constants = ld_constants
134 self.sym = sym
135 self.addr = addr
136 # Point to the handles instance associated with the device;
137 # assigned by correlating the device struct handles pointer
138 # value with the addr of a Handles instance.
139 self.__handles = None
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700140 self.__pm = None
Peter Bigotd554d342020-06-30 10:05:35 -0500141
142 @property
143 def obj_handles(self):
144 """
145 Returns the value from the device struct handles field, pointing to the
146 array of handles for devices this device depends on.
147 """
148 if self.__handles is None:
149 data = symbol_data(self.elf, self.sym)
150 format = "<" if self.elf.little_endian else ">"
151 if self.elf.elfclass == 32:
152 format += "I"
153 size = 4
154 else:
155 format += "Q"
156 size = 8
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200157 offset = self.ld_constants["_DEVICE_STRUCT_HANDLES_OFFSET"]
Peter Bigotd554d342020-06-30 10:05:35 -0500158 self.__handles = struct.unpack(format, data[offset:offset + size])[0]
159 return self.__handles
160
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700161 @property
162 def obj_pm(self):
163 """
164 Returns the value from the device struct pm field, pointing to the
165 pm struct for this device.
166 """
167 if self.__pm is None:
168 data = symbol_data(self.elf, self.sym)
169 format = "<" if self.elf.little_endian else ">"
170 if self.elf.elfclass == 32:
171 format += "I"
172 size = 4
173 else:
174 format += "Q"
175 size = 8
176 offset = self.ld_constants["_DEVICE_STRUCT_PM_OFFSET"]
177 self.__pm = struct.unpack(format, data[offset:offset + size])[0]
178 return self.__pm
179
180class PMDevice:
181 """
182 Represents information about a pm_device object and its references to other objects.
183 """
184 def __init__(self, elf, ld_constants, sym, addr):
185 self.elf = elf
186 self.ld_constants = ld_constants
187 self.sym = sym
188 self.addr = addr
189
190 # Point to the device instance associated with the pm_device;
191 self.__flags = None
192
193 def is_domain(self):
194 """
195 Checks if the device that this pm struct belongs is a power domain.
196 """
197 if self.__flags is None:
198 data = symbol_data(self.elf, self.sym)
199 format = "<" if self.elf.little_endian else ">"
200 if self.elf.elfclass == 32:
201 format += "I"
202 size = 4
203 else:
204 format += "Q"
205 size = 8
206 offset = self.ld_constants["_PM_DEVICE_STRUCT_FLAGS_OFFSET"]
207 self.__flags = struct.unpack(format, data[offset:offset + size])[0]
208 return self.__flags & (1 << self.ld_constants["_PM_DEVICE_FLAG_PD"])
209
Peter Bigotd554d342020-06-30 10:05:35 -0500210class Handles:
211 def __init__(self, sym, addr, handles, node):
212 self.sym = sym
213 self.addr = addr
214 self.handles = handles
215 self.node = node
216 self.dep_ord = None
217 self.dev_deps = None
218 self.ext_deps = None
219
220def main():
221 parse_args()
222
223 assert args.kernel, "--kernel ELF required to extract data"
224 elf = ELFFile(open(args.kernel, "rb"))
225
226 edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle")
227 with open(edtser, 'rb') as f:
228 edt = pickle.load(f)
229
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700230 pm_devices = {}
Peter Bigotd554d342020-06-30 10:05:35 -0500231 devices = []
232 handles = []
233 # Leading _ are stripped from the stored constant key
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200234
235 want_constants = set([args.start_symbol,
Peter Bigotd554d342020-06-30 10:05:35 -0500236 "_DEVICE_STRUCT_SIZEOF",
237 "_DEVICE_STRUCT_HANDLES_OFFSET"])
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700238 if args.num_dynamic_devices != 0:
239 want_constants.update(["_PM_DEVICE_FLAG_PD",
240 "_DEVICE_STRUCT_PM_OFFSET",
241 "_PM_DEVICE_STRUCT_FLAGS_OFFSET"])
Peter Bigotd554d342020-06-30 10:05:35 -0500242 ld_constants = dict()
243
244 for section in elf.iter_sections():
245 if isinstance(section, SymbolTableSection):
246 for sym in section.iter_symbols():
247 if sym.name in want_constants:
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200248 ld_constants[sym.name] = sym.entry.st_value
Peter Bigotd554d342020-06-30 10:05:35 -0500249 continue
250 if sym.entry.st_info.type != 'STT_OBJECT':
251 continue
252 if sym.name.startswith("__device"):
253 addr = sym.entry.st_value
254 if sym.name.startswith("__device_"):
255 devices.append(Device(elf, ld_constants, sym, addr))
256 debug("device %s" % (sym.name,))
257 elif sym.name.startswith("__devicehdl_"):
258 hdls = symbol_handle_data(elf, sym)
259
260 # The first element of the hdls array is the dependency
261 # ordinal of the device, which identifies the devicetree
262 # node.
263 node = edt.dep_ord2node[hdls[0]] if (hdls and hdls[0] != 0) else None
264 handles.append(Handles(sym, addr, hdls, node))
265 debug("handles %s %d %s" % (sym.name, hdls[0] if hdls else -1, node))
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700266 if sym.name.startswith("__pm_device__") and not sym.name.endswith("_slot"):
267 addr = sym.entry.st_value
268 pm_devices[addr] = PMDevice(elf, ld_constants, sym, addr)
269 debug("pm device %s" % (sym.name,))
Peter Bigotd554d342020-06-30 10:05:35 -0500270
271 assert len(want_constants) == len(ld_constants), "linker map data incomplete"
272
273 devices = sorted(devices, key = lambda k: k.sym.entry.st_value)
274
Torsten Rasmussenc9804d22021-05-21 21:34:58 +0200275 device_start_addr = ld_constants[args.start_symbol]
Peter Bigotd554d342020-06-30 10:05:35 -0500276 device_size = 0
277
278 assert len(devices) == len(handles), 'mismatch devices and handles'
279
280 used_nodes = set()
281 for handle in handles:
Jordan Yates371a83b2021-04-17 17:03:49 +1000282 handle.device = None
Peter Bigotd554d342020-06-30 10:05:35 -0500283 for device in devices:
284 if handle.addr == device.obj_handles:
285 handle.device = device
286 break
287 device = handle.device
288 assert device, 'no device for %s' % (handle.sym.name,)
289
290 device.handle = handle
291
292 if device_size == 0:
293 device_size = device.sym.entry.st_size
294
295 # The device handle is one plus the ordinal of this device in
296 # the device table.
297 device.dev_handle = 1 + int((device.sym.entry.st_value - device_start_addr) / device_size)
298 debug("%s dev ordinal %d" % (device.sym.name, device.dev_handle))
299
300 n = handle.node
301 if n is not None:
302 debug("%s dev ordinal %d\n\t%s" % (n.path, device.dev_handle, ' ; '.join(str(_) for _ in handle.handles)))
303 used_nodes.add(n)
304 n.__device = device
305 else:
306 debug("orphan %d" % (device.dev_handle,))
307 hv = handle.handles
308 hvi = 1
309 handle.dev_deps = []
310 handle.ext_deps = []
Jordan Yates106f7102021-08-20 18:31:18 +1000311 handle.dev_sups = []
312 hdls = handle.dev_deps
Jordan Yatesb5c41402021-08-20 18:12:47 +1000313 while hvi < len(hv):
Peter Bigotd554d342020-06-30 10:05:35 -0500314 h = hv[hvi]
315 if h == DEVICE_HANDLE_ENDS:
316 break
317 if h == DEVICE_HANDLE_SEP:
Jordan Yates106f7102021-08-20 18:31:18 +1000318 if hdls == handle.dev_deps:
319 hdls = handle.ext_deps
320 else:
321 hdls = handle.dev_sups
Peter Bigotd554d342020-06-30 10:05:35 -0500322 else:
Jordan Yates106f7102021-08-20 18:31:18 +1000323 hdls.append(h)
Peter Bigotd554d342020-06-30 10:05:35 -0500324 n = edt
325 hvi += 1
326
327 # Compute the dependency graph induced from the full graph restricted to the
328 # the nodes that exist in the application. Note that the edges in the
329 # induced graph correspond to paths in the full graph.
330 root = edt.dep_ord2node[0]
331 assert root not in used_nodes
332
Jordan Yates106f7102021-08-20 18:31:18 +1000333 for n in used_nodes:
Peter Bigotd554d342020-06-30 10:05:35 -0500334 # Where we're storing the final set of nodes: these are all used
Jordan Yates106f7102021-08-20 18:31:18 +1000335 n.__depends = set()
336 n.__supports = set()
Peter Bigotd554d342020-06-30 10:05:35 -0500337
Jordan Yates106f7102021-08-20 18:31:18 +1000338 deps = set(n.depends_on)
339 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 -0500340 while len(deps) > 0:
341 dn = deps.pop()
342 if dn in used_nodes:
343 # this is used
Jordan Yates106f7102021-08-20 18:31:18 +1000344 n.__depends.add(dn)
Peter Bigotd554d342020-06-30 10:05:35 -0500345 elif dn != root:
346 # forward the dependency up one level
347 for ddn in dn.depends_on:
348 deps.add(ddn)
Jordan Yates106f7102021-08-20 18:31:18 +1000349 debug("Final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in n.__depends])))
350
351 sups = set(n.required_by)
352 debug("\nOrig sups:\n\t%s" % ("\n\t".join([dn.path for dn in sups])))
353 while len(sups) > 0:
354 sn = sups.pop()
355 if sn in used_nodes:
356 # this is used
357 n.__supports.add(sn)
358 else:
359 # forward the support down one level
360 for ssn in sn.required_by:
361 sups.add(ssn)
362 debug("\nFinal sups:\n\t%s" % ("\n\t".join([_sn.path for _sn in n.__supports])))
Peter Bigotd554d342020-06-30 10:05:35 -0500363
364 with open(args.output_source, "w") as fp:
365 fp.write('#include <device.h>\n')
366 fp.write('#include <toolchain.h>\n')
367
368 for dev in devices:
369 hs = dev.handle
370 assert hs, "no hs for %s" % (dev.sym.name,)
371 dep_paths = []
372 ext_paths = []
Jordan Yates106f7102021-08-20 18:31:18 +1000373 sup_paths = []
Peter Bigotd554d342020-06-30 10:05:35 -0500374 hdls = []
375
376 sn = hs.node
377 if sn:
378 hdls.extend(dn.__device.dev_handle for dn in sn.__depends)
379 for dn in sn.depends_on:
380 if dn in sn.__depends:
381 dep_paths.append(dn.path)
382 else:
383 dep_paths.append('(%s)' % dn.path)
Jordan Yates106f7102021-08-20 18:31:18 +1000384 # Force separator to signal start of injected dependencies
385 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500386 if len(hs.ext_deps) > 0:
387 # TODO: map these to something smaller?
388 ext_paths.extend(map(str, hs.ext_deps))
Martí Bolívar2c991732021-09-24 12:36:56 -0700389 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500390 hdls.extend(hs.ext_deps)
391
Jordan Yates106f7102021-08-20 18:31:18 +1000392 # Force separator to signal start of supported devices
393 hdls.append(DEVICE_HANDLE_SEP)
394 if len(hs.dev_sups) > 0:
395 for dn in sn.required_by:
396 if dn in sn.__supports:
397 sup_paths.append(dn.path)
398 else:
399 sup_paths.append('(%s)' % dn.path)
400 hdls.extend(dn.__device.dev_handle for dn in sn.__supports)
Flavio Ceolind02a1e92022-03-20 21:53:42 -0700401
402 if args.num_dynamic_devices != 0:
403 pm = pm_devices.get(dev.obj_pm)
404 if pm and pm.is_domain():
405 hdls.extend(DEVICE_HANDLE_NULL for dn in range(args.num_dynamic_devices))
Jordan Yates106f7102021-08-20 18:31:18 +1000406
Jordan Yates44b8a0d2021-10-20 20:29:10 +1000407 # Terminate the array with the end symbol
408 hdls.append(DEVICE_HANDLE_ENDS)
Peter Bigotd554d342020-06-30 10:05:35 -0500409
410 lines = [
411 '',
412 '/* %d : %s:' % (dev.dev_handle, (sn and sn.path) or "sysinit"),
413 ]
414
415 if len(dep_paths) > 0:
Jordan Yates106f7102021-08-20 18:31:18 +1000416 lines.append(' * Direct Dependencies:')
417 lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500418 if len(ext_paths) > 0:
Jordan Yates106f7102021-08-20 18:31:18 +1000419 lines.append(' * Injected Dependencies:')
420 lines.append(' * - %s' % ('\n * - '.join(ext_paths)))
421 if len(sup_paths) > 0:
422 lines.append(' * Supported:')
423 lines.append(' * - %s' % ('\n * - '.join(sup_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500424
425 lines.extend([
426 ' */',
427 'const device_handle_t __aligned(2) __attribute__((__section__(".__device_handles_pass2")))',
428 '%s[] = { %s };' % (hs.sym.name, ', '.join([handle_name(_h) for _h in hdls])),
429 '',
430 ])
431
432 fp.write('\n'.join(lines))
433
434if __name__ == "__main__":
435 main()