blob: 00de9098ca5b7b95fc7458a0f71728c4b0e2ea61 [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
Peter Bigotd554d342020-06-30 10:05:35 -050076 args = parser.parse_args()
77 if "VERBOSE" in os.environ:
78 args.verbose = 1
79
Torsten Rasmussenb92b5802021-03-12 15:28:54 +010080 ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE")
81
82 if ZEPHYR_BASE is None:
83 sys.exit("-z / --zephyr-base not provided. Please provide "
84 "--zephyr-base or set ZEPHYR_BASE in environment")
85
86 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts"))
87
Peter Bigotd554d342020-06-30 10:05:35 -050088
89def symbol_data(elf, sym):
90 addr = sym.entry.st_value
91 len = sym.entry.st_size
92 for section in elf.iter_sections():
93 start = section['sh_addr']
94 end = start + section['sh_size']
95
96 if (start <= addr) and (addr + len) <= end:
97 offset = addr - section['sh_addr']
98 return bytes(section.data()[offset:offset + len])
99
100def symbol_handle_data(elf, sym):
101 data = symbol_data(elf, sym)
102 if data:
103 format = "<" if elf.little_endian else ">"
104 format += "%uh" % (len(data) / 2)
105 return struct.unpack(format, data)
106
107# These match the corresponding constants in <device.h>
108DEVICE_HANDLE_SEP = -32768
109DEVICE_HANDLE_ENDS = 32767
110def handle_name(hdl):
111 if hdl == DEVICE_HANDLE_SEP:
112 return "DEVICE_HANDLE_SEP"
113 if hdl == DEVICE_HANDLE_ENDS:
114 return "DEVICE_HANDLE_ENDS"
115 if hdl == 0:
116 return "DEVICE_HANDLE_NULL"
117 return str(int(hdl))
118
119class Device:
120 """
121 Represents information about a device object and its references to other objects.
122 """
123 def __init__(self, elf, ld_constants, sym, addr):
124 self.elf = elf
125 self.ld_constants = ld_constants
126 self.sym = sym
127 self.addr = addr
128 # Point to the handles instance associated with the device;
129 # assigned by correlating the device struct handles pointer
130 # value with the addr of a Handles instance.
131 self.__handles = None
132
133 @property
134 def obj_handles(self):
135 """
136 Returns the value from the device struct handles field, pointing to the
137 array of handles for devices this device depends on.
138 """
139 if self.__handles is None:
140 data = symbol_data(self.elf, self.sym)
141 format = "<" if self.elf.little_endian else ">"
142 if self.elf.elfclass == 32:
143 format += "I"
144 size = 4
145 else:
146 format += "Q"
147 size = 8
148 offset = self.ld_constants["DEVICE_STRUCT_HANDLES_OFFSET"]
149 self.__handles = struct.unpack(format, data[offset:offset + size])[0]
150 return self.__handles
151
152class Handles:
153 def __init__(self, sym, addr, handles, node):
154 self.sym = sym
155 self.addr = addr
156 self.handles = handles
157 self.node = node
158 self.dep_ord = None
159 self.dev_deps = None
160 self.ext_deps = None
161
162def main():
163 parse_args()
164
165 assert args.kernel, "--kernel ELF required to extract data"
166 elf = ELFFile(open(args.kernel, "rb"))
167
168 edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle")
169 with open(edtser, 'rb') as f:
170 edt = pickle.load(f)
171
172 devices = []
173 handles = []
174 # Leading _ are stripped from the stored constant key
175 want_constants = set(["__device_start",
176 "_DEVICE_STRUCT_SIZEOF",
177 "_DEVICE_STRUCT_HANDLES_OFFSET"])
178 ld_constants = dict()
179
180 for section in elf.iter_sections():
181 if isinstance(section, SymbolTableSection):
182 for sym in section.iter_symbols():
183 if sym.name in want_constants:
184 ld_constants[sym.name.lstrip("_")] = sym.entry.st_value
185 continue
186 if sym.entry.st_info.type != 'STT_OBJECT':
187 continue
188 if sym.name.startswith("__device"):
189 addr = sym.entry.st_value
190 if sym.name.startswith("__device_"):
191 devices.append(Device(elf, ld_constants, sym, addr))
192 debug("device %s" % (sym.name,))
193 elif sym.name.startswith("__devicehdl_"):
194 hdls = symbol_handle_data(elf, sym)
195
196 # The first element of the hdls array is the dependency
197 # ordinal of the device, which identifies the devicetree
198 # node.
199 node = edt.dep_ord2node[hdls[0]] if (hdls and hdls[0] != 0) else None
200 handles.append(Handles(sym, addr, hdls, node))
201 debug("handles %s %d %s" % (sym.name, hdls[0] if hdls else -1, node))
202
203 assert len(want_constants) == len(ld_constants), "linker map data incomplete"
204
205 devices = sorted(devices, key = lambda k: k.sym.entry.st_value)
206
207 device_start_addr = ld_constants["device_start"]
208 device_size = 0
209
210 assert len(devices) == len(handles), 'mismatch devices and handles'
211
212 used_nodes = set()
213 for handle in handles:
Jordan Yates371a83b2021-04-17 17:03:49 +1000214 handle.device = None
Peter Bigotd554d342020-06-30 10:05:35 -0500215 for device in devices:
216 if handle.addr == device.obj_handles:
217 handle.device = device
218 break
219 device = handle.device
220 assert device, 'no device for %s' % (handle.sym.name,)
221
222 device.handle = handle
223
224 if device_size == 0:
225 device_size = device.sym.entry.st_size
226
227 # The device handle is one plus the ordinal of this device in
228 # the device table.
229 device.dev_handle = 1 + int((device.sym.entry.st_value - device_start_addr) / device_size)
230 debug("%s dev ordinal %d" % (device.sym.name, device.dev_handle))
231
232 n = handle.node
233 if n is not None:
234 debug("%s dev ordinal %d\n\t%s" % (n.path, device.dev_handle, ' ; '.join(str(_) for _ in handle.handles)))
235 used_nodes.add(n)
236 n.__device = device
237 else:
238 debug("orphan %d" % (device.dev_handle,))
239 hv = handle.handles
240 hvi = 1
241 handle.dev_deps = []
242 handle.ext_deps = []
243 deps = handle.dev_deps
244 while True:
245 h = hv[hvi]
246 if h == DEVICE_HANDLE_ENDS:
247 break
248 if h == DEVICE_HANDLE_SEP:
249 deps = handle.ext_deps
250 else:
251 deps.append(h)
252 n = edt
253 hvi += 1
254
255 # Compute the dependency graph induced from the full graph restricted to the
256 # the nodes that exist in the application. Note that the edges in the
257 # induced graph correspond to paths in the full graph.
258 root = edt.dep_ord2node[0]
259 assert root not in used_nodes
260
261 for sn in used_nodes:
262 # Where we're storing the final set of nodes: these are all used
263 sn.__depends = set()
264
265 deps = set(sn.depends_on)
266 debug("\nNode: %s\nOrig deps:\n\t%s" % (sn.path, "\n\t".join([dn.path for dn in deps])))
267 while len(deps) > 0:
268 dn = deps.pop()
269 if dn in used_nodes:
270 # this is used
271 sn.__depends.add(dn)
272 elif dn != root:
273 # forward the dependency up one level
274 for ddn in dn.depends_on:
275 deps.add(ddn)
276 debug("final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in sn.__depends])))
277
278 with open(args.output_source, "w") as fp:
279 fp.write('#include <device.h>\n')
280 fp.write('#include <toolchain.h>\n')
281
282 for dev in devices:
283 hs = dev.handle
284 assert hs, "no hs for %s" % (dev.sym.name,)
285 dep_paths = []
286 ext_paths = []
287 hdls = []
288
289 sn = hs.node
290 if sn:
291 hdls.extend(dn.__device.dev_handle for dn in sn.__depends)
292 for dn in sn.depends_on:
293 if dn in sn.__depends:
294 dep_paths.append(dn.path)
295 else:
296 dep_paths.append('(%s)' % dn.path)
297 if len(hs.ext_deps) > 0:
298 # TODO: map these to something smaller?
299 ext_paths.extend(map(str, hs.ext_deps))
300 hdls.append(DEVICE_HANDLE_SEP)
301 hdls.extend(hs.ext_deps)
302
303 # When CONFIG_USERSPACE is enabled the pre-built elf is
304 # also used to get hashes that identify kernel objects by
305 # address. We can't allow the size of any object in the
306 # final elf to change.
307 while len(hdls) < len(hs.handles):
308 hdls.append(DEVICE_HANDLE_ENDS)
309 assert len(hdls) == len(hs.handles), "%s handle overflow" % (dev.sym.name,)
310
311 lines = [
312 '',
313 '/* %d : %s:' % (dev.dev_handle, (sn and sn.path) or "sysinit"),
314 ]
315
316 if len(dep_paths) > 0:
317 lines.append(' * - %s' % ('\n * - '.join(dep_paths)))
318 if len(ext_paths) > 0:
319 lines.append(' * + %s' % ('\n * + '.join(ext_paths)))
320
321 lines.extend([
322 ' */',
323 'const device_handle_t __aligned(2) __attribute__((__section__(".__device_handles_pass2")))',
324 '%s[] = { %s };' % (hs.sym.name, ', '.join([handle_name(_h) for _h in hdls])),
325 '',
326 ])
327
328 fp.write('\n'.join(lines))
329
330if __name__ == "__main__":
331 main()