| #!/usr/bin/env python3 |
| |
| # Copyright (c) 2019 Nordic Semiconductor ASA |
| # Copyright (c) 2019 Linaro Limited |
| # SPDX-License-Identifier: BSD-3-Clause |
| |
| # This script uses edtlib to generate a header file and a .conf file (both |
| # containing the same values) from a devicetree (.dts) file. Information from |
| # binding files in YAML format is used as well. |
| # |
| # Bindings are files that describe devicetree nodes. Devicetree nodes are |
| # usually mapped to bindings via their 'compatible = "..."' property. |
| # |
| # See the docstring/comments at the top of edtlib.py for more information. |
| # |
| # Note: Do not access private (_-prefixed) identifiers from edtlib here (and |
| # also note that edtlib is not meant to expose the dtlib API directly). |
| # Instead, think of what API you need, and add it as a public documented API in |
| # edtlib. This will keep this script simple. |
| |
| import argparse |
| import os |
| import pathlib |
| import sys |
| |
| import edtlib |
| |
| |
| def main(): |
| global conf_file |
| global header_file |
| |
| args = parse_args() |
| |
| try: |
| edt = edtlib.EDT(args.dts, args.bindings_dirs) |
| except edtlib.EDTError as e: |
| sys.exit("devicetree error: " + str(e)) |
| |
| conf_file = open(args.conf_out, "w", encoding="utf-8") |
| header_file = open(args.header_out, "w", encoding="utf-8") |
| |
| out_comment("""\ |
| Generated by gen_defines.py |
| |
| DTS input file: |
| {} |
| |
| Directories with bindings: |
| {}""".format( |
| args.dts, ", ".join(map(relativize, args.bindings_dirs)) |
| ), blank_before=False) |
| |
| out_comment("Nodes in dependency order (ordinal : path):") |
| for scc in edt.scc_order(): |
| if len(scc) > 1: |
| err("Cycle in devicetree involving: {}".format( |
| ", ".join([n.path for n in scc]))) |
| node = scc[0] |
| out_comment("{} : {}".format(node.dep_ordinal, node.path), |
| blank_before = False) |
| |
| active_compats = set() |
| |
| for node in edt.nodes: |
| if node.enabled and node.matching_compat: |
| # Skip 'fixed-partitions' devices since they are handled by |
| # write_flash() and would generate extra spurious #defines |
| if node.matching_compat == "fixed-partitions": |
| continue |
| |
| requires_text = "" |
| if node.depends_on: |
| requires_text = "Requires:\n" |
| for depends in node.depends_on: |
| requires_text += " {} {}\n".format(depends.dep_ordinal, depends.path) |
| requires_text += "\n" |
| |
| supports_text = "" |
| if node.required_by: |
| supports_text = "Supports:\n" |
| for required in node.required_by: |
| supports_text += " {} {}\n".format(required.dep_ordinal, required.path) |
| supports_text += "\n" |
| |
| out_comment("""\ |
| Devicetree node: |
| {} |
| |
| Binding (compatible = {}): |
| {} |
| |
| Dependency Ordinal: {} |
| |
| {}{}Description: |
| {}""".format( |
| node.path, node.matching_compat, relativize(node.binding_path), |
| node.dep_ordinal, requires_text, supports_text, |
| # Indent description by two spaces |
| "\n".join(" " + line for line in node.description.splitlines()) |
| )) |
| |
| write_regs(node) |
| write_irqs(node) |
| write_props(node) |
| write_clocks(node) |
| write_spi_dev(node) |
| write_bus(node) |
| write_existence_flags(node) |
| |
| active_compats.update(node.compats) |
| |
| out_comment("Active compatibles (mentioned in DTS + binding found)") |
| for compat in sorted(active_compats): |
| #define DT_COMPAT_<COMPAT> 1 |
| out("COMPAT_{}".format(str2ident(compat)), 1) |
| |
| # These are derived from /chosen |
| write_addr_size(edt, "zephyr,sram", "SRAM") |
| write_addr_size(edt, "zephyr,ccm", "CCM") |
| write_addr_size(edt, "zephyr,dtcm", "DTCM") |
| write_addr_size(edt, "zephyr,ipc_shm", "IPC_SHM") |
| |
| write_flash(edt.chosen_node("zephyr,flash")) |
| write_code_partition(edt.chosen_node("zephyr,code-partition")) |
| |
| flash_index = 0 |
| for node in edt.nodes: |
| if node.name.startswith("partition@"): |
| write_flash_partition(node, flash_index) |
| flash_index += 1 |
| |
| out_comment("Number of flash partitions") |
| if flash_index != 0: |
| out("FLASH_AREA_NUM", flash_index) |
| |
| print("Devicetree configuration written to " + args.conf_out) |
| |
| |
| def parse_args(): |
| # Returns parsed command-line arguments |
| |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--dts", required=True, help="DTS file") |
| parser.add_argument("--bindings-dirs", nargs='+', required=True, |
| help="directory with bindings in YAML format, " |
| "we allow multiple") |
| parser.add_argument("--header-out", required=True, |
| help="path to write header to") |
| parser.add_argument("--conf-out", required=True, |
| help="path to write configuration file to") |
| |
| return parser.parse_args() |
| |
| |
| def relativize(path): |
| # If 'path' is within $ZEPHYR_BASE, returns it relative to $ZEPHYR_BASE, |
| # with a "$ZEPHYR_BASE/..." hint at the start of the string. Otherwise, |
| # returns 'path' unchanged. |
| |
| zbase = os.getenv("ZEPHYR_BASE") |
| if zbase is None: |
| return path |
| |
| try: |
| return str("$ZEPHYR_BASE" / pathlib.Path(path).relative_to(zbase)) |
| except ValueError: |
| # Not within ZEPHYR_BASE |
| return path |
| |
| |
| def write_regs(node): |
| # Writes address/size output for the registers in the node's 'reg' property |
| |
| def reg_addr_name_alias(reg): |
| return str2ident(reg.name) + "_BASE_ADDRESS" if reg.name else None |
| |
| def reg_size_name_alias(reg): |
| return str2ident(reg.name) + "_SIZE" if reg.name else None |
| |
| for reg in node.regs: |
| out_dev(node, reg_addr_ident(reg), hex(reg.addr), |
| name_alias=reg_addr_name_alias(reg)) |
| |
| if reg.size: |
| out_dev(node, reg_size_ident(reg), reg.size, |
| name_alias=reg_size_name_alias(reg)) |
| |
| |
| def write_props(node): |
| # Writes any properties defined in the "properties" section of the binding |
| # for the node |
| |
| for prop in node.props.values(): |
| # Skip #size-cell and other property starting with #. Also skip mapping |
| # properties like 'gpio-map'. |
| if prop.name[0] == "#" or prop.name.endswith("-map"): |
| continue |
| |
| # See write_clocks() |
| if prop.name == "clocks": |
| continue |
| |
| # edtlib provides these as well (Property.val becomes an edtlib.Node |
| # and a list of edtlib.Nodes, respectively). Nothing is generated for |
| # them currently though. |
| if prop.type in {"phandle", "phandles"}: |
| continue |
| |
| # Skip properties that we handle elsewhere |
| if prop.name in { |
| "reg", "compatible", "status", "interrupts", |
| "interrupt-controller", "gpio-controller" |
| }: |
| continue |
| |
| if prop.description is not None: |
| out_comment(prop.description, blank_before=False) |
| |
| ident = str2ident(prop.name) |
| |
| if prop.type == "boolean": |
| out_dev(node, ident, 1 if prop.val else 0) |
| elif prop.type == "string": |
| out_dev_s(node, ident, prop.val) |
| elif prop.type == "int": |
| out_dev(node, ident, prop.val) |
| elif prop.type == "array": |
| for i, val in enumerate(prop.val): |
| out_dev(node, "{}_{}".format(ident, i), val) |
| out_dev(node, ident, |
| "{" + ", ".join(map(str, prop.val)) + "}") |
| elif prop.type == "string-array": |
| for i, val in enumerate(prop.val): |
| out_dev_s(node, "{}_{}".format(ident, i), val) |
| elif prop.type == "uint8-array": |
| out_dev(node, ident, |
| "{ " + ", ".join("0x{:02x}".format(b) for b in prop.val) + " }") |
| elif prop.type == "phandle-array": |
| write_phandle_val_list(prop) |
| |
| # Generate DT_..._ENUM if there's an 'enum:' key in the binding |
| if prop.enum_index is not None: |
| out_dev(node, ident + "_ENUM", prop.enum_index) |
| |
| |
| def write_bus(node): |
| # Generate bus-related #defines |
| |
| if not node.bus: |
| return |
| |
| if node.parent.label is None: |
| err("missing 'label' property on {!r}".format(node.parent)) |
| |
| # #define DT_<DEV-IDENT>_BUS_NAME <BUS-LABEL> |
| out_dev_s(node, "BUS_NAME", str2ident(node.parent.label)) |
| |
| for compat in node.compats: |
| # #define DT_<COMPAT>_BUS_<BUS-TYPE> 1 |
| out("{}_BUS_{}".format(str2ident(compat), str2ident(node.bus)), 1) |
| |
| |
| def write_existence_flags(node): |
| # Generate #defines of the form |
| # |
| # #define DT_INST_<INSTANCE>_<COMPAT> 1 |
| # |
| # These are flags for which devices exist. |
| |
| for compat in node.compats: |
| out("INST_{}_{}".format(node.instance_no[compat], |
| str2ident(compat)), 1) |
| |
| |
| def reg_addr_ident(reg): |
| # Returns the identifier (e.g., macro name) to be used for the address of |
| # 'reg' in the output |
| |
| node = reg.node |
| |
| # NOTE: to maintain compat wit the old script we special case if there's |
| # only a single register (we drop the '_0'). |
| if len(node.regs) > 1: |
| return "BASE_ADDRESS_{}".format(node.regs.index(reg)) |
| else: |
| return "BASE_ADDRESS" |
| |
| |
| def reg_size_ident(reg): |
| # Returns the identifier (e.g., macro name) to be used for the size of |
| # 'reg' in the output |
| |
| node = reg.node |
| |
| # NOTE: to maintain compat wit the old script we special case if there's |
| # only a single register (we drop the '_0'). |
| if len(node.regs) > 1: |
| return "SIZE_{}".format(node.regs.index(reg)) |
| else: |
| return "SIZE" |
| |
| |
| def dev_ident(node): |
| # Returns an identifier for the device given by 'node'. Used when building |
| # e.g. macro names. |
| |
| # TODO: Handle PWM on STM |
| # TODO: Better document the rules of how we generate things |
| |
| ident = "" |
| |
| if node.bus: |
| ident += "{}_{:X}_".format( |
| str2ident(node.parent.matching_compat), node.parent.unit_addr) |
| |
| ident += "{}_".format(str2ident(node.matching_compat)) |
| |
| if node.unit_addr is not None: |
| ident += "{:X}".format(node.unit_addr) |
| elif node.parent.unit_addr is not None: |
| ident += "{:X}_{}".format(node.parent.unit_addr, str2ident(node.name)) |
| else: |
| # This is a bit of a hack |
| ident += "{}".format(str2ident(node.name)) |
| |
| return ident |
| |
| |
| def dev_aliases(node): |
| # Returns a list of aliases for the device given by 'node', used e.g. when |
| # building macro names |
| |
| return dev_path_aliases(node) + dev_instance_aliases(node) |
| |
| |
| def dev_path_aliases(node): |
| # Returns a list of aliases for the device given by 'node', based on the |
| # aliases registered for it, in the /aliases node. Used when building e.g. |
| # macro names. |
| |
| if node.matching_compat is None: |
| return [] |
| |
| compat_s = str2ident(node.matching_compat) |
| |
| aliases = [] |
| for alias in node.aliases: |
| aliases.append("ALIAS_{}".format(str2ident(alias))) |
| # TODO: See if we can remove or deprecate this form |
| aliases.append("{}_{}".format(compat_s, str2ident(alias))) |
| |
| return aliases |
| |
| |
| def dev_instance_aliases(node): |
| # Returns a list of aliases for the device given by 'node', based on the |
| # instance number of the device (based on how many instances of that |
| # particular device there are). |
| # |
| # This is a list since a device can have multiple 'compatible' strings, |
| # each with their own instance number. |
| |
| return ["INST_{}_{}".format(node.instance_no[compat], str2ident(compat)) |
| for compat in node.compats] |
| |
| |
| def write_addr_size(edt, prop_name, prefix): |
| # Writes <prefix>_BASE_ADDRESS and <prefix>_SIZE for the node pointed at by |
| # the /chosen property named 'prop_name', if it exists |
| |
| node = edt.chosen_node(prop_name) |
| if not node: |
| return |
| |
| if not node.regs: |
| err("missing 'reg' property in node pointed at by /chosen/{} ({!r})" |
| .format(prop_name, node)) |
| |
| out_comment("/chosen/{} ({})".format(prop_name, node.path)) |
| out("{}_BASE_ADDRESS".format(prefix), hex(node.regs[0].addr)) |
| out("{}_SIZE".format(prefix), node.regs[0].size//1024) |
| |
| |
| def write_flash(flash_node): |
| # Writes output for the node pointed at by the zephyr,flash property in |
| # /chosen |
| |
| out_comment("/chosen/zephyr,flash ({})" |
| .format(flash_node.path if flash_node else "missing")) |
| |
| if not flash_node: |
| # No flash node. Write dummy values. |
| out("FLASH_BASE_ADDRESS", 0) |
| out("FLASH_SIZE", 0) |
| return |
| |
| if len(flash_node.regs) != 1: |
| err("expected zephyr,flash to have a single register, has {}" |
| .format(len(flash_node.regs))) |
| |
| if flash_node.bus == "spi" and len(flash_node.parent.regs) == 2: |
| reg = flash_node.parent.regs[1] # QSPI flash |
| else: |
| reg = flash_node.regs[0] |
| |
| out("FLASH_BASE_ADDRESS", hex(reg.addr)) |
| if reg.size: |
| out("FLASH_SIZE", reg.size//1024) |
| |
| if "erase-block-size" in flash_node.props: |
| out("FLASH_ERASE_BLOCK_SIZE", flash_node.props["erase-block-size"].val) |
| |
| if "write-block-size" in flash_node.props: |
| out("FLASH_WRITE_BLOCK_SIZE", flash_node.props["write-block-size"].val) |
| |
| |
| def write_code_partition(code_node): |
| # Writes output for the node pointed at by the zephyr,code-partition |
| # property in /chosen |
| |
| out_comment("/chosen/zephyr,code-partition ({})" |
| .format(code_node.path if code_node else "missing")) |
| |
| if not code_node: |
| # No code partition. Write dummy values. |
| out("CODE_PARTITION_OFFSET", 0) |
| out("CODE_PARTITION_SIZE", 0) |
| return |
| |
| if not code_node.regs: |
| err("missing 'regs' property on {!r}".format(code_node)) |
| |
| out("CODE_PARTITION_OFFSET", code_node.regs[0].addr) |
| out("CODE_PARTITION_SIZE", code_node.regs[0].size) |
| |
| |
| def write_flash_partition(partition_node, index): |
| out_comment("Flash partition at " + partition_node.path) |
| |
| if partition_node.label is None: |
| err("missing 'label' property on {!r}".format(partition_node)) |
| |
| # Generate label-based identifiers |
| write_flash_partition_prefix( |
| "FLASH_AREA_" + str2ident(partition_node.label), partition_node, index) |
| |
| # Generate index-based identifiers |
| write_flash_partition_prefix( |
| "FLASH_AREA_{}".format(index), partition_node, index) |
| |
| |
| def write_flash_partition_prefix(prefix, partition_node, index): |
| # write_flash_partition() helper. Generates identifiers starting with |
| # 'prefix'. |
| |
| out("{}_ID".format(prefix), index) |
| |
| out("{}_READ_ONLY".format(prefix), 1 if partition_node.read_only else 0) |
| |
| for i, reg in enumerate(partition_node.regs): |
| # Also add aliases that point to the first sector (TODO: get rid of the |
| # aliases?) |
| out("{}_OFFSET_{}".format(prefix, i), reg.addr, |
| aliases=["{}_OFFSET".format(prefix)] if i == 0 else []) |
| out("{}_SIZE_{}".format(prefix, i), reg.size, |
| aliases=["{}_SIZE".format(prefix)] if i == 0 else []) |
| |
| controller = partition_node.flash_controller |
| if controller.label is not None: |
| out_s("{}_DEV".format(prefix), controller.label) |
| |
| |
| def write_irqs(node): |
| # Writes IRQ num and data for the interrupts in the node's 'interrupt' |
| # property |
| |
| def irq_name_alias(irq, cell_name): |
| if not irq.name: |
| return None |
| |
| alias = "IRQ_{}".format(str2ident(irq.name)) |
| if cell_name != "irq": |
| alias += "_" + str2ident(cell_name) |
| return alias |
| |
| def encode_zephyr_multi_level_irq(irq, irq_num): |
| # See doc/reference/kernel/other/interrupts.rst for details |
| # on how this encoding works |
| |
| irq_ctrl = irq.controller |
| # Look for interrupt controller parent until we have none |
| while irq_ctrl.interrupts: |
| irq_num = (irq_num + 1) << 8 |
| if "irq" not in irq_ctrl.interrupts[0].data: |
| err("Expected binding for {!r} to have 'irq' in *-cells" |
| .format(irq_ctrl)) |
| irq_num |= irq_ctrl.interrupts[0].data["irq"] |
| irq_ctrl = irq_ctrl.interrupts[0].controller |
| return irq_num |
| |
| for irq_i, irq in enumerate(node.interrupts): |
| for cell_name, cell_value in irq.data.items(): |
| ident = "IRQ_{}".format(irq_i) |
| if cell_name == "irq": |
| cell_value = encode_zephyr_multi_level_irq(irq, cell_value) |
| else: |
| ident += "_" + str2ident(cell_name) |
| |
| out_dev(node, ident, cell_value, |
| name_alias=irq_name_alias(irq, cell_name)) |
| |
| |
| def write_spi_dev(node): |
| # Writes SPI device GPIO chip select data if there is any |
| |
| cs_gpio = edtlib.spi_dev_cs_gpio(node) |
| if cs_gpio is not None: |
| write_phandle_val_list_entry(node, cs_gpio, None, "CS_GPIOS") |
| |
| |
| def write_phandle_val_list(prop): |
| # Writes output for a phandle/value list, e.g. |
| # |
| # pwms = <&pwm-ctrl-1 10 20 |
| # &pwm-ctrl-2 30 40>; |
| # |
| # prop: |
| # phandle/value Property instance. |
| # |
| # If only one entry appears in 'prop' (the example above has two), the |
| # generated identifier won't get a '_0' suffix, and the '_COUNT' and |
| # group initializer are skipped too. |
| # |
| # The base identifier is derived from the property name. For example, 'pwms = ...' |
| # generates output like this: |
| # |
| # #define <device prefix>_PWMS_CONTROLLER_0 "PWM_0" (name taken from 'label = ...') |
| # #define <device prefix>_PWMS_CHANNEL_0 123 (name taken from *-cells in binding) |
| # #define <device prefix>_PWMS_0 {"PWM_0", 123} |
| # #define <device prefix>_PWMS_CONTROLLER_1 "PWM_1" |
| # #define <device prefix>_PWMS_CHANNEL_1 456 |
| # #define <device prefix>_PWMS_1 {"PWM_1", 456} |
| # #define <device prefix>_PWMS_COUNT 2 |
| # #define <device prefix>_PWMS {<device prefix>_PWMS_0, <device prefix>_PWMS_1} |
| # ... |
| |
| # pwms -> PWMS |
| # foo-gpios -> FOO_GPIOS |
| ident = str2ident(prop.name) |
| |
| initializer_vals = [] |
| for i, entry in enumerate(prop.val): |
| initializer_vals.append(write_phandle_val_list_entry( |
| prop.node, entry, i if len(prop.val) > 1 else None, ident)) |
| |
| if len(prop.val) > 1: |
| out_dev(prop.node, ident + "_COUNT", len(initializer_vals)) |
| out_dev(prop.node, ident, "{" + ", ".join(initializer_vals) + "}") |
| |
| |
| def write_phandle_val_list_entry(node, entry, i, ident): |
| # write_phandle_val_list() helper. We could get rid of it if it wasn't for |
| # write_spi_dev(). Adds 'i' as an index to identifiers unless it's None. |
| # |
| # 'entry' is an edtlib.ControllerAndData instance. |
| # |
| # Returns the identifier for the macro that provides the |
| # initializer for the entire entry. |
| |
| initializer_vals = [] |
| if entry.controller.label is not None: |
| ctrl_ident = ident + "_CONTROLLER" # e.g. PWMS_CONTROLLER |
| if entry.name: |
| name_alias = str2ident(entry.name) + "_" + ctrl_ident |
| else: |
| name_alias = None |
| # Ugly backwards compatibility hack. Only add the index if there's |
| # more than one entry. |
| if i is not None: |
| ctrl_ident += "_{}".format(i) |
| initializer_vals.append(quote_str(entry.controller.label)) |
| out_dev_s(node, ctrl_ident, entry.controller.label, name_alias) |
| |
| for cell, val in entry.data.items(): |
| cell_ident = ident + "_" + str2ident(cell) # e.g. PWMS_CHANNEL |
| if entry.name: |
| # From e.g. 'pwm-names = ...' |
| name_alias = str2ident(entry.name) + "_" + cell_ident |
| else: |
| name_alias = None |
| # Backwards compatibility (see above) |
| if i is not None: |
| cell_ident += "_{}".format(i) |
| out_dev(node, cell_ident, val, name_alias) |
| |
| initializer_vals += entry.data.values() |
| |
| initializer_ident = ident |
| if entry.name: |
| name_alias = initializer_ident + "_" + str2ident(entry.name) |
| else: |
| name_alias = None |
| if i is not None: |
| initializer_ident += "_{}".format(i) |
| return out_dev(node, initializer_ident, |
| "{" + ", ".join(map(str, initializer_vals)) + "}", |
| name_alias) |
| |
| |
| def write_clocks(node): |
| # Writes clock information. |
| # |
| # Most of this ought to be handled in write_props(), but the identifiers |
| # that get generated for 'clocks' are inconsistent with the with other |
| # 'phandle-array' properties. |
| # |
| # See https://github.com/zephyrproject-rtos/zephyr/pull/19327#issuecomment-534081845. |
| |
| if "clocks" not in node.props: |
| return |
| |
| for clock_i, clock in enumerate(node.props["clocks"].val): |
| controller = clock.controller |
| |
| if controller.label is not None: |
| out_dev_s(node, "CLOCK_CONTROLLER", controller.label) |
| |
| for name, val in clock.data.items(): |
| if clock_i == 0: |
| clk_name_alias = "CLOCK_" + str2ident(name) |
| else: |
| clk_name_alias = None |
| |
| out_dev(node, "CLOCK_{}_{}".format(str2ident(name), clock_i), val, |
| name_alias=clk_name_alias) |
| |
| if "fixed-clock" not in controller.compats: |
| continue |
| |
| if "clock-frequency" not in controller.props: |
| err("{!r} is a 'fixed-clock' but lacks a 'clock-frequency' " |
| "property".format(controller)) |
| |
| out_dev(node, "CLOCKS_CLOCK_FREQUENCY", |
| controller.props["clock-frequency"].val) |
| |
| |
| def str2ident(s): |
| # Converts 's' to a form suitable for (part of) an identifier |
| |
| return s.replace("-", "_") \ |
| .replace(",", "_") \ |
| .replace("@", "_") \ |
| .replace("/", "_") \ |
| .replace(".", "_") \ |
| .replace("+", "PLUS") \ |
| .upper() |
| |
| |
| def out_dev(node, ident, val, name_alias=None): |
| # Writes an |
| # |
| # <device prefix>_<ident> = <val> |
| # |
| # assignment, along with a set of |
| # |
| # <device alias>_<ident> |
| # |
| # aliases, for each device alias. If 'name_alias' (a string) is passed, |
| # then these additional aliases are generated: |
| # |
| # <device prefix>_<name alias> |
| # <device alias>_<name alias> (for each device alias) |
| # |
| # 'name_alias' is used for reg-names and the like. |
| # |
| # Returns the identifier used for the macro that provides the value |
| # for 'ident' within 'node', e.g. DT_MFG_MODEL_CTL_GPIOS_PIN. |
| |
| dev_prefix = dev_ident(node) |
| |
| aliases = [alias + "_" + ident for alias in dev_aliases(node)] |
| if name_alias is not None: |
| aliases.append(dev_prefix + "_" + name_alias) |
| aliases += [alias + "_" + name_alias for alias in dev_aliases(node)] |
| |
| return out(dev_prefix + "_" + ident, val, aliases) |
| |
| |
| def out_dev_s(node, ident, s, name_alias=None): |
| # Like out_dev(), but emits 's' as a string literal |
| # |
| # Returns the generated macro name for 'ident'. |
| |
| return out_dev(node, ident, quote_str(s), name_alias) |
| |
| |
| def out_s(ident, val): |
| # Like out(), but puts quotes around 'val' and escapes any double |
| # quotes and backslashes within it |
| # |
| # Returns the generated macro name for 'ident'. |
| |
| return out(ident, quote_str(val)) |
| |
| |
| def out(ident, val, aliases=()): |
| # Writes '#define <ident> <val>' to the header and '<ident>=<val>' to the |
| # the configuration file. |
| # |
| # Also writes any aliases listed in 'aliases' (an iterable). For the |
| # header, these look like '#define <alias> <ident>'. For the configuration |
| # file, the value is just repeated as '<alias>=<val>' for each alias. |
| # |
| # Returns the generated macro name for 'ident'. |
| |
| print("#define DT_{:40} {}".format(ident, val), file=header_file) |
| primary_ident = "DT_{}".format(ident) |
| |
| # Exclude things that aren't single token values from .conf. At |
| # the moment the only such items are unquoted string |
| # representations of initializer lists, which begin with a curly |
| # brace. |
| output_to_conf = not (isinstance(val, str) and val.startswith("{")) |
| if output_to_conf: |
| print("{}={}".format(primary_ident, val), file=conf_file) |
| |
| for alias in aliases: |
| if alias != ident: |
| print("#define DT_{:40} DT_{}".format(alias, ident), |
| file=header_file) |
| if output_to_conf: |
| # For the configuration file, the value is just repeated for all |
| # the aliases |
| print("DT_{}={}".format(alias, val), file=conf_file) |
| |
| return primary_ident |
| |
| |
| def out_comment(s, blank_before=True): |
| # Writes 's' as a comment to the header and configuration file. 's' is |
| # allowed to have multiple lines. blank_before=True adds a blank line |
| # before the comment. |
| |
| if blank_before: |
| print(file=header_file) |
| print(file=conf_file) |
| |
| if "\n" in s: |
| # Format multi-line comments like |
| # |
| # /* |
| # * first line |
| # * second line |
| # * |
| # * empty line before this line |
| # */ |
| res = ["/*"] |
| for line in s.splitlines(): |
| # Avoid an extra space after '*' for empty lines. They turn red in |
| # Vim if space error checking is on, which is annoying. |
| res.append(" *" if not line or line.isspace() else " * " + line) |
| res.append(" */") |
| print("\n".join(res), file=header_file) |
| else: |
| # Format single-line comments like |
| # |
| # /* foo bar */ |
| print("/* " + s + " */", file=header_file) |
| |
| print("\n".join("# " + line for line in s.splitlines()), file=conf_file) |
| |
| |
| def escape(s): |
| # Backslash-escapes any double quotes and backslashes in 's' |
| |
| # \ must be escaped before " to avoid double escaping |
| return s.replace("\\", "\\\\").replace('"', '\\"') |
| |
| |
| def quote_str(s): |
| # Puts quotes around 's' and escapes any double quotes and |
| # backslashes within it |
| |
| return '"{}"'.format(escape(s)) |
| |
| |
| def err(s): |
| raise Exception(s) |
| |
| |
| if __name__ == "__main__": |
| main() |