1#!/usr/bin/env python3
2
3# Copyright (c) 2019 - 2020 Nordic Semiconductor ASA
4# Copyright (c) 2019 Linaro Limited
5# Copyright (c) 2024 SILA Embedded Solutions GmbH
6# SPDX-License-Identifier: BSD-3-Clause
7
8# This script uses edtlib to generate a header file from a pickled
9# edt file.
10#
11# Note: Do not access private (_-prefixed) identifiers from edtlib here (and
12# also note that edtlib is not meant to expose the dtlib API directly).
13# Instead, think of what API you need, and add it as a public documented API in
14# edtlib. This will keep this script simple.
15
16import argparse
17from collections import defaultdict
18import os
19import pathlib
20import pickle
21import re
22import sys
23from typing import Iterable, NoReturn, Optional
24
25sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'python-devicetree',
26                                'src'))
27
28import edtlib_logger
29from devicetree import edtlib
30
31
32def main():
33    global header_file
34    global flash_area_num
35
36    args = parse_args()
37
38    edtlib_logger.setup_edtlib_logging()
39
40    with open(args.edt_pickle, 'rb') as f:
41        edt = pickle.load(f)
42
43    flash_area_num = 0
44
45    # Create the generated header.
46    with open(args.header_out, "w", encoding="utf-8") as header_file:
47        write_top_comment(edt)
48
49        write_utils()
50
51        sorted_nodes = sorted(edt.nodes, key=lambda node: node.dep_ordinal)
52
53        # populate all z_path_id first so any children references will
54        # work correctly.
55        for node in sorted_nodes:
56            node.z_path_id = node_z_path_id(node)
57
58        # Check to see if we have duplicate "zephyr,memory-region" property values.
59        regions = dict()
60        for node in sorted_nodes:
61            if 'zephyr,memory-region' in node.props:
62                region = node.props['zephyr,memory-region'].val
63                if region in regions:
64                    sys.exit(f"ERROR: Duplicate 'zephyr,memory-region' ({region}) properties "
65                             f"between {regions[region].path} and {node.path}")
66                regions[region] = node
67
68        for node in sorted_nodes:
69            write_node_comment(node)
70
71            out_comment("Node's full path:")
72            out_dt_define(f"{node.z_path_id}_PATH", f'"{escape(node.path)}"')
73
74            out_comment("Node's name with unit-address:")
75            out_dt_define(f"{node.z_path_id}_FULL_NAME",
76                          f'"{escape(node.name)}"')
77            out_dt_define(f"{node.z_path_id}_FULL_NAME_UNQUOTED",
78                          f'{escape(node.name)}')
79            out_dt_define(f"{node.z_path_id}_FULL_NAME_TOKEN",
80                          f'{edtlib.str_as_token(escape(node.name))}')
81            out_dt_define(f"{node.z_path_id}_FULL_NAME_UPPER_TOKEN",
82                          f'{edtlib.str_as_token(escape(node.name)).upper()}')
83
84            if node.parent is not None:
85                out_comment(f"Node parent ({node.parent.path}) identifier:")
86                out_dt_define(f"{node.z_path_id}_PARENT",
87                              f"DT_{node.parent.z_path_id}")
88
89                out_comment(f"Node's index in its parent's list of children:")
90                out_dt_define(f"{node.z_path_id}_CHILD_IDX",
91                              node.parent.child_index(node))
92
93            out_comment("Helpers for dealing with node labels:")
94            out_dt_define(f"{node.z_path_id}_NODELABEL_NUM", len(node.labels))
95            out_dt_define(f"{node.z_path_id}_FOREACH_NODELABEL(fn)",
96                          " ".join(f"fn({nodelabel})" for nodelabel in node.labels))
97            out_dt_define(f"{node.z_path_id}_FOREACH_NODELABEL_VARGS(fn, ...)",
98                          " ".join(f"fn({nodelabel}, __VA_ARGS__)" for nodelabel in node.labels))
99
100            write_parent(node)
101            write_children(node)
102            write_dep_info(node)
103            write_idents_and_existence(node)
104            write_bus(node)
105            write_special_props(node)
106            write_vanilla_props(node)
107
108        write_chosen(edt)
109        write_global_macros(edt)
110
111
112def node_z_path_id(node: edtlib.Node) -> str:
113    # Return the node specific bit of the node's path identifier:
114    #
115    # - the root node's path "/" has path identifier "N"
116    # - "/foo" has "N_S_foo"
117    # - "/foo/bar" has "N_S_foo_S_bar"
118    # - "/foo/bar@123" has "N_S_foo_S_bar_123"
119    #
120    # This is used throughout this file to generate macros related to
121    # the node.
122
123    components = ["N"]
124    if node.parent is not None:
125        components.extend(f"S_{str2ident(component)}" for component in
126                          node.path.split("/")[1:])
127
128    return "_".join(components)
129
130
131def parse_args() -> argparse.Namespace:
132    # Returns parsed command-line arguments
133
134    parser = argparse.ArgumentParser(allow_abbrev=False)
135    parser.add_argument("--header-out", required=True,
136                        help="path to write header to")
137    parser.add_argument("--edt-pickle",
138                        help="path to read pickled edtlib.EDT object from")
139
140    return parser.parse_args()
141
142
143def write_top_comment(edt: edtlib.EDT) -> None:
144    # Writes an overview comment with misc. info at the top of the header and
145    # configuration file
146
147    s = f"""\
148Generated by gen_defines.py
149
150DTS input file:
151  {edt.dts_path}
152
153Directories with bindings:
154  {", ".join(map(relativize, edt.bindings_dirs))}
155
156Node dependency ordering (ordinal and path):
157"""
158
159    for scc in edt.scc_order:
160        if len(scc) > 1:
161            err("cycle in devicetree involving "
162                + ", ".join(node.path for node in scc))
163        s += f"  {scc[0].dep_ordinal:<3} {scc[0].path}\n"
164
165    s += """
166Definitions derived from these nodes in dependency order are next,
167followed by /chosen nodes.
168"""
169
170    out_comment(s, blank_before=False)
171
172
173def write_utils() -> None:
174    # Writes utility macros
175
176    out_comment("Used to remove brackets from around a single argument")
177    out_define("DT_DEBRACKET_INTERNAL(...)", "__VA_ARGS__")
178
179
180def write_node_comment(node: edtlib.Node) -> None:
181    # Writes a comment describing 'node' to the header and configuration file
182
183    s = f"""\
184Devicetree node: {node.path}
185
186Node identifier: DT_{node.z_path_id}
187"""
188
189    if node.matching_compat:
190        if node.binding_path:
191            s += f"""
192Binding (compatible = {node.matching_compat}):
193  {relativize(node.binding_path)}
194"""
195        else:
196            s += f"""
197Binding (compatible = {node.matching_compat}):
198  No yaml (bindings inferred from properties)
199"""
200
201    if node.description:
202        # We used to put descriptions in the generated file, but
203        # devicetree bindings now have pages in the HTML
204        # documentation. Let users who are accustomed to digging
205        # around in the generated file where to find the descriptions
206        # now.
207        #
208        # Keeping them here would mean that the descriptions
209        # themselves couldn't contain C multi-line comments, which is
210        # inconvenient when we want to do things like quote snippets
211        # of .dtsi files within the descriptions, or otherwise
212        # include the string "*/".
213        s += ("\n(Descriptions have moved to the Devicetree Bindings Index\n"
214              "in the documentation.)\n")
215
216    out_comment(s)
217
218
219def relativize(path) -> Optional[str]:
220    # If 'path' is within $ZEPHYR_BASE, returns it relative to $ZEPHYR_BASE,
221    # with a "$ZEPHYR_BASE/..." hint at the start of the string. Otherwise,
222    # returns 'path' unchanged.
223
224    zbase = os.getenv("ZEPHYR_BASE")
225    if zbase is None:
226        return path
227
228    try:
229        return str("$ZEPHYR_BASE" / pathlib.Path(path).relative_to(zbase))
230    except ValueError:
231        # Not within ZEPHYR_BASE
232        return path
233
234
235def write_idents_and_existence(node: edtlib.Node) -> None:
236    # Writes macros related to the node's aliases, labels, etc.,
237    # as well as existence flags.
238
239    # Aliases
240    idents = [f"N_ALIAS_{str2ident(alias)}" for alias in node.aliases]
241    # Instances
242    for compat in node.compats:
243        instance_no = node.edt.compat2nodes[compat].index(node)
244        idents.append(f"N_INST_{instance_no}_{str2ident(compat)}")
245    # Node labels
246    idents.extend(f"N_NODELABEL_{str2ident(label)}" for label in node.labels)
247
248    out_comment("Existence and alternate IDs:")
249    out_dt_define(f"{node.z_path_id}_EXISTS", 1)
250
251    # Only determine maxlen if we have any idents
252    if idents:
253        maxlen = max(len(f"DT_{ident}") for ident in idents)
254    for ident in idents:
255        out_dt_define(ident, f"DT_{node.z_path_id}", width=maxlen)
256
257
258def write_bus(node: edtlib.Node) -> None:
259    # Macros about the node's bus controller, if there is one
260
261    bus = node.bus_node
262    if not bus:
263        return
264
265    out_comment(f"Bus info (controller: '{bus.path}', type: '{node.on_buses}')")
266
267    for one_bus in node.on_buses:
268        out_dt_define(f"{node.z_path_id}_BUS_{str2ident(one_bus)}", 1)
269
270    out_dt_define(f"{node.z_path_id}_BUS", f"DT_{bus.z_path_id}")
271
272
273def write_special_props(node: edtlib.Node) -> None:
274    # Writes required macros for special case properties, when the
275    # data cannot otherwise be obtained from write_vanilla_props()
276    # results
277
278    # Macros that are special to the devicetree specification
279    out_comment("Macros for properties that are special in the specification:")
280    write_regs(node)
281    write_ranges(node)
282    write_interrupts(node)
283    write_compatibles(node)
284    write_status(node)
285
286    # Macros that are special to bindings inherited from Linux, which
287    # we can't capture with the current bindings language.
288    write_pinctrls(node)
289    write_fixed_partitions(node)
290    write_gpio_hogs(node)
291
292
293def write_ranges(node: edtlib.Node) -> None:
294    # ranges property: edtlib knows the right #address-cells and
295    # #size-cells of parent and child, and can therefore pack the
296    # child & parent addresses and sizes correctly
297
298    idx_vals = []
299    path_id = node.z_path_id
300
301    if node.ranges is not None:
302        idx_vals.append((f"{path_id}_RANGES_NUM", len(node.ranges)))
303
304    for i,range in enumerate(node.ranges):
305        idx_vals.append((f"{path_id}_RANGES_IDX_{i}_EXISTS", 1))
306
307        if "pcie" in node.buses:
308            idx_vals.append((f"{path_id}_RANGES_IDX_{i}_VAL_CHILD_BUS_FLAGS_EXISTS", 1))
309            idx_macro = f"{path_id}_RANGES_IDX_{i}_VAL_CHILD_BUS_FLAGS"
310            idx_value = range.child_bus_addr >> ((range.child_bus_cells - 1) * 32)
311            idx_vals.append((idx_macro,
312                             f"{idx_value} /* {hex(idx_value)} */"))
313        if range.child_bus_addr is not None:
314            idx_macro = f"{path_id}_RANGES_IDX_{i}_VAL_CHILD_BUS_ADDRESS"
315            if "pcie" in node.buses:
316                idx_value = range.child_bus_addr & ((1 << (range.child_bus_cells - 1) * 32) - 1)
317            else:
318                idx_value = range.child_bus_addr
319            idx_vals.append((idx_macro,
320                             f"{idx_value} /* {hex(idx_value)} */"))
321        if range.parent_bus_addr is not None:
322            idx_macro = f"{path_id}_RANGES_IDX_{i}_VAL_PARENT_BUS_ADDRESS"
323            idx_vals.append((idx_macro,
324                             f"{range.parent_bus_addr} /* {hex(range.parent_bus_addr)} */"))
325        if range.length is not None:
326            idx_macro = f"{path_id}_RANGES_IDX_{i}_VAL_LENGTH"
327            idx_vals.append((idx_macro,
328                             f"{range.length} /* {hex(range.length)} */"))
329
330    for macro, val in idx_vals:
331        out_dt_define(macro, val)
332
333    out_dt_define(f"{path_id}_FOREACH_RANGE(fn)",
334            " ".join(f"fn(DT_{path_id}, {i})" for i,range in enumerate(node.ranges)))
335
336
337def write_regs(node: edtlib.Node) -> None:
338    # reg property: edtlib knows the right #address-cells and
339    # #size-cells, and can therefore pack the register base addresses
340    # and sizes correctly
341
342    idx_vals = []
343    name_vals = []
344    path_id = node.z_path_id
345
346    if node.regs is not None:
347        idx_vals.append((f"{path_id}_REG_NUM", len(node.regs)))
348
349    for i, reg in enumerate(node.regs):
350        idx_vals.append((f"{path_id}_REG_IDX_{i}_EXISTS", 1))
351        if reg.addr is not None:
352            idx_macro = f"{path_id}_REG_IDX_{i}_VAL_ADDRESS"
353            idx_vals.append((idx_macro,
354                             f"{reg.addr} /* {hex(reg.addr)} */"))
355            if reg.name:
356                name_vals.append((f"{path_id}_REG_NAME_{reg.name}_EXISTS", 1))
357                name_macro = f"{path_id}_REG_NAME_{reg.name}_VAL_ADDRESS"
358                name_vals.append((name_macro, f"DT_{idx_macro}"))
359
360        if reg.size is not None:
361            idx_macro = f"{path_id}_REG_IDX_{i}_VAL_SIZE"
362            idx_vals.append((idx_macro,
363                             f"{reg.size} /* {hex(reg.size)} */"))
364            if reg.name:
365                name_macro = f"{path_id}_REG_NAME_{reg.name}_VAL_SIZE"
366                name_vals.append((name_macro, f"DT_{idx_macro}"))
367
368    for macro, val in idx_vals:
369        out_dt_define(macro, val)
370    for macro, val in name_vals:
371        out_dt_define(macro, val)
372
373
374def write_interrupts(node: edtlib.Node) -> None:
375    # interrupts property: we have some hard-coded logic for interrupt
376    # mapping here.
377    #
378    # TODO: can we push map_arm_gic_irq_type() out of Python and into C with
379    # macro magic in devicetree.h?
380
381    def map_arm_gic_irq_type(irq, irq_num):
382        # Maps ARM GIC IRQ (type)+(index) combo to linear IRQ number
383        if "type" not in irq.data:
384            err(f"Expected binding for {irq.controller!r} to have 'type' in "
385                "interrupt-cells")
386        irq_type = irq.data["type"]
387
388        if irq_type == 0:  # GIC_SPI
389            return irq_num + 32
390        if irq_type == 1:  # GIC_PPI
391            return irq_num + 16
392        err(f"Invalid interrupt type specified for {irq!r}")
393
394    idx_vals = []
395    name_vals = []
396    path_id = node.z_path_id
397
398    if node.interrupts is not None:
399        idx_vals.append((f"{path_id}_IRQ_NUM", len(node.interrupts)))
400
401    for i, irq in enumerate(node.interrupts):
402        for cell_name, cell_value in irq.data.items():
403            name = str2ident(cell_name)
404
405            if cell_name == "irq":
406                if "arm,gic" in irq.controller.compats:
407                    cell_value = map_arm_gic_irq_type(irq, cell_value)
408
409            idx_vals.append((f"{path_id}_IRQ_IDX_{i}_EXISTS", 1))
410            idx_macro = f"{path_id}_IRQ_IDX_{i}_VAL_{name}"
411            idx_vals.append((idx_macro, cell_value))
412            idx_vals.append((idx_macro + "_EXISTS", 1))
413            if irq.name:
414                name_macro = (
415                    f"{path_id}_IRQ_NAME_{str2ident(irq.name)}_VAL_{name}")
416                name_vals.append((name_macro, f"DT_{idx_macro}"))
417                name_vals.append((name_macro + "_EXISTS", 1))
418
419        idx_controller_macro = f"{path_id}_IRQ_IDX_{i}_CONTROLLER"
420        idx_controller_path = f"DT_{irq.controller.z_path_id}"
421        idx_vals.append((idx_controller_macro, idx_controller_path))
422        if irq.name:
423            name_controller_macro = f"{path_id}_IRQ_NAME_{str2ident(irq.name)}_CONTROLLER"
424            name_vals.append((name_controller_macro, f"DT_{idx_controller_macro}"))
425
426    # Interrupt controller info
427    irqs = []
428    while node.interrupts is not None and len(node.interrupts) > 0:
429        irq = node.interrupts[0]
430        irqs.append(irq)
431        if node == irq.controller:
432            break
433        node = irq.controller
434    idx_vals.append((f"{path_id}_IRQ_LEVEL", len(irqs)))
435
436    for macro, val in idx_vals:
437        out_dt_define(macro, val)
438    for macro, val in name_vals:
439        out_dt_define(macro, val)
440
441
442def write_compatibles(node: edtlib.Node) -> None:
443    # Writes a macro for each of the node's compatibles. We don't care
444    # about whether edtlib / Zephyr's binding language recognizes
445    # them. The compatibles the node provides are what is important.
446
447    for i, compat in enumerate(node.compats):
448        out_dt_define(
449            f"{node.z_path_id}_COMPAT_MATCHES_{str2ident(compat)}", 1)
450
451        if node.edt.compat2vendor[compat]:
452            out_dt_define(f"{node.z_path_id}_COMPAT_VENDOR_IDX_{i}_EXISTS", 1)
453            out_dt_define(f"{node.z_path_id}_COMPAT_VENDOR_IDX_{i}",
454                          quote_str(node.edt.compat2vendor[compat]))
455
456        if node.edt.compat2model[compat]:
457            out_dt_define(f"{node.z_path_id}_COMPAT_MODEL_IDX_{i}_EXISTS", 1)
458            out_dt_define(f"{node.z_path_id}_COMPAT_MODEL_IDX_{i}",
459                          quote_str(node.edt.compat2model[compat]))
460
461def write_parent(node: edtlib.Node) -> None:
462    # Visit all parent nodes.
463    def _visit_parent_node(node: edtlib.Node):
464        while node is not None:
465            yield node.parent
466            node = node.parent
467
468    # Writes helper macros for dealing with node's parent.
469    out_dt_define(f"{node.z_path_id}_FOREACH_ANCESTOR(fn)",
470            " ".join(f"fn(DT_{parent.z_path_id})" for parent in
471            _visit_parent_node(node) if parent is not None))
472
473def write_children(node: edtlib.Node) -> None:
474    # Writes helper macros for dealing with node's children.
475
476    out_comment("Helper macros for child nodes of this node.")
477
478    out_dt_define(f"{node.z_path_id}_CHILD_NUM", len(node.children))
479
480    ok_nodes_num = 0
481    for child in node.children.values():
482        if child.status == "okay":
483            ok_nodes_num = ok_nodes_num + 1
484
485    out_dt_define(f"{node.z_path_id}_CHILD_NUM_STATUS_OKAY", ok_nodes_num)
486
487    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD(fn)",
488            " ".join(f"fn(DT_{child.z_path_id})" for child in
489                node.children.values()))
490
491    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_SEP(fn, sep)",
492            " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id})"
493            for child in node.children.values()))
494
495    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_VARGS(fn, ...)",
496            " ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)"
497            for child in node.children.values()))
498
499    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_SEP_VARGS(fn, sep, ...)",
500            " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)"
501            for child in node.children.values()))
502
503    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY(fn)",
504            " ".join(f"fn(DT_{child.z_path_id})"
505            for child in node.children.values() if child.status == "okay"))
506
507    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_SEP(fn, sep)",
508            " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id})"
509            for child in node.children.values() if child.status == "okay"))
510
511    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_VARGS(fn, ...)",
512            " ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)"
513            for child in node.children.values() if child.status == "okay"))
514
515    out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS(fn, sep, ...)",
516            " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)"
517            for child in node.children.values() if child.status == "okay"))
518
519
520def write_status(node: edtlib.Node) -> None:
521    out_dt_define(f"{node.z_path_id}_STATUS_{str2ident(node.status)}", 1)
522
523
524def write_pinctrls(node: edtlib.Node) -> None:
525    # Write special macros for pinctrl-<index> and pinctrl-names properties.
526
527    out_comment("Pin control (pinctrl-<i>, pinctrl-names) properties:")
528
529    out_dt_define(f"{node.z_path_id}_PINCTRL_NUM", len(node.pinctrls))
530
531    if not node.pinctrls:
532        return
533
534    for pc_idx, pinctrl in enumerate(node.pinctrls):
535        out_dt_define(f"{node.z_path_id}_PINCTRL_IDX_{pc_idx}_EXISTS", 1)
536
537        if not pinctrl.name:
538            continue
539
540        name = pinctrl.name_as_token
541
542        # Below we rely on the fact that edtlib ensures the
543        # pinctrl-<pc_idx> properties are contiguous, start from 0,
544        # and contain only phandles.
545        out_dt_define(f"{node.z_path_id}_PINCTRL_IDX_{pc_idx}_TOKEN", name)
546        out_dt_define(f"{node.z_path_id}_PINCTRL_IDX_{pc_idx}_UPPER_TOKEN", name.upper())
547        out_dt_define(f"{node.z_path_id}_PINCTRL_NAME_{name}_EXISTS", 1)
548        out_dt_define(f"{node.z_path_id}_PINCTRL_NAME_{name}_IDX", pc_idx)
549        for idx, ph in enumerate(pinctrl.conf_nodes):
550            out_dt_define(f"{node.z_path_id}_PINCTRL_NAME_{name}_IDX_{idx}_PH",
551                          f"DT_{ph.z_path_id}")
552
553
554def write_fixed_partitions(node: edtlib.Node) -> None:
555    # Macros for child nodes of each fixed-partitions node.
556
557    if not (node.parent and "fixed-partitions" in node.parent.compats):
558        return
559
560    global flash_area_num
561    out_comment("fixed-partitions identifier:")
562    out_dt_define(f"{node.z_path_id}_PARTITION_ID", flash_area_num)
563    flash_area_num += 1
564
565
566def write_gpio_hogs(node: edtlib.Node) -> None:
567    # Write special macros for gpio-hog node properties.
568
569    macro = f"{node.z_path_id}_GPIO_HOGS"
570    macro2val = {}
571    for i, entry in enumerate(node.gpio_hogs):
572        macro2val.update(controller_and_data_macros(entry, i, macro))
573
574    if macro2val:
575        out_comment("GPIO hog properties:")
576        out_dt_define(f"{macro}_EXISTS", 1)
577        out_dt_define(f"{macro}_NUM", len(node.gpio_hogs))
578        for macro, val in macro2val.items():
579            out_dt_define(macro, val)
580
581
582def write_vanilla_props(node: edtlib.Node) -> None:
583    # Writes macros for any and all properties defined in the
584    # "properties" section of the binding for the node.
585    #
586    # This does generate macros for special properties as well, like
587    # regs, etc. Just let that be rather than bothering to add
588    # never-ending amounts of special case code here to skip special
589    # properties. This function's macros can't conflict with
590    # write_special_props() macros, because they're in different
591    # namespaces. Special cases aren't special enough to break the rules.
592
593    macro2val = {}
594    for prop_name, prop in node.props.items():
595        prop_id = str2ident(prop_name)
596        macro = f"{node.z_path_id}_P_{prop_id}"
597        val = prop2value(prop)
598        if val is not None:
599            # DT_N_<node-id>_P_<prop-id>
600            macro2val[macro] = val
601
602        if prop.spec.type == 'string':
603            macro2val.update(string_macros(macro, prop.val))
604            # DT_N_<node-id>_P_<prop-id>_IDX_0:
605            # DT_N_<node-id>_P_<prop-id>_IDX_0_EXISTS:
606            # Allows treating the string like a degenerate case of a
607            # string-array of length 1.
608            macro2val[f"{macro}_IDX_0"] = quote_str(prop.val)
609            macro2val[f"{macro}_IDX_0_EXISTS"] = 1
610
611        if prop.enum_indices is not None:
612            macro2val.update(enum_macros(prop, macro))
613
614        if "phandle" in prop.type:
615            macro2val.update(phandle_macros(prop, macro))
616        elif "array" in prop.type:
617            macro2val.update(array_macros(prop, macro))
618
619        plen = prop_len(prop)
620        if plen is not None:
621            # DT_N_<node-id>_P_<prop-id>_FOREACH_PROP_ELEM
622            macro2val[f"{macro}_FOREACH_PROP_ELEM(fn)"] = (
623                ' \\\n\t'.join(f'fn(DT_{node.z_path_id}, {prop_id}, {i})'
624                               for i in range(plen)))
625
626            # DT_N_<node-id>_P_<prop-id>_FOREACH_PROP_ELEM_SEP
627            macro2val[f"{macro}_FOREACH_PROP_ELEM_SEP(fn, sep)"] = (
628                ' DT_DEBRACKET_INTERNAL sep \\\n\t'.join(
629                    f'fn(DT_{node.z_path_id}, {prop_id}, {i})'
630                    for i in range(plen)))
631
632            # DT_N_<node-id>_P_<prop-id>_FOREACH_PROP_ELEM_VARGS
633            macro2val[f"{macro}_FOREACH_PROP_ELEM_VARGS(fn, ...)"] = (
634                ' \\\n\t'.join(
635                    f'fn(DT_{node.z_path_id}, {prop_id}, {i}, __VA_ARGS__)'
636                    for i in range(plen)))
637
638            # DT_N_<node-id>_P_<prop-id>_FOREACH_PROP_ELEM_SEP_VARGS
639            macro2val[f"{macro}_FOREACH_PROP_ELEM_SEP_VARGS(fn, sep, ...)"] = (
640                ' DT_DEBRACKET_INTERNAL sep \\\n\t'.join(
641                    f'fn(DT_{node.z_path_id}, {prop_id}, {i}, __VA_ARGS__)'
642                    for i in range(plen)))
643
644            # DT_N_<node-id>_P_<prop-id>_LEN
645            macro2val[f"{macro}_LEN"] = plen
646
647        # DT_N_<node-id>_P_<prop-id>_EXISTS
648        macro2val[f"{macro}_EXISTS"] = 1
649
650    if macro2val:
651        out_comment("Generic property macros:")
652        for macro, val in macro2val.items():
653            out_dt_define(macro, val)
654    else:
655        out_comment("(No generic property macros)")
656
657
658def string_macros(macro: str, val: str):
659    # Returns a dict of macros for a string 'val'.
660    # The 'macro' argument is the N_<node-id>_P_<prop-id>... part.
661
662    as_token = edtlib.str_as_token(val)
663    return {
664        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UNQUOTED
665        f"{macro}_STRING_UNQUOTED": escape_unquoted(val),
666        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_TOKEN
667        f"{macro}_STRING_TOKEN": as_token,
668        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UPPER_TOKEN
669        f"{macro}_STRING_UPPER_TOKEN": as_token.upper()}
670
671
672def enum_macros(prop: edtlib.Property, macro: str):
673    # Returns a dict of macros for property 'prop' with a defined enum in their dt-binding.
674    # The 'macro' argument is the N_<node-id>_P_<prop-id> part.
675
676    spec = prop.spec
677    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_IDX
678    ret = {f"{macro}_IDX_{i}_ENUM_IDX": index for i, index in enumerate(prop.enum_indices)}
679    val = prop.val_as_tokens if spec.enum_tokenizable else (prop.val if isinstance(prop.val, list) else [prop.val])
680
681    for i, subval in enumerate(val):
682        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
683        ret[f"{macro}_IDX_{i}_EXISTS"] = 1
684        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_VAL_<val>_EXISTS 1
685        ret[f"{macro}_IDX_{i}_ENUM_VAL_{subval}_EXISTS"] = 1
686
687    return ret
688
689
690def array_macros(prop: edtlib.Property, macro: str):
691    # Returns a dict of macros for array property 'prop'.
692    # The 'macro' argument is the N_<node-id>_P_<prop-id> part.
693
694    ret = {}
695    for i, subval in enumerate(prop.val):
696        # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
697        ret[f"{macro}_IDX_{i}_EXISTS"] = 1
698
699        # DT_N_<node-id>_P_<prop-id>_IDX_<i>
700        if isinstance(subval, str):
701            ret[f"{macro}_IDX_{i}"] = quote_str(subval)
702            # DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_...
703            ret.update(string_macros(f"{macro}_IDX_{i}", subval))
704        else:
705            ret[f"{macro}_IDX_{i}"] = subval
706
707    return ret
708
709
710def write_dep_info(node: edtlib.Node) -> None:
711    # Write dependency-related information about the node.
712
713    def fmt_dep_list(dep_list):
714        if dep_list:
715            # Sort the list by dependency ordinal for predictability.
716            sorted_list = sorted(dep_list, key=lambda node: node.dep_ordinal)
717            return ("\\\n\t" + " \\\n\t"
718                    .join(f"{n.dep_ordinal}, /* {n.path} */"
719                          for n in sorted_list))
720        else:
721            return "/* nothing */"
722
723    out_comment("Node's hash:")
724    out_dt_define(f"{node.z_path_id}_HASH", node.hash)
725
726    out_comment("Node's dependency ordinal:")
727    out_dt_define(f"{node.z_path_id}_ORD", node.dep_ordinal)
728    out_dt_define(f"{node.z_path_id}_ORD_STR_SORTABLE", f"{node.dep_ordinal:0>5}")
729
730    out_comment("Ordinals for what this node depends on directly:")
731    out_dt_define(f"{node.z_path_id}_REQUIRES_ORDS",
732                  fmt_dep_list(node.depends_on))
733
734    out_comment("Ordinals for what depends directly on this node:")
735    out_dt_define(f"{node.z_path_id}_SUPPORTS_ORDS",
736                  fmt_dep_list(node.required_by))
737
738
739def prop2value(prop: edtlib.Property) -> edtlib.PropertyValType:
740    # Gets the macro value for property 'prop', if there is
741    # a single well-defined C rvalue that it can be represented as.
742    # Returns None if there isn't one.
743
744    if prop.type == "string":
745        return quote_str(prop.val)
746
747    if prop.type == "int":
748        return prop.val
749
750    if prop.type == "boolean":
751        return 1 if prop.val else 0
752
753    if prop.type in ["array", "uint8-array"]:
754        return list2init(f"{val} /* {hex(val)} */" for val in prop.val)
755
756    if prop.type == "string-array":
757        return list2init(quote_str(val) for val in prop.val)
758
759    # phandle, phandles, phandle-array, path, compound: nothing
760    return None
761
762
763def prop_len(prop: edtlib.Property) -> Optional[int]:
764    # Returns the property's length if and only if we should generate
765    # a _LEN macro for the property. Otherwise, returns None.
766    #
767    # The set of types handled here coincides with the allowable types
768    # that can be used with DT_PROP_LEN(). If you change this set,
769    # make sure to update the doxygen string for that macro, and make
770    # sure that DT_FOREACH_PROP_ELEM() works for the new types too.
771    #
772    # This deliberately excludes ranges, dma-ranges, reg and interrupts.
773    # While they have array type, their lengths as arrays are
774    # basically nonsense semantically due to #address-cells and
775    # #size-cells for "reg", #interrupt-cells for "interrupts"
776    # and #address-cells, #size-cells and the #address-cells from the
777    # parent node for "ranges" and "dma-ranges".
778    #
779    # We have special purpose macros for the number of register blocks
780    # / interrupt specifiers. Excluding them from this list means
781    # DT_PROP_LEN(node_id, ...) fails fast at the devicetree.h layer
782    # with a build error. This forces users to switch to the right
783    # macros.
784
785    if prop.type in ["phandle", "string"]:
786        # phandle is treated as a phandles of length 1.
787        # string is treated as a string-array of length 1.
788        return 1
789
790    if (prop.type in ["array", "uint8-array", "string-array",
791                      "phandles", "phandle-array"] and
792                prop.name not in ["ranges", "dma-ranges", "reg", "interrupts"]):
793        return len(prop.val)
794
795    return None
796
797
798def phandle_macros(prop: edtlib.Property, macro: str) -> dict:
799    # Returns a dict of macros for phandle or phandles property 'prop'.
800    #
801    # The 'macro' argument is the N_<node-id>_P_<prop-id> bit.
802    #
803    # These are currently special because we can't serialize their
804    # values without using label properties, which we're trying to get
805    # away from needing in Zephyr. (Label properties are great for
806    # humans, but have drawbacks for code size and boot time.)
807    #
808    # The names look a bit weird to make it easier for devicetree.h
809    # to use the same macros for phandle, phandles, and phandle-array.
810
811    ret = {}
812
813    if prop.type == "phandle":
814        # A phandle is treated as a phandles with fixed length 1.
815        ret[f"{macro}"] = f"DT_{prop.val.z_path_id}"
816        ret[f"{macro}_IDX_0"] = f"DT_{prop.val.z_path_id}"
817        ret[f"{macro}_IDX_0_PH"] = f"DT_{prop.val.z_path_id}"
818        ret[f"{macro}_IDX_0_EXISTS"] = 1
819    elif prop.type == "phandles":
820        for i, node in enumerate(prop.val):
821            ret[f"{macro}_IDX_{i}"] = f"DT_{node.z_path_id}"
822            ret[f"{macro}_IDX_{i}_PH"] = f"DT_{node.z_path_id}"
823            ret[f"{macro}_IDX_{i}_EXISTS"] = 1
824    elif prop.type == "phandle-array":
825        for i, entry in enumerate(prop.val):
826            if entry is None:
827                # Unspecified element. The phandle-array at this index
828                # does not point at a ControllerAndData value, but
829                # subsequent indices in the array may.
830                ret[f"{macro}_IDX_{i}_EXISTS"] = 0
831                continue
832
833            ret.update(controller_and_data_macros(entry, i, macro))
834
835    return ret
836
837
838def controller_and_data_macros(entry: edtlib.ControllerAndData, i: int, macro: str):
839    # Helper procedure used by phandle_macros().
840    #
841    # Its purpose is to write the "controller" (i.e. label property of
842    # the phandle's node) and associated data macros for a
843    # ControllerAndData.
844
845    ret = {}
846    data = entry.data
847
848    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
849    ret[f"{macro}_IDX_{i}_EXISTS"] = 1
850    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_PH
851    ret[f"{macro}_IDX_{i}_PH"] = f"DT_{entry.controller.z_path_id}"
852    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_VAL_<VAL>
853    for cell, val in data.items():
854        ret[f"{macro}_IDX_{i}_VAL_{str2ident(cell)}"] = val
855        ret[f"{macro}_IDX_{i}_VAL_{str2ident(cell)}_EXISTS"] = 1
856
857    if not entry.name:
858        return ret
859
860    name = str2ident(entry.name)
861    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
862    ret[f"{macro}_IDX_{i}_EXISTS"] = 1
863    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_NAME
864    ret[f"{macro}_IDX_{i}_NAME"] = quote_str(entry.name)
865    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_PH
866    ret[f"{macro}_NAME_{name}_PH"] = f"DT_{entry.controller.z_path_id}"
867    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_EXISTS
868    ret[f"{macro}_NAME_{name}_EXISTS"] = 1
869    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_VAL_<VAL>
870    for cell, val in data.items():
871        cell_ident = str2ident(cell)
872        ret[f"{macro}_NAME_{name}_VAL_{cell_ident}"] = (
873            f"DT_{macro}_IDX_{i}_VAL_{cell_ident}")
874        ret[f"{macro}_NAME_{name}_VAL_{cell_ident}_EXISTS"] = 1
875
876    return ret
877
878
879def write_chosen(edt: edtlib.EDT):
880    # Tree-wide information such as chosen nodes is printed here.
881
882    out_comment("Chosen nodes\n")
883    chosen = {}
884    for name, node in edt.chosen_nodes.items():
885        chosen[f"DT_CHOSEN_{str2ident(name)}"] = f"DT_{node.z_path_id}"
886        chosen[f"DT_CHOSEN_{str2ident(name)}_EXISTS"] = 1
887    max_len = max(map(len, chosen), default=0)
888    for macro, value in chosen.items():
889        out_define(macro, value, width=max_len)
890
891
892def write_global_macros(edt: edtlib.EDT):
893    # Global or tree-wide information, such as number of instances
894    # with status "okay" for each compatible, is printed here.
895
896
897    out_comment("Macros for iterating over all nodes and enabled nodes")
898    out_dt_define("FOREACH_HELPER(fn)",
899                  " ".join(f"fn(DT_{node.z_path_id})" for node in edt.nodes))
900    out_dt_define("FOREACH_OKAY_HELPER(fn)",
901                  " ".join(f"fn(DT_{node.z_path_id})" for node in edt.nodes
902                           if node.status == "okay"))
903    out_dt_define("FOREACH_VARGS_HELPER(fn, ...)",
904                  " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)" for node in edt.nodes))
905    out_dt_define("FOREACH_OKAY_VARGS_HELPER(fn, ...)",
906                  " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)" for node in edt.nodes
907                           if node.status == "okay"))
908
909    n_okay_macros = {}
910    for_each_macros = {}
911    compat2buses = defaultdict(list)  # just for "okay" nodes
912    for compat, okay_nodes in edt.compat2okay.items():
913        for node in okay_nodes:
914            buses = node.on_buses
915            for bus in buses:
916                if bus is not None and bus not in compat2buses[compat]:
917                    compat2buses[compat].append(bus)
918
919        ident = str2ident(compat)
920        n_okay_macros[f"DT_N_INST_{ident}_NUM_OKAY"] = len(okay_nodes)
921
922        # Helpers for non-INST for-each macros that take node
923        # identifiers as arguments.
924        for_each_macros[f"DT_FOREACH_OKAY_{ident}(fn)"] = (
925            " ".join(f"fn(DT_{node.z_path_id})"
926                     for node in okay_nodes))
927        for_each_macros[f"DT_FOREACH_OKAY_VARGS_{ident}(fn, ...)"] = (
928            " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)"
929                     for node in okay_nodes))
930
931        # Helpers for INST versions of for-each macros, which take
932        # instance numbers. We emit separate helpers for these because
933        # avoiding an intermediate node_id --> instance number
934        # conversion in the preprocessor helps to keep the macro
935        # expansions simpler. That hopefully eases debugging.
936        for_each_macros[f"DT_FOREACH_OKAY_INST_{ident}(fn)"] = (
937            " ".join(f"fn({edt.compat2nodes[compat].index(node)})"
938                     for node in okay_nodes))
939        for_each_macros[f"DT_FOREACH_OKAY_INST_VARGS_{ident}(fn, ...)"] = (
940            " ".join(f"fn({edt.compat2nodes[compat].index(node)}, __VA_ARGS__)"
941                     for node in okay_nodes))
942
943    for compat, nodes in edt.compat2nodes.items():
944        for node in nodes:
945            if compat == "fixed-partitions":
946                for child in node.children.values():
947                    if "label" in child.props:
948                        label = child.props["label"].val
949                        macro = f"COMPAT_{str2ident(compat)}_LABEL_{str2ident(label)}"
950                        val = f"DT_{child.z_path_id}"
951
952                        out_dt_define(macro, val)
953                        out_dt_define(macro + "_EXISTS", 1)
954
955    out_comment('Macros for compatibles with status "okay" nodes\n')
956    for compat, okay_nodes in edt.compat2okay.items():
957        if okay_nodes:
958            out_define(f"DT_COMPAT_HAS_OKAY_{str2ident(compat)}", 1)
959
960    out_comment('Macros for status "okay" instances of each compatible\n')
961    for macro, value in n_okay_macros.items():
962        out_define(macro, value)
963    for macro, value in for_each_macros.items():
964        out_define(macro, value)
965
966    out_comment('Bus information for status "okay" nodes of each compatible\n')
967    for compat, buses in compat2buses.items():
968        for bus in buses:
969            out_define(
970                f"DT_COMPAT_{str2ident(compat)}_BUS_{str2ident(bus)}", 1)
971
972
973def str2ident(s: str) -> str:
974    # Converts 's' to a form suitable for (part of) an identifier
975
976    return re.sub('[-,.@/+]', '_', s.lower())
977
978
979def list2init(l: Iterable[str]) -> str:
980    # Converts 'l', a Python list (or iterable), to a C array initializer
981
982    return "{" + ", ".join(l) + "}"
983
984
985def out_dt_define(
986    macro: str,
987    val: str,
988    width: Optional[int] = None,
989    deprecation_msg: Optional[str] = None,
990) -> str:
991    # Writes "#define DT_<macro> <val>" to the header file
992    #
993    # The macro will be left-justified to 'width' characters if that
994    # is specified, and the value will follow immediately after in
995    # that case. Otherwise, this function decides how to add
996    # whitespace between 'macro' and 'val'.
997    #
998    # If a 'deprecation_msg' string is passed, the generated identifiers will
999    # generate a warning if used, via __WARN(<deprecation_msg>)).
1000    #
1001    # Returns the full generated macro for 'macro', with leading "DT_".
1002    ret = f"DT_{macro}"
1003    out_define(ret, val, width=width, deprecation_msg=deprecation_msg)
1004    return ret
1005
1006
1007def out_define(
1008    macro: str,
1009    val: str,
1010    width: Optional[int] = None,
1011    deprecation_msg: Optional[str] = None,
1012) -> None:
1013    # Helper for out_dt_define(). Outputs "#define <macro> <val>",
1014    # adds a deprecation message if given, and allocates whitespace
1015    # unless told not to.
1016
1017    warn = fr' __WARN("{deprecation_msg}")' if deprecation_msg else ""
1018
1019    if width:
1020        s = f"#define {macro.ljust(width)}{warn} {val}"
1021    else:
1022        s = f"#define {macro}{warn} {val}"
1023
1024    print(s, file=header_file)
1025
1026
1027def out_comment(s: str, blank_before=True) -> None:
1028    # Writes 's' as a comment to the header and configuration file. 's' is
1029    # allowed to have multiple lines. blank_before=True adds a blank line
1030    # before the comment.
1031
1032    if blank_before:
1033        print(file=header_file)
1034
1035    if "\n" in s:
1036        # Format multi-line comments like
1037        #
1038        #   /*
1039        #    * first line
1040        #    * second line
1041        #    *
1042        #    * empty line before this line
1043        #    */
1044        res = ["/*"]
1045        for line in s.splitlines():
1046            # Avoid an extra space after '*' for empty lines. They turn red in
1047            # Vim if space error checking is on, which is annoying.
1048            res.append(f" * {line}".rstrip())
1049        res.append(" */")
1050        print("\n".join(res), file=header_file)
1051    else:
1052        # Format single-line comments like
1053        #
1054        #   /* foo bar */
1055        print(f"/* {s} */", file=header_file)
1056
1057ESCAPE_TABLE = str.maketrans(
1058    {
1059        "\n": "\\n",
1060        "\r": "\\r",
1061        "\\": "\\\\",
1062        '"': '\\"',
1063    }
1064)
1065
1066
1067def escape(s: str) -> str:
1068    # Backslash-escapes any double quotes, backslashes, and new lines in 's'
1069
1070    return s.translate(ESCAPE_TABLE)
1071
1072
1073def quote_str(s: str) -> str:
1074    # Puts quotes around 's' and escapes any double quotes and
1075    # backslashes within it
1076
1077    return f'"{escape(s)}"'
1078
1079
1080def escape_unquoted(s: str) -> str:
1081    # C macros cannot contain line breaks, so replace them with spaces.
1082    # Whitespace is used to separate preprocessor tokens, but it does not matter
1083    # which whitespace characters are used, so a line break and a space are
1084    # equivalent with regards to unquoted strings being used as C code.
1085
1086    return s.replace("\r", " ").replace("\n", " ")
1087
1088
1089def err(s: str) -> NoReturn:
1090    raise Exception(s)
1091
1092
1093if __name__ == "__main__":
1094    main()
1095