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 pickle 33 34from elf_parser import ZephyrElf 35 36# This is needed to load edt.pickle files. 37sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 38 'dts', 'python-devicetree', 'src')) 39 40def parse_args(): 41 global args 42 43 parser = argparse.ArgumentParser( 44 description=__doc__, 45 formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False) 46 47 parser.add_argument("-k", "--kernel", required=True, 48 help="Input zephyr ELF binary") 49 parser.add_argument("--dynamic-deps", action="store_true", 50 help="Indicates if device dependencies are dynamic") 51 parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0, 52 type=int, help="Input number of dynamic devices allowed") 53 parser.add_argument("-o", "--output-source", required=True, 54 help="Output source file") 55 parser.add_argument("-g", "--output-graphviz", 56 help="Output file for graphviz dependency graph") 57 parser.add_argument("-z", "--zephyr-base", 58 help="Path to current Zephyr base. If this argument \ 59 is not provided the environment will be checked for \ 60 the ZEPHYR_BASE environment variable.") 61 parser.add_argument("-s", "--start-symbol", required=True, 62 help="Symbol name of the section which contains the \ 63 devices. The symbol name must point to the first \ 64 device in that section.") 65 66 args = parser.parse_args() 67 68 ZEPHYR_BASE = args.zephyr_base or os.getenv("ZEPHYR_BASE") 69 70 if ZEPHYR_BASE is None: 71 sys.exit("-z / --zephyr-base not provided. Please provide " 72 "--zephyr-base or set ZEPHYR_BASE in environment") 73 74 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/dts")) 75 76def c_handle_comment(dev, handles): 77 def dev_path_str(dev): 78 return dev.edt_node and dev.edt_node.path or dev.sym.name 79 lines = [ 80 '', 81 '/* {:d} : {:s}:'.format(dev.handle, (dev_path_str(dev))), 82 ] 83 if len(handles["depends"]) > 0: 84 lines.append(' * Direct Dependencies:') 85 for dep in handles["depends"]: 86 lines.append(' * - {:s}'.format(dev_path_str(dep))) 87 if len(handles["injected"]) > 0: 88 lines.append(' * Injected Dependencies:') 89 for dep in handles["injected"]: 90 lines.append(' * - {:s}'.format(dev_path_str(dep))) 91 if len(handles["supports"]) > 0: 92 lines.append(' * Supported:') 93 for sup in handles["supports"]: 94 lines.append(' * - {:s}'.format(dev_path_str(sup))) 95 lines.append(' */') 96 return lines 97 98def c_handle_array(dev, handles, dynamic_deps, extra_support_handles=0): 99 handles = [ 100 *[str(d.handle) for d in handles["depends"]], 101 'Z_DEVICE_DEPS_SEP', 102 *[str(d.handle) for d in handles["injected"]], 103 'Z_DEVICE_DEPS_SEP', 104 *[str(d.handle) for d in handles["supports"]], 105 *(extra_support_handles * ['DEVICE_HANDLE_NULL']), 106 'Z_DEVICE_DEPS_ENDS', 107 ] 108 ctype = ( 109 '{:s}Z_DECL_ALIGN(device_handle_t) ' 110 '__attribute__((__section__(".__device_deps_pass2")))' 111 ).format('const ' if not dynamic_deps else '') 112 return [ 113 # The `extern` line pretends this was first declared in some .h 114 # file to silence "should it be static?" warnings in some 115 # compilers and static analyzers. 116 'extern {:s} {:s}[{:d}];'.format(ctype, dev.ordinals.sym.name, len(handles)), 117 ctype, 118 '{:s}[] = {{ {:s} }};'.format(dev.ordinals.sym.name, ', '.join(handles)), 119 ] 120 121def main(): 122 parse_args() 123 124 edtser = os.path.join(os.path.split(args.kernel)[0], "edt.pickle") 125 with open(edtser, 'rb') as f: 126 edt = pickle.load(f) 127 128 parsed_elf = ZephyrElf(args.kernel, edt, args.start_symbol) 129 if parsed_elf.relocatable: 130 # While relocatable elf files will load cleanly, the pointers pulled from 131 # the symbol table are invalid (as expected, because the structures have not 132 # yet been allocated addresses). Fixing this will require iterating over 133 # the relocation sections to find the symbols those pointers will end up 134 # referring to. 135 sys.exit('Relocatable elf files are not yet supported') 136 137 if args.output_graphviz: 138 # Try and output the dependency tree 139 try: 140 dot = parsed_elf.device_dependency_graph('Device dependency graph', args.kernel) 141 with open(args.output_graphviz, 'w') as f: 142 f.write(dot.source) 143 except ImportError: 144 pass 145 146 with open(args.output_source, "w") as fp: 147 fp.write('#include <zephyr/device.h>\n') 148 fp.write('#include <zephyr/toolchain.h>\n') 149 for dev in parsed_elf.devices: 150 # The device handle are collected up in a set, which has no 151 # specified order. Sort each sub-category of device handle types 152 # separately, so that the generated C array is reproducible across 153 # builds. 154 sorted_handles = { 155 "depends": sorted(dev.devs_depends_on, key=lambda d: d.handle), 156 "injected": sorted(dev.devs_depends_on_injected, key=lambda d: d.handle), 157 "supports": sorted(dev.devs_supports, key=lambda d: d.handle), 158 } 159 extra_sups = args.num_dynamic_devices if dev.pm and dev.pm.is_power_domain else 0 160 lines = c_handle_comment(dev, sorted_handles) 161 lines.extend( 162 c_handle_array(dev, sorted_handles, args.dynamic_deps, extra_sups) 163 ) 164 lines.extend(['']) 165 fp.write('\n'.join(lines)) 166 167if __name__ == "__main__": 168 main() 169