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