blob: ba6f476fba53c6486d1c22cbbeafc8408a0be22c [file] [log] [blame]
Fabio Baltierief1bd082023-03-08 17:50:56 +00001#!/usr/bin/env python3
2
3# Copyright 2023 Google LLC
4# SPDX-License-Identifier: Apache-2.0
5
6"""
7Checks the initialization priorities
8
Fabio Baltieri5212a4c2023-09-08 18:10:13 +00009This script parses a Zephyr executable file, creates a list of known devices
10and their effective initialization priorities and compares that with the device
11dependencies inferred from the devicetree hierarchy.
Fabio Baltierief1bd082023-03-08 17:50:56 +000012
13This can be used to detect devices that are initialized in the incorrect order,
14but also devices that are initialized at the same priority but depends on each
15other, which can potentially break if the linking order is changed.
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000016
17Optionally, it can also produce a human readable list of the initialization
18calls for the various init levels.
Fabio Baltierief1bd082023-03-08 17:50:56 +000019"""
20
21import argparse
22import logging
23import os
24import pathlib
25import pickle
26import sys
27
28from elftools.elf.elffile import ELFFile
Fabio Baltierief1bd082023-03-08 17:50:56 +000029from elftools.elf.sections import SymbolTableSection
30
31# This is needed to load edt.pickle files.
32sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..",
33 "dts", "python-devicetree", "src"))
34from devicetree import edtlib # pylint: disable=unused-import
35
Fabio Baltierief1bd082023-03-08 17:50:56 +000036# Prefix used for "struct device" reference initialized based on devicetree
37# entries with a known ordinal.
38_DEVICE_ORD_PREFIX = "__device_dts_ord_"
39
Fabio Baltierief1bd082023-03-08 17:50:56 +000040# Defined init level in order of priority.
41_DEVICE_INIT_LEVELS = ["EARLY", "PRE_KERNEL_1", "PRE_KERNEL_2", "POST_KERNEL",
42 "APPLICATION", "SMP"]
43
Fabio Baltierief1bd082023-03-08 17:50:56 +000044# List of compatibles for node where the initialization priority should be the
45# opposite of the device tree inferred dependency.
46_INVERTED_PRIORITY_COMPATIBLES = frozenset()
47
Fabio Baltieri6d99e382023-07-21 15:20:43 +000048# List of compatibles for nodes where we don't check the priority.
49_IGNORE_COMPATIBLES = frozenset([
50 # There is no direct dependency between the CDC ACM UART and the USB
51 # device controller, the logical connection is established after USB
52 # device support is enabled.
53 "zephyr,cdc-acm-uart",
54 ])
55
Fabio Baltierief1bd082023-03-08 17:50:56 +000056class Priority:
57 """Parses and holds a device initialization priority.
58
Fabio Baltierief1bd082023-03-08 17:50:56 +000059 The object can be used for comparing levels with one another.
60
61 Attributes:
62 name: the section name
63 """
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000064 def __init__(self, level, priority):
65 for idx, level_name in enumerate(_DEVICE_INIT_LEVELS):
66 if level_name == level:
Fabio Baltierief1bd082023-03-08 17:50:56 +000067 self._level = idx
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000068 self._priority = priority
Jordan Yates23280f42023-07-19 21:20:18 +100069 # Tuples compare elementwise in order
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000070 self._level_priority = (self._level, self._priority)
Fabio Baltierief1bd082023-03-08 17:50:56 +000071 return
72
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000073 raise ValueError("Unknown level in %s" % level)
Fabio Baltierief1bd082023-03-08 17:50:56 +000074
75 def __repr__(self):
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000076 return "<%s %s %d>" % (self.__class__.__name__,
77 _DEVICE_INIT_LEVELS[self._level], self._priority)
Fabio Baltierief1bd082023-03-08 17:50:56 +000078
79 def __str__(self):
Fabio Baltieridd178ce2023-10-27 10:47:37 +000080 return "%s+%d" % (_DEVICE_INIT_LEVELS[self._level], self._priority)
Fabio Baltierief1bd082023-03-08 17:50:56 +000081
82 def __lt__(self, other):
83 return self._level_priority < other._level_priority
84
85 def __eq__(self, other):
86 return self._level_priority == other._level_priority
87
88 def __hash__(self):
89 return self._level_priority
90
91
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000092class ZephyrInitLevels:
93 """Load an executable file and find the initialization calls and devices.
Fabio Baltierief1bd082023-03-08 17:50:56 +000094
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000095 Load a Zephyr executable file and scan for the list of initialization calls
96 and defined devices.
Fabio Baltierief1bd082023-03-08 17:50:56 +000097
Fabio Baltieri5212a4c2023-09-08 18:10:13 +000098 The list of devices is available in the "devices" class variable in the
99 {ordinal: Priority} format, the list of initilevels is in the "initlevels"
100 class variables in the {"level name": ["call", ...]} format.
Fabio Baltierief1bd082023-03-08 17:50:56 +0000101
102 Attributes:
103 file_path: path of the file to be loaded.
104 """
105 def __init__(self, file_path):
106 self.file_path = file_path
107 self._elf = ELFFile(open(file_path, "rb"))
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000108 self._load_objects()
109 self._load_level_addr()
110 self._process_initlevels()
Fabio Baltierief1bd082023-03-08 17:50:56 +0000111
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000112 def _load_objects(self):
113 """Initialize the object table."""
114 self._objects = {}
Fabio Baltierief1bd082023-03-08 17:50:56 +0000115
116 for section in self._elf.iter_sections():
117 if not isinstance(section, SymbolTableSection):
118 continue
119
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000120 for sym in section.iter_symbols():
121 if (sym.name and
122 sym.entry.st_size > 0 and
123 sym.entry.st_info.type in ["STT_OBJECT", "STT_FUNC"]):
124 self._objects[sym.entry.st_value] = (
125 sym.name, sym.entry.st_size, sym.entry.st_shndx)
Fabio Baltierief1bd082023-03-08 17:50:56 +0000126
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000127 def _load_level_addr(self):
128 """Find the address associated with known init levels."""
129 self._init_level_addr = {}
Fabio Baltierief1bd082023-03-08 17:50:56 +0000130
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000131 for section in self._elf.iter_sections():
132 if not isinstance(section, SymbolTableSection):
133 continue
134
135 for sym in section.iter_symbols():
136 for level in _DEVICE_INIT_LEVELS:
137 name = f"__init_{level}_start"
138 if sym.name == name:
139 self._init_level_addr[level] = sym.entry.st_value
140 elif sym.name == "__init_end":
141 self._init_level_end = sym.entry.st_value
142
143 if len(self._init_level_addr) != len(_DEVICE_INIT_LEVELS):
144 raise ValueError(f"Missing init symbols, found: {self._init_level_addr}")
145
146 if not self._init_level_end:
147 raise ValueError(f"Missing init section end symbol")
148
149 def _device_ord_from_name(self, sym_name):
150 """Find a device ordinal from a symbol name."""
Fabio Baltierief1bd082023-03-08 17:50:56 +0000151 if not sym_name:
152 return None
153
154 if not sym_name.startswith(_DEVICE_ORD_PREFIX):
155 return None
156
157 _, device_ord = sym_name.split(_DEVICE_ORD_PREFIX)
158 return int(device_ord)
159
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000160 def _object_name(self, addr):
161 if not addr:
162 return "NULL"
163 elif addr in self._objects:
164 return self._objects[addr][0]
165 else:
166 return "unknown"
Fabio Baltierief1bd082023-03-08 17:50:56 +0000167
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000168 def _initlevel_pointer(self, addr, idx, shidx):
169 elfclass = self._elf.elfclass
170 if elfclass == 32:
171 ptrsize = 4
172 elif elfclass == 64:
173 ptrsize = 8
174 else:
175 ValueError(f"Unknown pointer size for ELF class f{elfclass}")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000176
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000177 section = self._elf.get_section(shidx)
178 start = section.header.sh_addr
179 data = section.data()
Fabio Baltierief1bd082023-03-08 17:50:56 +0000180
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000181 offset = addr - start
Fabio Baltierief1bd082023-03-08 17:50:56 +0000182
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000183 start = offset + ptrsize * idx
184 stop = offset + ptrsize * (idx + 1)
Fabio Baltierief1bd082023-03-08 17:50:56 +0000185
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000186 return int.from_bytes(data[start:stop], byteorder="little")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000187
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000188 def _process_initlevels(self):
189 """Process the init level and find the init functions and devices."""
190 self.devices = {}
191 self.initlevels = {}
Fabio Baltierief1bd082023-03-08 17:50:56 +0000192
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000193 for i, level in enumerate(_DEVICE_INIT_LEVELS):
194 start = self._init_level_addr[level]
195 if i + 1 == len(_DEVICE_INIT_LEVELS):
196 stop = self._init_level_end
197 else:
198 stop = self._init_level_addr[_DEVICE_INIT_LEVELS[i + 1]]
199
200 self.initlevels[level] = []
201
202 priority = 0
203 addr = start
204 while addr < stop:
205 if addr not in self._objects:
206 raise ValueError(f"no symbol at addr {addr:08x}")
207 obj, size, shidx = self._objects[addr]
208
209 arg0_name = self._object_name(self._initlevel_pointer(addr, 0, shidx))
210 arg1_name = self._object_name(self._initlevel_pointer(addr, 1, shidx))
211
212 self.initlevels[level].append(f"{obj}: {arg0_name}({arg1_name})")
213
214 ordinal = self._device_ord_from_name(arg1_name)
215 if ordinal:
216 prio = Priority(level, priority)
Fabio Baltieridd178ce2023-10-27 10:47:37 +0000217 self.devices[ordinal] = (prio, arg0_name)
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000218
219 addr += size
220 priority += 1
Fabio Baltierief1bd082023-03-08 17:50:56 +0000221
222class Validator():
223 """Validates the initialization priorities.
224
225 Scans through a build folder for object files and list all the device
226 initialization priorities. Then compares that against the EDT derived
227 dependency list and log any found priority issue.
228
229 Attributes:
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000230 elf_file_path: path of the ELF file
231 edt_pickle: name of the EDT pickle file
Fabio Baltierief1bd082023-03-08 17:50:56 +0000232 log: a logging.Logger object
233 """
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000234 def __init__(self, elf_file_path, edt_pickle, log):
Fabio Baltierief1bd082023-03-08 17:50:56 +0000235 self.log = log
236
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000237 edt_pickle_path = pathlib.Path(
238 pathlib.Path(elf_file_path).parent,
239 edt_pickle)
240 with open(edt_pickle_path, "rb") as f:
Fabio Baltierief1bd082023-03-08 17:50:56 +0000241 edt = pickle.load(f)
242
243 self._ord2node = edt.dep_ord2node
244
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000245 self._obj = ZephyrInitLevels(elf_file_path)
Fabio Baltierief1bd082023-03-08 17:50:56 +0000246
Fabio Baltierief1bd082023-03-08 17:50:56 +0000247 self.errors = 0
248
Fabio Baltierief1bd082023-03-08 17:50:56 +0000249 def _check_dep(self, dev_ord, dep_ord):
250 """Validate the priority between two devices."""
251 if dev_ord == dep_ord:
252 return
253
254 dev_node = self._ord2node[dev_ord]
255 dep_node = self._ord2node[dep_ord]
256
Fabio Baltieri6d99e382023-07-21 15:20:43 +0000257 if dev_node._binding:
258 dev_compat = dev_node._binding.compatible
259 if dev_compat in _IGNORE_COMPATIBLES:
260 self.log.info(f"Ignoring priority: {dev_node._binding.compatible}")
261 return
262
Fabio Baltierief1bd082023-03-08 17:50:56 +0000263 if dev_node._binding and dep_node._binding:
264 dev_compat = dev_node._binding.compatible
265 dep_compat = dep_node._binding.compatible
266 if (dev_compat, dep_compat) in _INVERTED_PRIORITY_COMPATIBLES:
267 self.log.info(f"Swapped priority: {dev_compat}, {dep_compat}")
268 dev_ord, dep_ord = dep_ord, dev_ord
269
Fabio Baltieridd178ce2023-10-27 10:47:37 +0000270 dev_prio, dev_init = self._obj.devices.get(dev_ord, (None, None))
271 dep_prio, dep_init = self._obj.devices.get(dep_ord, (None, None))
Fabio Baltierief1bd082023-03-08 17:50:56 +0000272
273 if not dev_prio or not dep_prio:
274 return
275
276 if dev_prio == dep_prio:
Fabio Baltieri2a70c312023-10-27 09:27:48 +0000277 raise ValueError(f"{dev_node.path} and {dep_node.path} have the "
278 f"same priority: {dev_prio}")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000279 elif dev_prio < dep_prio:
Fabio Baltieridd178ce2023-10-27 10:47:37 +0000280 if not self.errors:
281 self.log.error("Device initialization priority validation failed, "
282 "the sequence of initialization calls does not match "
283 "the devicetree dependencies.")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000284 self.errors += 1
285 self.log.error(
Fabio Baltieridd178ce2023-10-27 10:47:37 +0000286 f"{dev_node.path} <{dev_init}> is initialized before its dependency "
287 f"{dep_node.path} <{dep_init}> ({dev_prio} < {dep_prio})")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000288 else:
289 self.log.info(
Fabio Baltieridd178ce2023-10-27 10:47:37 +0000290 f"{dev_node.path} <{dev_init}> {dev_prio} > "
291 f"{dep_node.path} <{dep_init}> {dep_prio}")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000292
Fabio Baltierief1bd082023-03-08 17:50:56 +0000293 def check_edt(self):
294 """Scan through all known devices and validate the init priorities."""
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000295 for dev_ord in self._obj.devices:
Fabio Baltierief1bd082023-03-08 17:50:56 +0000296 dev = self._ord2node[dev_ord]
Fabio Baltieri73d5ecc2023-09-07 16:27:32 +0000297 for dep in dev.depends_on:
298 self._check_dep(dev_ord, dep.dep_ordinal)
Fabio Baltierief1bd082023-03-08 17:50:56 +0000299
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000300 def print_initlevels(self):
301 for level, calls in self._obj.initlevels.items():
302 print(level)
303 for call in calls:
304 print(f" {call}")
305
Fabio Baltierief1bd082023-03-08 17:50:56 +0000306def _parse_args(argv):
307 """Parse the command line arguments."""
308 parser = argparse.ArgumentParser(
309 description=__doc__,
310 formatter_class=argparse.RawDescriptionHelpFormatter,
311 allow_abbrev=False)
312
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000313 parser.add_argument("-f", "--elf-file", default=pathlib.Path("build", "zephyr", "zephyr.elf"),
314 help="ELF file to use")
Fabio Baltieri44e691e2023-06-01 15:11:19 +0000315 parser.add_argument("-v", "--verbose", action="count",
316 help=("enable verbose output, can be used multiple times "
317 "to increase verbosity level"))
Fabio Baltierief1bd082023-03-08 17:50:56 +0000318 parser.add_argument("--always-succeed", action="store_true",
319 help="always exit with a return code of 0, used for testing")
320 parser.add_argument("-o", "--output",
321 help="write the output to a file in addition to stdout")
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000322 parser.add_argument("-i", "--initlevels", action="store_true",
323 help="print the initlevel functions instead of checking the device dependencies")
324 parser.add_argument("--edt-pickle", default=pathlib.Path("edt.pickle"),
325 help="name of the the pickled edtlib.EDT file",
Fabio Baltierief1bd082023-03-08 17:50:56 +0000326 type=pathlib.Path)
327
328 return parser.parse_args(argv)
329
330def _init_log(verbose, output):
331 """Initialize a logger object."""
332 log = logging.getLogger(__file__)
333
334 console = logging.StreamHandler()
335 console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
336 log.addHandler(console)
337
338 if output:
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000339 file = logging.FileHandler(output, mode="w")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000340 file.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
341 log.addHandler(file)
342
Fabio Baltieri44e691e2023-06-01 15:11:19 +0000343 if verbose and verbose > 1:
344 log.setLevel(logging.DEBUG)
345 elif verbose and verbose > 0:
Fabio Baltierief1bd082023-03-08 17:50:56 +0000346 log.setLevel(logging.INFO)
347 else:
348 log.setLevel(logging.WARNING)
349
350 return log
351
352def main(argv=None):
353 args = _parse_args(argv)
354
355 log = _init_log(args.verbose, args.output)
356
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000357 log.info(f"check_init_priorities: {args.elf_file}")
Fabio Baltierief1bd082023-03-08 17:50:56 +0000358
Fabio Baltieri5212a4c2023-09-08 18:10:13 +0000359 validator = Validator(args.elf_file, args.edt_pickle, log)
360 if args.initlevels:
361 validator.print_initlevels()
362 else:
363 validator.check_edt()
Fabio Baltierief1bd082023-03-08 17:50:56 +0000364
365 if args.always_succeed:
366 return 0
367
Fabio Baltierief1bd082023-03-08 17:50:56 +0000368 if validator.errors:
369 return 1
370
371 return 0
372
373if __name__ == "__main__":
374 sys.exit(main(sys.argv[1:]))