blob: 558ce82c707c4530e5b437ee44fa642d8e6ab572 [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 = []
Martí Bolívar288bcb92021-09-24 12:25:49 -0700249 deps = handle.dev_deps
Jordan Yatesb5c41402021-08-20 18:12:47 +1000250 while hvi < len(hv):
Peter Bigotd554d342020-06-30 10:05:35 -0500251 h = hv[hvi]
252 if h == DEVICE_HANDLE_ENDS:
253 break
254 if h == DEVICE_HANDLE_SEP:
Martí Bolívar288bcb92021-09-24 12:25:49 -0700255 deps = handle.ext_deps
Peter Bigotd554d342020-06-30 10:05:35 -0500256 else:
Martí Bolívar288bcb92021-09-24 12:25:49 -0700257 deps.append(h)
Peter Bigotd554d342020-06-30 10:05:35 -0500258 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)
Martí Bolívar288bcb92021-09-24 12:25:49 -0700282 debug("final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in sn.__depends])))
Peter Bigotd554d342020-06-30 10:05:35 -0500283
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))
Martí Bolívar2c991732021-09-24 12:36:56 -0700306 hdls.append(DEVICE_HANDLE_SEP)
Peter Bigotd554d342020-06-30 10:05:35 -0500307 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
Jordan Yatesb5c41402021-08-20 18:12:47 +1000311 # 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.
Martí Bolívara6276662021-09-24 12:54:17 -0700314 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:
Peter Bigotd554d342020-06-30 10:05:35 -0500321 hdls.append(DEVICE_HANDLE_ENDS)
Martí Bolívara6276662021-09-24 12:54:17 -0700322 padding -= 1
Peter Bigotd554d342020-06-30 10:05:35 -0500323 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:
Martí Bolívar288bcb92021-09-24 12:25:49 -0700331 lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500332 if len(ext_paths) > 0:
Martí Bolívar288bcb92021-09-24 12:25:49 -0700333 lines.append(' * + %s' % ('\n * + '.join(ext_paths)))
Peter Bigotd554d342020-06-30 10:05:35 -0500334
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()