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 dependency ordinal:")
724    out_dt_define(f"{node.z_path_id}_ORD", node.dep_ordinal)
725    out_dt_define(f"{node.z_path_id}_ORD_STR_SORTABLE", f"{node.dep_ordinal:0>5}")
726
727    out_comment("Ordinals for what this node depends on directly:")
728    out_dt_define(f"{node.z_path_id}_REQUIRES_ORDS",
729                  fmt_dep_list(node.depends_on))
730
731    out_comment("Ordinals for what depends directly on this node:")
732    out_dt_define(f"{node.z_path_id}_SUPPORTS_ORDS",
733                  fmt_dep_list(node.required_by))
734
735
736def prop2value(prop: edtlib.Property) -> edtlib.PropertyValType:
737    # Gets the macro value for property 'prop', if there is
738    # a single well-defined C rvalue that it can be represented as.
739    # Returns None if there isn't one.
740
741    if prop.type == "string":
742        return quote_str(prop.val)
743
744    if prop.type == "int":
745        return prop.val
746
747    if prop.type == "boolean":
748        return 1 if prop.val else 0
749
750    if prop.type in ["array", "uint8-array"]:
751        return list2init(f"{val} /* {hex(val)} */" for val in prop.val)
752
753    if prop.type == "string-array":
754        return list2init(quote_str(val) for val in prop.val)
755
756    # phandle, phandles, phandle-array, path, compound: nothing
757    return None
758
759
760def prop_len(prop: edtlib.Property) -> Optional[int]:
761    # Returns the property's length if and only if we should generate
762    # a _LEN macro for the property. Otherwise, returns None.
763    #
764    # The set of types handled here coincides with the allowable types
765    # that can be used with DT_PROP_LEN(). If you change this set,
766    # make sure to update the doxygen string for that macro, and make
767    # sure that DT_FOREACH_PROP_ELEM() works for the new types too.
768    #
769    # This deliberately excludes ranges, dma-ranges, reg and interrupts.
770    # While they have array type, their lengths as arrays are
771    # basically nonsense semantically due to #address-cells and
772    # #size-cells for "reg", #interrupt-cells for "interrupts"
773    # and #address-cells, #size-cells and the #address-cells from the
774    # parent node for "ranges" and "dma-ranges".
775    #
776    # We have special purpose macros for the number of register blocks
777    # / interrupt specifiers. Excluding them from this list means
778    # DT_PROP_LEN(node_id, ...) fails fast at the devicetree.h layer
779    # with a build error. This forces users to switch to the right
780    # macros.
781
782    if prop.type in ["phandle", "string"]:
783        # phandle is treated as a phandles of length 1.
784        # string is treated as a string-array of length 1.
785        return 1
786
787    if (prop.type in ["array", "uint8-array", "string-array",
788                      "phandles", "phandle-array"] and
789                prop.name not in ["ranges", "dma-ranges", "reg", "interrupts"]):
790        return len(prop.val)
791
792    return None
793
794
795def phandle_macros(prop: edtlib.Property, macro: str) -> dict:
796    # Returns a dict of macros for phandle or phandles property 'prop'.
797    #
798    # The 'macro' argument is the N_<node-id>_P_<prop-id> bit.
799    #
800    # These are currently special because we can't serialize their
801    # values without using label properties, which we're trying to get
802    # away from needing in Zephyr. (Label properties are great for
803    # humans, but have drawbacks for code size and boot time.)
804    #
805    # The names look a bit weird to make it easier for devicetree.h
806    # to use the same macros for phandle, phandles, and phandle-array.
807
808    ret = {}
809
810    if prop.type == "phandle":
811        # A phandle is treated as a phandles with fixed length 1.
812        ret[f"{macro}"] = f"DT_{prop.val.z_path_id}"
813        ret[f"{macro}_IDX_0"] = f"DT_{prop.val.z_path_id}"
814        ret[f"{macro}_IDX_0_PH"] = f"DT_{prop.val.z_path_id}"
815        ret[f"{macro}_IDX_0_EXISTS"] = 1
816    elif prop.type == "phandles":
817        for i, node in enumerate(prop.val):
818            ret[f"{macro}_IDX_{i}"] = f"DT_{node.z_path_id}"
819            ret[f"{macro}_IDX_{i}_PH"] = f"DT_{node.z_path_id}"
820            ret[f"{macro}_IDX_{i}_EXISTS"] = 1
821    elif prop.type == "phandle-array":
822        for i, entry in enumerate(prop.val):
823            if entry is None:
824                # Unspecified element. The phandle-array at this index
825                # does not point at a ControllerAndData value, but
826                # subsequent indices in the array may.
827                ret[f"{macro}_IDX_{i}_EXISTS"] = 0
828                continue
829
830            ret.update(controller_and_data_macros(entry, i, macro))
831
832    return ret
833
834
835def controller_and_data_macros(entry: edtlib.ControllerAndData, i: int, macro: str):
836    # Helper procedure used by phandle_macros().
837    #
838    # Its purpose is to write the "controller" (i.e. label property of
839    # the phandle's node) and associated data macros for a
840    # ControllerAndData.
841
842    ret = {}
843    data = entry.data
844
845    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
846    ret[f"{macro}_IDX_{i}_EXISTS"] = 1
847    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_PH
848    ret[f"{macro}_IDX_{i}_PH"] = f"DT_{entry.controller.z_path_id}"
849    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_VAL_<VAL>
850    for cell, val in data.items():
851        ret[f"{macro}_IDX_{i}_VAL_{str2ident(cell)}"] = val
852        ret[f"{macro}_IDX_{i}_VAL_{str2ident(cell)}_EXISTS"] = 1
853
854    if not entry.name:
855        return ret
856
857    name = str2ident(entry.name)
858    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
859    ret[f"{macro}_IDX_{i}_EXISTS"] = 1
860    # DT_N_<node-id>_P_<prop-id>_IDX_<i>_NAME
861    ret[f"{macro}_IDX_{i}_NAME"] = quote_str(entry.name)
862    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_PH
863    ret[f"{macro}_NAME_{name}_PH"] = f"DT_{entry.controller.z_path_id}"
864    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_EXISTS
865    ret[f"{macro}_NAME_{name}_EXISTS"] = 1
866    # DT_N_<node-id>_P_<prop-id>_NAME_<NAME>_VAL_<VAL>
867    for cell, val in data.items():
868        cell_ident = str2ident(cell)
869        ret[f"{macro}_NAME_{name}_VAL_{cell_ident}"] = (
870            f"DT_{macro}_IDX_{i}_VAL_{cell_ident}")
871        ret[f"{macro}_NAME_{name}_VAL_{cell_ident}_EXISTS"] = 1
872
873    return ret
874
875
876def write_chosen(edt: edtlib.EDT):
877    # Tree-wide information such as chosen nodes is printed here.
878
879    out_comment("Chosen nodes\n")
880    chosen = {}
881    for name, node in edt.chosen_nodes.items():
882        chosen[f"DT_CHOSEN_{str2ident(name)}"] = f"DT_{node.z_path_id}"
883        chosen[f"DT_CHOSEN_{str2ident(name)}_EXISTS"] = 1
884    max_len = max(map(len, chosen), default=0)
885    for macro, value in chosen.items():
886        out_define(macro, value, width=max_len)
887
888
889def write_global_macros(edt: edtlib.EDT):
890    # Global or tree-wide information, such as number of instances
891    # with status "okay" for each compatible, is printed here.
892
893
894    out_comment("Macros for iterating over all nodes and enabled nodes")
895    out_dt_define("FOREACH_HELPER(fn)",
896                  " ".join(f"fn(DT_{node.z_path_id})" for node in edt.nodes))
897    out_dt_define("FOREACH_OKAY_HELPER(fn)",
898                  " ".join(f"fn(DT_{node.z_path_id})" for node in edt.nodes
899                           if node.status == "okay"))
900    out_dt_define("FOREACH_VARGS_HELPER(fn, ...)",
901                  " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)" for node in edt.nodes))
902    out_dt_define("FOREACH_OKAY_VARGS_HELPER(fn, ...)",
903                  " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)" for node in edt.nodes
904                           if node.status == "okay"))
905
906    n_okay_macros = {}
907    for_each_macros = {}
908    compat2buses = defaultdict(list)  # just for "okay" nodes
909    for compat, okay_nodes in edt.compat2okay.items():
910        for node in okay_nodes:
911            buses = node.on_buses
912            for bus in buses:
913                if bus is not None and bus not in compat2buses[compat]:
914                    compat2buses[compat].append(bus)
915
916        ident = str2ident(compat)
917        n_okay_macros[f"DT_N_INST_{ident}_NUM_OKAY"] = len(okay_nodes)
918
919        # Helpers for non-INST for-each macros that take node
920        # identifiers as arguments.
921        for_each_macros[f"DT_FOREACH_OKAY_{ident}(fn)"] = (
922            " ".join(f"fn(DT_{node.z_path_id})"
923                     for node in okay_nodes))
924        for_each_macros[f"DT_FOREACH_OKAY_VARGS_{ident}(fn, ...)"] = (
925            " ".join(f"fn(DT_{node.z_path_id}, __VA_ARGS__)"
926                     for node in okay_nodes))
927
928        # Helpers for INST versions of for-each macros, which take
929        # instance numbers. We emit separate helpers for these because
930        # avoiding an intermediate node_id --> instance number
931        # conversion in the preprocessor helps to keep the macro
932        # expansions simpler. That hopefully eases debugging.
933        for_each_macros[f"DT_FOREACH_OKAY_INST_{ident}(fn)"] = (
934            " ".join(f"fn({edt.compat2nodes[compat].index(node)})"
935                     for node in okay_nodes))
936        for_each_macros[f"DT_FOREACH_OKAY_INST_VARGS_{ident}(fn, ...)"] = (
937            " ".join(f"fn({edt.compat2nodes[compat].index(node)}, __VA_ARGS__)"
938                     for node in okay_nodes))
939
940    for compat, nodes in edt.compat2nodes.items():
941        for node in nodes:
942            if compat == "fixed-partitions":
943                for child in node.children.values():
944                    if "label" in child.props:
945                        label = child.props["label"].val
946                        macro = f"COMPAT_{str2ident(compat)}_LABEL_{str2ident(label)}"
947                        val = f"DT_{child.z_path_id}"
948
949                        out_dt_define(macro, val)
950                        out_dt_define(macro + "_EXISTS", 1)
951
952    out_comment('Macros for compatibles with status "okay" nodes\n')
953    for compat, okay_nodes in edt.compat2okay.items():
954        if okay_nodes:
955            out_define(f"DT_COMPAT_HAS_OKAY_{str2ident(compat)}", 1)
956
957    out_comment('Macros for status "okay" instances of each compatible\n')
958    for macro, value in n_okay_macros.items():
959        out_define(macro, value)
960    for macro, value in for_each_macros.items():
961        out_define(macro, value)
962
963    out_comment('Bus information for status "okay" nodes of each compatible\n')
964    for compat, buses in compat2buses.items():
965        for bus in buses:
966            out_define(
967                f"DT_COMPAT_{str2ident(compat)}_BUS_{str2ident(bus)}", 1)
968
969
970def str2ident(s: str) -> str:
971    # Converts 's' to a form suitable for (part of) an identifier
972
973    return re.sub('[-,.@/+]', '_', s.lower())
974
975
976def list2init(l: Iterable[str]) -> str:
977    # Converts 'l', a Python list (or iterable), to a C array initializer
978
979    return "{" + ", ".join(l) + "}"
980
981
982def out_dt_define(
983    macro: str,
984    val: str,
985    width: Optional[int] = None,
986    deprecation_msg: Optional[str] = None,
987) -> str:
988    # Writes "#define DT_<macro> <val>" to the header file
989    #
990    # The macro will be left-justified to 'width' characters if that
991    # is specified, and the value will follow immediately after in
992    # that case. Otherwise, this function decides how to add
993    # whitespace between 'macro' and 'val'.
994    #
995    # If a 'deprecation_msg' string is passed, the generated identifiers will
996    # generate a warning if used, via __WARN(<deprecation_msg>)).
997    #
998    # Returns the full generated macro for 'macro', with leading "DT_".
999    ret = f"DT_{macro}"
1000    out_define(ret, val, width=width, deprecation_msg=deprecation_msg)
1001    return ret
1002
1003
1004def out_define(
1005    macro: str,
1006    val: str,
1007    width: Optional[int] = None,
1008    deprecation_msg: Optional[str] = None,
1009) -> None:
1010    # Helper for out_dt_define(). Outputs "#define <macro> <val>",
1011    # adds a deprecation message if given, and allocates whitespace
1012    # unless told not to.
1013
1014    warn = fr' __WARN("{deprecation_msg}")' if deprecation_msg else ""
1015
1016    if width:
1017        s = f"#define {macro.ljust(width)}{warn} {val}"
1018    else:
1019        s = f"#define {macro}{warn} {val}"
1020
1021    print(s, file=header_file)
1022
1023
1024def out_comment(s: str, blank_before=True) -> None:
1025    # Writes 's' as a comment to the header and configuration file. 's' is
1026    # allowed to have multiple lines. blank_before=True adds a blank line
1027    # before the comment.
1028
1029    if blank_before:
1030        print(file=header_file)
1031
1032    if "\n" in s:
1033        # Format multi-line comments like
1034        #
1035        #   /*
1036        #    * first line
1037        #    * second line
1038        #    *
1039        #    * empty line before this line
1040        #    */
1041        res = ["/*"]
1042        for line in s.splitlines():
1043            # Avoid an extra space after '*' for empty lines. They turn red in
1044            # Vim if space error checking is on, which is annoying.
1045            res.append(f" * {line}".rstrip())
1046        res.append(" */")
1047        print("\n".join(res), file=header_file)
1048    else:
1049        # Format single-line comments like
1050        #
1051        #   /* foo bar */
1052        print(f"/* {s} */", file=header_file)
1053
1054ESCAPE_TABLE = str.maketrans(
1055    {
1056        "\n": "\\n",
1057        "\r": "\\r",
1058        "\\": "\\\\",
1059        '"': '\\"',
1060    }
1061)
1062
1063
1064def escape(s: str) -> str:
1065    # Backslash-escapes any double quotes, backslashes, and new lines in 's'
1066
1067    return s.translate(ESCAPE_TABLE)
1068
1069
1070def quote_str(s: str) -> str:
1071    # Puts quotes around 's' and escapes any double quotes and
1072    # backslashes within it
1073
1074    return f'"{escape(s)}"'
1075
1076
1077def escape_unquoted(s: str) -> str:
1078    # C macros cannot contain line breaks, so replace them with spaces.
1079    # Whitespace is used to separate preprocessor tokens, but it does not matter
1080    # which whitespace characters are used, so a line break and a space are
1081    # equivalent with regards to unquoted strings being used as C code.
1082
1083    return s.replace("\r", " ").replace("\n", " ")
1084
1085
1086def err(s: str) -> NoReturn:
1087    raise Exception(s)
1088
1089
1090if __name__ == "__main__":
1091    main()
1092