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