Add a new DTS/binding parser to scripts/dts/ for generating generated_dts_board.conf and generated_dts_board_unfixed.h. The old code is kept to generate some deprecated defines, using the --deprecated-only flag. It will be removed later. The new parser is implemented in three files in scripts/dts/: dtlib.py: A low-level .dts parsing library. This is similar to devicetree.py in the old code, but is a general robust DTS parser that doesn't rely on preprocessing. edtlib.py (e for extended): A library built on top of dtlib.py that brings together data from DTS files and bindings and creates Device instances with all the data for a device. gen_defines.py: A script that uses edtlib.py to generate generated_dts_board.conf and generated_dts_board_unfixed.h. Corresponds to extract_dts_includes.py and the files in extract/ in the old code. testdtlib.py: Test suite for dtlib.py. Can be run directly as a script. testedtlib.py (uses test.dts and test-bindings/): Test suite for edtlib.py. Can be run directly as a script. The test suites will be run automatically in CI. The new code turns some things that were warnings (or not checked) in the old code into errors, like missing properties that are specified with 'category: required' in the binding for the node. The code includes lots of documentation and tries to give helpful error messages instead of Python errors. Co-authored-by: Kumar Gala <kumar.gala@linaro.org> Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
1640 lines
53 KiB
Python
1640 lines
53 KiB
Python
# Copyright (c) 2019 Nordic Semiconductor ASA
|
|
# Copyright (c) 2019 Linaro Limited
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
# Tip: You can view just the documentation with 'pydoc3 edtlib'
|
|
|
|
"""
|
|
Library for working with .dts files and bindings at a higher level compared to
|
|
dtlib. Deals with things at the level of devices, registers, interrupts,
|
|
compatibles, bindings, etc., as opposed to dtlib, which is just a low-level
|
|
device tree parser.
|
|
|
|
Each device tree node (dtlib.Node) gets a Device instance, which has all the
|
|
information related to the device, derived from both the device tree and from
|
|
the binding for the device.
|
|
|
|
Bindings are files that describe device tree nodes. Device tree nodes are
|
|
usually mapped to bindings via their 'compatible = "..."' property, but binding
|
|
data can also come from a 'sub-node:' key in the binding for the parent device
|
|
tree node.
|
|
|
|
The top-level entry point of the library is the EDT class. EDT.__init__() takes
|
|
a .dts file to parse and the path of a directory containing bindings.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import yaml
|
|
|
|
from dtlib import DT, DTError, to_num, to_nums
|
|
|
|
# NOTE: testedtlib.py is the test suite for this library. It can be run
|
|
# directly.
|
|
|
|
# Implementation notes
|
|
# --------------------
|
|
#
|
|
# A '_' prefix on an identifier in Python is a convention for marking it private.
|
|
# Please do not access private things. Instead, think of what API you need, and
|
|
# add it.
|
|
#
|
|
# This library is layered on top of dtlib, and is not meant to expose it to
|
|
# clients. This keeps the header generation script simple.
|
|
#
|
|
# General biased advice:
|
|
#
|
|
# - Consider using @property for APIs that don't need parameters. It makes
|
|
# functions look like attributes, which is less awkward in clients, and makes
|
|
# it easy to switch back and forth between variables and functions.
|
|
#
|
|
# - Think about the data type of the thing you're exposing. Exposing something
|
|
# as e.g. a list or a dictionary is often nicer and more flexible than adding
|
|
# a function.
|
|
#
|
|
# - Avoid get_*() prefixes on functions. Name them after the thing they return
|
|
# instead. This often makes the code read more naturally in callers.
|
|
#
|
|
# Also, consider using @property instead of get_*().
|
|
#
|
|
# - Don't expose dtlib stuff directly.
|
|
#
|
|
# - Add documentation for any new APIs you add.
|
|
#
|
|
# The convention here is that docstrings (quoted strings) are used for public
|
|
# APIs, and "doc comments" for internal functions.
|
|
#
|
|
# @properties are documented in the class docstring, as if they were
|
|
# variables. See the existing @properties for a template.
|
|
#
|
|
# - Please use ""-quoted strings instead of ''-quoted strings, just to make
|
|
# things consistent (''-quoting is more common otherwise in Python)
|
|
|
|
#
|
|
# Public classes
|
|
#
|
|
|
|
|
|
class EDT:
|
|
"""
|
|
Represents a "high-level" view of a device tree, with a list of devices
|
|
that each have some number of registers, etc.
|
|
|
|
These attributes are available on EDT objects:
|
|
|
|
devices:
|
|
A list of Device objects for the devices
|
|
|
|
dts_path:
|
|
The .dts path passed to __init__()
|
|
|
|
bindings_dir:
|
|
The bindings directory path passed to __init__()
|
|
"""
|
|
def __init__(self, dts, bindings_dir):
|
|
"""
|
|
EDT constructor. This is the top-level entry point to the library.
|
|
|
|
dts:
|
|
Path to device tree .dts file
|
|
|
|
bindings_dir:
|
|
Path to directory containing bindings, in YAML format. This directory
|
|
is recursively searched for .yaml files.
|
|
"""
|
|
self.dts_path = dts
|
|
self.bindings_dir = bindings_dir
|
|
|
|
self._dt = DT(dts)
|
|
|
|
self._init_compat2binding(bindings_dir)
|
|
self._init_devices()
|
|
|
|
def get_dev(self, path):
|
|
"""
|
|
Returns the Device at the DT path or alias 'path'. Raises EDTError if
|
|
the path or alias doesn't exist.
|
|
"""
|
|
try:
|
|
return self._node2dev[self._dt.get_node(path)]
|
|
except DTError as e:
|
|
_err(e)
|
|
|
|
def chosen_dev(self, name):
|
|
"""
|
|
Returns the Device pointed at by the property named 'name' in /chosen,
|
|
or None if the property is missing
|
|
"""
|
|
try:
|
|
chosen = self._dt.get_node("/chosen")
|
|
except DTError:
|
|
# No /chosen node
|
|
return None
|
|
|
|
if name not in chosen.props:
|
|
return None
|
|
|
|
path = chosen.props[name].to_string()
|
|
try:
|
|
return self._node2dev[self._dt.get_node(path)]
|
|
except DTError:
|
|
_err("{} in /chosen points to {}, which does not exist"
|
|
.format(name, path))
|
|
|
|
def _init_compat2binding(self, bindings_dir):
|
|
# Creates self._compat2binding. This is a dictionary that maps
|
|
# (<compatible>, <bus>) tuples (both strings) to (<binding>, <path>)
|
|
# tuples. <binding> is the binding in parsed PyYAML format, and <path>
|
|
# the path to the binding (nice for binding-related error messages).
|
|
#
|
|
# For example, self._compat2binding["company,dev", "can"] contains the
|
|
# binding/path for the 'company,dev' device, when it appears on the CAN
|
|
# bus.
|
|
#
|
|
# For bindings that don't specify a bus, <bus> is None, so that e.g.
|
|
# self._compat2binding["company,notonbus", None] contains the binding.
|
|
#
|
|
# Only bindings for 'compatible' strings that appear in the device tree
|
|
# are loaded.
|
|
|
|
dt_compats = _dt_compats(self._dt)
|
|
self._binding_paths = _binding_paths(bindings_dir)
|
|
|
|
# Add '!include foo.yaml' handling.
|
|
#
|
|
# Do yaml.Loader.add_constructor() instead of yaml.add_constructor() to be
|
|
# compatible with both version 3.13 and version 5.1 of PyYAML.
|
|
#
|
|
# TODO: Is there some 3.13/5.1-compatible way to only do this once, even
|
|
# if multiple EDT objects are created?
|
|
yaml.Loader.add_constructor("!include", self._binding_include)
|
|
|
|
self._compat2binding = {}
|
|
for binding_path in self._binding_paths:
|
|
compat = _binding_compat(binding_path)
|
|
if compat in dt_compats:
|
|
binding = _load_binding(binding_path)
|
|
self._compat2binding[compat, _binding_bus(binding)] = \
|
|
(binding, binding_path)
|
|
|
|
def _binding_include(self, loader, node):
|
|
# Implements !include. Returns a list with the YAML structures for the
|
|
# included files (a single-element list if the !include is for a single
|
|
# file).
|
|
|
|
if isinstance(node, yaml.ScalarNode):
|
|
# !include foo.yaml
|
|
return [self._binding_include_file(loader.construct_scalar(node))]
|
|
|
|
if isinstance(node, yaml.SequenceNode):
|
|
# !include [foo.yaml, bar.yaml]
|
|
return [self._binding_include_file(filename)
|
|
for filename in loader.construct_sequence(node)]
|
|
|
|
_binding_inc_error("unrecognised node type in !include statement")
|
|
|
|
def _binding_include_file(self, filename):
|
|
# _binding_include() helper for loading an !include'd file. !include
|
|
# takes just the basename of the file, so we need to make sure there
|
|
# aren't multiple candidates.
|
|
|
|
paths = [path for path in self._binding_paths
|
|
if os.path.basename(path) == filename]
|
|
|
|
if not paths:
|
|
_binding_inc_error("'{}' not found".format(filename))
|
|
|
|
if len(paths) > 1:
|
|
_binding_inc_error("multiple candidates for '{}' in !include: {}"
|
|
.format(filename, ", ".join(paths)))
|
|
|
|
with open(paths[0], encoding="utf-8") as f:
|
|
return yaml.load(f, Loader=yaml.Loader)
|
|
|
|
def _init_devices(self):
|
|
# Creates a list of devices (Device objects) from the DT nodes, in
|
|
# self.devices. 'dt' is the dtlib.DT instance for the device tree.
|
|
|
|
# Maps dtlib.Node's to their corresponding Devices
|
|
self._node2dev = {}
|
|
|
|
self.devices = []
|
|
|
|
for node in self._dt.node_iter():
|
|
# Warning: Device.__init__() relies on parent Devices being created
|
|
# before their children. This is guaranteed by node_iter().
|
|
dev = Device(self, node)
|
|
self.devices.append(dev)
|
|
self._node2dev[node] = dev
|
|
|
|
for dev in self.devices:
|
|
# These depend on all Device objects having been created, so we do
|
|
# them separately
|
|
dev._init_interrupts()
|
|
dev._init_gpios()
|
|
dev._init_pwms()
|
|
dev._init_clocks()
|
|
|
|
def __repr__(self):
|
|
return "<EDT for '{}', binding directory '{}'>".format(
|
|
self.dts_path, self.bindings_dir)
|
|
|
|
|
|
class Device:
|
|
"""
|
|
Represents a device. There's a one-to-one correspondence between device
|
|
tree nodes and Devices.
|
|
|
|
These attributes are available on Device objects:
|
|
|
|
edt:
|
|
The EDT instance this device is from
|
|
|
|
name:
|
|
The name of the device. This is fetched from the node name.
|
|
|
|
unit_addr:
|
|
An integer with the ...@<unit-address> portion of the node name,
|
|
translated through any 'ranges' properties on parent nodes, or None if
|
|
the node name has no unit-address portion
|
|
|
|
path:
|
|
The device tree path of the device
|
|
|
|
label:
|
|
The text from the 'label' property on the DT node of the Device, or None
|
|
if the node has no 'label'
|
|
|
|
parent:
|
|
The parent Device, or None if there is no parent
|
|
|
|
enabled:
|
|
True unless the device's node has 'status = "disabled"'
|
|
|
|
read_only:
|
|
True if the DT node of the Device has a 'read-only' property, and False
|
|
otherwise
|
|
|
|
instance_no:
|
|
Dictionary that maps each 'compatible' string for the device to a unique
|
|
index among all devices that have that 'compatible' string.
|
|
|
|
As an example, 'instance_no["foo,led"] == 3' can be read as "this is the
|
|
fourth foo,led device".
|
|
|
|
Only enabled devices (status != "disabled") are counted. 'instance_no' is
|
|
meaningless for disabled devices.
|
|
|
|
matching_compat:
|
|
The 'compatible' string for the binding that matched the device, or
|
|
None if the device has no binding
|
|
|
|
description:
|
|
The description string from the binding file for the device, or None if
|
|
the device has no binding. Trailing whitespace (including newlines) is
|
|
removed.
|
|
|
|
binding_path:
|
|
The path to the binding file for the device, or None if the device has no
|
|
binding
|
|
|
|
compats:
|
|
A list of 'compatible' strings for the device, in the same order that
|
|
they're listed in the .dts file
|
|
|
|
regs:
|
|
A list of Register objects for the device's registers
|
|
|
|
props:
|
|
A dictionary that maps property names to Property objects. Property
|
|
objects are created for all DT properties on the device that are
|
|
mentioned in 'properties:' in the binding.
|
|
|
|
aliases:
|
|
A list of aliases for the device. This is fetched from the /aliases node.
|
|
|
|
interrupts:
|
|
A list of Interrupt objects for the interrupts generated by the device
|
|
|
|
gpios:
|
|
A dictionary that maps the <prefix> part in '<prefix>-gpios' properties
|
|
to a list of GPIO objects (see the GPIO class).
|
|
|
|
For example, 'foo-gpios = <&gpio1 1 2 &gpio2 3 4>' makes gpios["foo"] a
|
|
list of two GPIO objects.
|
|
|
|
pwms:
|
|
A list of PWM objects, derived from the 'pwms' property. The list is
|
|
empty if the device has no 'pwms' property.
|
|
|
|
clocks:
|
|
A list of Clock objects, derived from the 'clocks' property. The list is
|
|
empty if the device has no 'clocks' property.
|
|
|
|
bus:
|
|
The bus for the device as specified in its binding, e.g. "i2c" or "spi".
|
|
None if the binding doesn't specify a bus.
|
|
|
|
flash_controller:
|
|
The flash controller for the device. Only meaningful for devices
|
|
representing flash partitions.
|
|
"""
|
|
@property
|
|
def name(self):
|
|
"See the class docstring"
|
|
return self._node.name
|
|
|
|
@property
|
|
def unit_addr(self):
|
|
"See the class docstring"
|
|
|
|
# TODO: Return a plain string here later, like dtlib.Node.unit_addr?
|
|
|
|
if "@" not in self.name:
|
|
return None
|
|
|
|
try:
|
|
addr = int(self.name.split("@", 1)[1], 16)
|
|
except ValueError:
|
|
_err("{!r} has non-hex unit address".format(self))
|
|
|
|
addr = _translate(addr, self._node)
|
|
|
|
if self.regs and self.regs[0].addr != addr:
|
|
_warn("unit-address and first reg (0x{:x}) don't match for {}"
|
|
.format(self.regs[0].addr, self.name))
|
|
|
|
return addr
|
|
|
|
@property
|
|
def path(self):
|
|
"See the class docstring"
|
|
return self._node.path
|
|
|
|
@property
|
|
def label(self):
|
|
"See the class docstring"
|
|
if "label" in self._node.props:
|
|
return self._node.props["label"].to_string()
|
|
return None
|
|
|
|
@property
|
|
def parent(self):
|
|
"See the class docstring"
|
|
return self.edt._node2dev.get(self._node.parent)
|
|
|
|
@property
|
|
def enabled(self):
|
|
"See the class docstring"
|
|
return "status" not in self._node.props or \
|
|
self._node.props["status"].to_string() != "disabled"
|
|
|
|
@property
|
|
def read_only(self):
|
|
"See the class docstring"
|
|
return "read-only" in self._node.props
|
|
|
|
@property
|
|
def aliases(self):
|
|
"See the class docstring"
|
|
return [alias for alias, node in self._node.dt.alias2node.items()
|
|
if node is self._node]
|
|
|
|
@property
|
|
def bus(self):
|
|
"See the class docstring"
|
|
return _binding_bus(self._binding)
|
|
|
|
@property
|
|
def flash_controller(self):
|
|
"See the class docstring"
|
|
|
|
# The node path might be something like
|
|
# /flash-controller@4001E000/flash@0/partitions/partition@fc000. We go
|
|
# up two levels to get the flash and check its compat. The flash
|
|
# controller might be the flash itself (for cases like NOR flashes).
|
|
# For the case of 'soc-nv-flash', we assume the controller is the
|
|
# parent of the flash node.
|
|
|
|
if not self.parent or not self.parent.parent:
|
|
_err("flash partition {!r} lacks parent or grandparent node"
|
|
.format(self))
|
|
|
|
controller = self.parent.parent
|
|
if controller.matching_compat == "soc-nv-flash":
|
|
return controller.parent
|
|
return controller
|
|
|
|
def __repr__(self):
|
|
return "<Device {} in '{}', {}>".format(
|
|
self.path, self.edt.dts_path,
|
|
"binding " + self.binding_path if self.binding_path
|
|
else "no binding")
|
|
|
|
def __init__(self, edt, node):
|
|
"Private constructor. Not meant to be called by clients."
|
|
|
|
# Interrupts, GPIOs, PWMs, and clocks are initialized separately,
|
|
# because they depend on all Devices existing
|
|
|
|
self.edt = edt
|
|
self._node = node
|
|
|
|
self._init_binding()
|
|
self._init_props()
|
|
self._init_regs()
|
|
self._set_instance_no()
|
|
|
|
def _init_binding(self):
|
|
# Initializes Device.matching_compat, Device._binding, and
|
|
# Device.binding_path.
|
|
#
|
|
# Device._binding holds the data from the device's binding file, in the
|
|
# format returned by PyYAML (plain Python lists, dicts, etc.), or None
|
|
# if the device has no binding.
|
|
|
|
# This relies on the parent of the Device having already been
|
|
# initialized, which is guaranteed by going through the nodes in
|
|
# node_iter() order.
|
|
|
|
if "compatible" in self._node.props:
|
|
self.compats = self._node.props["compatible"].to_strings()
|
|
bus = self._bus_from_parent_binding()
|
|
|
|
for compat in self.compats:
|
|
if (compat, bus) in self.edt._compat2binding:
|
|
# Binding found
|
|
self.matching_compat = compat
|
|
self._binding, self.binding_path = \
|
|
self.edt._compat2binding[compat, bus]
|
|
|
|
self.description = self._binding.get("description")
|
|
if self.description:
|
|
self.description = self.description.rstrip()
|
|
|
|
return
|
|
else:
|
|
# No 'compatible' property. See if the parent has a 'sub-node:' key
|
|
# that gives the binding.
|
|
|
|
self.compats = []
|
|
|
|
if self.parent and self.parent._binding and \
|
|
"sub-node" in self.parent._binding:
|
|
|
|
# Binding found
|
|
self._binding = self.parent._binding["sub-node"]
|
|
self.binding_path = self.parent.binding_path
|
|
|
|
self.description = self.parent._binding.get("description")
|
|
if self.description:
|
|
self.description = self.description.rstrip()
|
|
|
|
self.matching_compat = self.parent.matching_compat
|
|
return
|
|
|
|
# No binding found
|
|
self.matching_compat = self._binding = self.binding_path = \
|
|
self.description = None
|
|
|
|
def _bus_from_parent_binding(self):
|
|
# _init_binding() helper. Returns the bus specified by
|
|
# 'child: bus: ...' in the parent binding, or None if missing.
|
|
|
|
if not self.parent:
|
|
return None
|
|
|
|
binding = self.parent._binding
|
|
if binding and "child" in binding:
|
|
return binding["child"].get("bus")
|
|
return None
|
|
|
|
def _init_props(self):
|
|
# Creates self.props. See the class docstring.
|
|
|
|
self.props = {}
|
|
|
|
if not self._binding or "properties" not in self._binding:
|
|
return
|
|
|
|
for name, options in self._binding["properties"].items():
|
|
self._init_prop(name, options)
|
|
|
|
def _init_prop(self, name, options):
|
|
# _init_props() helper for initializing a single property
|
|
|
|
# Skip properties that start with '#', like '#size-cells', and mapping
|
|
# properties like 'gpio-map'/'interrupt-map'
|
|
if name[0] == "#" or name.endswith("-map"):
|
|
return
|
|
|
|
prop_type = options.get("type")
|
|
if not prop_type:
|
|
_err("'{}' in {} lacks 'type'".format(name, self.binding_path))
|
|
|
|
# "Dummy" type for properties like '...-gpios', so that we can require
|
|
# all entries in 'properties:' to have a 'type: ...'. It might make
|
|
# sense to have gpios in 'properties:' for other reasons, e.g. to set
|
|
# 'category: required'.
|
|
if prop_type == "compound":
|
|
return
|
|
|
|
val = self._prop_val(name, prop_type,
|
|
options.get("category") == "optional")
|
|
if val is None:
|
|
# 'category: optional' property that wasn't there
|
|
return
|
|
|
|
prop = Property()
|
|
prop.dev = self
|
|
prop.name = name
|
|
prop.description = options.get("description")
|
|
if prop.description:
|
|
prop.description = prop.description.rstrip()
|
|
prop.val = val
|
|
enum = options.get("enum")
|
|
if enum is None:
|
|
prop.enum_index = None
|
|
else:
|
|
if val not in enum:
|
|
_err("value ({}) for property ({}) is not in enumerated "
|
|
"list {} for node {}".format(val, name, enum, self.name))
|
|
|
|
prop.enum_index = enum.index(val)
|
|
|
|
self.props[name] = prop
|
|
|
|
def _prop_val(self, name, prop_type, optional):
|
|
# _init_prop() helper for getting the property's value
|
|
|
|
node = self._node
|
|
|
|
if prop_type == "boolean":
|
|
# True/False
|
|
return name in node.props
|
|
|
|
prop = node.props.get(name)
|
|
if not prop:
|
|
if not optional and \
|
|
("status" not in node.props or
|
|
node.props["status"].to_string() != "disabled"):
|
|
|
|
_err("'{}' is marked as required in 'properties:' in {}, but "
|
|
"does not appear in {!r}".format(
|
|
name, self.binding_path, node))
|
|
|
|
return None
|
|
|
|
if prop_type == "int":
|
|
return prop.to_num()
|
|
|
|
if prop_type == "array":
|
|
return prop.to_nums()
|
|
|
|
if prop_type == "uint8-array":
|
|
return prop.value # Plain 'bytes'
|
|
|
|
if prop_type == "string":
|
|
return prop.to_string()
|
|
|
|
if prop_type == "string-array":
|
|
return prop.to_strings()
|
|
|
|
_err("'{}' in 'properties:' in {} has unknown type '{}'"
|
|
.format(name, self.binding_path, prop_type))
|
|
|
|
def _init_regs(self):
|
|
# Initializes self.regs with a list of Register objects
|
|
|
|
node = self._node
|
|
|
|
self.regs = []
|
|
|
|
if "reg" not in node.props:
|
|
return
|
|
|
|
address_cells = _address_cells(node)
|
|
size_cells = _size_cells(node)
|
|
|
|
for raw_reg in _slice(node, "reg", 4*(address_cells + size_cells)):
|
|
reg = Register()
|
|
reg.dev = self
|
|
reg.addr = _translate(to_num(raw_reg[:4*address_cells]), node)
|
|
reg.size = to_num(raw_reg[4*address_cells:])
|
|
if size_cells != 0 and reg.size == 0:
|
|
_err("zero-sized 'reg' in {!r} seems meaningless (maybe you "
|
|
"want a size of one or #size-cells = 0 instead)"
|
|
.format(self._node))
|
|
|
|
self.regs.append(reg)
|
|
|
|
_add_names(node, "reg-names", self.regs)
|
|
|
|
def _init_interrupts(self):
|
|
# Initializes self.interrupts with a list of Interrupt objects
|
|
|
|
node = self._node
|
|
|
|
self.interrupts = []
|
|
|
|
for controller_node, spec in _interrupts(node):
|
|
controller = self.edt._node2dev[controller_node]
|
|
|
|
interrupt = Interrupt()
|
|
interrupt.dev = self
|
|
interrupt.controller = controller
|
|
interrupt.specifier = self._named_cells(controller, spec, "interrupt")
|
|
|
|
self.interrupts.append(interrupt)
|
|
|
|
_add_names(node, "interrupt-names", self.interrupts)
|
|
|
|
def _init_gpios(self):
|
|
# Initializes self.gpios
|
|
|
|
self.gpios = {}
|
|
|
|
for prefix, gpios in _gpios(self._node).items():
|
|
self.gpios[prefix] = []
|
|
for controller_node, spec in gpios:
|
|
controller = self.edt._node2dev[controller_node]
|
|
|
|
gpio = GPIO()
|
|
gpio.dev = self
|
|
gpio.controller = controller
|
|
gpio.specifier = self._named_cells(controller, spec, "GPIOS")
|
|
gpio.name = prefix
|
|
|
|
self.gpios[prefix].append(gpio)
|
|
|
|
def _init_clocks(self):
|
|
# Initializes self.clocks
|
|
|
|
node = self._node
|
|
|
|
self.clocks = []
|
|
|
|
for controller_node, spec in _clocks(node):
|
|
controller = self.edt._node2dev[controller_node]
|
|
|
|
clock = Clock()
|
|
clock.dev = self
|
|
clock.controller = controller
|
|
clock.specifier = self._named_cells(controller, spec, "clocks")
|
|
if "fixed-clock" in controller.compats:
|
|
if "clock-frequency" not in controller.props:
|
|
_err("{!r} is a 'fixed-clock', but either lacks a "
|
|
"'clock-frequency' property or does not have "
|
|
"it specified in its binding".format(controller))
|
|
|
|
clock.frequency = controller.props["clock-frequency"].val
|
|
else:
|
|
clock.frequency = None
|
|
|
|
self.clocks.append(clock)
|
|
|
|
_add_names(node, "clock-names", self.clocks)
|
|
|
|
def _init_pwms(self):
|
|
# Initializes self.pwms
|
|
|
|
self.pwms = []
|
|
|
|
for controller_node, spec in _pwms(self._node):
|
|
controller = self.edt._node2dev[controller_node]
|
|
|
|
pwm = PWM()
|
|
pwm.dev = self
|
|
pwm.controller = controller
|
|
pwm.specifier = self._named_cells(controller, spec, "pwms")
|
|
|
|
self.pwms.append(pwm)
|
|
|
|
_add_names(self._node, "pwm-names", self.pwms)
|
|
|
|
def _named_cells(self, controller, spec, controller_s):
|
|
# _init_{interrupts,gpios}() helper. Returns a dictionary that maps
|
|
# #cell names given in the binding for 'controller' to cell values.
|
|
# 'spec' is the raw interrupt/GPIO data, and 'controller_s' a string
|
|
# that gives the context (for error messages).
|
|
|
|
if not controller._binding:
|
|
_err("{} controller {!r} for {!r} lacks binding"
|
|
.format(controller_s, controller._node, self._node))
|
|
|
|
if "#cells" in controller._binding:
|
|
cell_names = controller._binding["#cells"]
|
|
if not isinstance(cell_names, list):
|
|
_err("binding for {} controller {!r} has malformed #cells array"
|
|
.format(controller_s, controller._node))
|
|
else:
|
|
# Treat no #cells in the binding the same as an empty #cells, so
|
|
# that bindings don't have to have an empty #cells for e.g.
|
|
# '#clock-cells = <0>'.
|
|
cell_names = []
|
|
|
|
spec_list = to_nums(spec)
|
|
if len(spec_list) != len(cell_names):
|
|
_err("unexpected #cells length in binding for {!r} - {} instead "
|
|
"of {}".format(controller._node, len(cell_names),
|
|
len(spec_list)))
|
|
|
|
return dict(zip(cell_names, spec_list))
|
|
|
|
def _set_instance_no(self):
|
|
# Initializes self.instance_no
|
|
|
|
self.instance_no = {}
|
|
|
|
for compat in self.compats:
|
|
self.instance_no[compat] = 0
|
|
for other_dev in self.edt.devices:
|
|
if compat in other_dev.compats and other_dev.enabled:
|
|
self.instance_no[compat] += 1
|
|
|
|
|
|
class Register:
|
|
"""
|
|
Represents a register on a device.
|
|
|
|
These attributes are available on Register objects:
|
|
|
|
dev:
|
|
The Device instance this register is from
|
|
|
|
name:
|
|
The name of the register as given in the 'reg-names' property, or None if
|
|
there is no 'reg-names' property
|
|
|
|
addr:
|
|
The starting address of the register, in the parent address space. Any
|
|
'ranges' properties are taken into account.
|
|
|
|
size:
|
|
The length of the register in bytes
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
fields.append("addr: " + hex(self.addr))
|
|
fields.append("size: " + hex(self.size))
|
|
|
|
return "<Register, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Interrupt:
|
|
"""
|
|
Represents an interrupt generated by a device.
|
|
|
|
These attributes are available on Interrupt objects:
|
|
|
|
dev:
|
|
The Device instance that generated the interrupt
|
|
|
|
name:
|
|
The name of the interrupt as given in the 'interrupt-names' property, or
|
|
None if there is no 'interrupt-names' property
|
|
|
|
controller:
|
|
The Device instance for the controller the interrupt gets sent to. Any
|
|
'interrupt-map' is taken into account, so that this is the final
|
|
controller node.
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the interrupt specifier. 'interrupts = <1 2>' might give
|
|
{"irq": 1, "level": 2}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<Interrupt, {}>".format(", ".join(fields))
|
|
|
|
|
|
class GPIO:
|
|
"""
|
|
Represents a GPIO used by a device.
|
|
|
|
These attributes are available on GPIO objects:
|
|
|
|
dev:
|
|
The Device instance that uses the GPIO
|
|
|
|
name:
|
|
The name of the gpio as extracted out of the "<NAME>-gpios" property. If
|
|
the property is just "gpios" than there is no name.
|
|
|
|
controller:
|
|
The Device instance for the controller of the GPIO
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the gpio specifier. 'foo-gpios = <&gpioc 5 0>' might give
|
|
{"pin": 5, "flags": 0}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<GPIO, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Clock:
|
|
"""
|
|
Represents a clock used by a device.
|
|
|
|
These attributes are available on Clock objects:
|
|
|
|
dev:
|
|
The Device instance that uses the clock
|
|
|
|
name:
|
|
The name of the clock as given in the 'clock-names' property, or
|
|
None if there is no 'clock-names' property
|
|
|
|
controller:
|
|
The Device instance for the controller of the clock.
|
|
|
|
frequency:
|
|
The frequency of the clock for fixed clocks ('fixed-clock' in
|
|
'compatible'), as an integer. Derived from the 'clock-frequency'
|
|
property. None if the clock is not a fixed clock.
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the clock specifier
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
if self.frequency is not None:
|
|
fields.append("frequency: {}".format(self.frequency))
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<Clock, {}>".format(", ".join(fields))
|
|
|
|
|
|
class PWM:
|
|
"""
|
|
Represents a PWM used by a device.
|
|
|
|
These attributes are available on PWM objects:
|
|
|
|
dev:
|
|
The Device instance that uses the PWM
|
|
|
|
name:
|
|
The name of the pwm as given in the 'pwm-names' property, or the
|
|
node name if the 'pwm-names' property doesn't exist.
|
|
|
|
controller:
|
|
The Device instance for the controller of the PWM
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the pwm specifier. 'pwms = <&pwm 0 5000000>' might give
|
|
{"channel": 0, "period": 5000000}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<PWM, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Property:
|
|
"""
|
|
Represents a property on a Device, as set in its DT node and with
|
|
additional info from the 'properties:' section of the binding.
|
|
|
|
Only properties mentioned in 'properties:' get created.
|
|
|
|
These attributes are available on Property objects:
|
|
|
|
dev:
|
|
The Device instance the property is on
|
|
|
|
name:
|
|
The name of the property
|
|
|
|
description:
|
|
The description string from the property as given in the binding, or None
|
|
if missing. Trailing whitespace (including newlines) is removed.
|
|
|
|
val:
|
|
The value of the property, with the format determined by the 'type:'
|
|
key from the binding
|
|
|
|
enum_index:
|
|
The index of the property's value in the 'enum:' list in the binding, or
|
|
None if the binding has no 'enum:'
|
|
"""
|
|
def __repr__(self):
|
|
fields = ["name: " + self.name,
|
|
# repr() to deal with lists
|
|
"value: " + repr(self.val)]
|
|
|
|
if self.enum_index is not None:
|
|
fields.append("enum index: {}".format(self.enum_index))
|
|
|
|
return "<Property, {}>".format(", ".join(fields))
|
|
|
|
|
|
class EDTError(Exception):
|
|
"Exception raised for Extended Device Tree-related errors"
|
|
|
|
|
|
#
|
|
# Public global functions
|
|
#
|
|
|
|
|
|
def spi_dev_cs_gpio(dev):
|
|
# Returns an SPI device's GPIO chip select if it exists, as a GPIO
|
|
# instance, and None otherwise. See
|
|
# Documentation/devicetree/bindings/spi/spi-bus.txt in the Linux kernel.
|
|
|
|
if dev.bus == "spi" and dev.parent:
|
|
parent_cs = dev.parent.gpios.get("cs")
|
|
if parent_cs:
|
|
# cs-gpios is indexed by the unit address
|
|
cs_index = dev.regs[0].addr
|
|
if cs_index >= len(parent_cs):
|
|
_err("index from 'regs' in {!r} ({}) is >= number of cs-gpios "
|
|
"in {!r} ({})".format(
|
|
dev, cs_index, dev.parent, len(parent_cs)))
|
|
|
|
return parent_cs[cs_index]
|
|
|
|
return None
|
|
|
|
|
|
#
|
|
# Private global functions
|
|
#
|
|
|
|
|
|
def _dt_compats(dt):
|
|
# Returns a set() with all 'compatible' strings in the device tree
|
|
# represented by dt (a dtlib.DT instance)
|
|
|
|
return {compat
|
|
for node in dt.node_iter()
|
|
if "compatible" in node.props
|
|
for compat in node.props["compatible"].to_strings()}
|
|
|
|
|
|
def _binding_paths(bindings_dir):
|
|
# Returns a list with the paths to all bindings (.yaml files) in
|
|
# 'bindings_dir'
|
|
|
|
return [os.path.join(root, filename)
|
|
for root, _, filenames in os.walk(bindings_dir)
|
|
for filename in filenames
|
|
if filename.endswith(".yaml")]
|
|
|
|
|
|
def _binding_compat(binding_path):
|
|
# Returns the compatible string specified in the binding at 'binding_path'.
|
|
# Uses a regex to avoid having to parse the bindings, which is slow when
|
|
# done for all bindings.
|
|
|
|
with open(binding_path) as binding:
|
|
for line in binding:
|
|
match = re.match(r'\s+constraint:\s*"([^"]*)"', line)
|
|
if match:
|
|
return match.group(1)
|
|
|
|
return None
|
|
|
|
|
|
def _binding_bus(binding):
|
|
# Returns the bus specified in 'binding' (the bus the device described by
|
|
# 'binding' is on), e.g. "i2c", or None if 'binding' is None or doesn't
|
|
# specify a bus
|
|
|
|
if binding and "parent" in binding:
|
|
return binding["parent"].get("bus")
|
|
return None
|
|
|
|
|
|
def _binding_inc_error(msg):
|
|
# Helper for reporting errors in the !include implementation
|
|
|
|
raise yaml.constructor.ConstructorError(None, None, "error: " + msg)
|
|
|
|
|
|
def _load_binding(path):
|
|
# Loads a top-level binding .yaml file from 'path', also handling any
|
|
# !include'd files. Returns the parsed PyYAML output (Python
|
|
# lists/dictionaries/strings/etc. representing the file).
|
|
|
|
with open(path, encoding="utf-8") as f:
|
|
return _merge_included_bindings(yaml.load(f, Loader=yaml.Loader), path)
|
|
|
|
|
|
def _merge_included_bindings(binding, binding_path):
|
|
# Merges any bindings in the 'inherits:' section of 'binding' into the top
|
|
# level of 'binding'. !includes have already been processed at this point,
|
|
# and leave the data for the included binding(s) in 'inherits:'.
|
|
#
|
|
# Properties in 'binding' take precedence over properties from included
|
|
# bindings.
|
|
|
|
# Currently, we require that each !include'd file is a well-formed
|
|
# binding in itself
|
|
_check_binding(binding, binding_path)
|
|
|
|
if "inherits" in binding:
|
|
for inherited in binding.pop("inherits"):
|
|
_merge_props(
|
|
binding, _merge_included_bindings(inherited, binding_path),
|
|
None, binding_path)
|
|
|
|
return binding
|
|
|
|
|
|
def _merge_props(to_dict, from_dict, parent, binding_path):
|
|
# Recursively merges 'from_dict' into 'to_dict', to implement !include. If
|
|
# a key exists in both 'from_dict' and 'to_dict', then the value in
|
|
# 'to_dict' takes precedence.
|
|
#
|
|
# 'parent' is the name of the parent key containing 'to_dict' and
|
|
# 'from_dict', and 'binding_path' is the path to the top-level binding.
|
|
# These are used to generate errors for sketchy property overwrites.
|
|
|
|
for prop in from_dict:
|
|
if isinstance(to_dict.get(prop), dict) and \
|
|
isinstance(from_dict[prop], dict):
|
|
_merge_props(to_dict[prop], from_dict[prop], prop, binding_path)
|
|
elif prop not in to_dict:
|
|
to_dict[prop] = from_dict[prop]
|
|
elif _bad_overwrite(to_dict, from_dict, prop):
|
|
_err("{} (in '{}'): '{}' from !included file overwritten "
|
|
"('{}' replaced with '{}')".format(
|
|
binding_path, parent, prop, from_dict[prop],
|
|
to_dict[prop]))
|
|
|
|
|
|
def _bad_overwrite(to_dict, from_dict, prop):
|
|
# _merge_props() helper. Returns True in cases where it's bad that
|
|
# to_dict[prop] takes precedence over from_dict[prop].
|
|
|
|
# These are overridden deliberately
|
|
if prop in {"title", "description"}:
|
|
return False
|
|
|
|
if to_dict[prop] == from_dict[prop]:
|
|
return False
|
|
|
|
# Allow a property to be made required when it previously was optional
|
|
# without a warning
|
|
if prop == "category" and to_dict["category"] == "required" and \
|
|
from_dict["category"] == "optional":
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _check_binding(binding, binding_path):
|
|
# Does sanity checking on 'binding'
|
|
|
|
for prop in "title", "description":
|
|
if prop not in binding:
|
|
_err("missing '{}' property in {}".format(prop, binding_path))
|
|
|
|
for prop in "title", "description":
|
|
if not isinstance(binding[prop], str) or not binding[prop]:
|
|
_err("missing, malformed, or empty '{}' in {}"
|
|
.format(prop, binding_path))
|
|
|
|
ok_top = {"title", "description", "inherits", "properties", "#cells",
|
|
"parent", "child", "sub-node"}
|
|
|
|
for prop in binding:
|
|
if prop not in ok_top:
|
|
_err("unknown key '{}' in {}, expected one of {}"
|
|
.format(prop, binding_path, ", ".join(ok_top)))
|
|
|
|
for pc in "parent", "child":
|
|
if pc in binding:
|
|
# Just 'bus:' is expected at the moment
|
|
if binding[pc].keys() != {"bus"}:
|
|
_err("expected (just) 'bus:' in '{}:' in {}"
|
|
.format(pc, binding_path))
|
|
|
|
if not isinstance(binding[pc]["bus"], str):
|
|
_err("malformed '{}: bus:' value in {}, expected string"
|
|
.format(pc, binding_path))
|
|
|
|
# Check properties
|
|
|
|
if "properties" not in binding:
|
|
return
|
|
|
|
ok_prop_keys = {"description", "type", "category", "constraint", "enum"}
|
|
ok_categories = {"required", "optional"}
|
|
|
|
for prop, keys in binding["properties"].items():
|
|
for key in keys:
|
|
if key not in ok_prop_keys:
|
|
_err("unknown setting '{}' in 'properties: {}: ...' in {}, "
|
|
"expected one of {}".format(
|
|
key, prop, binding_path, ", ".join(ok_prop_keys)))
|
|
|
|
if "category" in keys and keys["category"] not in ok_categories:
|
|
_err("unrecognized category '{}' for '{}' in 'properties' in {}, "
|
|
"expected one of {}".format(
|
|
keys["category"], prop, binding_path,
|
|
", ".join(ok_categories)))
|
|
|
|
if "description" in keys and not isinstance(keys["description"], str):
|
|
_err("missing, malformed, or empty 'description' for '{}' in "
|
|
"'properties' in {}".format(prop, binding_path))
|
|
|
|
|
|
def _translate(addr, node):
|
|
# Recursively translates 'addr' on 'node' to the address space(s) of its
|
|
# parent(s), by looking at 'ranges' properties. Returns the translated
|
|
# address.
|
|
|
|
if not node.parent or "ranges" not in node.parent.props:
|
|
# No translation
|
|
return addr
|
|
|
|
if not node.parent.props["ranges"].value:
|
|
# DT spec.: "If the property is defined with an <empty> value, it
|
|
# specifies that the parent and child address space is identical, and
|
|
# no address translation is required."
|
|
#
|
|
# Treat this the same as a 'range' that explicitly does a one-to-one
|
|
# mapping, as opposed to there not being any translation.
|
|
return _translate(addr, node.parent)
|
|
|
|
# Gives the size of each component in a translation 3-tuple in 'ranges'
|
|
child_address_cells = _address_cells(node)
|
|
parent_address_cells = _address_cells(node.parent)
|
|
child_size_cells = _size_cells(node)
|
|
|
|
# Number of cells for one translation 3-tuple in 'ranges'
|
|
entry_cells = child_address_cells + parent_address_cells + child_size_cells
|
|
|
|
for raw_range in _slice(node.parent, "ranges", 4*entry_cells):
|
|
child_addr = to_num(raw_range[:4*child_address_cells])
|
|
raw_range = raw_range[4*child_address_cells:]
|
|
|
|
parent_addr = to_num(raw_range[:4*parent_address_cells])
|
|
raw_range = raw_range[4*parent_address_cells:]
|
|
|
|
child_len = to_num(raw_range)
|
|
|
|
if child_addr <= addr < child_addr + child_len:
|
|
# 'addr' is within range of a translation in 'ranges'. Recursively
|
|
# translate it and return the result.
|
|
return _translate(parent_addr + addr - child_addr, node.parent)
|
|
|
|
# 'addr' is not within range of any translation in 'ranges'
|
|
return addr
|
|
|
|
|
|
def _add_names(node, names_ident, objs):
|
|
# Helper for registering names from <foo>-names properties.
|
|
#
|
|
# node:
|
|
# Device tree node
|
|
#
|
|
# names-ident:
|
|
# name of property holding names, e.g. "reg-names"
|
|
#
|
|
# objs:
|
|
# list of objects whose .name field should be set
|
|
|
|
if names_ident in node.props:
|
|
names = node.props[names_ident].to_strings()
|
|
if len(names) != len(objs):
|
|
_err("{} property in {} has {} strings, expected {} strings"
|
|
.format(names_ident, node.name, len(names), len(objs)))
|
|
|
|
for obj, name in zip(objs, names):
|
|
obj.name = name
|
|
else:
|
|
for obj in objs:
|
|
obj.name = None
|
|
|
|
|
|
def _interrupt_parent(node):
|
|
# Returns the node pointed at by the closest 'interrupt-parent', searching
|
|
# the parents of 'node'. As of writing, this behavior isn't specified in
|
|
# the DT spec., but seems to match what some .dts files except.
|
|
|
|
while node:
|
|
if "interrupt-parent" in node.props:
|
|
return node.props["interrupt-parent"].to_node()
|
|
node = node.parent
|
|
|
|
_err("{!r} has an 'interrupts' property, but neither the node nor any "
|
|
"of its parents has an 'interrupt-parent' property".format(node))
|
|
|
|
|
|
def _interrupts(node):
|
|
# Returns a list of (<controller>, <specifier>) tuples, with one tuple per
|
|
# interrupt generated by 'node'. <controller> is the destination of the
|
|
# interrupt (possibly after mapping through an 'interrupt-map'), and
|
|
# <specifier> the data associated with the interrupt (as a 'bytes' object).
|
|
|
|
# Takes precedence over 'interrupts' if both are present
|
|
if "interrupts-extended" in node.props:
|
|
prop = node.props["interrupts-extended"]
|
|
return [_map_interrupt(node, iparent, spec)
|
|
for iparent, spec in _phandle_val_list(prop, _interrupt_cells)]
|
|
|
|
if "interrupts" in node.props:
|
|
# Treat 'interrupts' as a special case of 'interrupts-extended', with
|
|
# the same interrupt parent for all interrupts
|
|
|
|
iparent = _interrupt_parent(node)
|
|
interrupt_cells = _interrupt_cells(iparent)
|
|
|
|
return [_map_interrupt(node, iparent, raw)
|
|
for raw in _slice(node, "interrupts", 4*interrupt_cells)]
|
|
|
|
return []
|
|
|
|
|
|
def _map_interrupt(child, parent, child_spec):
|
|
# Translates an interrupt headed from 'child' to 'parent' with specifier
|
|
# 'child_spec' through any 'interrupt-map' properties. Returns a
|
|
# (<controller>, <specifier>) tuple with the final destination after
|
|
# mapping.
|
|
|
|
if "interrupt-controller" in parent.props:
|
|
return (parent, child_spec)
|
|
|
|
def own_address_cells(node):
|
|
# Used for parents pointed at by 'interrupt-map'. We can't use
|
|
# _address_cells(), because it's the #address-cells property on 'node'
|
|
# itself that matters.
|
|
|
|
address_cells = node.props.get("#address-cells")
|
|
if not address_cells:
|
|
_err("missing #address-cells on {!r} (while handling interrupt-map)"
|
|
.format(node))
|
|
return address_cells.to_num()
|
|
|
|
def spec_len_fn(node):
|
|
# Can't use _address_cells() here, because it's the #address-cells
|
|
# property on 'node' itself that matters
|
|
return 4*(own_address_cells(node) + _interrupt_cells(node))
|
|
|
|
parent, raw_spec = _map(
|
|
"interrupt", child, parent, _raw_unit_addr(child) + child_spec,
|
|
spec_len_fn)
|
|
|
|
# Strip the parent unit address part, if any
|
|
return (parent, raw_spec[4*own_address_cells(parent):])
|
|
|
|
|
|
def _clocks(node):
|
|
# Returns a list of (<controller>, <specifier>) tuples for any 'clocks'
|
|
# property on 'node', or an empty list if 'node' has no 'clocks'.
|
|
# <controller> is a dtlib.Node.
|
|
|
|
if "clocks" not in node.props:
|
|
return []
|
|
return _phandle_val_list(node.props["clocks"], _clock_cells)
|
|
|
|
|
|
def _pwms(node):
|
|
# Returns a list of (<controller>, <specifier>) tuples for any 'pwms'
|
|
# property on 'node', or an empty list if 'node' has no 'pwms'.
|
|
# <controller> is a dtlib.Node.
|
|
|
|
if "pwms" not in node.props:
|
|
return []
|
|
return _phandle_val_list(node.props["pwms"], _pwm_cells)
|
|
|
|
|
|
def _gpios(node):
|
|
# Returns a dictionary that maps '<prefix>-gpios' prefixes to lists of
|
|
# (<controller>, <specifier>) tuples (possibly after mapping through an
|
|
# gpio-map). <controller> is a dtlib.Node.
|
|
|
|
res = {}
|
|
|
|
for name, prop in node.props.items():
|
|
if name.endswith("gpios"):
|
|
# Get the prefix from the property name:
|
|
# - gpios -> "" (deprecated, should have a prefix)
|
|
# - foo-gpios -> "foo"
|
|
# - etc.
|
|
prefix = name[:-5]
|
|
if prefix.endswith("-"):
|
|
prefix = prefix[:-1]
|
|
|
|
res[prefix] = [
|
|
_map_gpio(prop.node, controller, spec)
|
|
for controller, spec in _phandle_val_list(prop, _gpio_cells)
|
|
]
|
|
|
|
return res
|
|
|
|
|
|
def _map_gpio(child, parent, child_spec):
|
|
# Returns a (<controller>, <specifier>) tuple with the final destination
|
|
# after mapping through any 'gpio-map' properties. See _map_interrupt().
|
|
|
|
if "gpio-map" not in parent.props:
|
|
return (parent, child_spec)
|
|
|
|
return _map("gpio", child, parent, child_spec,
|
|
lambda node: 4*_gpio_cells(node))
|
|
|
|
|
|
def _map(prefix, child, parent, child_spec, spec_len_fn):
|
|
# Common code for mapping through <prefix>-map properties, e.g.
|
|
# interrupt-map and gpio-map.
|
|
#
|
|
# prefix:
|
|
# The prefix, e.g. "interrupt" or "gpio"
|
|
#
|
|
# child:
|
|
# The "sender", e.g. the node with 'interrupts = <...>'
|
|
#
|
|
# parent:
|
|
# The "receiver", e.g. a node with 'interrupt-map = <...>' or
|
|
# 'interrupt-controller' (no mapping)
|
|
#
|
|
# child_spec:
|
|
# The data associated with the interrupt/GPIO/etc., as a 'bytes' object,
|
|
# e.g. <1 2> for 'foo-gpios = <&gpio1 1 2>'.
|
|
#
|
|
# spec_len_fn:
|
|
# Function called on a parent specified in a *-map property to get its
|
|
# *-cells value, e.g. #interrupt-cells
|
|
|
|
map_prop = parent.props.get(prefix + "-map")
|
|
if not map_prop:
|
|
if prefix + "-controller" not in parent.props:
|
|
_err("expected '{}-controller' property on {!r} "
|
|
"(referenced by {!r})".format(prefix, parent, child))
|
|
|
|
# No mapping
|
|
return (parent, child_spec)
|
|
|
|
masked_child_spec = _mask(prefix, child, parent, child_spec)
|
|
|
|
raw = map_prop.value
|
|
while raw:
|
|
if len(raw) < len(child_spec):
|
|
_err("bad value for {!r}, missing/truncated child specifier"
|
|
.format(map_prop))
|
|
child_spec_entry = raw[:len(child_spec)]
|
|
raw = raw[len(child_spec):]
|
|
|
|
if len(raw) < 4:
|
|
_err("bad value for {!r}, missing/truncated phandle"
|
|
.format(map_prop))
|
|
phandle = to_num(raw[:4])
|
|
raw = raw[4:]
|
|
|
|
# Parent specified in *-map
|
|
map_parent = parent.dt.phandle2node.get(phandle)
|
|
if not map_parent:
|
|
_err("bad phandle in " + repr(map_prop))
|
|
|
|
map_parent_spec_len = spec_len_fn(map_parent)
|
|
if len(raw) < map_parent_spec_len:
|
|
_err("bad value for {!r}, missing/truncated parent specifier"
|
|
.format(map_prop))
|
|
parent_spec = raw[:map_parent_spec_len]
|
|
raw = raw[map_parent_spec_len:]
|
|
|
|
# Got one *-map row. Check if it matches the child specifier.
|
|
if child_spec_entry == masked_child_spec:
|
|
# Handle *-map-pass-thru
|
|
parent_spec = _pass_thru(
|
|
prefix, child, parent, child_spec, parent_spec)
|
|
|
|
# Found match. Recursively map and return it.
|
|
return _map(prefix, parent, map_parent, parent_spec, spec_len_fn)
|
|
|
|
_err("child specifier for {!r} ({}) does not appear in {!r}"
|
|
.format(child, child_spec, map_prop))
|
|
|
|
|
|
def _mask(prefix, child, parent, child_spec):
|
|
# Common code for handling <prefix>-mask properties, e.g. interrupt-mask.
|
|
# See _map() for the parameters.
|
|
|
|
mask_prop = parent.props.get(prefix + "-map-mask")
|
|
if not mask_prop:
|
|
# No mask
|
|
return child_spec
|
|
|
|
mask = mask_prop.value
|
|
if len(mask) != len(child_spec):
|
|
_err("{!r}: expected '{}-mask' in {!r} to be {} bytes, is {} bytes"
|
|
.format(child, prefix, parent, len(child_spec), len(mask)))
|
|
|
|
return _and(child_spec, mask)
|
|
|
|
|
|
def _pass_thru(prefix, child, parent, child_spec, parent_spec):
|
|
# Common code for handling <prefix>-map-thru properties, e.g.
|
|
# interrupt-pass-thru.
|
|
#
|
|
# parent_spec:
|
|
# The parent specifier from the matched entry in the <prefix>-map
|
|
# property
|
|
#
|
|
# See _map() for the other parameters.
|
|
|
|
pass_thru_prop = parent.props.get(prefix + "-map-pass-thru")
|
|
if not pass_thru_prop:
|
|
# No pass-thru
|
|
return parent_spec
|
|
|
|
pass_thru = pass_thru_prop.value
|
|
if len(pass_thru) != len(child_spec):
|
|
_err("{!r}: expected '{}-map-pass-thru' in {!r} to be {} bytes, is {} bytes"
|
|
.format(child, prefix, parent, len(child_spec), len(pass_thru)))
|
|
|
|
res = _or(_and(child_spec, pass_thru),
|
|
_and(parent_spec, _not(pass_thru)))
|
|
|
|
# Truncate to length of parent spec.
|
|
return res[-len(parent_spec):]
|
|
|
|
|
|
def _raw_unit_addr(node):
|
|
# _map_interrupt() helper. Returns the unit address (derived from 'reg' and
|
|
# #address-cells) as a raw 'bytes'
|
|
|
|
if 'reg' not in node.props:
|
|
_err("{!r} lacks 'reg' property (needed for 'interrupt-map' unit "
|
|
"address lookup)".format(node))
|
|
|
|
addr_len = 4*_address_cells(node)
|
|
|
|
if len(node.props['reg'].value) < addr_len:
|
|
_err("{!r} has too short 'reg' property (while doing 'interrupt-map' "
|
|
"unit address lookup)".format(node))
|
|
|
|
return node.props['reg'].value[:addr_len]
|
|
|
|
|
|
def _and(b1, b2):
|
|
# Returns the bitwise AND of the two 'bytes' objects b1 and b2. Pads
|
|
# with ones on the left if the lengths are not equal.
|
|
|
|
# Pad on the left, to equal length
|
|
maxlen = max(len(b1), len(b2))
|
|
return bytes(x & y for x, y in zip(b1.rjust(maxlen, b'\xff'),
|
|
b2.rjust(maxlen, b'\xff')))
|
|
|
|
|
|
def _or(b1, b2):
|
|
# Returns the bitwise OR of the two 'bytes' objects b1 and b2. Pads with
|
|
# zeros on the left if the lengths are not equal.
|
|
|
|
# Pad on the left, to equal length
|
|
maxlen = max(len(b1), len(b2))
|
|
return bytes(x | y for x, y in zip(b1.rjust(maxlen, b'\x00'),
|
|
b2.rjust(maxlen, b'\x00')))
|
|
|
|
|
|
def _not(b):
|
|
# Returns the bitwise not of the 'bytes' object 'b'
|
|
|
|
# ANDing with 0xFF avoids negative numbers
|
|
return bytes(~x & 0xFF for x in b)
|
|
|
|
|
|
def _phandle_val_list(prop, n_cells_fn):
|
|
# Parses a '<phandle> <value> <phandle> <value> ...' value
|
|
#
|
|
# prop:
|
|
# dtlib.Property with value to parse
|
|
#
|
|
# cells_fn:
|
|
# A function that returns the number of cells for a <value>. Takes the
|
|
# node pointed at by the preceding <phandle> as its argument.
|
|
#
|
|
# Returns a list of (<node>, <value>) tuples, where <node> is the node
|
|
# pointed at by <phandle>. 'cells_fn' is a function called on <node> to get
|
|
# the number of cells for <value>.
|
|
|
|
res = []
|
|
|
|
raw = prop.value
|
|
while raw:
|
|
if len(raw) < 4:
|
|
# Not enough room for phandle
|
|
_err("bad value for " + repr(prop))
|
|
phandle = to_num(raw[:4])
|
|
raw = raw[4:]
|
|
|
|
node = prop.node.dt.phandle2node.get(phandle)
|
|
if not node:
|
|
_err("bad phandle in " + repr(prop))
|
|
|
|
n_cells = n_cells_fn(node)
|
|
if len(raw) < 4*n_cells:
|
|
_err("missing data after phandle in " + repr(prop))
|
|
|
|
res.append((node, raw[:4*n_cells]))
|
|
raw = raw[4*n_cells:]
|
|
|
|
return res
|
|
|
|
|
|
def _address_cells(node):
|
|
# Returns the #address-cells setting for 'node', giving the number of <u32>
|
|
# cells used to encode the address in the 'reg' property
|
|
|
|
if "#address-cells" in node.parent.props:
|
|
return node.parent.props["#address-cells"].to_num()
|
|
return 2 # Default value per DT spec.
|
|
|
|
|
|
def _size_cells(node):
|
|
# Returns the #size-cells setting for 'node', giving the number of <u32>
|
|
# cells used to encode the size in the 'reg' property
|
|
|
|
if "#size-cells" in node.parent.props:
|
|
return node.parent.props["#size-cells"].to_num()
|
|
return 1 # Default value per DT spec.
|
|
|
|
|
|
def _interrupt_cells(node):
|
|
# Returns the #interrupt-cells property value on 'node', erroring out if
|
|
# 'node' has no #interrupt-cells property
|
|
|
|
if "#interrupt-cells" not in node.props:
|
|
_err("{!r} lacks #interrupt-cells".format(node))
|
|
return node.props["#interrupt-cells"].to_num()
|
|
|
|
|
|
def _gpio_cells(node):
|
|
if "#gpio-cells" not in node.props:
|
|
_err("{!r} lacks #gpio-cells".format(node))
|
|
return node.props["#gpio-cells"].to_num()
|
|
|
|
|
|
def _pwm_cells(node):
|
|
if "#pwm-cells" not in node.props:
|
|
_err("{!r} lacks #pwm-cells".format(node))
|
|
return node.props["#pwm-cells"].to_num()
|
|
|
|
|
|
def _clock_cells(node):
|
|
if "#clock-cells" not in node.props:
|
|
_err("{!r} lacks #clock-cells".format(node))
|
|
return node.props["#clock-cells"].to_num()
|
|
|
|
|
|
def _slice(node, prop_name, size):
|
|
# Splits node.props[prop_name].value into 'size'-sized chunks, returning a
|
|
# list of chunks. Raises EDTError if the length of the property is not
|
|
# evenly divisible by 'size'.
|
|
|
|
raw = node.props[prop_name].value
|
|
if len(raw) % size:
|
|
_err("'{}' property in {!r} has length {}, which is not evenly "
|
|
"divisible by {}".format(prop_name, node, len(raw), size))
|
|
|
|
return [raw[i:i + size] for i in range(0, len(raw), size)]
|
|
|
|
|
|
def _err(msg):
|
|
raise EDTError(msg)
|
|
|
|
|
|
def _warn(msg):
|
|
print("warning: " + msg, file=sys.stderr)
|