1#!/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 41# 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 46if LooseVersion(elftools.__version__) < LooseVersion('0.24'): 47 sys.exit("pyelftools is out of date, need version 0.24 or later") 48 49scr = 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") 70 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 76 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 81 args = parser.parse_args() 82 if "VERBOSE" in os.environ: 83 args.verbose = 1 84 85 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 93 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 153 offset = self.ld_constants["_DEVICE_STRUCT_HANDLES_OFFSET"] 154 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 180 181 want_constants = set([args.start_symbol, 182 "_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: 190 ld_constants[sym.name] = sym.entry.st_value 191 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 213 device_start_addr = ld_constants[args.start_symbol] 214 device_size = 0 215 216 assert len(devices) == len(handles), 'mismatch devices and handles' 217 218 used_nodes = set() 219 for handle in handles: 220 handle.device = None 221 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 = [] 249 deps = handle.dev_deps 250 while hvi < len(hv): 251 h = hv[hvi] 252 if h == DEVICE_HANDLE_ENDS: 253 break 254 if h == DEVICE_HANDLE_SEP: 255 deps = handle.ext_deps 256 else: 257 deps.append(h) 258 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) 282 debug("final deps:\n\t%s\n" % ("\n\t".join([ _dn.path for _dn in sn.__depends]))) 283 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)) 306 hdls.append(DEVICE_HANDLE_SEP) 307 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 311 # 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. 314 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: 321 hdls.append(DEVICE_HANDLE_ENDS) 322 padding -= 1 323 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: 331 lines.append(' * - %s' % ('\n * - '.join(dep_paths))) 332 if len(ext_paths) > 0: 333 lines.append(' * + %s' % ('\n * + '.join(ext_paths))) 334 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() 346