dts: dtlib/edtlib: Add a syntax-based type-checking system

Property type-checking has been pretty rudimentary until now, only
checking things like the length being divisible by 4 for 'type: array',
and strings being null-terminated. In particular, no checking was done
for 'type: uint8-array', letting

  jedec-id = < 0xc8 0x28 0x17 >;

slip through when

  jedec-id = [ 0xc8 0x28 0x17 ];

was intended.

Fix it by adding a syntax-based type checker:

  1. Add Property.type, which gives a high-level type for the property,
     derived from the markers added in the previous commit.

     This includes types like TYPE_EMPTY ('foo;'),
     TYPE_NUM ('foo = < 3 >;'), TYPE_BYTES ('foo = [ 01 02 ];'),
     TYPE_STRINGS ('foo = "bar", "baz"'),
     TYPE_PHANDLE ('foo = < &bar >;'), and TYPE_COMPOUND (everything not
     recognized).

     See the Property.type docstring in dtlib for more info.

  2. Use the high-level type in
     Property.to_num()/to_string()/to_node()/etc. to verify that the
     property was assigned in an expected way for the type.

     If the assignment looks bad, give a helpful error:

       expected property 'nums' on /foo/bar in some.dts to be assigned
       with 'nums = < (number) (number) ... >', not 'nums = "oops";'

Some other related changes are included as well:

  - There's a new Property.to_bytes() function that works like accessing
    Property.bytes, except with an added check for the value being
    assigned like 'foo = [ ... ]'.

    This function solves problems like the jedec-id one.

  - There's a new Property.to_path() function for fetching the
    referenced node for assignments like 'foo = &node;', with type
    checking. (Strings are accepted too, as long as they give the path
    to an existing node.)

    This function is used for /chosen and /aliases.

  - A new 'type: phandle' type can now be given in bindings, for
    properties that are assigned like 'foo = < &node >;'.

  - Property.__str__() now displays phandles and path references as they
    were written (e.g. '< &foo >' instead of '< 0x1 >', if the
    allocated phandle happened to be 1).

  - Property.to_num() and Property.to_nums() no longer take a 'length'
    parameter, because it makes no sense with the type checking.

  - The global dtlib.to_string() and dtlib.to_strings() functions were
    removed, because they're not that useful.

  - More tests were added, along with misc. minor cleanup in various
    places.

  - Probably other stuff I forgot.

The more strict type checking in dtlib indirectly makes some parts of
edtlib more strict as well (wherever Property.to_*() is used).

Fixes: #18131

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2019-08-09 20:38:17 +02:00 committed by Kumar Gala
commit 06b746cc58
11 changed files with 596 additions and 337 deletions

View file

@ -56,7 +56,7 @@ sub-node:
# #
# <property name>: # <property name>:
# category: <required | optional> # category: <required | optional>
# type: <string | int | boolean | array | uint8-array | string-array | compound> # type: <string | int | boolean | array | uint8-array | string-array | phandle | compound>
# description: <description of the property> # description: <description of the property>
# enum: # enum:
# - <item1> # - <item1>
@ -64,12 +64,20 @@ sub-node:
# ... # ...
# - <itemN> # - <itemN>
# #
# 'uint8-array' is our name for what the device tree specification calls # 'type: uint8-array' is for what the device tree specification calls
# 'bytestring'. Properties of type 'uint8-array' should be set like this: # 'bytestring'. Properties of type 'uint8-array' should be set like this:
# #
# foo = [89 AB CD]; # foo = [89 AB CD];
# #
# Each value is a byte in hex. # Each value is a byte in hex.
#
# 'phandle' is for properties that are assigned a single phandle, like this:
#
# foo = <&label>;
#
# 'compound' is a catch-all for more complex types, e.g.
#
# foo = <&label1 1 2 &label2 7>;
properties: properties:
# An entry for 'compatible' must appear, as it's used to map nodes to # An entry for 'compatible' must appear, as it's used to map nodes to
# bindings # bindings

View file

@ -29,7 +29,7 @@ properties:
manual defines a shared FIFO size. manual defines a shared FIFO size.
phys: phys:
type: array type: phandle
category: optional category: optional
description: PHY provider specifier description: PHY provider specifier

View file

@ -29,7 +29,7 @@ properties:
manual defines a shared FIFO size. manual defines a shared FIFO size.
phys: phys:
type: array type: phandle
category: optional category: optional
description: PHY provider specifier description: PHY provider specifier

View file

@ -35,7 +35,7 @@ properties:
the pull-up resistor on USB Data Positive signal. the pull-up resistor on USB Data Positive signal.
phys: phys:
type: array type: phandle
category: optional category: optional
description: PHY provider specifier description: PHY provider specifier

View file

@ -1069,15 +1069,8 @@ class DT:
"include only characters from [0-9a-z-]" "include only characters from [0-9a-z-]"
.format(prop.name)) .format(prop.name))
# Any errors this generates will already point to the property # Property.to_path() already checks that the node exists
# and /aliases alias2node[prop.name] = prop.to_path()
path = prop.to_string()
try:
alias2node[prop.name] = self.get_node(path)
except DTError as e:
raise DTError("/aliases: bad path for '{}': {}"
.format(prop.name, e))
self.alias2node = alias2node self.alias2node = alias2node
@ -1354,6 +1347,32 @@ class Property:
See the to_*() methods for converting the value to other types. See the to_*() methods for converting the value to other types.
type:
The type of the property, inferred from the syntax used in the
assignment. This is one of the following constants (with example
assignments):
Assignment | Property.type
-------------------+------------------------
foo; | dtlib.TYPE_EMPTY
foo = [] | dtlib.TYPE_BYTES
foo = [01 02] | dtlib.TYPE_BYTES
foo = /bits/ 8 <1> | dtlib.TYPE_BYTES
foo = <1> | dtlib.TYPE_NUM
foo = <> | dtlib.TYPE_NUMS
foo = <1 2 3> | dtlib.TYPE_NUMS
foo = <1 2>, <3> | dtlib.TYPE_NUMS
foo = "foo" | dtlib.TYPE_STRING
foo = "foo", "bar" | dtlib.TYPE_STRINGS
foo = <&label> | dtlib.TYPE_PHANDLE
foo = &label | dtlib.TYPE_PATH
*Anything else* | dtlib.TYPE_COMPOUND
*Anything else* includes properties mixing (<&label>) and node path
(&label) references with other data.
Data labels in the property value do not influence the type.
labels: labels:
A list with all labels pointing to the property, in the same order as the A list with all labels pointing to the property, in the same order as the
labels appear, but with duplicates removed. labels appear, but with duplicates removed.
@ -1394,88 +1413,218 @@ class Property:
# to be patched in after parsing. # to be patched in after parsing.
self._markers = [] self._markers = []
def to_num(self, length=4, signed=False): def to_num(self, signed=False):
""" """
Returns the property value interpreted as a number. Returns the value of the property as a number.
length (default: 4): Raises DTError if the property was not assigned with this syntax (has
The expected length of the value in bytes. Raises DTError if it has a Property.type TYPE_NUM):
different length. This is provided as a simple type check.
Four bytes is the length of a cell, so the value of e.g. foo = < 1 >;
'x = < 73 >;' can be fetched with a plain prop.to_num().
If 'length' is None, the entire property value is used, with no
length check.
signed (default: False): signed (default: False):
If True, the value will be interpreted as signed rather than If True, the value will be interpreted as signed rather than
unsigned. unsigned.
""" """
try: if self.type is not TYPE_NUM:
return to_num(self.value, length, signed) raise DTError("expected property '{0}' on {1} in {2} to be "
except DTError as e: "assigned with '{0} = < (number) >;', not '{3}'"
self._err_with_context(e) .format(self.name, self.node.path,
self.node.dt.filename, self))
def to_nums(self, length=4, signed=False): return int.from_bytes(self.value, "big", signed=signed)
def to_nums(self, signed=False):
""" """
Returns the property value interpreted as a list of numbers. Returns the value of the property as a list of numbers.
length (default: 4): Raises DTError if the property was not assigned with this syntax (has
The length in bytes of each number. Raises DTError if the length of Property.type TYPE_NUM or TYPE_NUMS):
the value is not a multiple of 'length'.
foo = < 1 2 ... >;
signed (default: False): signed (default: False):
If True, the values will be interpreted as signed rather than If True, the values will be interpreted as signed rather than
unsigned. unsigned.
""" """
try: if self.type not in (TYPE_NUM, TYPE_NUMS):
return to_nums(self.value, length, signed) raise DTError("expected property '{0}' on {1} in {2} to be "
except DTError as e: "assigned with '{0} = < (number) (number) ... >', "
self._err_with_context(e) "not '{3}'"
.format(self.name, self.node.path,
self.node.dt.filename, self))
return [int.from_bytes(self.value[i:i + 4], "big", signed=signed)
for i in range(0, len(self.value), 4)]
def to_bytes(self):
"""
Returns the value of the property as a raw 'bytes', like
Property.value, except with added type checking.
Raises DTError if the property was not assigned with this syntax (has
Property.type TYPE_BYTES):
foo = [ 01 ... ];
"""
if self.type is not TYPE_BYTES:
raise DTError("expected property '{0}' on {1} in {2} to be "
"assigned with '{0} = [ (byte) (byte) ... ]', "
"not '{3}'".format(self.name, self.node.path,
self.node.dt.filename, self))
return self.value
def to_string(self): def to_string(self):
""" """
Returns the property value interpreted as a string. Returns the value of the property as a string.
Raises DTError if the value is not valid UTF-8, is not null-terminated, Raises DTError if the property was not assigned with this syntax (has
or if contains more than one null terminator (the null terminator is Property.type TYPE_STRING):
stripped from the returned string). Strings in Device Tree (e.g., 'x =
"foo"') are implicitly null-terminated. foo = "string";
This function might also raise UnicodeDecodeError if the string is
not valid UTF-8.
""" """
if self.type is not TYPE_STRING:
raise DTError("expected property '{0}' on {1} in {2} to be "
"assigned with '{0} = \"string\"', not '{3}'"
.format(self.name, self.node.path,
self.node.dt.filename, self))
try: try:
return to_string(self.value) return self.value.decode("utf-8")[:-1] # Strip null
except DTError as e: except UnicodeDecodeError:
self._err_with_context(e) raise DTError("value of property '{}' ({}) on {} in {} is not "
"valid UTF-8"
.format(self.name, self.value, self.node.path,
self.node.dt.filename))
def to_strings(self): def to_strings(self):
""" """
Returns the property value interpreted as a list of strings. Returns the value of the property as a list of strings.
Raises DTError if the value is not valid UTF-8 or is not Raises DTError if the property was not assigned with this syntax (has
null-terminated (the null terminators are stripped from the returned Property.type TYPE_STRING or TYPE_STRINGS):
string). Strings in Device Tree (e.g., 'x = "foo"') are implicitly
null-terminated. foo = "string", "string", ... ;
Also raises DTError if any of the strings are not valid UTF-8.
""" """
if self.type not in (TYPE_STRING, TYPE_STRINGS):
raise DTError("expected property '{0}' on {1} in {2} to be "
"assigned with '{0} = \"string\", \"string\", ...', "
"not {3}"
.format(self.name, self.node.path,
self.node.dt.filename, self))
try: try:
return to_strings(self.value) return self.value.decode("utf-8").split("\0")[:-1]
except DTError as e: except UnicodeDecodeError:
self._err_with_context(e) raise DTError("value of property '{}' ({}) on {} in {} is not "
"valid UTF-8"
.format(self.name, self.value, self.node.path,
self.node.dt.filename))
def to_node(self): def to_node(self):
""" """
Interprets the property value as a phandle and returns the Returns the Node the phandle in the property points to.
corresponding Node.
Raises DTError if the value is not a valid phandle or if no node with Raises DTError if the property was not assigned with either of these
that phandle exists. syntaxes (has Property.type TYPE_PHANDLE or TYPE_NUM).
foo = < &bar >;
foo = < 1 >;
For the second case, DTError is raised if the phandle does not exist.
""" """
phandle = self.to_num() if self.type not in (TYPE_PHANDLE, TYPE_NUM):
raise DTError("expected property '{0}' on {1} in {2} to be "
"assigned with either '{0} = < &foo >' or "
"'{0} = < (valid phandle number) >', not {3}"
.format(self.name, self.node.path,
self.node.dt.filename, self))
phandle = int.from_bytes(self.value, "big")
node = self.node.dt.phandle2node.get(phandle) node = self.node.dt.phandle2node.get(phandle)
if not node: if not node:
self._err_with_context("non-existent phandle " + str(phandle)) raise DTError("the phandle given in property '{}' ({}) on {} in "
"{} does not exist"
.format(self.name, phandle, self.node.path,
self.node.dt.filename))
return node return node
def to_path(self):
"""
Returns the Node referenced by the path stored in the property.
Raises DTError if the property was not assigned with either of these
syntaxes (has Property.type TYPE_PATH or TYPE_STRING):
foo = &bar;
foo = "/bar";
For the second case, DTError is raised if the path does not exist.
"""
if self.type not in (TYPE_PATH, TYPE_STRING):
raise DTError("expected property '{0}' on {1} in {2} to be "
"assigned with either '{0} = &foo' or "
"'{0} = \"/path/to/node\"', not '{3}'"
.format(self.name, self.node.path,
self.node.dt.filename, self))
try:
path = self.value.decode("utf-8")[:-1]
except UnicodeDecodeError:
raise DTError("value of property '{}' ({}) on {} in {} is not "
"valid UTF-8"
.format(self.name, self.value, self.node.path,
self.node.dt.filename))
try:
return self.node.dt.get_node(path)
except DTError:
raise DTError("property '{}' on {} in {} points to the "
'non-existent node "{}"'
.format(self.name, self.node.path,
self.node.dt.filename, path))
@property
def type(self):
"""
See the class docstring.
"""
# Data labels (e.g. 'foo = label: <3>') are irrelevant, so filter them
# out
types = [marker[1] for marker in self._markers
if marker[1] != _REF_LABEL]
if not types:
return TYPE_EMPTY
if types == [_TYPE_UINT8]:
return TYPE_BYTES
if types == [_TYPE_UINT32]:
return TYPE_NUM if len(self.value) == 4 else TYPE_NUMS
# Treat 'foo = <1 2 3>, <4 5>, ...' as TYPE_NUMS too
if set(types) == {_TYPE_UINT32}:
return TYPE_NUMS
if set(types) == {_TYPE_STRING}:
return TYPE_STRING if len(types) == 1 else TYPE_STRINGS
if types == [_REF_PATH]:
return TYPE_PATH
if types == [_TYPE_UINT32, _REF_PHANDLE] and len(self.value) == 4:
return TYPE_PHANDLE
return TYPE_COMPOUND
def __str__(self): def __str__(self):
s = "".join(label + ": " for label in self.labels) + self.name s = "".join(label + ": " for label in self.labels) + self.name
if not self.value: if not self.value:
@ -1492,19 +1641,27 @@ class Property:
# End of current marker # End of current marker
end = next_marker[0] if next_marker else len(self.value) end = next_marker[0] if next_marker else len(self.value)
if marker_type in (_TYPE_STRING, _REF_PATH): if marker_type is _TYPE_STRING:
# end - 1 to strip off the null terminator # end - 1 to strip off the null terminator
s += ' "{}"'.format(_decode_and_escape( s += ' "{}"'.format(_decode_and_escape(
self.value[pos:end - 1])) self.value[pos:end - 1]))
if end != len(self.value): if end != len(self.value):
s += "," s += ","
elif marker_type is _REF_PATH:
s += " &" + ref
if end != len(self.value):
s += ","
else: else:
# Raw data (<>/[]) # <> or []
if marker_type is _REF_LABEL: if marker_type is _REF_LABEL:
s += " {}:".format(ref) s += " {}:".format(ref)
elif marker_type is not _REF_PHANDLE: elif marker_type is _REF_PHANDLE:
# marker_type is _TYPE_UINT_* s += " &" + ref
pos += 4
# Subtle: There might be more data between the phandle and
# the next marker, so we can't 'continue' here
else: # marker_type is _TYPE_UINT*
elm_size = _TYPE_TO_N_BYTES[marker_type] elm_size = _TYPE_TO_N_BYTES[marker_type]
s += _N_BYTES_TO_START_STR[elm_size] s += _N_BYTES_TO_START_STR[elm_size]
@ -1526,7 +1683,6 @@ class Property:
if pos != len(self.value): if pos != len(self.value):
s += "," s += ","
return s + ";" return s + ";"
@ -1566,14 +1722,15 @@ class Property:
def to_num(data, length=None, signed=False): def to_num(data, length=None, signed=False):
""" """
Like Property.to_num(), but takes an arbitrary 'bytes' array. The value is Converts the 'bytes' array 'data' to a number. The value is expected to be
assumed to be in big-endian format, which is standard in Device Tree. in big-endian format, which is standard in Device Tree.
length (default: None): length (default: None):
The expected length of the value in bytes. See Property.to_num(). The expected length of the value in bytes, as a simple type check. If
None, the length check is skipped.
Unlike for Property.to_num(), 'length' defaults to None, meaning to skip signed (default: False):
the length check and use the entire property value. If True, the value will be interpreted as signed rather than unsigned.
""" """
_check_is_bytes(data) _check_is_bytes(data)
if length is not None: if length is not None:
@ -1601,35 +1758,20 @@ def to_nums(data, length=4, signed=False):
for i in range(0, len(data), length)] for i in range(0, len(data), length)]
def to_string(data): #
""" # Public constants
Like Property.to_string(), but takes an arbitrary 'bytes' array. The string #
should be null-terminated, which is standard in Device Tree. The
null terminator is stripped from the returned value.
"""
strings = to_strings(data)
if len(strings) != 1:
raise DTError("{} contains more than one string".format(data))
return strings[0]
# See Property.type
def to_strings(data): TYPE_EMPTY = 0
""" TYPE_BYTES = 1
Like Property.to_strings(), but takes an arbitrary 'bytes' array. The TYPE_NUM = 2
strings should be null-terminated, which is standard in Device Tree. The TYPE_NUMS = 3
null terminators are stripped from the returned value. TYPE_STRING = 4
""" TYPE_STRINGS = 5
_check_is_bytes(data) TYPE_PATH = 6
TYPE_PHANDLE = 7
try: TYPE_COMPOUND = 8
s = data.decode("utf-8")
except UnicodeDecodeError:
raise DTError("{} is not valid UTF-8".format(data))
if not s.endswith("\0"):
raise DTError("{} is not null-terminated".format(data))
return s.split("\0")[:-1]
def _check_is_bytes(data): def _check_is_bytes(data):
@ -1640,7 +1782,7 @@ def _check_is_bytes(data):
def _check_length_positive(length): def _check_length_positive(length):
if length < 1: if length < 1:
raise DTError("'size' must be greater than zero, was " + str(length)) raise DTError("'length' must be greater than zero, was " + str(length))
def _append_no_dup(lst, elm): def _append_no_dup(lst, elm):

View file

@ -29,7 +29,7 @@ import sys
import yaml import yaml
from dtlib import DT, DTError, to_num, to_nums from dtlib import DT, DTError, to_num, to_nums, TYPE_EMPTY
# NOTE: testedtlib.py is the test suite for this library. It can be run # NOTE: testedtlib.py is the test suite for this library. It can be run
# directly. # directly.
@ -137,12 +137,8 @@ class EDT:
if name not in chosen.props: if name not in chosen.props:
return None return None
path = chosen.props[name].to_string() # to_path() checks that the node exists
try: return self._node2dev[chosen.props[name].to_path()]
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_dirs): def _init_compat2binding(self, bindings_dirs):
# Creates self._compat2binding. This is a dictionary that maps # Creates self._compat2binding. This is a dictionary that maps
@ -224,15 +220,29 @@ class EDT:
self.devices = [] self.devices = []
for node in self._dt.node_iter(): for node in self._dt.node_iter():
# Warning: Device.__init__() relies on parent Devices being created # Warning: We depend on parent Devices being created before their
# before their children. This is guaranteed by node_iter(). # children. This is guaranteed by node_iter().
dev = Device(self, node) dev = Device()
dev.edt = self
dev._node = node
dev._init_binding()
dev._init_regs()
dev._set_instance_no()
self.devices.append(dev) self.devices.append(dev)
self._node2dev[node] = dev self._node2dev[node] = dev
for dev in self.devices: for dev in self.devices:
# These depend on all Device objects having been created, so we do # Device._init_props() depends on all Device objects having been
# them separately # created, due to 'type: phandle', so we run it separately.
# Property.val is set to the pointed-to Device instance for
# phandles, which must exist.
dev._init_props()
for dev in self.devices:
# These also depend on all Device objects having been created, and
# might also depend on all Device.props having been initialized
# (_init_clocks() does as of writing).
dev._init_interrupts() dev._init_interrupts()
dev._init_gpios() dev._init_gpios()
dev._init_pwms() dev._init_pwms()
@ -440,21 +450,6 @@ class Device:
"binding " + self.binding_path if self.binding_path "binding " + self.binding_path if self.binding_path
else "no binding") else "no binding")
def __init__(self, edt, node):
"Private constructor. Not meant to be called by clients."
# Interrupts, GPIOs, PWMs, io-channels, 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): def _init_binding(self):
# Initializes Device.matching_compat, Device._binding, and # Initializes Device.matching_compat, Device._binding, and
# Device.binding_path. # Device.binding_path.
@ -578,12 +573,17 @@ class Device:
# _init_prop() helper for getting the property's value # _init_prop() helper for getting the property's value
node = self._node node = self._node
prop = node.props.get(name)
if prop_type == "boolean": if prop_type == "boolean":
# True/False if not prop:
return name in node.props return False
if prop.type is not TYPE_EMPTY:
_err("'{0}' in {1!r} is defined with 'type: boolean' in {2}, "
"but is assigned a value ('{3}') instead of being empty "
"('{0};')".format(name, node, self.binding_path, prop))
return True
prop = node.props.get(name)
if not prop: if not prop:
if not optional and self.enabled: if not optional and self.enabled:
_err("'{}' is marked as required in 'properties:' in {}, but " _err("'{}' is marked as required in 'properties:' in {}, but "
@ -599,7 +599,7 @@ class Device:
return prop.to_nums() return prop.to_nums()
if prop_type == "uint8-array": if prop_type == "uint8-array":
return prop.value # Plain 'bytes' return prop.to_bytes()
if prop_type == "string": if prop_type == "string":
return prop.to_string() return prop.to_string()
@ -607,6 +607,9 @@ class Device:
if prop_type == "string-array": if prop_type == "string-array":
return prop.to_strings() return prop.to_strings()
if prop_type == "phandle":
return self.edt._node2dev[prop.to_node()]
_err("'{}' in 'properties:' in {} has unknown type '{}'" _err("'{}' in 'properties:' in {} has unknown type '{}'"
.format(name, self.binding_path, prop_type)) .format(name, self.binding_path, prop_type))
@ -1013,8 +1016,9 @@ class Property:
if missing. Trailing whitespace (including newlines) is removed. if missing. Trailing whitespace (including newlines) is removed.
val: val:
The value of the property, with the format determined by the 'type:' The value of the property, with the format determined by the 'type:' key
key from the binding from the binding. For 'type: phandle' properties, this is the pointed-to
Device instance.
enum_index: enum_index:
The index of the property's value in the 'enum:' list in the binding, or The index of the property's value in the 'enum:' list in the binding, or

View file

@ -154,6 +154,10 @@ def write_props(dev):
if prop.name[0] == "#" or prop.name.endswith("-map"): if prop.name[0] == "#" or prop.name.endswith("-map"):
continue continue
# Skip phandles
if isinstance(prop.val, edtlib.Device):
continue
# Skip properties that we handle elsewhere # Skip properties that we handle elsewhere
if prop.name in {"reg", "interrupts", "compatible", "interrupt-controller", if prop.name in {"reg", "interrupts", "compatible", "interrupt-controller",
"gpio-controller"}: "gpio-controller"}:

View file

@ -8,6 +8,12 @@ properties:
constraint: "props" constraint: "props"
type: string-array type: string-array
nonexistent-boolean:
type: boolean
existent-boolean:
type: boolean
int: int:
type: int type: int
@ -22,3 +28,6 @@ properties:
string-array: string-array:
type: string-array type: string-array
phandle-ref:
type: phandle

View file

@ -290,14 +290,19 @@
props { props {
compatible = "props"; compatible = "props";
existent-boolean;
int = <1>; int = <1>;
array = <1 2 3>; array = <1 2 3>;
uint8-array = [ 12 34 ]; uint8-array = [ 12 34 ];
string = "foo"; string = "foo";
string-array = "foo", "bar", "baz"; string-array = "foo", "bar", "baz";
phandle-ref = < &{/props/node} >;
// Does not appear in the binding, so won't create an entry in // Does not appear in the binding, so won't create an entry in
// Device.props // Device.props
not-speced = <0>; not-speced = <0>;
node {
};
}; };
// //

View file

@ -422,7 +422,7 @@ l3: &l1 {
/dts-v1/; /dts-v1/;
/ { / {
a = l01: l02: < l03: 0x1 l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A"; a = l01: l02: < l03: &node l04: l05: 0x2 l06: l07: l08: >, [ l09: 03 l10: l11: 04 l12: l13: l14: ], "A";
b = < 0x0 l23: l24: >; b = < 0x0 l23: l24: >;
node: node { node: node {
phandle = < 0x1 >; phandle = < 0x1 >;
@ -504,13 +504,13 @@ l3: &l1 {
/dts-v1/; /dts-v1/;
/ { / {
a = "/abc"; a = &label;
b = [ 01 ], "/abc"; b = [ 01 ], &label;
c = [ 01 ], "/abc", < 0x2 >; c = [ 01 ], &label, < 0x2 >;
d = "/abc"; d = &{/abc};
label: abc { label: abc {
e = "/abc"; e = &label;
f = "/abc"; f = &{/abc};
}; };
}; };
""") """)
@ -541,7 +541,6 @@ l3: &l1 {
# Test phandles # Test phandles
# #
# Check that existing phandles are used (and not reused)
verify_parse(""" verify_parse("""
/dts-v1/; /dts-v1/;
@ -571,7 +570,7 @@ l3: &l1 {
/dts-v1/; /dts-v1/;
/ { / {
x = < 0x2 0x4 0xff >; x = < &a &{/b} &c >;
dummy1 { dummy1 {
phandle = < 0x1 >; phandle = < 0x1 >;
}; };
@ -617,10 +616,10 @@ l3: &l1 {
phandle = < 0x1 >; phandle = < 0x1 >;
}; };
a { a {
foo: phandle = < 0x2 >; foo: phandle = < &{/a} >;
}; };
label: b { label: b {
bar: phandle = < 0x3 >; bar: phandle = < &label >;
}; };
}; };
""") """)
@ -765,7 +764,7 @@ l3: &l1 {
/dts-v1/; /dts-v1/;
/ { / {
x = [ FF FF ], "/abc", < 0xff 0x1 0xff 0x1 >, "/abc", [ FF FF ], "abc"; x = [ FF FF ], &abc, < 0xff &abc 0xff &abc >, &abc, [ FF FF ], "abc";
abc: abc { abc: abc {
phandle = < 0x1 >; phandle = < 0x1 >;
}; };
@ -1007,7 +1006,7 @@ y /include/ "via-include-path-1"
/dts-v1/; /dts-v1/;
/ { / {
x = < 0x1 >, "/referenced2"; x = < &{/referenced} >, &referenced2;
referenced { referenced {
phandle = < 0x1 >; phandle = < 0x1 >;
}; };
@ -1299,7 +1298,7 @@ foo
alias1 = &l1; alias1 = &l1;
alias2 = &l2; alias2 = &l2;
alias3 = &{/sub/node3}; alias3 = &{/sub/node3};
alias4 = [2F 6E 6F 64 65 34 00]; // "/node4"; alias4 = &{/node4};
}; };
l1: node1 { l1: node1 {
@ -1323,7 +1322,6 @@ foo
verify_alias_target("alias1", "node1") verify_alias_target("alias1", "node1")
verify_alias_target("alias2", "node2") verify_alias_target("alias2", "node2")
verify_alias_target("alias3", "node3") verify_alias_target("alias3", "node3")
verify_alias_target("alias4", "node4")
verify_path_is("alias4/node5", "node5") verify_path_is("alias4/node5", "node5")
verify_path_error("alias4/node5/node6", verify_path_error("alias4/node5/node6",
@ -1332,6 +1330,28 @@ foo
verify_error(""" verify_error("""
/dts-v1/; /dts-v1/;
/ {
aliases {
a = [ 00 ];
};
};
""",
"expected property 'a' on /aliases in .tmp.dts to be assigned with either 'a = &foo' or 'a = \"/path/to/node\"', not 'a = [ 00 ];'")
verify_error(r"""
/dts-v1/;
/ {
aliases {
a = "\xFF";
};
};
""",
r"value of property 'a' (b'\xff\x00') on /aliases in .tmp.dts is not valid UTF-8")
verify_error("""
/dts-v1/;
/ { / {
aliases { aliases {
A = "/aliases"; A = "/aliases";
@ -1343,58 +1363,132 @@ foo
verify_error(r""" verify_error(r"""
/dts-v1/; /dts-v1/;
/ {
aliases {
a = "\xFF";
};
};
""",
r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
verify_error(r"""
/dts-v1/;
/ {
aliases {
a = [ 41 ]; // "A"
};
};
""",
"b'A' is not null-terminated (for property 'a' on /aliases)")
verify_error(r"""
/dts-v1/;
/ { / {
aliases { aliases {
a = "/missing"; a = "/missing";
}; };
}; };
""", """,
"/aliases: bad path for 'a': component 1 ('missing') in path '/missing' does not exist") "property 'a' on /aliases in .tmp.dts points to the non-existent node \"/missing\"")
# #
# Test to_{num,nums,string,strings,node}() # Test Property.type
# #
def verify_to_num(prop, size, signed, expected): def verify_type(prop, expected):
actual = dt.root.props[prop].type
if actual != expected:
fail("expected {} to have type {}, had type {}"
.format(prop, expected, actual))
dt = parse("""
/dts-v1/;
/ {
empty;
bytes1 = [ ];
bytes2 = [ 01 ];
bytes3 = [ 01 02 ];
bytes4 = foo: [ 01 bar: 02 ];
bytes5 = /bits/ 8 < 1 2 3 >;
num = < 1 >;
nums1 = < >;
nums2 = < >, < >;
nums3 = < 1 2 >;
nums4 = < 1 2 >, < 3 >, < 4 >;
string = "foo";
strings = "foo", "bar";
phandle1 = < &node >;
phandle2 = < &{/node} >;
path1 = &node;
path2 = &{/node};
compound1 = < 1 >, [ 02 ];
compound2 = "foo", < >;
compound3 = < 1 &{/node} 2>;
node: node {
};
};
""")
verify_type("empty", dtlib.TYPE_EMPTY)
verify_type("bytes1", dtlib.TYPE_BYTES)
verify_type("bytes2", dtlib.TYPE_BYTES)
verify_type("bytes3", dtlib.TYPE_BYTES)
verify_type("bytes4", dtlib.TYPE_BYTES)
verify_type("bytes5", dtlib.TYPE_BYTES)
verify_type("num", dtlib.TYPE_NUM)
verify_type("nums1", dtlib.TYPE_NUMS)
verify_type("nums2", dtlib.TYPE_NUMS)
verify_type("nums3", dtlib.TYPE_NUMS)
verify_type("nums4", dtlib.TYPE_NUMS)
verify_type("string", dtlib.TYPE_STRING)
verify_type("strings", dtlib.TYPE_STRINGS)
verify_type("phandle1", dtlib.TYPE_PHANDLE)
verify_type("phandle2", dtlib.TYPE_PHANDLE)
verify_type("path1", dtlib.TYPE_PATH)
verify_type("path2", dtlib.TYPE_PATH)
verify_type("compound1", dtlib.TYPE_COMPOUND)
verify_type("compound2", dtlib.TYPE_COMPOUND)
verify_type("compound3", dtlib.TYPE_COMPOUND)
#
# Test Property.to_{num,nums,string,strings,node}()
#
dt = parse(r"""
/dts-v1/;
/ {
u = < 1 >;
s = < 0xFFFFFFFF >;
u8 = /bits/ 8 < 1 >;
u16 = /bits/ 16 < 1 2 >;
u64 = /bits/ 64 < 1 >;
bytes = [ 01 02 03 ];
empty;
zero = < >;
two_u = < 1 2 >;
two_s = < 0xFFFFFFFF 0xFFFFFFFE >;
three_u = < 1 2 3 >;
three_u_split = < 1 >, < 2 >, < 3 >;
empty_string = "";
string = "foo\tbar baz";
invalid_string = "\xff";
strings = "foo", "bar", "baz";
invalid_strings = "foo", "\xff", "bar";
ref = <&{/target}>;
manualref = < 100 >;
missingref = < 123 >;
path = &{/target};
manualpath = "/target";
missingpath = "/missing";
target {
phandle = < 100 >;
};
};
""")
# Test Property.to_num()
def verify_to_num(prop, signed, expected):
try: try:
actual = dt.root.props[prop].to_num(size, signed) actual = dt.root.props[prop].to_num(signed)
except dtlib.DTError as e: except dtlib.DTError as e:
fail("failed to convert {} to {} number with {} bytes: {}" fail("failed to convert '{}' to {} number: {}"
.format(prop, "a signed" if signed else "an unsigned", size, .format(prop, "a signed" if signed else "an unsigned", e))
e))
if actual != expected: if actual != expected:
fail("expected {} to have the {} numeric value {:#x}, had the " fail("expected {} to have the {} numeric value {:#x}, had the "
"value {:#x}".format(prop, "signed" if signed else "unsigned", "value {:#x}".format(prop, "signed" if signed else "unsigned",
expected, actual)) expected, actual))
def verify_to_num_error(prop, size, msg): def verify_to_num_error(prop, msg):
prefix = "expected {} converted from {} bytes to generate the error " \ prefix = "expected fetching '{}' as a number to generate the error " \
"'{}', generated".format(prop, size, msg) "'{}', generated".format(prop, msg)
try: try:
dt.root.props[prop].to_num(size) dt.root.props[prop].to_num()
fail(prefix + " no error") fail(prefix + " no error")
except dtlib.DTError as e: except dtlib.DTError as e:
if str(e) != msg: if str(e) != msg:
@ -1402,23 +1496,36 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
def verify_to_nums(prop, size, signed, expected): verify_to_num("u", False, 1)
verify_to_num("u", True, 1)
verify_to_num("s", False, 0xFFFFFFFF)
verify_to_num("s", True, -1)
verify_to_num_error("two_u", "expected property 'two_u' on / in .tmp.dts to be assigned with 'two_u = < (number) >;', not 'two_u = < 0x1 0x2 >;'")
verify_to_num_error("u8", "expected property 'u8' on / in .tmp.dts to be assigned with 'u8 = < (number) >;', not 'u8 = [ 01 ];'")
verify_to_num_error("u16", "expected property 'u16' on / in .tmp.dts to be assigned with 'u16 = < (number) >;', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")
verify_to_num_error("u64", "expected property 'u64' on / in .tmp.dts to be assigned with 'u64 = < (number) >;', not 'u64 = /bits/ 64 < 0x1 >;'")
verify_to_num_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < (number) >;', not 'string = \"foo\\tbar baz\";'")
# Test Property.to_nums()
def verify_to_nums(prop, signed, expected):
try: try:
actual = dt.root.props[prop].to_nums(size, signed) actual = dt.root.props[prop].to_nums(signed)
except dtlib.DTError as e: except dtlib.DTError as e:
fail("failed to convert {} to {} numbers with {} bytes each: {}" fail("failed to convert '{}' to {} numbers: {}"
.format(prop, "signed" if signed else "unsigned", size, e)) .format(prop, "signed" if signed else "unsigned", e))
if actual != expected: if actual != expected:
fail("expected {} to give the {} numbers {} for size {}, gave {}" fail("expected {} to give the {} numbers {}, gave {}"
.format(prop, "signed" if signed else "unsigned", expected, .format(prop, "signed" if signed else "unsigned", expected,
size, actual)) actual))
def verify_to_nums_error(prop, size, msg): def verify_to_nums_error(prop, msg):
prefix = "expected {} converted to numbers with {} bytes each to " \ prefix = "expected converting '{}' to numbers to generate the error " \
"generate the error '{}', generated".format(prop, size, msg) "'{}', generated".format(prop, msg)
try: try:
dt.root.props[prop].to_nums(size) dt.root.props[prop].to_nums()
fail(prefix + " no error") fail(prefix + " no error")
except dtlib.DTError as e: except dtlib.DTError as e:
if str(e) != msg: if str(e) != msg:
@ -1426,11 +1533,35 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
def verify_raw_to_num_error(fn, data, size, msg): verify_to_nums("zero", False, [])
prefix = "expected {}() called with data='{}', size='{}' to generate " \ verify_to_nums("u", False, [1])
"the error '{}', generated".format(fn.__name__, data, size, msg) verify_to_nums("two_u", False, [1, 2])
verify_to_nums("two_u", True, [1, 2])
verify_to_nums("two_s", False, [0xFFFFFFFF, 0xFFFFFFFE])
verify_to_nums("two_s", True, [-1, -2])
verify_to_nums("three_u", False, [1, 2, 3])
verify_to_nums("three_u_split", False, [1, 2, 3])
verify_to_nums_error("empty", "expected property 'empty' on / in .tmp.dts to be assigned with 'empty = < (number) (number) ... >', not 'empty;'")
verify_to_nums_error("string", "expected property 'string' on / in .tmp.dts to be assigned with 'string = < (number) (number) ... >', not 'string = \"foo\\tbar baz\";'")
# Test Property.to_bytes()
def verify_to_bytes(prop, expected):
try: try:
dtlib.to_num(data, size) actual = dt.root.props[prop].to_bytes()
except dtlib.DTError as e:
fail("failed to convert '{}' to bytes: {}".format(prop, e))
if actual != expected:
fail("expected {} to give the bytes {}, gave {}"
.format(prop, expected, actual))
def verify_to_bytes_error(prop, msg):
prefix = "expected converting '{}' to bytes to generate the error " \
"'{}', generated".format(prop, msg)
try:
dt.root.props[prop].to_bytes()
fail(prefix + " no error") fail(prefix + " no error")
except dtlib.DTError as e: except dtlib.DTError as e:
if str(e) != msg: if str(e) != msg:
@ -1438,18 +1569,26 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
verify_to_bytes("u8", b"\x01")
verify_to_bytes("bytes", b"\x01\x02\x03")
verify_to_bytes_error("u16", "expected property 'u16' on / in .tmp.dts to be assigned with 'u16 = [ (byte) (byte) ... ]', not 'u16 = /bits/ 16 < 0x1 0x2 >;'")
verify_to_bytes_error("empty", "expected property 'empty' on / in .tmp.dts to be assigned with 'empty = [ (byte) (byte) ... ]', not 'empty;'")
# Test Property.to_string()
def verify_to_string(prop, expected): def verify_to_string(prop, expected):
try: try:
actual = dt.root.props[prop].to_string() actual = dt.root.props[prop].to_string()
except dtlib.DTError as e: except dtlib.DTError as e:
fail("failed to convert {} to string: {}".format(prop, e)) fail("failed to convert '{}' to string: {}".format(prop, e))
if actual != expected: if actual != expected:
fail("expected {} to have the value '{}', had the value '{}'" fail("expected {} to have the value '{}', had the value '{}'"
.format(prop, expected, actual)) .format(prop, expected, actual))
def verify_to_string_error(prop, msg): def verify_to_string_error(prop, msg):
prefix = "expected converting {} to string to generate the error " \ prefix = "expected converting '{}' to string to generate the error " \
"'{}', generated".format(prop, msg) "'{}', generated".format(prop, msg)
try: try:
dt.root.props[prop].to_string() dt.root.props[prop].to_string()
@ -1460,33 +1599,30 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
def verify_raw_to_string_error(data, msg): verify_to_string("empty_string", "")
prefix = "expected to_string() called with data='{}' to generate " \ verify_to_string("string", "foo\tbar baz")
"the error '{}', generated".format(data, msg)
try: verify_to_string_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = \"string\"', not 'u = < 0x1 >;'")
dtlib.to_string(data) verify_to_string_error("strings", "expected property 'strings' on / in .tmp.dts to be assigned with 'strings = \"string\"', not 'strings = \"foo\", \"bar\", \"baz\";'")
fail(prefix + " no error") verify_to_string_error("invalid_string", r"value of property 'invalid_string' (b'\xff\x00') on / in .tmp.dts is not valid UTF-8")
except dtlib.DTError as e:
if str(e) != msg: # Test Property.to_strings()
fail("{} the error '{}'".format(prefix, e))
except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e))
def verify_to_strings(prop, expected): def verify_to_strings(prop, expected):
try: try:
actual = dt.root.props[prop].to_strings() actual = dt.root.props[prop].to_strings()
except dtlib.DTError as e: except dtlib.DTError as e:
fail("failed to convert {} to strings: {}".format(prop, e)) fail("failed to convert '{}' to strings: {}".format(prop, e))
if actual != expected: if actual != expected:
fail("expected {} to have the value '{}', had the value '{}'" fail("expected {} to have the value '{}', had the value '{}'"
.format(prop, expected, actual)) .format(prop, expected, actual))
def verify_to_strings_error(prop, msg): def verify_to_strings_error(prop, msg):
prefix = "expected converting {} to strings to generate the error " \ prefix = "expected converting '{}' to strings to generate the error " \
"'{}', generated".format(prop, msg) "'{}', generated".format(prop, msg)
try: try:
dt.root.props[prop].to_string() dt.root.props[prop].to_strings()
fail(prefix + " no error") fail(prefix + " no error")
except dtlib.DTError as e: except dtlib.DTError as e:
if str(e) != msg: if str(e) != msg:
@ -1494,18 +1630,27 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
verify_to_strings("empty_string", [""])
verify_to_strings("string", ["foo\tbar baz"])
verify_to_strings("strings", ["foo", "bar", "baz"])
verify_to_strings_error("u", "expected property 'u' on / in .tmp.dts to be assigned with 'u = \"string\", \"string\", ...', not u = < 0x1 >;")
verify_to_strings_error("invalid_strings", r"value of property 'invalid_strings' (b'foo\x00\xff\x00bar\x00') on / in .tmp.dts is not valid UTF-8")
# Test Property.to_node()
def verify_to_node(prop, path): def verify_to_node(prop, path):
try: try:
actual = dt.root.props[prop].to_node().path actual = dt.root.props[prop].to_node().path
except dtlib.DTError as e: except dtlib.DTError as e:
fail("failed to convert {} to node: {}".format(prop, e)) fail("failed to convert '{}' to node: {}".format(prop, e))
if actual != path: if actual != path:
fail("expected {} to point to {}, pointed to {}" fail("expected {} to point to {}, pointed to {}"
.format(prop, path, actual)) .format(prop, path, actual))
def verify_to_node_error(prop, msg): def verify_to_node_error(prop, msg):
prefix = "expected converting {} to node to generate the error " \ prefix = "expected converting '{}' to a node to generate the error " \
"'{}', generated".format(prop, msg) "'{}', generated".format(prop, msg)
try: try:
dt.root.props[prop].to_node() dt.root.props[prop].to_node()
@ -1516,140 +1661,82 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
except Exception as e: except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e)) fail("{} the non-DTError '{}'".format(prefix, e))
dt = parse(r""" verify_to_node("ref", "/target")
/dts-v1/; verify_to_node("manualref", "/target")
/ { verify_to_node_error("string", "expected property 'string' on / in .tmp.dts to be assigned with either 'string = < &foo >' or 'string = < (valid phandle number) >', not string = \"foo\\tbar baz\";")
empty; verify_to_node_error("missingref", "the phandle given in property 'missingref' (123) on / in .tmp.dts does not exist")
u1 = /bits/ 8 < 0x01 >;
u2 = /bits/ 8 < 0x01 0x02 >;
u3 = /bits/ 8 < 0x01 0x02 0x03 >;
u4 = /bits/ 8 < 0x01 0x02 0x03 0x04 >;
s1 = /bits/ 8 < 0xFF >;
s2 = /bits/ 8 < 0xFF 0xFE >;
s3 = /bits/ 8 < 0xFF 0xFF 0xFD >;
s4 = /bits/ 8 < 0xFF 0xFF 0xFF 0xFC >;
empty_string = "";
string = "foo\tbar baz";
invalid_string = "\xff";
non_null_terminated_string = [ 41 ]; // A
strings = "foo", "bar", "baz";
invalid_strings = "foo", "\xff", "bar";
non_null_terminated_strings = "foo", "bar", [ 01 ];
ref = <&{/target}>;
missingref = < 123 >;
badref;
target { # Test Property.to_path()
};
};
""")
# Test to_num() def verify_to_path(prop, path):
try:
actual = dt.root.props[prop].to_path().path
except dtlib.DTError as e:
fail("failed to convert '{}' to path: {}".format(prop, e))
verify_to_num("u1", 1, False, 0x01) if actual != path:
verify_to_num("u2", 2, False, 0x0102) fail("expected {} to contain the path {}, contained {}"
verify_to_num("u3", 3, False, 0x010203) .format(prop, path, actual))
verify_to_num("u4", 4, False, 0x01020304)
verify_to_num("s1", 1, False, 0xFF)
verify_to_num("s2", 2, False, 0xFFFE)
verify_to_num("s3", 3, False, 0xFFFFFD)
verify_to_num("s4", 4, False, 0xFFFFFFFC)
verify_to_num("u1", 1, True, 0x01) def verify_to_path_error(prop, msg):
verify_to_num("u2", 2, True, 0x0102) prefix = "expected converting '{}' to a path to generate the error " \
verify_to_num("u3", 3, True, 0x010203) "'{}', generated".format(prop, msg)
verify_to_num("u4", 4, True, 0x01020304) try:
verify_to_num("s1", 1, True, -1) dt.root.props[prop].to_path()
verify_to_num("s2", 2, True, -2) fail(prefix + " no error")
verify_to_num("s3", 3, True, -3) except dtlib.DTError as e:
verify_to_num("s4", 4, True, -4) if str(e) != msg:
fail("{} the error '{}'".format(prefix, e))
except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e))
verify_to_num("u1", None, False, 0x01) verify_to_path("path", "/target")
verify_to_num("u2", None, False, 0x0102) verify_to_path("manualpath", "/target")
verify_to_num("u3", None, False, 0x010203)
verify_to_num("u4", None, False, 0x01020304)
verify_to_num("s1", None, False, 0xFF)
verify_to_num("s2", None, False, 0xFFFE)
verify_to_num("s3", None, False, 0xFFFFFD)
verify_to_num("s4", None, False, 0xFFFFFFFC)
verify_to_num("u1", None, True, 0x01) verify_to_path_error("u", "expected property 'u' on / in .tmp.dts to be assigned with either 'u = &foo' or 'u = \"/path/to/node\"', not 'u = < 0x1 >;'")
verify_to_num("u2", None, True, 0x0102) verify_to_path_error("missingpath", "property 'missingpath' on / in .tmp.dts points to the non-existent node \"/missing\"")
verify_to_num("u3", None, True, 0x010203)
verify_to_num("u4", None, True, 0x01020304)
verify_to_num("s1", None, True, -1)
verify_to_num("s2", None, True, -2)
verify_to_num("s3", None, True, -3)
verify_to_num("s4", None, True, -4)
verify_to_num_error("u1", 0, "'size' must be greater than zero, was 0 (for property 'u1' on /)") # Test top-level to_num() and to_nums()
verify_to_num_error("u1", -1, "'size' must be greater than zero, was -1 (for property 'u1' on /)")
verify_to_num_error("u1", 2, r"b'\x01' is 1 bytes long, expected 2 (for property 'u1' on /)") def verify_raw_to_num(fn, prop, length, signed, expected):
verify_to_num_error("u2", 1, r"b'\x01\x02' is 2 bytes long, expected 1 (for property 'u2' on /)") try:
actual = fn(dt.root.props[prop].value, length, signed)
except dtlib.DTError as e:
fail("failed to convert '{}' to {} number(s) with {}: {}"
.format(prop, "signed" if signed else "unsigned",
fn.__name__, e))
if actual != expected:
fail("expected {}(<{}>, {}, {}) to be {}, was {}"
.format(fn.__name__, prop, length, signed, expected, actual))
def verify_raw_to_num_error(fn, data, length, msg):
prefix = "expected {}() called with data='{}', length='{}' to " \
"generate the error '{}', generated" \
.format(fn.__name__, data, length, msg)
try:
fn(data, length)
fail(prefix + " no error")
except dtlib.DTError as e:
if str(e) != msg:
fail("{} the error '{}'".format(prefix, e))
except Exception as e:
fail("{} the non-DTError '{}'".format(prefix, e))
verify_raw_to_num(dtlib.to_num, "u", None, False, 1)
verify_raw_to_num(dtlib.to_num, "u", 4, False, 1)
verify_raw_to_num(dtlib.to_num, "s", None, False, 0xFFFFFFFF)
verify_raw_to_num(dtlib.to_num, "s", None, True, -1)
verify_raw_to_num(dtlib.to_nums, "empty", 4, False, [])
verify_raw_to_num(dtlib.to_nums, "u16", 2, False, [1, 2])
verify_raw_to_num(dtlib.to_nums, "two_s", 4, False, [0xFFFFFFFF, 0xFFFFFFFE])
verify_raw_to_num(dtlib.to_nums, "two_s", 4, True, [-1, -2])
verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'") verify_raw_to_num_error(dtlib.to_num, 0, 0, "'0' has type 'int', expected 'bytes'")
verify_raw_to_num_error(dtlib.to_num, b"", 0, "'size' must be greater than zero, was 0") verify_raw_to_num_error(dtlib.to_num, b"", 0, "'length' must be greater than zero, was 0")
# Test to_nums()
verify_to_nums("empty", 1, False, [])
verify_to_nums("u1", 1, False, [1])
verify_to_nums("u2", 1, False, [1, 2])
verify_to_nums("u3", 1, False, [1, 2, 3])
verify_to_nums("u4", 1, False, [1, 2, 3, 4])
verify_to_nums("s1", 1, False, [0xFF])
verify_to_nums("s2", 1, False, [0xFF, 0xFE])
verify_to_nums("s3", 1, False, [0xFF, 0xFF, 0xFD])
verify_to_nums("s4", 1, False, [0xFF, 0xFF, 0xFF, 0xFC])
verify_to_nums("u2", 2, False, [0x0102])
verify_to_nums("u4", 2, False, [0x0102, 0x0304])
verify_to_nums("u1", 1, True, [1])
verify_to_nums("u2", 1, True, [1, 2])
verify_to_nums("u3", 1, True, [1, 2, 3])
verify_to_nums("u4", 1, True, [1, 2, 3, 4])
verify_to_nums("s1", 1, True, [-1])
verify_to_nums("s2", 1, True, [-1, -2])
verify_to_nums("s3", 1, True, [-1, -1, -3])
verify_to_nums("s4", 1, True, [-1, -1, -1, -4])
verify_to_nums("s2", 2, True, [-2])
verify_to_nums("s4", 2, True, [-1, -4])
verify_to_nums_error("u1", 0, "'size' must be greater than zero, was 0 (for property 'u1' on /)")
verify_to_nums_error("u1", 2, r"b'\x01' is 1 bytes long, expected a length that's a multiple of 2 (for property 'u1' on /)")
verify_to_nums_error("u2", 3, r"b'\x01\x02' is 2 bytes long, expected a length that's a multiple of 3 (for property 'u2' on /)")
verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'") verify_raw_to_num_error(dtlib.to_nums, 0, 0, "'0' has type 'int', expected 'bytes'")
verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'size' must be greater than zero, was 0") verify_raw_to_num_error(dtlib.to_nums, b"", 0, "'length' must be greater than zero, was 0")
# Test to_string()
verify_to_string("empty_string", "")
verify_to_string("string", "foo\tbar baz")
verify_to_string_error("invalid_string", r"b'\xff\x00' is not valid UTF-8 (for property 'invalid_string' on /)")
verify_to_string_error("non_null_terminated_string", "b'A' is not null-terminated (for property 'non_null_terminated_string' on /)")
verify_raw_to_string_error(0, "'0' has type 'int', expected 'bytes'")
# Test to_strings()
verify_to_strings("empty_string", [""])
verify_to_strings("string", ["foo\tbar baz"])
verify_to_strings("strings", ["foo", "bar", "baz"])
verify_to_strings_error("invalid_strings", r"b'foo\x00\xff\x00bar\x00' is not valid UTF-8 (for property 'invalid_strings' on /)")
verify_to_strings_error("non_null_terminated_strings", r"b'foo\x00bar\x00\x01' is not null-terminated (for property 'non_null_terminated_strings' on /)")
# Test to_node()
verify_to_node("ref", "/target")
verify_to_node_error("missingref", "non-existent phandle 123 (for property 'missingref' on /)")
verify_to_node_error("badref", "b'' is 0 bytes long, expected 4 (for property 'badref' on /)")
# #
# Test duplicate label error # Test duplicate label error
@ -1820,7 +1907,7 @@ r"b'\xff\x00' is not valid UTF-8 (for property 'a' on /aliases)")
/ { / {
label: foo { label: foo {
x = "/foo", "/foo", < 0x1 >; x = &{/foo}, &label, < &label >;
phandle = < 0x1 >; phandle = < 0x1 >;
}; };
}; };
@ -1907,7 +1994,7 @@ l1: l2: /memreserve/ 0x0000000000000002 0x0000000000000004;
/dts-v1/; /dts-v1/;
/ { / {
aA0,._+*#?- = "/aA0,._+*#?@-", "/aA0,._+*#?@-"; aA0,._+*#?- = &_, &{/aA0,._+*#?@-};
+ = [ 00 ]; + = [ 00 ];
* = [ 02 ]; * = [ 02 ];
- = [ 01 ]; - = [ 01 ];

View file

@ -120,7 +120,7 @@ def run():
# #
verify_streq(edt.get_dev("/props").props, verify_streq(edt.get_dev("/props").props,
r"{'compatible': <Property, name: compatible, value: ['props']>, 'int': <Property, name: int, value: 1>, 'array': <Property, name: array, value: [1, 2, 3]>, 'uint8-array': <Property, name: uint8-array, value: b'\x124'>, 'string': <Property, name: string, value: 'foo'>, 'string-array': <Property, name: string-array, value: ['foo', 'bar', 'baz']>}") r"{'compatible': <Property, name: compatible, value: ['props']>, 'nonexistent-boolean': <Property, name: nonexistent-boolean, value: False>, 'existent-boolean': <Property, name: existent-boolean, value: True>, 'int': <Property, name: int, value: 1>, 'array': <Property, name: array, value: [1, 2, 3]>, 'uint8-array': <Property, name: uint8-array, value: b'\x124'>, 'string': <Property, name: string, value: 'foo'>, 'string-array': <Property, name: string-array, value: ['foo', 'bar', 'baz']>, 'phandle-ref': <Property, name: phandle-ref, value: <Device /props/node in 'test.dts', no binding>>}")
# #
# Test having multiple directories with bindings, with a different .dts file # Test having multiple directories with bindings, with a different .dts file