Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | #!/usr/bin/env python3 # Copyright (c) 2021 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 ''' This script uses edtlib and the devicetree data in the build directory to generate a CMake file which contains devicetree data. That data can then be used in the rest of the build system. The generated CMake file looks like this: set_target_properties(${DEVICETREE_TARGET} PROPERTIES "DT_PROP|/soc|compatible" "vnd,soc;") ... It takes an input variable - DEVICETREE_TARGET - and saves various values in the devicetree as properties of this CMake target. Be careful: "Property" here can refer to a CMake target property or a DTS property. DTS property values are stored inside CMake target properties, along with other devicetree data. The build system includes this generated file early on, so devicetree values can be used at CMake processing time. Access is not done directly, but with Zephyr CMake extension APIs, like this: # sets 'compat' to "vnd,soc" in CMake dt_prop(compat PATH "/soc" PROPERTY compatible INDEX 0) This is analogous to how DTS values are encoded as C macros, which can be read in source code using C APIs like DT_PROP(node_id, foo) from devicetree.h. ''' import argparse import os import pickle import sys from collections import defaultdict sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python-devicetree', 'src')) ESCAPE_TABLE = str.maketrans( { "\n": "\\n", "\r": "\\r", '\"': '\\"', "\\": "\\\\", } ) def escape(value): if isinstance(value, str): return value.translate(ESCAPE_TABLE) return value def parse_args(): # Returns parsed command-line arguments parser = argparse.ArgumentParser(allow_abbrev=False) parser.add_argument("--cmake-out", required=True, help="path to write the CMake property file") parser.add_argument("--edt-pickle", required=True, help="path to read the pickled edtlib.EDT object from") return parser.parse_args() def main(): args = parse_args() with open(args.edt_pickle, 'rb') as f: edt = pickle.load(f) # In what looks like an undocumented implementation detail, CMake # target properties are stored in a C++ standard library map whose # keys and values are each arbitrary strings, so we can use # whatever we want as target property names. # # We therefore use '|' as a field separator character below within # because it's not a valid character in DTS node paths or property # names. This lets us store the "real" paths and property names # without conversion to lowercase-and-underscores like we have to # do in C. # # If CMake adds restrictions on target property names later, we # can just tweak the generated file to use a more restrictive # property encoding, perhaps reusing the same style documented in # macros.bnf for C macros. cmake_props = [] chosen_nodes = edt.chosen_nodes for node in chosen_nodes: path = chosen_nodes[node].path cmake_props.append(f'"DT_CHOSEN|{node}" "{path}"') # The separate loop over edt.nodes here is meant to keep # all of the alias-related properties in one place. for node in edt.nodes: path = node.path for alias in node.aliases: cmake_props.append(f'"DT_ALIAS|{alias}" "{path}"') compatible2paths = defaultdict(list) for node in edt.nodes: cmake_props.append(f'"DT_NODE|{node.path}" TRUE') for label in node.labels: cmake_props.append(f'"DT_NODELABEL|{label}" "{node.path}"') for item in node.props: # We currently do not support phandles for edt -> cmake conversion. if "phandle" not in node.props[item].type: if "array" in node.props[item].type: # Convert array to CMake list cmake_value = '' for val in node.props[item].val: cmake_value = f'{cmake_value}{val};' else: cmake_value = node.props[item].val # Encode node's property 'item' as a CMake target property # with a name like 'DT_PROP|<path>|<property>'. cmake_prop = f'DT_PROP|{node.path}|{item}' cmake_props.append(f'"{cmake_prop}" "{escape(cmake_value)}"') for comp in node.compats: compatible2paths[comp].append(node.path) if node.regs is not None: cmake_props.append(f'"DT_REG|{node.path}|NUM" "{len(node.regs)}"') cmake_addr = '' cmake_size = '' for reg in node.regs: if reg.addr is None: cmake_addr = f'{cmake_addr}NONE;' else: cmake_addr = f'{cmake_addr}{hex(reg.addr)};' if reg.size is None: cmake_size = f'{cmake_size}NONE;' else: cmake_size = f'{cmake_size}{hex(reg.size)};' cmake_props.append(f'"DT_REG|{node.path}|ADDR" "{cmake_addr}"') cmake_props.append(f'"DT_REG|{node.path}|SIZE" "{cmake_size}"') for comp in compatible2paths.keys(): cmake_path = '' for path in compatible2paths[comp]: cmake_path = f'{cmake_path}{path};' # Remove the last ';' cmake_path = cmake_path[:-1] cmake_comp = f'DT_COMP|{comp}' cmake_props.append(f'"{cmake_comp}" "{cmake_path}"') cmake_props = map( 'set_target_properties(${{DEVICETREE_TARGET}} PROPERTIES {})'.format, cmake_props ) with open(args.cmake_out, "w", encoding="utf-8") as cmake_file: print("\n".join(cmake_props), file=cmake_file) if __name__ == "__main__": main() |