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 51 52def parse_args(): 53 # Returns parsed command-line arguments 54 55 parser = argparse.ArgumentParser(allow_abbrev=False) 56 parser.add_argument("--cmake-out", required=True, 57 help="path to write the CMake property file") 58 parser.add_argument("--edt-pickle", required=True, 59 help="path to read the pickled edtlib.EDT object from") 60 61 return parser.parse_args() 62 63 64def main(): 65 args = parse_args() 66 67 with open(args.edt_pickle, 'rb') as f: 68 edt = pickle.load(f) 69 70 # In what looks like an undocumented implementation detail, CMake 71 # target properties are stored in a C++ standard library map whose 72 # keys and values are each arbitrary strings, so we can use 73 # whatever we want as target property names. 74 # 75 # We therefore use '|' as a field separator character below within 76 # because it's not a valid character in DTS node paths or property 77 # names. This lets us store the "real" paths and property names 78 # without conversion to lowercase-and-underscores like we have to 79 # do in C. 80 # 81 # If CMake adds restrictions on target property names later, we 82 # can just tweak the generated file to use a more restrictive 83 # property encoding, perhaps reusing the same style documented in 84 # macros.bnf for C macros. 85 86 cmake_props = [] 87 chosen_nodes = edt.chosen_nodes 88 for node in chosen_nodes: 89 path = chosen_nodes[node].path 90 cmake_props.append(f'"DT_CHOSEN|{node}" "{path}"') 91 92 # The separate loop over edt.nodes here is meant to keep 93 # all of the alias-related properties in one place. 94 for node in edt.nodes: 95 path = node.path 96 for alias in node.aliases: 97 cmake_props.append(f'"DT_ALIAS|{alias}" "{path}"') 98 99 compatible2paths = defaultdict(list) 100 for node in edt.nodes: 101 cmake_props.append(f'"DT_NODE|{node.path}" TRUE') 102 103 for label in node.labels: 104 cmake_props.append(f'"DT_NODELABEL|{label}" "{node.path}"') 105 106 for item in node.props: 107 # We currently do not support phandles for edt -> cmake conversion. 108 if "phandle" not in node.props[item].type: 109 if "array" in node.props[item].type: 110 # Convert array to CMake list 111 cmake_value = '' 112 for val in node.props[item].val: 113 cmake_value = f'{cmake_value}{val};' 114 else: 115 cmake_value = node.props[item].val 116 117 # Encode node's property 'item' as a CMake target property 118 # with a name like 'DT_PROP|<path>|<property>'. 119 cmake_prop = f'DT_PROP|{node.path}|{item}' 120 cmake_props.append(f'"{cmake_prop}" "{cmake_value}"') 121 122 if item == 'compatible': 123 # compatibles is always an array 124 for comp in node.props[item].val: 125 compatible2paths[comp].append(node.path) 126 127 if node.regs is not None: 128 cmake_props.append(f'"DT_REG|{node.path}|NUM" "{len(node.regs)}"') 129 cmake_addr = '' 130 cmake_size = '' 131 132 for reg in node.regs: 133 if reg.addr is None: 134 cmake_addr = f'{cmake_addr}NONE;' 135 else: 136 cmake_addr = f'{cmake_addr}{hex(reg.addr)};' 137 138 if reg.size is None: 139 cmake_size = f'{cmake_size}NONE;' 140 else: 141 cmake_size = f'{cmake_size}{hex(reg.size)};' 142 143 cmake_props.append(f'"DT_REG|{node.path}|ADDR" "{cmake_addr}"') 144 cmake_props.append(f'"DT_REG|{node.path}|SIZE" "{cmake_size}"') 145 146 for comp in compatible2paths.keys(): 147 cmake_path = '' 148 for path in compatible2paths[comp]: 149 cmake_path = f'{cmake_path}{path};' 150 151 # Remove the last ';' 152 cmake_path = cmake_path[:-1] 153 154 cmake_comp = f'DT_COMP|{comp}' 155 cmake_props.append(f'"{cmake_comp}" "{cmake_path}"') 156 157 with open(args.cmake_out, "w", encoding="utf-8") as cmake_file: 158 print('add_custom_target(devicetree_target)', file=cmake_file) 159 print(file=cmake_file) 160 161 for prop in cmake_props: 162 print( 163 f'set_target_properties(devicetree_target PROPERTIES {prop})', 164 file=cmake_file 165 ) 166 167 168if __name__ == "__main__": 169 main() 170