zephyr/scripts/dts/gen_legacy_defines.py
Kumar Gala ce4e3b5c7c scripts: dts: gen_legacy_defines: mark DT_*_BUS_<BUS> macros deprecated
Add a __WARN("Macro is deprecated") to all DT_<COMPAT>_BUS_<BUS> macros
now that all in tree users should have been converted to the new macros.

This is intended to make sure any PRs don't introduce new usages of
these macros.

Signed-off-by: Kumar Gala <kumar.gala@linaro.org>
2020-04-20 17:48:39 +02:00

823 lines
26 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2019 Nordic Semiconductor ASA
# Copyright (c) 2019 Linaro Limited
# SPDX-License-Identifier: BSD-3-Clause
# This script is similar to gen_defines.py, but is for the legacy
# macro syntax used in e.g. Zephyr 2.2.
#
# It should be considered frozen code. New macro-related functionality
# should be done by modifying the macro namespaces managed by
# gen_defines.py.
import argparse
import os
import pathlib
import sys
import edtlib
def main():
global header_file
global flash_area_num
args = parse_args()
try:
edt = edtlib.EDT(args.dts, args.bindings_dirs,
# Suppress this warning if it's suppressed in dtc
warn_reg_unit_address_mismatch=
"-Wno-simple_bus_reg" not in args.dtc_flags)
except edtlib.EDTError as e:
sys.exit(f"devicetree error: {e}")
header_file = open(args.header_out, "w", encoding="utf-8")
flash_area_num = 0
write_top_comment(edt)
for node in sorted(edt.nodes, key=lambda node: node.dep_ordinal):
write_node_comment(node)
# Flash partition nodes are handled as a special case. It
# would be nicer if we had bindings that would let us
# avoid that, but this will do for now.
if node.name.startswith("partition@"):
write_flash_partition(node, flash_area_num)
flash_area_num += 1
if node.enabled and node.matching_compat:
write_regs(node)
write_irqs(node)
write_props(node)
write_clocks(node)
write_spi_dev(node)
write_bus(node)
write_existence_flags(node)
out_comment("Compatibles appearing on enabled nodes")
for compat in sorted(edt.compat2enabled):
#define DT_COMPAT_<COMPAT> 1
out(f"COMPAT_{str2ident(compat)}", 1)
# Definitions derived from /chosen nodes
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)
header_file.close()
def parse_args():
# Returns parsed command-line arguments
parser = argparse.ArgumentParser()
parser.add_argument("--dts", required=True, help="DTS file")
parser.add_argument("--dtc-flags",
help="'dtc' devicetree compiler flags, some of which "
"might be respected here")
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")
return parser.parse_args()
def write_top_comment(edt):
# Writes an overview comment with misc. info at the top of the header and
# configuration file
s = f"""\
Generated by gen_legacy_defines.py
DTS input file:
{edt.dts_path}
Directories with bindings:
{", ".join(map(relativize, edt.bindings_dirs))}
Nodes in dependency order (ordinal and path):
"""
for scc in edt.scc_order():
if len(scc) > 1:
err("cycle in devicetree involving "
+ ", ".join(node.path for node in scc))
s += f" {scc[0].dep_ordinal:<3} {scc[0].path}\n"
s += """
Definitions derived from these nodes in dependency order are next,
followed by tree-wide information (active compatibles, chosen nodes,
etc.).
"""
out_comment(s, blank_before=False)
def write_node_comment(node):
# Writes a comment describing 'node' to the header and configuration file
s = f"""\
Devicetree node:
{node.path}
"""
if node.matching_compat:
s += f"""
Binding (compatible = {node.matching_compat}):
{relativize(node.binding_path)}
"""
else:
s += "\nNo matching binding.\n"
s += f"\nDependency Ordinal: {node.dep_ordinal}\n"
if node.depends_on:
s += "\nRequires:\n"
for dep in node.depends_on:
s += f" {dep.dep_ordinal:<3} {dep.path}\n"
if node.required_by:
s += "\nSupports:\n"
for req in node.required_by:
s += f" {req.dep_ordinal:<3} {req.path}\n"
if node.description:
# Indent description by two spaces
s += "\nDescription:\n" + \
"\n".join(" " + line for line in
node.description.splitlines()) + \
"\n"
if not node.enabled:
s += "\nNode is disabled.\n"
out_comment(s)
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 write_reg(reg, base_ident, val):
# Drop '_0' from the identifier if there's a single register, for
# backwards compatibility
if len(reg.node.regs) > 1:
ident = f"{base_ident}_{reg.node.regs.index(reg)}"
else:
ident = base_ident
out_node(node, ident, val,
# Name alias from 'reg-names = ...'
f"{str2ident(reg.name)}_{base_ident}" if reg.name else None)
for reg in node.regs:
write_reg(reg, "BASE_ADDRESS", hex(reg.addr))
if reg.size:
write_reg(reg, "SIZE", reg.size)
def write_props(node):
# Writes any properties defined in the "properties" section of the binding
# for the node
for prop in node.props.values():
if not should_write(prop):
continue
if prop.description is not None:
out_comment(prop.description, blank_before=False)
ident = str2ident(prop.name)
if prop.type == "boolean":
out_node(node, ident, 1 if prop.val else 0)
elif prop.type == "string":
out_node_s(node, ident, prop.val)
elif prop.type == "int":
out_node(node, ident, prop.val)
elif prop.type == "array":
for i, val in enumerate(prop.val):
out_node(node, f"{ident}_{i}", val)
out_node_init(node, ident, prop.val)
elif prop.type == "string-array":
for i, val in enumerate(prop.val):
out_node_s(node, f"{ident}_{i}", val)
elif prop.type == "uint8-array":
out_node_init(node, ident,
[f"0x{b:02x}" for b in prop.val])
else: # 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_node(node, ident + "_ENUM", prop.enum_index)
def should_write(prop):
# write_props() helper. Returns True if output should be generated for
# 'prop'.
# Skip #size-cell and other property starting with #. Also skip mapping
# properties like 'gpio-map'.
if prop.name[0] == "#" or prop.name.endswith("-map"):
return False
# See write_clocks()
if prop.name == "clocks":
return False
# For these, Property.val becomes an edtlib.Node, a list of edtlib.Nodes,
# or None. Nothing is generated for them at the moment.
if prop.type in {"phandle", "phandles", "path", "compound"}:
return False
# Skip properties that we handle elsewhere
if prop.name in {
"reg", "compatible", "status", "interrupts",
"interrupt-controller", "gpio-controller"
}:
return False
return True
def write_bus(node):
# Generate bus-related #defines
if not node.bus_node:
return
if node.bus_node.label is None:
err(f"missing 'label' property on bus node {node.bus_node!r}")
# #define DT_<DEV-IDENT>_BUS_NAME <BUS-LABEL>
out_node_s(node, "BUS_NAME", str2ident(node.bus_node.label), "Macro is deprecated")
for compat in node.compats:
# #define DT_<COMPAT>_BUS_<BUS-TYPE> 1
out(f"{str2ident(compat)}_BUS_{str2ident(node.on_bus)}", 1)
def write_existence_flags(node):
# Generate #defines of the form
#
# #define DT_INST_<instance no.>_<compatible string> 1
#
# for enabled nodes. These are flags for which devices exist.
for compat in node.compats:
instance_no = node.edt.compat2enabled[compat].index(node)
out(f"INST_{instance_no}_{str2ident(compat)}", 1)
def node_ident(node):
# Returns an identifier for 'node'. Used e.g. when building macro names.
# TODO: Handle PWM on STM
# TODO: Better document the rules of how we generate things
ident = ""
if node.bus_node:
ident += "{}_{:X}_".format(
str2ident(node.bus_node.matching_compat), node.bus_node.unit_addr)
ident += f"{str2ident(node.matching_compat)}_"
if node.unit_addr is not None:
ident += f"{node.unit_addr:X}"
elif node.parent.unit_addr is not None:
ident += f"{node.parent.unit_addr:X}_{str2ident(node.name)}"
else:
# This is a bit of a hack
ident += str2ident(node.name)
return ident
def node_aliases(node):
# Returns a list of aliases for 'node', used e.g. when building macro names
return node_path_aliases(node) + node_instance_aliases(node)
def node_path_aliases(node):
# Returns a list of aliases for 'node', based on the aliases registered for
# it in the /aliases node. Used e.g. when building macro names.
if node.matching_compat is None:
return []
compat_s = str2ident(node.matching_compat)
aliases = []
for alias in node.aliases:
aliases.append(f"ALIAS_{str2ident(alias)}")
# TODO: See if we can remove or deprecate this form
aliases.append(f"{compat_s}_{str2ident(alias)}")
return aliases
def node_instance_aliases(node):
# Returns a list of aliases for 'node', based on the compatible string and
# the instance number (each node with a particular compatible gets its own
# instance number, starting from zero).
#
# This is a list since a node can have multiple 'compatible' strings, each
# with their own instance number.
res = []
for compat in node.compats:
instance_no = node.edt.compat2enabled[compat].index(node)
res.append(f"INST_{instance_no}_{str2ident(compat)}")
return res
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 "
f"/chosen/{prop_name} ({node!r})")
out_comment(f"/chosen/{prop_name} ({node.path})")
out(f"{prefix}_BASE_ADDRESS", hex(node.regs[0].addr))
out(f"{prefix}_SIZE", node.regs[0].size//1024)
def write_flash(edt):
# Writes chosen and tree-wide flash-related output
write_flash_node(edt)
write_code_partition(edt)
if flash_area_num != 0:
out_comment("Number of flash partitions")
out("FLASH_AREA_NUM", flash_area_num)
def write_flash_node(edt):
# Writes output for the top-level flash node pointed at by
# zephyr,flash in /chosen
node = edt.chosen_node("zephyr,flash")
out_comment(f"/chosen/zephyr,flash ({node.path if node else 'missing'})")
if not node:
# No flash node. Write dummy values.
out("FLASH_BASE_ADDRESS", 0)
out("FLASH_SIZE", 0)
return
if len(node.regs) != 1:
err("expected zephyr,flash to have a single register, has "
f"{len(node.regs)}")
if node.on_bus == "spi" and len(node.bus_node.regs) == 2:
reg = node.bus_node.regs[1] # QSPI flash
else:
reg = node.regs[0]
out("FLASH_BASE_ADDRESS", hex(reg.addr))
if reg.size:
out("FLASH_SIZE", reg.size//1024)
if "erase-block-size" in node.props:
out("FLASH_ERASE_BLOCK_SIZE", node.props["erase-block-size"].val)
if "write-block-size" in node.props:
out("FLASH_WRITE_BLOCK_SIZE", node.props["write-block-size"].val)
def write_code_partition(edt):
# Writes output for the node pointed at by zephyr,code-partition in /chosen
node = edt.chosen_node("zephyr,code-partition")
out_comment("/chosen/zephyr,code-partition "
f"({node.path if node else 'missing'})")
if not node:
# No code partition. Write dummy values.
out("CODE_PARTITION_OFFSET", 0)
out("CODE_PARTITION_SIZE", 0)
return
if not node.regs:
err(f"missing 'regs' property on {node!r}")
out("CODE_PARTITION_OFFSET", node.regs[0].addr)
out("CODE_PARTITION_SIZE", node.regs[0].size)
def write_flash_partition(partition_node, index):
if partition_node.label is None:
err(f"missing 'label' property on {partition_node!r}")
# 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(f"FLASH_AREA_{index}", partition_node, index)
def write_flash_partition_prefix(prefix, partition_node, index):
# write_flash_partition() helper. Generates identifiers starting with
# 'prefix'.
out(f"{prefix}_ID", index)
out(f"{prefix}_READ_ONLY", 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(f"{prefix}_OFFSET_{i}", reg.addr,
aliases=[f"{prefix}_OFFSET"] if i == 0 else [])
out(f"{prefix}_SIZE_{i}", reg.size,
aliases=[f"{prefix}_SIZE"] if i == 0 else [])
controller = partition_node.flash_controller
if controller.label is not None:
out_s(f"{prefix}_DEV", 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 = f"IRQ_{str2ident(irq.name)}"
if cell_name != "irq":
alias += f"_{str2ident(cell_name)}"
return alias
def map_arm_gic_irq_type(irq, irq_num):
# Maps ARM GIC IRQ (type)+(index) combo to linear IRQ number
if "type" not in irq.data:
err(f"Expected binding for {irq.controller!r} to have 'type' in "
"interrupt-cells")
irq_type = irq.data["type"]
if irq_type == 0: # GIC_SPI
return irq_num + 32
if irq_type == 1: # GIC_PPI
return irq_num + 16
err(f"Invalid interrupt type specified for {irq!r}")
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(f"Expected binding for {irq_ctrl!r} to have 'irq' in "
"interrupt-cells")
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 = f"IRQ_{irq_i}"
if cell_name == "irq":
if "arm,gic" in irq.controller.compats:
cell_value = map_arm_gic_irq_type(irq, cell_value)
cell_value = encode_zephyr_multi_level_irq(irq, cell_value)
else:
ident += f"_{str2ident(cell_name)}"
out_node(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 = node.spi_cs_gpio
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 <node prefix>_PWMS_CONTROLLER_0 "PWM_0" (name taken from 'label = ...')
# #define <node prefix>_PWMS_CHANNEL_0 123 (name taken from *-cells in binding)
# #define <node prefix>_PWMS_0 {"PWM_0", 123}
# #define <node prefix>_PWMS_CONTROLLER_1 "PWM_1"
# #define <node prefix>_PWMS_CHANNEL_1 456
# #define <node prefix>_PWMS_1 {"PWM_1", 456}
# #define <node prefix>_PWMS_COUNT 2
# #define <node prefix>_PWMS {<node prefix>_PWMS_0, <node 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_node(prop.node, ident + "_COUNT", len(initializer_vals))
out_node_init(prop.node, ident, 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 = f"{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 += f"_{i}"
initializer_vals.append(quote_str(entry.controller.label))
out_node_s(node, ctrl_ident, entry.controller.label, name_alias)
for cell, val in entry.data.items():
cell_ident = f"{ident}_{str2ident(cell)}" # e.g. PWMS_CHANNEL
if entry.name:
# From e.g. 'pwm-names = ...'
name_alias = f"{str2ident(entry.name)}_{cell_ident}"
else:
name_alias = None
# Backwards compatibility (see above)
if i is not None:
cell_ident += f"_{i}"
out_node(node, cell_ident, val, name_alias)
initializer_vals += entry.data.values()
initializer_ident = ident
if entry.name:
name_alias = f"{initializer_ident}_{str2ident(entry.name)}"
else:
name_alias = None
if i is not None:
initializer_ident += f"_{i}"
return out_node_init(node, initializer_ident, 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_node_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_node(node, f"CLOCK_{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(f"{controller!r} is a 'fixed-clock' but lacks a "
"'clock-frequency' property")
out_node(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_node(node, ident, val, name_alias=None, deprecation_msg=None):
# Writes a
#
# <node prefix>_<ident> = <val>
#
# assignment, along with a set of
#
# <node alias>_<ident>
#
# aliases, for each path/instance alias for the node. If 'name_alias' (a
# string) is passed, then these additional aliases are generated:
#
# <node prefix>_<name alias>
# <node alias>_<name alias> (for each node alias)
#
# 'name_alias' is used for reg-names and the like.
#
# If a 'deprecation_msg' string is passed, the generated identifiers will
# generate a warning if used, via __WARN(<deprecation_msg>)).
#
# Returns the identifier used for the macro that provides the value
# for 'ident' within 'node', e.g. DT_MFG_MODEL_CTL_GPIOS_PIN.
node_prefix = node_ident(node)
aliases = [f"{alias}_{ident}" for alias in node_aliases(node)]
if name_alias is not None:
aliases.append(f"{node_prefix}_{name_alias}")
aliases += [f"{alias}_{name_alias}" for alias in node_aliases(node)]
return out(f"{node_prefix}_{ident}", val, aliases, deprecation_msg)
def out_node_s(node, ident, s, name_alias=None, deprecation_msg=None):
# Like out_node(), but emits 's' as a string literal
#
# Returns the generated macro name for 'ident'.
return out_node(node, ident, quote_str(s), name_alias, deprecation_msg)
def out_node_init(node, ident, elms, name_alias=None, deprecation_msg=None):
# Like out_node(), but generates an {e1, e2, ...} initializer with the
# elements in the iterable 'elms'.
#
# Returns the generated macro name for 'ident'.
return out_node(node, ident, "{" + ", ".join(map(str, elms)) + "}",
name_alias, deprecation_msg)
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=(), deprecation_msg=None):
# 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.
#
# See out_node() for the meaning of 'deprecation_msg'.
#
# Returns the generated macro name for 'ident'.
out_define(ident, val, deprecation_msg, header_file)
primary_ident = f"DT_{ident}"
d_msg = deprecation_msg
for alias in aliases:
if alias != ident:
if alias.startswith("INST_"):
deprecation_msg = "Macro is deprecated"
out_define(alias, "DT_" + ident, deprecation_msg, header_file)
deprecation_msg = d_msg
return primary_ident
def out_define(ident, val, deprecation_msg, out_file):
# out() helper for writing a #define. See out_node() for the meaning of
# 'deprecation_msg'.
s = f"#define DT_{ident:40}"
if deprecation_msg:
s += fr' __WARN("{deprecation_msg}")'
s += f" {val}"
print(s, file=out_file)
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)
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.strip() else " * " + line)
res.append(" */")
print("\n".join(res), file=header_file)
else:
# Format single-line comments like
#
# /* foo bar */
print("/* " + s + " */", file=header_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 f'"{escape(s)}"'
def err(s):
raise Exception(s)
if __name__ == "__main__":
main()