feat(auth): standalone CoT dt2c tool
Add the standalone CoT dt2c tool for CoT DTB conversion to
c file
Change-Id: If28e580a4c2825f5dc9008e93cd2aae3fc173e73
Signed-off-by: Xialin Liu <Xialin.Liu@ARM.com>
diff --git a/tools/cot_dt2c/Makefile b/tools/cot_dt2c/Makefile
new file mode 100644
index 0000000..ad8d9f5
--- /dev/null
+++ b/tools/cot_dt2c/Makefile
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+##* Variables
+SHELL := /usr/bin/env bash
+PYTHON := python
+PYTHONPATH := `pwd`
+
+.PHONY: dist
+dist: clean
+ poetry build
+
+#* Installation
+.PHONY: dev-install
+dev-install:
+ pip3 install mypy
+ pip3 install pytest
+ pip install -r requirements.txt
+ poetry lock -n && poetry export --without-hashes > requirements.txt
+ poetry install -n
+ -poetry run mypy --install-types --non-interactive ./
+
+.PHONY: install
+install: dist
+ pip install mypy
+ pip install pytest
+ pip install -r requirements.txt
+ pip install dist/*.whl
+
+clean-test: ## remove test and coverage artifacts
+ rm -fr .tox/
+ rm -f .coverage
+ rm -fr htmlcov/
+
+clean-pyc: ## remove Python file artifacts
+ find . -name '*.pyc' -exec rm -f {} +
+ find . -name '*.pyo' -exec rm -f {} +
+ find . -name '*~' -exec rm -f {} +
+ find . -name '__pycache__' -exec rm -fr {} +
+ find . | grep -E ".pytest_cache" | xargs rm -rf
+ find . | grep -E ".mypy_cache" | xargs rm -rf
+
+
+clean-build: ## remove build artifacts
+ rm -fr build/
+ rm -fr dist/
+ rm -fr .eggs/
+ find . -name '*.egg-info' -exec rm -fr {} +
+ find . -name '*.egg' -exec rm -f {} +
+
+clean-tmp:
+ rm -rf ./tmp
+
+#* Cleaning
+.PHONY: clean clean-build clean-pyc clean-test
+clean: uninstall clean-build clean-pyc clean-test clean-tmp ## remove all build, test, coverage and Python artifacts
+
+uninstall:
+ pip uninstall -y cot-dt2c
+
+.PHONY: reinstall
+reinstall: clean install
+
+.PHONY: test
+test:
+ PYTHONPATH=$(PYTHONPATH) poetry run pytest -c pyproject.toml tests/
diff --git a/tools/cot_dt2c/cot_dt2c/LICENSE b/tools/cot_dt2c/cot_dt2c/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/tools/cot_dt2c/cot_dt2c/__init__.py b/tools/cot_dt2c/cot_dt2c/__init__.py
new file mode 100644
index 0000000..621c55a
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/__init__.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+# type: ignore[attr-defined]
+
+#
+# Copyright (c) 2024, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+
+if sys.version_info >= (3, 8):
+ from importlib import metadata as importlib_metadata
+else:
+ import importlib_metadata
+
+
+def get_version() -> str:
+ try:
+ return importlib_metadata.version(__name__)
+ except importlib_metadata.PackageNotFoundError: # pragma: no cover
+ return "unknown"
+
+
+version: str = get_version()
diff --git a/tools/cot_dt2c/cot_dt2c/__main__.py b/tools/cot_dt2c/cot_dt2c/__main__.py
new file mode 100644
index 0000000..5aa4a92
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/__main__.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+# type: ignore[attr-defined]
+#
+# Copyright (c) 2024, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+from cot_dt2c.cli import cli
+if __name__ == "__main__":
+ cli()
diff --git a/tools/cot_dt2c/cot_dt2c/cli.py b/tools/cot_dt2c/cot_dt2c/cli.py
new file mode 100644
index 0000000..d338430
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cli.py
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+from pathlib import Path
+from cot_dt2c.cot_dt2c import generateMain
+from cot_dt2c.cot_dt2c import validateMain
+from cot_dt2c.cot_dt2c import visualizeMain
+from cot_dt2c.dt_validator import dtValidatorMain
+
+import click
+
+@click.group()
+@click.version_option()
+def cli():
+ pass
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+@click.argument("outputfile", type=click.Path(dir_okay=True))
+def convert_to_c(inputfile, outputfile):
+ generateMain(inputfile, outputfile)
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+def validate_cot(inputfile):
+ validateMain(inputfile)
+
+@cli.command()
+@click.argument("inputfile", type=click.Path(dir_okay=True))
+def visualize_cot(inputfile):
+ visualizeMain(inputfile)
+
+@cli.command()
+@click.argument("inputfiledir", type=click.Path(dir_okay=True))
+def validate_dt(inputfiledir):
+ dtValidatorMain(inputfiledir)
diff --git a/tools/cot_dt2c/cot_dt2c/cot_dt2c.py b/tools/cot_dt2c/cot_dt2c/cot_dt2c.py
new file mode 100644
index 0000000..4056aac
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cot_dt2c.py
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+from cot_dt2c.cot_parser import COT
+
+def generateMain(input, output=None):
+ cot = COT(input, output)
+ cot.generate_c_file()
+
+def validateMain(input):
+ cot = COT(input)
+ if not cot.validate_nodes():
+ print("not a valid CoT DT file")
+
+def visualizeMain(input):
+ cot = COT(input)
+ cot.tree_visualization()
+
+if __name__=="__main__":
+ if (len(sys.argv) < 2):
+ print("usage: python3 " + sys.argv[0] + " [dtsi file path] [optional output c file path]")
+ exit()
+ if len(sys.argv) == 3:
+ generateMain(sys.argv[1], sys.argv[2])
+ if len(sys.argv) == 2:
+ validateMain(sys.argv[1])
diff --git a/tools/cot_dt2c/cot_dt2c/cot_parser.py b/tools/cot_dt2c/cot_dt2c/cot_parser.py
new file mode 100644
index 0000000..c1d53e2
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/cot_parser.py
@@ -0,0 +1,804 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+import re
+from cot_dt2c.pydevicetree.source.parser import ifdef_stack
+from cot_dt2c.pydevicetree.ast import CellArray, LabelReference
+from cot_dt2c.pydevicetree import *
+from pathlib import Path
+
+def extractNumber(s):
+ for i in s:
+ if i.isdigit():
+ return (int)(i)
+
+ return -1
+
+def removeNumber(s):
+ result = ''.join([i for i in s if not i.isdigit()])
+ return result
+
+class COT:
+ def __init__(self, inputfile: str, outputfile=None):
+ with open(inputfile, 'r') as f:
+ contents = f.read()
+ pos = contents.find("cot")
+ if pos == -1:
+ print("not a valid CoT DT file")
+ exit(1)
+
+ contents = contents[pos:]
+
+ try:
+ self.tree = Devicetree.parseStr(contents)
+ except:
+ print("not a valid CoT DT file")
+ exit(1)
+
+ self.output = outputfile
+ self.input = inputfile
+ self.has_root = False
+
+ # edge cases
+ certs = self.get_all_certificates()
+ for c in certs:
+ if self.if_root(c):
+ if not c.get_fields("signing-key"):
+ c.properties.append(Property("signing-key", CellArray([LabelReference("subject_pk")])))
+
+ def print_cert_info(self, node:Node):
+ img_id = node.get_field("image-id").values[0].replace('"', "")
+ sign_key = self.get_sign_key(node)
+ nv = self.get_nv_ctr(node)
+
+ info = "<b>name:</b> {}<br><b>image-id:</b> {}<br>{}{}{}"\
+ .format(node.name, img_id, "<b>root-certificate</b><br>" if self.if_root(node) else "", \
+ "<b>signing-key:</b> " + self.extract_label(sign_key) + "<br>" if sign_key else "", \
+ "<b>nv counter:</b> " + self.extract_label(nv) + "<br>" if nv else "")
+ return info
+
+ def print_data_info(self, node:Node):
+ oid = node.get_field("oid")
+ info = "<b>name:</b> {}<br><b>oid:</b> {}<br>" \
+ .format(node.name, oid)
+
+ return info
+
+ def print_img_info(self, node:Node):
+ hash = self.extract_label(node.get_fields("hash"))
+ img_id = node.get_field("image-id").values[0].replace('"', "")
+ info = "<b>name:</b> {}<br><b>image-id:</b> {}<br><b>hash:</b> {}"\
+ .format(node.name, img_id, hash)
+
+ return info
+
+ def tree_width(self, parent_set, root):
+ ans = 1
+ stack = [root]
+
+ while stack:
+ tmp_stack = []
+ while stack:
+ cur_node = stack.pop()
+ child = parent_set[cur_node]
+ for c in child:
+ tmp_stack.append(c)
+
+ stack = tmp_stack.copy()
+ ans = max(ans, len(tmp_stack))
+
+ return ans
+
+ def resolve_lay(self, parent_set, lay, name_idx, root, bounds, break_name):
+ child = parent_set[root]
+
+ if len(child) == 0:
+ return
+
+ width = []
+ total_width = 0
+ for c in child:
+ w = self.tree_width(parent_set, c)
+ width.append(w)
+ total_width += w
+
+ allow_width = bounds[1] - bounds[0]
+ interval = allow_width / total_width
+ start = bounds[0]
+ for i, c in enumerate(child):
+ end = start + interval * width[i]
+ new_bounds = [start, end]
+ lay[name_idx[c]][0] = start + (end - start) / 2
+ if end - start < 0.28:
+ break_name.add(c)
+ start = end
+ self.resolve_lay(parent_set, lay, name_idx, c, new_bounds, break_name)
+
+ def tree_visualization(self):
+ import igraph
+ from igraph import Graph, EdgeSeq
+ import collections
+
+ cert = self.get_certificates()
+ pk = self.get_rot_keys()
+ nv = self.get_nv_counters()
+ image = self.get_images()
+
+ certs = cert.children
+ if pk:
+ pks = pk.children
+ else:
+ pks = []
+ nvs = nv.children
+ images = image.children
+
+ root_name = "CoT"
+
+ G = Graph()
+ detail = []
+ lay = []
+ name_idx = {}
+ parent_set = collections.defaultdict(list)
+
+ G.add_vertex(root_name)
+ detail.append("CoT Root")
+ name_idx[root_name] = len(lay)
+ lay.append([0,0])
+
+ G.add_vertex(cert.name)
+ G.add_edge(root_name, cert.name)
+ detail.append("All Certificates")
+ name_idx[cert.name] = len(lay)
+ lay.append([0, 1])
+ parent_set[root_name].append(cert.name)
+
+ if pk:
+ G.add_vertex(pk.name)
+ detail.append("All Public Trusted Key")
+ G.add_edge(root_name, pk.name)
+ name_idx[pk.name] = len(lay)
+ lay.append([-2.0, 1])
+ parent_set[root_name].append(pk.name)
+
+ G.add_vertex(nv.name)
+ detail.append("All NV Counters")
+ G.add_edge(root_name, nv.name)
+ name_idx[nv.name] = len(lay)
+ lay.append([2.0, 1])
+ parent_set[root_name].append(nv.name)
+
+ if pks:
+ for i, p in enumerate(pks):
+ G.add_vertex(p.name)
+ detail.append(self.print_data_info(p))
+ G.add_edge(pk.name, p.name)
+ name_idx[p.name] = len(lay)
+ parent_set[pk.name].append(p.name)
+ lay.append([0, lay[name_idx[pk.name]][1] + 1])
+
+ for c in certs:
+ G.add_vertex(c.name)
+ detail.append(self.print_cert_info(c))
+ name_idx[c.name] = len(lay)
+ if self.if_root(c):
+ G.add_edge(cert.name, c.name)
+ parent_set[cert.name].append(c.name)
+ lay.append([0, 2])
+ else:
+ parent = self.extract_label(c.get_fields("parent"))
+ G.add_edge(parent, c.name)
+ parent_set[parent].append(c.name)
+ lay.append([0, lay[name_idx[parent]][1] + 1])
+
+ for idx, i in enumerate(images):
+ G.add_vertex(i.name)
+ detail.append(self.print_img_info(i))
+ parent = self.extract_label(i.get_fields("parent"))
+ G.add_edge(parent, i.name)
+ parent_set[parent].append(i.name)
+ name_idx[i.name] = len(lay)
+ lay.append([0, lay[name_idx[parent]][1] + 1])
+
+ for i, n in enumerate(nvs):
+ G.add_vertex(n.name)
+ detail.append(self.print_data_info(n))
+ G.add_edge(nv.name, n.name)
+ name_idx[n.name] = len(lay)
+ parent_set[nv.name].append(n.name)
+ lay.append([0, lay[name_idx[nv.name]][1] + 1])
+
+ break_name = set()
+ self.resolve_lay(parent_set, lay, name_idx, root_name, [-3, 3], break_name)
+ #lay = G.layout('rt')
+
+ numVertex = len(G.get_vertex_dataframe())
+ vertices = G.get_vertex_dataframe()
+ v_label = []
+
+ for i in vertices['name']:
+ if i in break_name and len(i) > 10:
+ middle = len(i) // 2
+ v_label.append(i[:middle] + "<br>" + i[middle:])
+ else:
+ v_label.append(i)
+
+ position = {k: lay[k] for k in range(numVertex)}
+ Y = [lay[k][1] for k in range(numVertex)]
+ M = max(Y)
+
+ es = EdgeSeq(G) # sequence of edges
+ E = [e.tuple for e in G.es] # list of edges
+
+ L = len(position)
+ Xn = [position[k][0] for k in range(L)]
+ Yn = [2*M-position[k][1] for k in range(L)]
+ Xe = []
+ Ye = []
+ for edge in E:
+ Xe += [position[edge[0]][0], position[edge[1]][0], None]
+ Ye += [2*M-position[edge[0]][1], 2*M-position[edge[1]][1], None]
+
+ labels = v_label
+
+ import plotly.graph_objects as go
+ fig = go.Figure()
+ fig.add_trace(go.Scatter(x = Xe,
+ y = Ye,
+ mode = 'lines',
+ line = dict(color='rgb(210,210,210)', width=2),
+ hoverinfo = 'none'
+ ))
+ fig.add_trace(go.Scatter(x = Xn,
+ y = Yn,
+ mode = 'markers',
+ name = 'detail',
+ marker = dict(symbol = 'circle-dot',
+ size = 50,
+ color = 'rgba(135, 206, 250, 0.8)', #'#DB4551',
+ line = dict(color='MediumPurple', width=3)
+ ),
+ text=detail,
+ hoverinfo='text',
+ hovertemplate =
+ '<b>Detail</b><br>'
+ '%{text}',
+ opacity=0.8
+ ))
+
+ def make_annotations(pos, text, font_size=10, font_color='rgb(0,0,0)'):
+ L = len(pos)
+ if len(text) != L:
+ raise ValueError('The lists pos and text must have the same len')
+ annotations = []
+ for k in range(L):
+ annotations.append(
+ dict(
+ text = labels[k],
+ x = pos[k][0], y = 2*M-position[k][1],
+ xref = 'x1', yref = 'y1',
+ font = dict(color = font_color, size = font_size),
+ showarrow = False)
+ )
+ return annotations
+
+ axis = dict(showline=False, # hide axis line, grid, ticklabels and title
+ zeroline=False,
+ showgrid=False,
+ showticklabels=False,
+ )
+
+ fig.update_layout(title= 'CoT Device Tree',
+ annotations=make_annotations(position, v_label),
+ font_size=12,
+ showlegend=False,
+ xaxis=axis,
+ yaxis=axis,
+ margin=dict(l=40, r=40, b=85, t=100),
+ hovermode='closest',
+ plot_bgcolor='rgb(248,248,248)'
+ )
+
+ fig.show()
+
+ return
+
+ def if_root(self, node:Node) -> bool:
+ for p in node.properties:
+ if p.name == "root-certificate":
+ return True
+ return False
+
+ def get_sign_key(self, node:Node):
+ for p in node.properties:
+ if p.name == "signing-key":
+ return p.values
+
+ return None
+
+ def get_nv_ctr(self, node:Node):
+ for nv in node.properties:
+ if nv.name == "antirollback-counter":
+ return nv.values
+
+ return None
+
+ def extract_label(self, label) -> str:
+ if not label:
+ return label
+ return label[0].label.name
+
+ def get_auth_data(self, node:Node):
+ return node.children
+
+ def format_auth_data_val(self, node:Node, cert:Node):
+ type_desc = node.name
+ if "sp_pkg" in type_desc:
+ ptr = removeNumber(type_desc) + "_buf"
+ else:
+ ptr = type_desc + "_buf"
+ len = "(unsigned int)HASH_DER_LEN"
+ if "pk" in type_desc:
+ len = "(unsigned int)PK_DER_LEN"
+
+ # edge case
+ if not self.if_root(cert) and "key_cert" in cert.name:
+ if "content_pk" in ptr:
+ ptr = "content_pk_buf"
+
+ return type_desc, ptr, len
+
+ def get_node(self, nodes: list[Node], name: str) -> Node:
+ for i in nodes:
+ if i.name == name:
+ return i
+
+ def get_certificates(self) -> Node:
+ children = self.tree.children
+ for i in children:
+ if i.name == "cot":
+ return self.get_node(i.children, "manifests")
+
+ def get_images(self)-> Node:
+ children = self.tree.children
+ for i in children:
+ if i.name == "cot":
+ return self.get_node(i.children, "images")
+
+ def get_nv_counters(self) -> Node:
+ children = self.tree.children
+ return self.get_node(children, "non_volatile_counters")
+
+ def get_rot_keys(self) -> Node:
+ children = self.tree.children
+ return self.get_node(children, "rot_keys")
+
+ def get_all_certificates(self) -> Node:
+ cert = self.get_certificates()
+ return cert.children
+
+ def get_all_images(self) -> Node:
+ image = self.get_images()
+ return image.children
+
+ def get_all_nv_counters(self) -> Node:
+ nv = self.get_nv_counters()
+ return nv.children
+
+ def get_all_pks(self) -> Node:
+ pk = self.get_rot_keys()
+ if not pk:
+ return []
+ return pk.children
+
+ def validate_cert(self, node:Node) -> bool:
+ valid = True
+ if not node.has_field("image-id"):
+ print("{} missing mandatory attribute image-id".format(node.name))
+ valid = False
+
+ if not node.has_field("root-certificate"):
+ if not node.has_field("parent"):
+ print("{} missing mandatory attribute parent".format(node.name))
+ valid = False
+ else:
+ # check if refer to non existing parent
+ certs = self.get_all_certificates()
+ found = False
+ for c in certs:
+ if c.name == self.extract_label(node.get_fields("parent")):
+ found = True
+
+ if not found:
+ print("{} refer to non existing parent".format(node.name))
+ valid = False
+
+ else:
+ self.has_root = True
+
+ child = node.children
+ if child:
+ for c in child:
+ if not c.has_field("oid"):
+ print("{} missing mandatory attribute oid".format(c.name))
+ valid = False
+
+ return valid
+
+ def validate_img(self, node:Node) -> bool:
+ valid = True
+ if not node.has_field("image-id"):
+ print("{} missing mandatory attribute image-id".format(node.name))
+ valid = False
+
+ if not node.has_field("parent"):
+ print("{} missing mandatory attribute parent".format(node.name))
+ valid = False
+
+ if not node.has_field("hash"):
+ print("{} missing mandatory attribute hash".format(node.name))
+ valid = False
+
+ # check if refer to non existing parent
+ certs = self.get_all_certificates()
+ found = False
+ for c in certs:
+ if c.name == self.extract_label(node.get_fields("parent")):
+ found = True
+
+ if not found:
+ print("{} refer to non existing parent".format(node.name))
+ valid = False
+
+ return valid
+
+ def validate_nodes(self) -> bool:
+ valid = True
+
+ if ifdef_stack:
+ print("invalid ifdef macro")
+ valid = False
+
+ certs = self.get_all_certificates()
+ images = self.get_all_images()
+
+ for n in certs:
+ node_valid = self.validate_cert(n)
+ valid = valid and node_valid
+
+ for i in images:
+ node_valid = self.validate_img(i)
+ valid = valid and node_valid
+
+ if not self.has_root:
+ print("missing root certificate")
+
+ return valid
+
+ def extract_licence(self, f):
+ licence = []
+
+ licencereg = re.compile(r'/\*')
+ licenceendReg = re.compile(r'\*/')
+
+ licencePre = False
+
+ for line in f:
+ match = licencereg.search(line)
+ if match != None:
+ licence.append(line)
+ licencePre = True
+ continue
+
+ match = licenceendReg.search(line)
+ if match != None:
+ licence.append(line)
+ licencePre = False
+ return licence
+
+ if licencePre:
+ licence.append(line)
+ else:
+ return licence
+
+ return licence
+
+ def licence_to_c(self, licence, f):
+ if len(licence) != 0:
+ for i in licence:
+ f.write(i)
+
+ f.write("\n")
+ return
+
+ def extract_include(self, f):
+ include = []
+
+ for line in f:
+ if "cot" in line:
+ return include
+
+ if line != "" and "common" not in line and line != "\n":
+ include.append(line)
+
+ return include
+
+ def include_to_c(self, include, f):
+ f.write("#include <stddef.h>\n")
+ f.write("#include <mbedtls/version.h>\n")
+ f.write("#include <common/tbbr/cot_def.h>\n")
+ f.write("#include <drivers/auth/auth_mod.h>\n")
+ f.write("\n")
+ for i in include:
+ f.write(i)
+ f.write("\n")
+ f.write("#include <platform_def.h>\n\n")
+ return
+
+ def generate_header(self, input, output):
+ licence = self.extract_licence(input)
+ include = self.extract_include(input)
+ self.licence_to_c(licence, output)
+ self.include_to_c(include, output)
+
+ def all_cert_to_c(self, f):
+ certs = self.get_all_certificates()
+ for c in certs:
+ self.cert_to_c(c, f)
+
+ f.write("\n")
+
+ def cert_to_c(self, node: Node, f):
+ ifdef = node.get_fields("ifdef")
+ if ifdef:
+ for i in ifdef:
+ f.write("{}\n".format(i))
+
+ f.write("static const auth_img_desc_t {} = {{\n".format(node.name))
+ f.write("\t.img_id = {},\n".format(node.get_field("image-id").values[0].replace('"', "")))
+ f.write("\t.img_type = IMG_CERT,\n")
+
+ if not self.if_root(node):
+ f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+ else:
+ f.write("\t.parent = NULL,\n")
+
+ sign = self.get_sign_key(node)
+ nv_ctr = self.get_nv_ctr(node)
+
+ if sign or nv_ctr:
+ f.write("\t.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {\n")
+
+ if sign:
+ f.write("\t\t[0] = {\n")
+ f.write("\t\t\t.type = AUTH_METHOD_SIG,\n")
+ f.write("\t\t\t.param.sig = {\n")
+
+ f.write("\t\t\t\t.pk = &{},\n".format(self.extract_label(sign)))
+ f.write("\t\t\t\t.sig = &sig,\n")
+ f.write("\t\t\t\t.alg = &sig_alg,\n")
+ f.write("\t\t\t\t.data = &raw_data\n")
+ f.write("\t\t\t}\n")
+ f.write("\t\t}}{}\n".format("," if nv_ctr else ""))
+
+ if nv_ctr:
+ f.write("\t\t[1] = {\n")
+ f.write("\t\t\t.type = AUTH_METHOD_NV_CTR,\n")
+ f.write("\t\t\t.param.nv_ctr = {\n")
+
+ f.write("\t\t\t\t.cert_nv_ctr = &{},\n".format(self.extract_label(nv_ctr)))
+ f.write("\t\t\t\t.plat_nv_ctr = &{}\n".format(self.extract_label(nv_ctr)))
+
+ f.write("\t\t\t}\n")
+ f.write("\t\t}\n")
+
+ f.write("\t},\n")
+
+ auth_data = self.get_auth_data(node)
+ if auth_data:
+ f.write("\t.authenticated_data = (const auth_param_desc_t[COT_MAX_VERIFIED_PARAMS]) {\n")
+
+ for i, d in enumerate(auth_data):
+ type_desc, ptr, data_len = self.format_auth_data_val(d, node)
+
+ f.write("\t\t[{}] = {{\n".format(i))
+ f.write("\t\t\t.type_desc = &{},\n".format(type_desc))
+ f.write("\t\t\t.data = {\n")
+
+ n = extractNumber(type_desc)
+ if "pkg" not in type_desc or n == -1:
+ f.write("\t\t\t\t.ptr = (void *){},\n".format(ptr))
+ else:
+ f.write("\t\t\t\t.ptr = (void *){}[{}],\n".format(ptr, n-1))
+
+ f.write("\t\t\t\t.len = {}\n".format(data_len))
+ f.write("\t\t\t}\n")
+
+ f.write("\t\t}}{}\n".format("," if i != len(auth_data) - 1 else ""))
+
+ f.write("\t}\n")
+
+ f.write("};\n\n")
+
+ if ifdef:
+ for i in ifdef:
+ f.write("#endif\n")
+ f.write("\n")
+
+ return
+
+
+ def img_to_c(self, node:Node, f):
+ ifdef = node.get_fields("ifdef")
+ if ifdef:
+ for i in ifdef:
+ f.write("{}\n".format(i))
+
+ f.write("static const auth_img_desc_t {} = {{\n".format(node.name))
+ f.write("\t.img_id = {},\n".format(node.get_field("image-id").values[0].replace('"', "")))
+ f.write("\t.img_type = IMG_RAW,\n")
+ f.write("\t.parent = &{},\n".format(node.get_field("parent").label.name))
+ f.write("\t.img_auth_methods = (const auth_method_desc_t[AUTH_METHOD_NUM]) {\n")
+
+ f.write("\t\t[0] = {\n")
+ f.write("\t\t\t.type = AUTH_METHOD_HASH,\n")
+ f.write("\t\t\t.param.hash = {\n")
+ f.write("\t\t\t\t.data = &raw_data,\n")
+ f.write("\t\t\t\t.hash = &{}\n".format(node.get_field("hash").label.name))
+ f.write("\t\t\t}\n")
+
+ f.write("\t\t}\n")
+ f.write("\t}\n")
+ f.write("};\n\n")
+
+ if ifdef:
+ for i in ifdef:
+ f.write("#endif\n")
+ f.write("\n")
+
+ return
+
+ def all_img_to_c(self, f):
+ images = self.get_all_images()
+ for i in images:
+ self.img_to_c(i, f)
+
+ f.write("\n")
+
+ def nv_to_c(self, f):
+ nv_ctr = self.get_all_nv_counters()
+
+ for nv in nv_ctr:
+ f.write("static auth_param_type_desc_t {} = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_NV_CTR, {});\n".format(nv.name, nv.get_field("oid")))
+
+ f.write("\n")
+
+ return
+
+ def pk_to_c(self, f):
+ pks = self.get_all_pks()
+
+ for p in pks:
+ f.write("static auth_param_type_desc_t {} = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, {});\n".format(p.name, p.get_field("oid")))
+
+ f.write("\n")
+ return
+
+ def buf_to_c(self, f):
+ certs = self.get_all_certificates()
+
+ buffers = {}
+
+ for c in certs:
+ auth_data = self.get_auth_data(c)
+ for a in auth_data:
+ type_desc, ptr, data_len = self.format_auth_data_val(a, c)
+ if ptr not in buffers:
+ buffers[ptr] = c.get_fields("ifdef")
+
+ for key, values in buffers.items():
+ if values:
+ for i in values:
+ f.write("{}\n".format(i))
+
+ if "sp_pkg_hash_buf" in key:
+ f.write("static unsigned char {}[MAX_SP_IDS][HASH_DER_LEN];\n".format(key))
+ elif "pk" in key:
+ f.write("static unsigned char {}[PK_DER_LEN];\n".format(key))
+ else:
+ f.write("static unsigned char {}[HASH_DER_LEN];\n".format(key))
+
+ if values:
+ for i in values:
+ f.write("#endif\n")
+
+ f.write("\n")
+
+ def param_to_c(self, f):
+ f.write("static auth_param_type_desc_t subject_pk = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, 0);\n")
+ f.write("static auth_param_type_desc_t sig = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_SIG, 0);\n")
+ f.write("static auth_param_type_desc_t sig_alg = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_SIG_ALG, 0);\n")
+ f.write("static auth_param_type_desc_t raw_data = AUTH_PARAM_TYPE_DESC(AUTH_PARAM_RAW_DATA, 0);\n")
+ f.write("\n")
+
+ certs = self.get_all_certificates()
+ for c in certs:
+ ifdef = c.get_fields("ifdef")
+ if ifdef:
+ for i in ifdef:
+ f.write("{}\n".format(i))
+
+ hash = c.children
+ for h in hash:
+ name = h.name
+ oid = h.get_field("oid")
+
+ if "pk" in name and "pkg" not in name:
+ f.write("static auth_param_type_desc_t {} = "\
+ "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_PUB_KEY, {});\n".format(name, oid))
+ elif "hash" in name:
+ f.write("static auth_param_type_desc_t {} = "\
+ "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_HASH, {});\n".format(name, oid))
+ elif "ctr" in name:
+ f.write("static auth_param_type_desc_t {} = "\
+ "AUTH_PARAM_TYPE_DESC(AUTH_PARAM_NV_CTR, {});\n".format(name, oid))
+
+ if ifdef:
+ for i in ifdef:
+ f.write("#endif\n")
+
+ f.write("\n")
+
+ def cot_to_c(self, f):
+ certs = self.get_all_certificates()
+ images = self.get_all_images()
+
+ f.write("static const auth_img_desc_t * const cot_desc[] = {\n")
+
+ for i, c in enumerate(certs):
+ ifdef = c.get_fields("ifdef")
+ if ifdef:
+ for i in ifdef:
+ f.write("{}\n".format(i))
+
+ f.write("\t[{}] = &{}{}\n".format(c.get_field("image-id").values[0], c.name, ","))
+
+ if ifdef:
+ for i in ifdef:
+ f.write("#endif\n")
+
+ for i, c in enumerate(images):
+ ifdef = c.get_fields("ifdef")
+ if ifdef:
+ for i in ifdef:
+ f.write("{}\n".format(i))
+
+ f.write("\t[{}] = &{}{}\n".format(c.get_field("image-id").values[0], c.name, "," if i != len(images) - 1 else ""))
+
+ if ifdef:
+ for i in ifdef:
+ f.write("#endif\n")
+
+ f.write("};\n\n")
+ f.write("REGISTER_COT(cot_desc);\n")
+ return
+
+ def generate_c_file(self):
+ filename = Path(self.output)
+ filename.parent.mkdir(exist_ok=True, parents=True)
+ output = open(self.output, 'w+')
+ input = open(self.input, "r")
+
+ self.generate_header(input, output)
+ self.buf_to_c(output)
+ self.param_to_c(output)
+ self.nv_to_c(output)
+ self.pk_to_c(output)
+ self.all_cert_to_c(output)
+ self.all_img_to_c(output)
+ self.cot_to_c(output)
+
+ return
diff --git a/tools/cot_dt2c/cot_dt2c/dt_validator.py b/tools/cot_dt2c/cot_dt2c/dt_validator.py
new file mode 100644
index 0000000..65e8ca2
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/dt_validator.py
@@ -0,0 +1,130 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import sys
+from os import path, walk, mkdir
+import subprocess
+from cot_dt2c.pydevicetree import *
+
+class bcolors:
+ HEADER = '\033[95m'
+ OKBLUE = '\033[94m'
+ OKCYAN = '\033[96m'
+ OKGREEN = '\033[92m'
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+ BOLD = '\033[1m'
+ UNDERLINE = '\033[4m'
+
+class DTTree:
+ def __init__(self, input):
+ self.input = input
+ self.test_dir = "./tmp"
+ self.logging_file = self.test_dir + "/result.log"
+
+ def dtValidate(self):
+ subprocess.run(["rm", "-rf", self.test_dir])
+
+ if not path.exists(self.test_dir):
+ mkdir(self.test_dir)
+
+ if path.isfile(self.input):
+ self.dtValidateFile(self.input, printInfo=True)
+ return
+
+ if path.isdir(self.input):
+ self.dtValidateFiles()
+ return
+
+ def dtValidateFile(self, input, printInfo=False):
+ valid, tree = self.dtParseFile(input, printInfo)
+
+ if not valid:
+ return False
+
+ if input.rfind("/") != -1:
+ filename = self.test_dir + input[input.rfind("/"):]
+ else:
+ filename = self.test_dir + "/" + input
+
+ f = open(filename, "w+")
+ if "/dts-v1/;" not in str(tree):
+ f.write("/dts-v1/;\n\n")
+ f.write(str(tree))
+ f.close()
+
+ if str(tree) == "":
+ return valid
+
+ return valid
+
+ def dtParseFile(self, input, printInfo=False):
+ with open(input, 'r') as f:
+ contents = f.read()
+
+ pos = contents.find("/ {")
+ if pos != -1:
+ contents = contents[pos:]
+
+ try:
+ tree = Devicetree.parseStr(contents)
+ if printInfo:
+ print(bcolors.OKGREEN + "{} parse tree successfully".format(input) + bcolors.ENDC)
+ except Exception as e:
+ if printInfo:
+ print(bcolors.FAIL + "{} parse tree failed:\t{}".format(input, str(e)) + bcolors.ENDC)
+ else:
+ f = open(self.logging_file, "a")
+ f.write("=====================================================================================\n")
+ f.write("{} result:\n".format(input))
+ f.write("{} INVALID:\t{}\n".format(input, str(e)))
+ f.close()
+ return False, None
+
+ return True, tree
+
+ def dtValidateFiles(self):
+ f = []
+ for (dirpath, dirnames, filenames) in walk(self.input):
+ f.extend(filenames)
+
+ allFile = len(f)
+ dtsiFile = 0
+ validFile = 0
+ invalidFile = 0
+
+ for i in f:
+ if (".dtsi" in i or ".dts" in i) and "cot" not in i and "fw-config" not in i:
+ dtsiFile += 1
+ valid = True
+
+ if self.input[-1] == "/":
+ valid = self.dtValidateFile(self.input + i)
+ else:
+ valid = self.dtValidateFile(self.input + "/" + i)
+
+ if valid:
+ validFile += 1
+ else:
+ invalidFile += 1
+
+ print("=====================================================")
+ print("Total File: " + str(allFile))
+ print("Total DT File: " + str(dtsiFile))
+ print("Total Valid File: " + str(validFile))
+ print("Total Invalid File: " + str(invalidFile))
+
+def dtValidatorMain(input):
+ dt = DTTree(input)
+ dt.dtValidate()
+
+if __name__=="__main__":
+ if (len(sys.argv) < 2):
+ print("usage: python3 " + sys.argv[0] + " [dtsi file path] or [dtsi folder path]")
+ exit()
+ if len(sys.argv) == 2:
+ dtValidatorMain(sys.argv[1])
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
new file mode 100644
index 0000000..49595a7
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from cot_dt2c.pydevicetree.ast import Devicetree, Node, Property, Directive, CellArray, LabelReference
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py
new file mode 100644
index 0000000..f30d897
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/__init__.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from cot_dt2c.pydevicetree.ast.directive import Directive
+from cot_dt2c.pydevicetree.ast.node import Node, NodeReference, Devicetree
+from cot_dt2c.pydevicetree.ast.property import PropertyValues, Bytestring, CellArray, StringList, Property, \
+ RegArray, OneString
+from cot_dt2c.pydevicetree.ast.reference import Label, Path, Reference, LabelReference, PathReference
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py
new file mode 100644
index 0000000..fdd6f0e
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/directive.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import Any
+
+from cot_dt2c.pydevicetree.ast.helpers import formatLevel, wrapStrings
+
+class Directive:
+ """Represents a Devicetree directive
+
+ Directives in Devicetree source are statements of the form
+
+ /directive-name/ [option1 [option2 [...]]];
+
+ Common directive examples include:
+
+ /dts-v1/;
+ /include/ "overlay.dtsi";
+ /delete-node/ &uart0;
+ /delete-property/ status;
+
+ Their semantic meaning depends on the directive name, their location in the Devicetree,
+ and their options.
+ """
+ def __init__(self, directive: str, option: Any = None):
+ """Create a directive object"""
+ self.directive = directive
+ self.option = option
+
+ def __repr__(self) -> str:
+ return "<Directive %s>" % self.directive
+
+ def __str__(self) -> str:
+ return self.to_dts()
+
+ def to_dts(self, level: int = 0) -> str:
+ """Format the Directive in Devicetree Source format"""
+ if isinstance(self.option, list):
+ return formatLevel(level, "%s %s;\n" % (self.directive,
+ wrapStrings(self.option)))
+ if isinstance(self.option, str):
+ if self.directive == "/include/":
+ return formatLevel(level, "%s \"%s\"\n" % (self.directive, self.option))
+ return formatLevel(level, "%s \"%s\";\n" % (self.directive, self.option))
+ return formatLevel(level, "%s;\n" % self.directive)
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py
new file mode 100644
index 0000000..30c54dc
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/helpers.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import List, Any
+
+from cot_dt2c.pydevicetree.ast.reference import Reference
+
+def formatLevel(level: int, s: str) -> str:
+ """Helper to indent a string with a number of tabs"""
+ return "\t" * level + s
+
+def wrapStrings(values: List[Any], formatHex: bool = False) -> List[Any]:
+ """Helper to wrap strings in quotes where appropriate"""
+ wrapped = []
+ for v in values:
+ if isinstance(v, Reference):
+ wrapped.append(v.to_dts())
+ elif isinstance(v, str):
+ wrapped.append("\"%s\"" % v)
+ elif isinstance(v, int):
+ if formatHex:
+ wrapped.append("0x%x" % v)
+ else:
+ wrapped.append(str(v))
+ else:
+ wrapped.append(str(v))
+ return wrapped
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py
new file mode 100644
index 0000000..d203af8
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/node.py
@@ -0,0 +1,514 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+import os
+from typing import List, Union, Optional, Iterable, Callable, Any, cast, Pattern
+
+from cot_dt2c.pydevicetree.ast.helpers import formatLevel
+from cot_dt2c.pydevicetree.ast.property import Property, PropertyValues, RegArray, RangeArray
+from cot_dt2c.pydevicetree.ast.directive import Directive
+from cot_dt2c.pydevicetree.ast.reference import Label, Path, Reference, LabelReference, PathReference
+
+# Type signature for elements passed to Devicetree constructor
+ElementList = Iterable[Union['Node', Property, Directive]]
+
+# Callback type signatures for Devicetree.match() and Devicetree.chosen()
+MatchFunc = Callable[['Node'], bool]
+MatchCallback = Optional[Callable[['Node'], None]]
+ChosenCallback = Optional[Callable[[PropertyValues], None]]
+
+class Node:
+ """Represents a Devicetree Node
+
+ A Devicetree Node generally takes the form
+
+ [label:] node-name@unit-address {
+ [directives]
+ [properties]
+ [child nodes]
+ };
+
+ The structure formed by creating trees of Nodes is the bulk of any Devicetree. As the naming
+ system implies, then, each node roughly corresponds to some conceptual device, subsystem of
+ devices, bus, etc.
+
+ Devices can be referenced by label or by path, and are generally uniquely identified by a
+ collection of string identifiers assigned to the "compatible" property.
+
+ For instance, a UART device might look like
+
+ uart0: uart@10013000 {
+ compatible = "sifive,uart0";
+ reg = <0x10013000 0x1000>;
+ reg-names = "control";
+ interrupt-parent = <&plic>;
+ interrupts = <3>;
+ clocks = <&busclk>;
+ status = "okay";
+ };
+
+ This node can be identified in the following ways:
+
+ - By label: uart0
+ - By path: /path/to/uart@10013000
+ - By name: uart@10013000 (for example when referenced in a /delete-node/ directive)
+ """
+ # pylint: disable=too-many-arguments
+ def __init__(self, name: str, label: Optional[str], address: Optional[int],
+ properties: List[Property], directives: List[Directive],
+ children: List['Node']):
+ """Initializes a Devicetree Node
+
+ Also evaluates the /delete-node/ and /delete-property/ directives found in the node
+ and deletes the respective nodes and properties.
+ """
+ self.name = name
+ self.parent = None # type: Optional['Node']
+
+ self.label = label
+ self.address = address
+ self.properties = properties
+ self.directives = directives
+ self.children = children
+ self.ifdef = []
+
+ for d in self.directives:
+ if d.directive == "/delete-node/":
+ if isinstance(d.option, LabelReference):
+ node = self.get_by_reference(d.option)
+ elif isinstance(d.option, str):
+ node = self.__get_child_by_handle(d.option)
+ if node:
+ self.remove_child(node)
+ elif d.directive == "/delete-property/":
+ # pylint: disable=cell-var-from-loop
+ properties = list(filter(lambda p: p.name == d.option, self.properties))
+ if properties:
+ del self.properties[self.properties.index(properties[0])]
+
+ def __repr__(self) -> str:
+ if self.address:
+ return "<Node %s@%x>" % (self.name, self.address)
+ return "<Node %s>" % self.name
+
+ def __str__(self) -> str:
+ return self.to_dts()
+
+ def __eq__(self, other) -> bool:
+ return self.name == other.name and self.address == other.address
+
+ def __hash__(self):
+ return hash((self.name, self.address))
+
+ @staticmethod
+ def from_dts(source: str) -> 'Node':
+ """Create a node from Devicetree Source"""
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from pydevicetree.source import parseNode
+ return parseNode(source)
+
+ def add_child(self, node: 'Node', merge: bool = True):
+ """Add a child node and merge it into the tree"""
+ node.parent = self
+ self.children.append(node)
+ if merge:
+ self.merge_tree()
+
+ def to_dts(self, level: int = 0) -> str:
+ """Format the subtree starting at the node as Devicetree Source"""
+ out = ""
+ if isinstance(self.address, int) and self.label:
+ out += formatLevel(level,
+ "%s: %s@%x {\n" % (self.label, self.name, self.address))
+ elif isinstance(self.address, int):
+ out += formatLevel(level, "%s@%x {\n" % (self.name, self.address))
+ elif self.label:
+ out += formatLevel(level, "%s: %s {\n" % (self.label, self.name))
+ elif self.name != "":
+ out += formatLevel(level, "%s {\n" % self.name)
+
+ for d in self.directives:
+ out += d.to_dts(level + 1)
+ for p in self.properties:
+ out += p.to_dts(level + 1)
+ for c in self.children:
+ out += c.to_dts(level + 1)
+
+ if self.name != "":
+ out += formatLevel(level, "};\n")
+
+ return out
+
+ def merge_tree(self):
+ """Recursively merge child nodes into a single tree
+
+ Parsed Devicetrees can describe the same tree multiple times, adding nodes and properties
+ each time. After parsing, this method is called to recursively merge the tree.
+ """
+ partitioned_children = []
+ for n in self.children:
+ partitioned_children.append([e for e in self.children if e == n])
+
+ new_children = []
+ for part in partitioned_children:
+ first = part[0]
+ rest = part[1:]
+ if first not in new_children:
+ for n in rest:
+ first.merge(n)
+ new_children.append(first)
+
+ self.children = new_children
+
+ for n in self.children:
+ n.parent = self
+ n.merge_tree()
+
+ def merge(self, other: 'Node'):
+ """Merge the contents of a node into this node.
+
+ Used by Node.merge_trees()
+ """
+ if not self.label and other.label:
+ self.label = other.label
+ self.properties += other.properties
+ self.directives += other.directives
+ self.children += other.children
+ self.ifdef += other.ifdef
+
+ def get_path(self, includeAddress: bool = True) -> str:
+ """Get the path of a node (ex. /cpus/cpu@0)"""
+ if self.name == "/":
+ return ""
+ if self.parent is None:
+ return "/" + self.name
+ if isinstance(self.address, int) and includeAddress:
+ return self.parent.get_path() + "/" + self.name + "@" + ("%x" % self.address)
+ return self.parent.get_path() + "/" + self.name
+
+ def get_by_reference(self, reference: Reference) -> Optional['Node']:
+ """Get a node from the subtree by reference (ex. &label, &{/path/to/node})"""
+ if isinstance(reference, LabelReference):
+ return self.get_by_label(reference.label)
+ if isinstance(reference, PathReference):
+ return self.get_by_path(reference.path)
+
+ return None
+
+ def get_by_label(self, label: Union[Label, str]) -> Optional['Node']:
+ """Get a node from the subtree by label"""
+ matching_nodes = list(filter(lambda n: n.label == label, self.child_nodes()))
+ if len(matching_nodes) != 0:
+ return matching_nodes[0]
+ return None
+
+ def __get_child_by_handle(self, handle: str) -> Optional['Node']:
+ """Get a child node by name or name and unit address"""
+ if '@' in handle:
+ name, addr_s = handle.split('@')
+ address = int(addr_s, base=16)
+ nodes = list(filter(lambda n: n.name == name and n.address == address, self.children))
+ else:
+ name = handle
+ nodes = list(filter(lambda n: n.name == name, self.children))
+
+ if not nodes:
+ return None
+ if len(nodes) > 1:
+ raise Exception("Handle %s is ambiguous!" % handle)
+ return nodes[0]
+
+ def get_by_path(self, path: Union[Path, str]) -> Optional['Node']:
+ """Get a node in the subtree by path"""
+ matching_nodes = list(filter(lambda n: path == n.get_path(includeAddress=True), \
+ self.child_nodes()))
+ if len(matching_nodes) != 0:
+ return matching_nodes[0]
+
+ matching_nodes = list(filter(lambda n: path == n.get_path(includeAddress=False), \
+ self.child_nodes()))
+ if len(matching_nodes) != 0:
+ return matching_nodes[0]
+ return None
+
+ def filter(self, matchFunc: MatchFunc, cbFunc: MatchCallback = None) -> List['Node']:
+ """Filter all child nodes by matchFunc
+
+ If cbFunc is provided, this method will iterate over the Nodes selected by matchFunc
+ and call cbFunc on each Node
+
+ Returns a list of all matching Nodes
+ """
+ nodes = list(filter(matchFunc, self.child_nodes()))
+
+ if cbFunc is not None:
+ for n in nodes:
+ cbFunc(n)
+
+ return nodes
+
+ def match(self, compatible: Pattern, func: MatchCallback = None) -> List['Node']:
+ """Get a node from the subtree by compatible string
+
+ Accepts a regular expression to match one of the strings in the compatible property.
+ """
+ regex = re.compile(compatible)
+
+ def match_compat(node: Node) -> bool:
+ compatibles = node.get_fields("compatible")
+ if compatibles is not None:
+ return any(regex.match(c) for c in compatibles)
+ return False
+
+ return self.filter(match_compat, func)
+
+ def child_nodes(self) -> Iterable['Node']:
+ """Get an iterable over all the nodes in the subtree"""
+ for n in self.children:
+ yield n
+ for m in n.child_nodes():
+ yield m
+
+ def remove_child(self, node):
+ """Remove a child node"""
+ del self.children[self.children.index(node)]
+
+ def get_fields(self, field_name: str) -> Optional[PropertyValues]:
+ """Get all the values of a property"""
+ for p in self.properties:
+ if p.name == field_name:
+ return p.values
+ return None
+
+ def has_field(self, field_name: str) -> bool:
+ for p in self.properties:
+ if p.name == field_name:
+ return True
+ return False
+
+ def get_field(self, field_name: str) -> Any:
+ """Get the first value of a property"""
+ fields = self.get_fields(field_name)
+ if fields is not None:
+ if len(cast(PropertyValues, fields)) != 0:
+ return fields[0]
+ return None
+
+ def get_reg(self) -> Optional[RegArray]:
+ """If the node defines a `reg` property, return a RegArray for easier querying"""
+ reg = self.get_fields("reg")
+ reg_names = self.get_fields("reg-names")
+ if reg is not None:
+ if reg_names is not None:
+ return RegArray(reg.values, self.address_cells(), self.size_cells(),
+ reg_names.values)
+ return RegArray(reg.values, self.address_cells(), self.size_cells())
+ return None
+
+ def get_ranges(self) -> Optional[RangeArray]:
+ """If the node defines a `ranges` property, return a RangeArray for easier querying"""
+ ranges = self.get_fields("ranges")
+ child_address_cells = self.get_field("#address-cells")
+ parent_address_cells = self.address_cells()
+ size_cells = self.get_field("#size-cells")
+ if ranges is not None:
+ return RangeArray(ranges.values, child_address_cells, parent_address_cells, size_cells)
+ return None
+
+ def address_cells(self):
+ """Get the number of address cells
+
+ The #address-cells property is defined by the parent of a node and describes how addresses
+ are encoded in cell arrays. If no property is defined, the default value is 2.
+ """
+ if self.parent is not None:
+ cells = self.parent.get_field("#address-cells")
+ if cells is not None:
+ return cells
+ return 2
+ return 2
+
+ def size_cells(self):
+ """Get the number of size cells
+
+ The #size-cells property is defined by the parent of a node and describes how addresses
+ are encoded in cell arrays. If no property is defined, the default value is 1.
+ """
+ if self.parent is not None:
+ cells = self.parent.get_field("#size-cells")
+ if cells is not None:
+ return cells
+ return 1
+ return 1
+
+class NodeReference(Node):
+ """A NodeReference is used to extend the definition of a previously-defined Node
+
+ NodeReferences are commonly used by Devicetree "overlays" to extend the properties of a node
+ or add child devices, such as to a bus like I2C.
+ """
+ def __init__(self, reference: Reference, properties: List[Property],
+ directives: List[Directive], children: List[Node]):
+ """Instantiate a Node identified by reference to another node"""
+ self.reference = reference
+ Node.__init__(self, label=None, name="", address=None, properties=properties,
+ directives=directives, children=children)
+
+ def __repr__(self) -> str:
+ return "<NodeReference %s>" % self.reference.to_dts()
+
+ def resolve_reference(self, tree: 'Devicetree') -> Node:
+ """Given the full tree, get the node being referenced"""
+ node = tree.get_by_reference(self.reference)
+ if node is None:
+ raise Exception("Node reference %s cannot be resolved" % self.reference.to_dts())
+ return cast(Node, node)
+
+ def to_dts(self, level: int = 0) -> str:
+ out = formatLevel(level, self.reference.to_dts() + " {\n")
+
+ for d in self.directives:
+ out += d.to_dts(level + 1)
+ for p in self.properties:
+ out += p.to_dts(level + 1)
+ for c in self.children:
+ out += c.to_dts(level + 1)
+
+ out += formatLevel(level, "};\n")
+
+ return out
+
+
+class Devicetree(Node):
+ """A Devicetree object describes the full Devicetree tree
+
+ This class encapsulates both the tree itself (starting at the root node /) and any Directives
+ or nodes which exist at the top level of the Devicetree Source files.
+
+ Devicetree Source files can be parsed by calling Devicetree.parseFile().
+ """
+ def __init__(self, elements: ElementList):
+ """Instantiate a Devicetree with the list of parsed elements
+
+ Resolves all reference nodes and merges the tree to combine all identical nodes.
+ """
+ properties = [] # type: List[Property]
+ directives = [] # type: List[Directive]
+ children = [] # type: List[Node]
+
+ for e in elements:
+ if isinstance(e, Node):
+ children.append(cast(Node, e))
+ elif isinstance(e, Property):
+ properties.append(cast(Property, e))
+ elif isinstance(e, Directive):
+ directives.append(cast(Directive, e))
+
+ Node.__init__(self, label=None, name="", address=None,
+ properties=properties, directives=directives, children=children)
+
+ for node in self.children:
+ node.parent = self
+
+ reference_nodes = self.filter(lambda n: isinstance(n, NodeReference))
+ for refnode in reference_nodes:
+ refnode = cast(NodeReference, refnode)
+
+ node = refnode.resolve_reference(self)
+
+ if refnode.parent:
+ cast(Node, refnode.parent).remove_child(refnode)
+
+ node.properties += refnode.properties
+ node.directives += refnode.directives
+ node.children += refnode.children
+
+ self.merge_tree()
+
+ def __repr__(self) -> str:
+ name = self.root().get_field("compatible")
+ return "<Devicetree %s>" % name
+
+ def to_dts(self, level: int = 0) -> str:
+ """Convert the tree back to Devicetree Source"""
+ out = ""
+
+ for d in self.directives:
+ out += d.to_dts()
+ for p in self.properties:
+ out += p.to_dts()
+ for c in self.children:
+ out += c.to_dts()
+
+ return out
+
+ def get_by_path(self, path: Union[Path, str]) -> Optional[Node]:
+ """Get a node in the tree by path (ex. /cpus/cpu@0)"""
+
+ # Find and replace all aliases in the path
+ aliases = self.aliases()
+ if aliases:
+ for prop in aliases.properties:
+ if prop.name in path and len(prop.values) > 0:
+ path = path.replace(prop.name, prop.values[0])
+
+ return self.root().get_by_path(path)
+
+ @staticmethod
+ # pylint: disable=arguments-differ
+ def from_dts(dts: str) -> 'Devicetree':
+ """Parse a string and return a Devicetree object"""
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from pydevicetree.source import parseTree
+ return parseTree(dts)
+
+ @staticmethod
+ def parseFile(filename: str, followIncludes: bool = False) -> 'Devicetree':
+ """Parse a file and return a Devicetree object"""
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from cot_dt2c.pydevicetree.source.parser import parseTree
+ with open(filename, 'r') as f:
+ contents = f.read()
+ dirname = os.path.dirname(filename)
+ if dirname != "":
+ dirname += "/"
+ return parseTree(contents, dirname, followIncludes)
+
+ @staticmethod
+ def parseStr(input: str, followIncludes: bool = False) -> 'Devicetree':
+ from cot_dt2c.pydevicetree.source.parser import parseTree
+ return parseTree(input, "", followIncludes)
+
+ def all_nodes(self) -> Iterable[Node]:
+ """Get an iterable over all nodes in the tree"""
+ return self.child_nodes()
+
+ def root(self) -> Node:
+ """Get the root node of the tree"""
+ for n in self.all_nodes():
+ if n.name == "/":
+ return n
+ raise Exception("Devicetree has no root node!")
+
+ def aliases(self) -> Optional[Node]:
+ """Get the aliases node of the tree if it exists"""
+ for n in self.all_nodes():
+ if n.name == "aliases":
+ return n
+ return None
+
+ def chosen(self, property_name: str, func: ChosenCallback = None) -> Optional[PropertyValues]:
+ """Get the values associated with one of the properties in the chosen node"""
+ def match_chosen(node: Node) -> bool:
+ return node.name == "chosen"
+
+ for n in filter(match_chosen, self.all_nodes()):
+ for p in n.properties:
+ if p.name == property_name:
+ if func is not None:
+ func(p.values)
+ return p.values
+
+ return None
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py
new file mode 100644
index 0000000..d5fb687
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/property.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import List, Any, cast, Tuple, Optional, Iterable
+from itertools import zip_longest
+
+from cot_dt2c.pydevicetree.ast.helpers import wrapStrings, formatLevel
+
+class PropertyValues:
+ """PropertyValues is the parent class of all values which can be assigned to a Property
+
+ Child classes include
+
+ Bytestring
+ CellArray
+ StringList
+ """
+ def __init__(self, values: List[Any]):
+ """Create a PropertyValue"""
+ self.values = values
+
+ def __repr__(self) -> str:
+ return "<PropertyValues " + self.values.__repr__() + ">"
+
+ def __str__(self) -> str:
+ return self.to_dts()
+
+ def __iter__(self):
+ return iter(self.values)
+
+ def __len__(self) -> int:
+ return len(self.values)
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the values in Devicetree Source format"""
+ return ", ".join(wrapStrings(self.values, formatHex))
+
+ def __getitem__(self, key) -> Any:
+ return self.values[key]
+
+ def __eq__(self, other) -> bool:
+ if isinstance(other, PropertyValues):
+ return self.values == other.values
+ return self.values == other
+
+class Bytestring(PropertyValues):
+ """A Bytestring is a sequence of bytes
+
+ In Devicetree, Bytestrings are represented as a sequence of two-digit hexadecimal integers,
+ optionally space-separated, enclosed by square brackets:
+
+ [de ad be eef]
+ """
+ def __init__(self, bytelist: List[int]):
+ """Create a Bytestring object"""
+ PropertyValues.__init__(self, cast(List[Any], bytearray(bytelist)))
+
+ def __repr__(self) -> str:
+ return "<Bytestring " + str(self.values) + ">"
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the bytestring in Devicetree Source format"""
+ return "[" + " ".join("%02x" % v for v in self.values) + "]"
+
+class CellArray(PropertyValues):
+ """A CellArray is an array of integer values
+
+ CellArrays are commonly used as the value of Devicetree properties like `reg` and `interrupts`.
+ The interpretation of each element of a CellArray is device-dependent. For example, the `reg`
+ property encodes a CellArray as a list of tuples (base address, size), while the `interrupts`
+ property encodes a CellArray as simply a list of interrupt line numbers.
+ """
+ def __init__(self, cells: List[Any]):
+ """Create a CellArray object"""
+ PropertyValues.__init__(self, cells)
+
+ def __repr__(self) -> str:
+ return "<CellArray " + self.values.__repr__() + ">"
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the cell array in Devicetree Source format"""
+ dtsValues = []
+ for i in self.values:
+ if not isinstance(i, OneString) and not isinstance(i, str):
+ dtsValues.append(i)
+ return "<" + " ".join(wrapStrings(dtsValues, formatHex)) + ">"
+
+class RegArray(CellArray):
+ """A RegArray is the CellArray assigned to the reg property"""
+ def __init__(self, cells: List[int],
+ address_cells: int, size_cells: int,
+ names: Optional[List[str]] = None):
+ """Create a RegArray from a list of ints"""
+ # pylint: disable=too-many-locals
+ CellArray.__init__(self, cells)
+ self.address_cells = address_cells
+ self.size_cells = size_cells
+
+ self.tuples = [] # type: List[Tuple[int, int, Optional[str]]]
+
+ group_size = self.address_cells + self.size_cells
+
+ if len(cells) % group_size != 0:
+ raise Exception("CellArray does not contain enough cells")
+
+ grouped_cells = [cells[i:i+group_size] for i in range(0, len(cells), group_size)]
+
+ if not names:
+ names = []
+
+ for group, name in zip_longest(grouped_cells, cast(Iterable[Any], names)):
+ address = 0
+ a_cells = list(reversed(group[:self.address_cells]))
+ for a, i in zip(a_cells, range(len(a_cells))):
+ address += (1 << (32 * i)) * a
+
+ size = 0
+ s_cells = list(reversed(group[self.address_cells:]))
+ for s, i in zip(s_cells, range(len(s_cells))):
+ size += (1 << (32 * i)) * s
+
+ self.tuples.append(cast(Tuple[int, int, Optional[str]], tuple([address, size, name])))
+
+ def get_by_name(self, name: str) -> Optional[Tuple[int, int]]:
+ """Returns the (address, size) tuple with a given name"""
+ for t in self.tuples:
+ if t[2] == name:
+ return cast(Tuple[int, int], tuple(t[:2]))
+ return None
+
+ def __repr__(self) -> str:
+ return "<RegArray " + self.values.__repr__() + ">"
+
+ def __iter__(self) -> Iterable[Tuple[int, int]]:
+ return cast(Iterable[Tuple[int, int]], map(lambda t: tuple(t[:2]), self.tuples))
+
+ def __len__(self) -> int:
+ return len(self.tuples)
+
+ def __getitem__(self, key) -> Optional[Tuple[int, int]]:
+ return list(self.__iter__())[key]
+
+class RangeArray(CellArray):
+ """A RangeArray is the CellArray assigned to the range property"""
+ def __init__(self, cells: List[int], child_address_cells: int,
+ parent_address_cells: int, size_cells: int):
+ """Create a RangeArray from a list of ints"""
+ # pylint: disable=too-many-locals
+ CellArray.__init__(self, cells)
+ self.child_address_cells = child_address_cells
+ self.parent_address_cells = parent_address_cells
+ self.size_cells = size_cells
+
+ self.tuples = [] # type: List[Tuple[int, int, int]]
+
+ group_size = self.child_address_cells + self.parent_address_cells + self.size_cells
+
+ if len(cells) % group_size != 0:
+ raise Exception("CellArray does not contain enough cells")
+
+ grouped_cells = [cells[i:i+group_size] for i in range(0, len(cells), group_size)]
+
+ def sum_cells(cells: List[int]):
+ value = 0
+ for cell, index in zip(list(reversed(cells)), range(len(cells))):
+ value += (1 << (32 * index)) * cell
+ return value
+
+ for group in grouped_cells:
+ child_address = sum_cells(group[:self.child_address_cells])
+ parent_address = sum_cells(group[self.child_address_cells: \
+ self.child_address_cells + self.parent_address_cells])
+ size = sum_cells(group[self.child_address_cells + self.parent_address_cells:])
+
+ self.tuples.append(cast(Tuple[int, int, int],
+ tuple([child_address, parent_address, size])))
+
+ def __repr__(self) -> str:
+ return "<RangeArray " + self.values.__repr__() + ">"
+
+ def __iter__(self):
+ return iter(self.tuples)
+
+ def __len__(self) -> int:
+ return len(self.tuples)
+
+ def __getitem__(self, key) -> Any:
+ return self.tuples[key]
+
+class StringList(PropertyValues):
+ """A StringList is a list of null-terminated strings
+
+ The most common use of a StringList in Devicetree is to describe the `compatible` property.
+ """
+ def __init__(self, strings: List[str]):
+ """Create a StringList object"""
+ PropertyValues.__init__(self, strings)
+
+ def __repr__(self) -> str:
+ return "<StringList " + self.values.__repr__() + ">"
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the list of strings in Devicetree Source format"""
+ return ", ".join(wrapStrings(self.values))
+
+class OneString(PropertyValues):
+ def __init__(self, string: str):
+ PropertyValues.__init__(self, string)
+
+ def __repr__(self) -> str:
+ return self.values.__repr__()
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ return super().to_dts(formatHex)
+
+class Property:
+ """A Property is a key-value pair for a Devicetree Node
+
+ Properties are used to describe Nodes in the tree. There are many common properties, like
+
+ - compatible
+ - reg
+ - reg-names
+ - ranges
+ - interrupt-controller
+ - interrupts
+ - interrupt-parent
+ - clocks
+ - status
+
+ Which might commonly describe many or all nodes in a tree, and there are device, vendor,
+ operating system, runtime-specific properties.
+
+ Properties can possess no value, conveing meaning solely by their presence:
+
+ interrupt-controller;
+
+ Properties can also possess values such as an array of cells, a list of strings, etc.
+
+ reg = <0x10013000 0x1000>;
+ compatible = "sifive,rocket0", "riscv";
+
+ And properties can posses arbitrarily complex values, such as the following from the
+ Devicetree specification:
+
+ example = <0xf00f0000 19>, "a strange property format";
+ """
+ def __init__(self, name: str, values: PropertyValues):
+ """Create a Property object"""
+ self.name = name
+ self.values = values
+
+ def __repr__(self) -> str:
+ return "<Property %s>" % self.name
+
+ def __str__(self) -> str:
+ return self.to_dts()
+
+ @staticmethod
+ def from_dts(dts: str) -> 'Property':
+ """Parse a file and return a Devicetree object"""
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from pydevicetree.source import parseProperty
+ return parseProperty(dts)
+
+ def to_dts(self, level: int = 0) -> str:
+ """Format the Property assignment in Devicetree Source format"""
+ if self.name in ["reg", "ranges"]:
+ value = self.values.to_dts(formatHex=True)
+ else:
+ value = self.values.to_dts(formatHex=False)
+
+ if value != "":
+ return formatLevel(level, "%s = %s;\n" % (self.name, value))
+ if self.name == "ifdef":
+ return ""
+ return formatLevel(level, "%s;\n" % self.name)
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py
new file mode 100644
index 0000000..54b2d28
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/ast/reference.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from typing import Union, Iterator
+
+class Label:
+ """A Label is a unique identifier for a Node
+
+ For example, the following node has the label "uart0":
+
+ uart0: uart@10013000 {
+ ...
+ };
+ """
+ def __init__(self, name: str):
+ """Create a Label"""
+ self.name = name
+
+ def __repr__(self) -> str:
+ return "<Label " + self.name + ">"
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, Label):
+ return self.name == other.name
+ if isinstance(other, str):
+ return self.name == other
+ return False
+
+ def to_dts(self) -> str:
+ """Format the label in Devicetree Source format"""
+ return self.name + ":"
+
+class Path:
+ """A Path uniquely identifies a Node by its parents and (optionally) unit address"""
+ def __init__(self, path: str):
+ """Create a path out of a string"""
+ self.path = path
+
+ def to_dts(self) -> str:
+ """Format the Path in Devicetree Source format"""
+ return self.path
+
+ def __repr__(self) -> str:
+ return "<Path " + self.to_dts() + ">"
+
+ def __eq__(self, other: object) -> bool:
+ if isinstance(other, Path):
+ return self.to_dts() == other.to_dts()
+ if isinstance(other, str):
+ return self.to_dts() == other
+ return False
+
+ def __iter__(self) -> Iterator[str]:
+ return iter(self.path.split("/"))
+
+ def replace(self, old: str, new: str) -> 'Path':
+ """Replace any elements of the path which match 'old' with a new element 'new'"""
+ return Path(self.path.replace(old, new))
+
+class Reference:
+ """A Reference is a Devicetree construct which points to a Node in the tree
+
+ The following are types of references:
+
+ - A reference to a label:
+
+ &my-label;
+
+ - A reference to a node by path:
+
+ &{/path/to/node@deadbeef}
+
+ This is the parent class for both types of references, LabelReference and PathReference
+ """
+ # pylint: disable=no-self-use
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the Reference in Devicetree Source format"""
+ return ""
+
+class LabelReference(Reference):
+ """A LabelReference is a reference to a Node by label"""
+ def __init__(self, label: Union[Label, str]):
+ """Create a LabelReference from a Label or string"""
+ if isinstance(label, Label):
+ self.label = label
+ elif isinstance(label, str):
+ self.label = Label(label)
+
+ def __repr__(self) -> str:
+ return "<LabelReference " + self.to_dts() + ">"
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the LabelReference in Devicetree Source format"""
+ return "&" + self.label.name
+
+class PathReference(Reference):
+ """A PathReference is a reference to a Node by path"""
+ def __init__(self, path: Union[Path, str]):
+ """Create a PathReference from a Path or string"""
+ if isinstance(path, Path):
+ self.path = path
+ elif isinstance(path, str):
+ self.path = Path(path)
+
+ def __repr__(self) -> str:
+ return "<PathReference " + self.to_dts() + ">"
+
+ def to_dts(self, formatHex: bool = False) -> str:
+ """Format the PathReference in Devicetree Source format"""
+ return "&{" + self.path.to_dts() + "}"
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py
new file mode 100644
index 0000000..96768b3
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from cot_dt2c.pydevicetree.source.parser import parseTree, parseNode, parseProperty
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py
new file mode 100644
index 0000000..fb165e1
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/grammar.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+import os
+import sys
+
+import pyparsing as p # type: ignore
+
+ENV_CACHE_OPTION = "PYDEVICETREE_CACHE_SIZE_BOUND"
+
+cache_bound = None
+if ENV_CACHE_OPTION in os.environ:
+ option = os.environ[ENV_CACHE_OPTION]
+ if option != "None":
+ try:
+ cache_bound = int(option)
+ except ValueError:
+ print("%s requires a valid integer" % ENV_CACHE_OPTION, file=sys.stderr)
+p.ParserElement.enablePackrat(cache_bound)
+
+node_name = p.Word(p.alphanums + ",.-+_") ^ p.Literal("/")
+integer = p.pyparsing_common.integer ^ (p.Literal("0x").suppress() + p.pyparsing_common.hex_integer)
+unit_address = p.pyparsing_common.hex_integer
+unit_addresses = p.delimitedList(unit_address("address"), delim=",")
+node_handle = node_name("node_name") + p.Optional(p.Literal("@") + unit_addresses)
+property_name = p.Word(p.alphanums + ",.-_+?#")
+label = p.Word(p.alphanums + "_").setResultsName("label")
+label_creation = p.Combine(label + p.Literal(":"))
+string = p.QuotedString(quoteChar='"')
+stringlist = p.delimitedList(string)
+node_path = p.Combine(p.Literal("/") + \
+ p.delimitedList(node_handle, delim="/", combine=True)).setResultsName("path")
+path_reference = p.Literal("&{").suppress() + node_path + p.Literal("}").suppress()
+label_reference = p.Literal("&").suppress() + label
+label_raw = p.Word(p.alphanums + "_")
+reference = path_reference ^ label_reference ^ label_raw
+include_directive = p.Literal("/include/") + p.QuotedString(quoteChar='"')
+generic_directive = p.QuotedString(quoteChar="/", unquoteResults=False) + \
+ p.Optional(string ^ property_name ^ node_name ^ reference ^ (integer * 2)) + \
+ p.Literal(";").suppress()
+directive = include_directive ^ generic_directive
+
+operator = p.oneOf("~ ! * / + - << >> < <= > >= == != & ^ | && ||")
+arith_expr = p.Forward()
+ternary_element = arith_expr ^ integer
+ternary_expr = ternary_element + p.Literal("?") + ternary_element + p.Literal(":") + ternary_element
+arith_expr = p.nestedExpr(content=(p.OneOrMore(operator ^ integer) ^ ternary_expr))
+arth_str = p.Forward()
+arith_str_expr = p.nestedExpr(content=(p.OneOrMore(operator ^ integer ^ label_raw ^ p.Literal(",")) ^ ternary_expr))
+
+label_list = p.OneOrMore(p.Combine(label + p.Literal("\n")))
+
+cell_array = p.Literal("<").suppress() + \
+ p.ZeroOrMore(integer ^ arith_expr ^ arith_str_expr ^ label_list ^ string ^ reference ^ label_creation.suppress()) + \
+ p.Literal(">").suppress()
+bytestring = p.Literal("[").suppress() + \
+ (p.OneOrMore(p.Word(p.hexnums, exact=2) ^ label_creation.suppress())) + \
+ p.Literal("]").suppress()
+property_values = p.Forward()
+property_values = p.delimitedList(property_values ^ cell_array ^ bytestring ^ stringlist ^ \
+ reference ^ label_raw)
+property_assignment = property_name("property_name") + p.Optional(p.Literal("=").suppress() + \
+ (property_values)).setResultsName("value") + p.Optional(p.Literal(";").suppress())
+
+ifdef_label = p.ZeroOrMore(p.Word(p.alphanums + " _|//*=/(/)"))
+ifdef_define = p.Combine(p.Keyword("#if") + ifdef_label)
+ifdef_end = p.Combine(p.Keyword("#endif") + ifdef_label)
+ifdef_define_values = p.Forward()
+ifdef_define_values = p.ZeroOrMore(ifdef_define)
+ifdef_end_values = p.Forward()
+ifdef_end_values = p.ZeroOrMore(ifdef_end)
+
+node_opener = ifdef_define_values + p.Optional(label_creation) + node_handle + p.Literal("{").suppress()
+node_reference_opener = reference + p.Literal("{").suppress()
+node_closer = p.Literal("}").suppress() + p.Literal(";").suppress() + ifdef_end_values
+node_definition = p.Forward()
+# pylint: disable=expression-not-assigned
+node_definition << (node_opener ^ node_reference_opener) + \
+ p.ZeroOrMore(property_assignment ^ directive ^ node_definition ^ ifdef_define ^ ifdef_end) + \
+ node_closer
+
+devicetree = p.ZeroOrMore(directive ^ node_definition)
+
+devicetree.ignore(p.cStyleComment)
+devicetree.ignore("//" + p.SkipTo(p.lineEnd))
+devicetree.ignore("#include" + p.SkipTo(p.lineEnd))
+devicetree.ignore("#define" + p.SkipTo(p.lineEnd))
+devicetree.ignore("#else" + p.SkipTo(p.lineEnd))
+devicetree.ignore("#error" + p.SkipTo(p.lineEnd))
+devicetree.ignore("#ifndef" + p.SkipTo(p.lineEnd))
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ devicetree.parseFile(sys.argv[1]).pprint()
diff --git a/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py
new file mode 100644
index 0000000..0692482
--- /dev/null
+++ b/tools/cot_dt2c/cot_dt2c/pydevicetree/source/parser.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+# Copyright (c) 2019 SiFive Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+from itertools import chain
+
+from cot_dt2c.pydevicetree.source import grammar
+from cot_dt2c.pydevicetree.ast import *
+
+ifdef_stack = []
+
+def transformNode(string, location, tokens):
+ """Transforms a ParseResult into a Node"""
+ properties = [e for e in tokens.asList() if isinstance(e, Property)]
+ directives = [e for e in tokens.asList() if isinstance(e, Directive)]
+ children = [e for e in tokens.asList() if isinstance(e, Node)]
+
+ if isinstance(tokens[0], Reference):
+ return NodeReference(tokens[0], properties=properties,
+ directives=directives, children=children)
+ return Node(tokens.node_name, tokens.label, tokens.address, properties=properties,
+ directives=directives, children=children)
+
+def transformPropertyAssignment(string, location, tokens):
+ """Transforms a ParseResult into a Property"""
+ for v in tokens.value:
+ if isinstance(v, PropertyValues):
+ return Property(tokens.property_name, v)
+ if isinstance(v, CellArray):
+ return Property(tokens.property_name, v)
+ if isinstance(v, StringList):
+ return Property(tokens.property_name, v)
+ if isinstance(v, Reference):
+ return Property(tokens.property_name, v)
+
+ return Property(tokens.property_name, PropertyValues([]))
+
+def transformDirective(string, location, tokens):
+ """Transforms a ParseResult into a Directive"""
+ if len(tokens.asList()) > 1:
+ return Directive(tokens[0], tokens[1])
+ return Directive(tokens[0])
+
+def evaluateArithExpr(string, location, tokens):
+ """Evaluates a ParseResult as a python expression"""
+ flat_tokens = list(chain.from_iterable(tokens.asList()))
+ expr = " ".join(str(t) for t in flat_tokens)
+ # pylint: disable=eval-used
+ return eval(expr)
+
+def transformTernary(string, location, tokens):
+ """Evaluates a ParseResult as a ternary expression"""
+ # pylint: disable=eval-used
+ return eval(str(tokens[2]) +" if " + str(tokens[0]) + " else " + str(tokens[4]))
+
+def transformPropertyValues(string, location, tokens):
+ """Transforms a ParseResult into a PropertyValues"""
+ if len(tokens.asList()) == 1:
+ return tokens.asList()[0]
+ return PropertyValues(tokens.asList())
+
+def transformStringList(string, location, tokens):
+ """Transforms a ParseResult into a StringList"""
+ return StringList(tokens.asList())
+
+def transformString(string, location, token):
+ return OneString(token)
+
+def transformIfdefMacro(string, location, tokens):
+ tokenlist = tokens.asList()
+ for t in tokenlist:
+ ifdef_stack.append(t)
+ return Property("ifdef", PropertyValues(ifdef_stack.copy()))
+
+def transformIfdefEnd(string, location, tokens):
+ tokenlist = tokens.asList()
+ for t in tokenlist:
+ ifdef_stack.pop()
+
+def transformIfdef(string, location, tokens):
+ return Property("ifdef", PropertyValues(tokens))
+
+def evaluateStrArithExpr(string, location, tokens):
+ """Evaluates a ParseResult as a python expression"""
+ flat_tokens = list(chain.from_iterable(tokens.asList()))
+ for i, t in enumerate(flat_tokens):
+ if isinstance(t, int):
+ flat_tokens[i] = "(" + str(t) + ")"
+ expr = " ".join(str(t) for t in flat_tokens)
+ # pylint: disable=eval-used
+ return expr
+
+def transformBytestring(string, location, tokens):
+ """Transforms a ParseResult into a Bytestring"""
+ inttokens = []
+ for t in tokens.asList():
+ if all(c in "0123456789abcdefABCDEF" for c in t):
+ inttokens.append(int(t, base=16))
+ return Bytestring(inttokens)
+
+def transformCellArray(string, location, tokens):
+ """Transforms a ParseResult into a CellArray"""
+ return CellArray(tokens.asList())
+
+def transformLabel(string, location, tokens):
+ """Transforms a ParseResult into a Label"""
+ return Label(tokens.label)
+
+def transformPath(string, location, tokens):
+ """Transforms a ParseResult into a Path"""
+ path = ""
+ for handle in tokens.path[0].split("/"):
+ if "@" in handle:
+ node, address = handle.split("@")
+ path += "/%s@%x" % (node, int(address))
+ elif handle != "":
+ path += "/" + handle
+ return Path(path)
+
+def transformPathReference(string, location, tokens):
+ """Transforms a ParseResult into a PathReference"""
+ return PathReference(tokens[0])
+
+def transformLabelReference(string, location, tokens):
+ """Transforms a ParseResult into a LabelReference"""
+ return LabelReference(tokens[0])
+
+def transformReference(string, location, tokens):
+ """Transforms a ParseResult into a Reference"""
+ if isinstance(tokens[0], Reference):
+ return tokens[0]
+ return None
+
+grammar.label.setParseAction(transformLabel)
+grammar.node_path.setParseAction(transformPath)
+grammar.path_reference.setParseAction(transformPathReference)
+grammar.label_reference.setParseAction(transformLabelReference)
+grammar.reference.setParseAction(transformReference)
+grammar.node_definition.setParseAction(transformNode)
+grammar.property_assignment.setParseAction(transformPropertyAssignment)
+grammar.directive.setParseAction(transformDirective)
+grammar.arith_expr.setParseAction(evaluateArithExpr)
+grammar.ternary_expr.setParseAction(transformTernary)
+grammar.stringlist.setParseAction(transformStringList)
+grammar.bytestring.setParseAction(transformBytestring)
+grammar.cell_array.setParseAction(transformCellArray)
+grammar.property_values.setParseAction(transformPropertyValues)
+grammar.label_raw.setParseAction(transformString)
+grammar.ifdef_define_values.setParseAction(transformIfdefMacro)
+grammar.ifdef_end_values.setParseAction(transformIfdefEnd)
+grammar.arith_str_expr.setParseAction(transformPropertyValues)
+
+def printTree(tree, level=0):
+ """Helper function to print a bunch of elements as a tree"""
+ def printlevel(level, s):
+ print(" " * level + s)
+
+ for item in tree:
+ if isinstance(item, Node):
+ if item.address:
+ printlevel(level, "Node %s@%x" % (item.name, item.address))
+ else:
+ printlevel(level, "Node %s" % item.name)
+
+ if item.label:
+ printlevel(level, " Label: %s" % item.label)
+
+ if item.parent:
+ printlevel(level, " Parent: %s" % item.parent)
+
+ printTree(item.properties, level=(level + 1))
+
+ printTree(item.children, level=(level + 1))
+ elif isinstance(item, Property):
+ if item.values:
+ printlevel(level, "Property %s: %s" % (item.name, item.values))
+ else:
+ printlevel(level, "Property %s" % item.name)
+ elif isinstance(item, Directive):
+ if item.options:
+ printlevel(level, "Directive %s: %s" % (item.directive, item.options))
+ else:
+ printlevel(level, "Directive %s" % item.directive)
+
+def parentNodes(tree, parent=None):
+ """Walks a tree and sets Nodes' parent field to point at their parent"""
+ for item in tree:
+ if isinstance(item, Node):
+ item.parent = parent
+ parentNodes(item.children, item)
+
+def recurseIncludeFiles(elements, pwd):
+ """Recursively follows and parses /include/ directives an a tree"""
+ for e in elements:
+ if isinstance(e, Directive):
+ if e.directive == "/include/":
+ # Prefix with current directory if path is not absolute
+ if e.option[0] != '/':
+ e.option = pwd + e.option
+
+ with open(e.option, 'r') as f:
+ contents = f.read()
+
+ elements += parseElements(contents)
+
+ del elements[elements.asList().index(e)]
+
+def parseElements(dts, pwd="", followIncludes=False):
+ """Parses a string into a list of elements"""
+ elements = grammar.devicetree.parseString(dts, parseAll=True)
+ parentNodes(elements)
+ if followIncludes:
+ recurseIncludeFiles(elements, pwd)
+ return elements
+
+def parseTree(dts, pwd="", followIncludes=False):
+ """Parses a string into a full Devicetree"""
+ return Devicetree(parseElements(dts, pwd, followIncludes))
+
+def parseNode(dts):
+ """Parses a string into a Devictreee Node"""
+ return grammar.node_definition.parseString(dts, parseAll=True)[0]
+
+def parseProperty(dts):
+ """Parses a string into a Devicetree Property"""
+ return grammar.property_assignment.parseString(dts, parseAll=True)[0]
+
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) > 1:
+ with open(sys.argv[1], 'r') as f:
+ dts = f.read()
+ tree = parseTree(dts)
+ printTree(tree)
+ print(tree)
+ else:
+ print("Please pass the devicetree source file as an argument")
+ sys.exit(1)
diff --git a/tools/cot_dt2c/pyproject.toml b/tools/cot_dt2c/pyproject.toml
new file mode 100644
index 0000000..d383924
--- /dev/null
+++ b/tools/cot_dt2c/pyproject.toml
@@ -0,0 +1,60 @@
+# Poetry pyproject.toml: https://python-poetry.org/docs/pyproject/
+[build-system]
+requires = ["poetry_core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry]
+name = "cot_dt2c"
+version = "0.1.0"
+description = "CoT-dt2c Tool is a python script to convert CoT DT file into corresponding C file"
+authors = ["Arm Ltd <tf-a@lists.trustedfirmware.org>"]
+license = "BSD-3"
+repository = "https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git/"
+homepage = "https://trustedfirmware-a.readthedocs.io/en/latest/index.html"
+
+# Pypi classifiers: https://pypi.org/classifiers/
+classifiers = [
+ "Development Status :: 3 - Alpha",
+ "Intended Audience :: Developers",
+ "Operating System :: OS Independent",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "License :: OSI Approved :: BSD License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+]
+
+
+[tool.poetry.dependencies]
+python = "^3.8"
+click = "^8.1.7"
+pyparsing = "^2.4.7"
+plotly = "^5.23.0"
+pandas = "^2.2.2"
+igraph = "^0.11.6"
+
+[tool.poetry.dev-dependencies]
+mypy = "^0.910"
+pytest = "^6.2.5"
+pyparsing = "^2.4.7"
+plotly = "^5.23.0"
+pandas = "^2.2.2"
+igraph = "^0.11.6"
+
+[tool.mypy]
+# https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file
+python_version = 3.8
+pretty = true
+show_traceback = true
+color_output = true
+
+[tool.coverage.run]
+source = ["tests"]
+
+[coverage.paths]
+source = "cot_dt2c"
+
+[tool.poetry.scripts]
+# Entry points for the package https://python-poetry.org/docs/pyproject/#scripts
+# "cot-dt2c" = "cot_dt2c.__main__:cli"
+"cot-dt2c" = "cot_dt2c.__main__:cli"
diff --git a/tools/cot_dt2c/requirements.txt b/tools/cot_dt2c/requirements.txt
new file mode 100644
index 0000000..246b81d
--- /dev/null
+++ b/tools/cot_dt2c/requirements.txt
@@ -0,0 +1,6 @@
+mypy
+pylint
+pyparsing
+igraph
+pandas
+plotly
diff --git a/tools/cot_dt2c/tests/test.dtsi b/tools/cot_dt2c/tests/test.dtsi
new file mode 100644
index 0000000..ee744e6
--- /dev/null
+++ b/tools/cot_dt2c/tests/test.dtsi
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a valid CoT DT file
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+#if defined(test)
+ example_cert: example_cert {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+ };
+#endif
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ example {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ example_ctr: example_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ example_pk: example_pk {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test2.dtsi b/tools/cot_dt2c/tests/test2.dtsi
new file mode 100644
index 0000000..c4dbf83
--- /dev/null
+++ b/tools/cot_dt2c/tests/test2.dtsi
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a valid CoT DT file
+ *
+ */
+
+#if test
+#include <example/example.h>
+#include <example/example/example.h>
+#endif
+
+cot
+{
+ manifests
+ {
+ compatible = "arm, cert-descs";
+#if defined (test)
+ example_cert: example_cert
+ {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+
+ };
+#endif
+ };
+
+ images
+ {
+ compatible = "arm, img-descs";
+
+ example
+ {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+ };
+};
+
+non_volatile_counters: non_volatile_counters
+{
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ example_ctr: example_ctr
+ {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys
+{
+ example_pk: example_pk
+ {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_bracket.dtsi b/tools/cot_dt2c/tests/test_invalid_bracket.dtsi
new file mode 100644
index 0000000..ec5f9c7
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_bracket.dtsi
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching bracket
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ example_cert: example_cert {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+
+ };
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ example {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ example_ctr: example_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ example_pk: example_pk {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi b/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi
new file mode 100644
index 0000000..5bc6bb9
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_ifdef.dtsi
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching ifdef macro
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ example_cert: example_cert {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+
+ };
+ };
+
+#if defined(test)
+ images {
+ compatible = "arm, img-descs";
+
+ example {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ example_ctr: example_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ example_pk: example_pk {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi b/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi
new file mode 100644
index 0000000..c915168
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_ifdef2.dtsi
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there is
+ * unmatching ifdef macro
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ example_cert: example_cert {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+
+ };
+ };
+
+#if defined(test)
+ images {
+ compatible = "arm, img-descs";
+
+ example {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+ };
+#endif
+#endif
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ example_ctr: example_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ example_pk: example_pk {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi
new file mode 100644
index 0000000..9c0a5f2
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_attribute.dtsi
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing mandantory attributes
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ cca_content_cert: cca_content_cert {
+ root-certificate;
+ antirollback-counter = <&cca_nv_ctr>;
+
+ tb_fw_hash: tb_fw_hash {
+ oid = TRUSTED_BOOT_FW_HASH_OID;
+ };
+ tb_fw_config_hash: tb_fw_config_hash {
+ oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+ };
+ hw_config_hash: hw_config_hash {
+ };
+ fw_config_hash: fw_config_hash {
+ oid = FW_CONFIG_HASH_OID;
+ };
+ soc_fw_hash: soc_fw_hash {
+ oid = SOC_AP_FW_HASH_OID;
+ };
+ soc_fw_config_hash: soc_fw_config_hash {
+ oid = SOC_FW_CONFIG_HASH_OID;
+ };
+ rmm_hash: rmm_hash {
+ oid = RMM_HASH_OID;
+ };
+ };
+
+ core_swd_key_cert: core_swd_key_cert {
+ root-certificate;
+ image-id = <CORE_SWD_KEY_CERT_ID>;
+ signing-key = <&swd_rot_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ core_swd_pk: core_swd_pk {
+ oid = CORE_SWD_PK_OID;
+ };
+ };
+
+ trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+ image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ tos_fw_hash: tos_fw_hash {
+ oid = TRUSTED_OS_FW_HASH_OID;
+ };
+ tos_fw_config_hash: tos_fw_config_hash {
+ oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+ };
+ };
+
+ plat_key_cert: plat_key_cert {
+ root-certificate;
+ image-id = <PLAT_KEY_CERT_ID>;
+ signing-key = <&prot_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ plat_pk: plat_pk {
+ oid = PLAT_PK_OID;
+ };
+ };
+
+ non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+ image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+ parent = <&plat_key_cert>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ nt_world_bl_hash: nt_world_bl_hash {
+ oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+ };
+ nt_fw_config_hash: nt_fw_config_hash {
+ oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+ };
+ };
+
+#if defined(SPD_spmd)
+ sip_sp_content_cert: sip_sp_content_cert {
+ image-id = <SIP_SP_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ sp_pkg1_hash: sp_pkg1_hash {
+ oid = SP_PKG1_HASH_OID;
+ };
+ sp_pkg2_hash: sp_pkg2_hash {
+ oid = SP_PKG2_HASH_OID;
+ };
+ sp_pkg3_hash: sp_pkg3_hash {
+ oid = SP_PKG3_HASH_OID;
+ };
+ sp_pkg4_hash: sp_pkg4_hash {
+ oid = SP_PKG4_HASH_OID;
+ };
+ };
+
+ plat_sp_content_cert: plat_sp_content_cert {
+ parent = <&plat_key_cert>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ sp_pkg5_hash: sp_pkg5_hash {
+ oid = SP_PKG5_HASH_OID;
+ };
+ sp_pkg6_hash: sp_pkg6_hash {
+ oid = SP_PKG6_HASH_OID;
+ };
+ sp_pkg7_hash: sp_pkg7_hash {
+ oid = SP_PKG7_HASH_OID;
+ };
+ sp_pkg8_hash: sp_pkg8_hash {
+ oid = SP_PKG8_HASH_OID;
+ };
+ };
+#endif
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ hw_config {
+ image-id = <HW_CONFIG_ID>;
+ hash = <&hw_config_hash>;
+ };
+
+ bl31_image {
+ image-id = <BL31_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_hash>;
+ };
+
+ soc_fw_config {
+ image-id = <SOC_FW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_config_hash>;
+ };
+
+ rmm_image {
+ image-id = <RMM_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&rmm_hash>;
+ };
+
+ bl32_image {
+ image-id = <BL32_IMAGE_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_hash>;
+ };
+
+ tos_fw_config {
+ image-id = <TOS_FW_CONFIG_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_config_hash>;
+ };
+
+ bl33_image {
+ image-id = <BL33_IMAGE_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_world_bl_hash>;
+ };
+
+ nt_fw_config {
+ image-id = <NT_FW_CONFIG_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_fw_config_hash>;
+ };
+
+#if defined(SPD_spmd)
+ sp_pkg1 {
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg1_hash>;
+ };
+
+ sp_pkg2 {
+ image-id = <SP_PKG2_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg2_hash>;
+ };
+
+ sp_pkg3 {
+ image-id = <SP_PKG3_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg3_hash>;
+ };
+
+ sp_pkg4 {
+ image-id = <SP_PKG4_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg4_hash>;
+ };
+
+ sp_pkg5 {
+ image-id = <SP_PKG5_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg5_hash>;
+ };
+
+ sp_pkg6 {
+ image-id = <SP_PKG6_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg6_hash>;
+ };
+
+ sp_pkg7 {
+ image-id = <SP_PKG7_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg7_hash>;
+ };
+
+ sp_pkg8 {
+ image-id = <SP_PKG8_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg8_hash>;
+ };
+#endif
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cca_nv_ctr: cca_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+
+ trusted_nv_ctr: trusted_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = TRUSTED_FW_NVCOUNTER_OID;
+ };
+
+ non_trusted_nv_ctr: non_trusted_nv_ctr {
+ id = <NON_TRUSTED_NV_CTR_ID>;
+ oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ swd_rot_pk: swd_rot_pk {
+ oid = SWD_ROT_PK_OID;
+ };
+ prot_pk: prot_pk {
+ oid = PROT_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi
new file mode 100644
index 0000000..01b2597
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_attribute2.dtsi
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that points to invalid parent
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ cca_content_cert: cca_content_cert {
+ root-certificate;
+ image-id =<CCA_CONTENT_CERT_ID>;
+ antirollback-counter = <&cca_nv_ctr>;
+
+ tb_fw_hash: tb_fw_hash {
+ oid = TRUSTED_BOOT_FW_HASH_OID;
+ };
+ tb_fw_config_hash: tb_fw_config_hash {
+ oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+ };
+ hw_config_hash: hw_config_hash {
+ oid = HW_CONFIG_HASH_OID;
+ };
+ fw_config_hash: fw_config_hash {
+ oid = FW_CONFIG_HASH_OID;
+ };
+ soc_fw_hash: soc_fw_hash {
+ oid = SOC_AP_FW_HASH_OID;
+ };
+ soc_fw_config_hash: soc_fw_config_hash {
+ oid = SOC_FW_CONFIG_HASH_OID;
+ };
+ rmm_hash: rmm_hash {
+ oid = RMM_HASH_OID;
+ };
+ };
+
+ core_swd_key_cert: core_swd_key_cert {
+ root-certificate;
+ image-id = <CORE_SWD_KEY_CERT_ID>;
+ signing-key = <&swd_rot_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ core_swd_pk: core_swd_pk {
+ oid = CORE_SWD_PK_OID;
+ };
+ };
+
+ trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+ image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ tos_fw_hash: tos_fw_hash {
+ oid = TRUSTED_OS_FW_HASH_OID;
+ };
+ tos_fw_config_hash: tos_fw_config_hash {
+ oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+ };
+ };
+
+ plat_key_cert: plat_key_cert {
+ root-certificate;
+ image-id = <PLAT_KEY_CERT_ID>;
+ signing-key = <&prot_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ plat_pk: plat_pk {
+ oid = PLAT_PK_OID;
+ };
+ };
+
+ non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+ image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+ parent = <&cca_content_cert>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ nt_world_bl_hash: nt_world_bl_hash {
+ oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+ };
+ nt_fw_config_hash: nt_fw_config_hash {
+ oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+ };
+ };
+
+#if defined(SPD_spmd)
+ sip_sp_content_cert: sip_sp_content_cert {
+ image-id = <SIP_SP_CONTENT_CERT_ID>;
+ parent = <&cca_content_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ sp_pkg1_hash: sp_pkg1_hash {
+ oid = SP_PKG1_HASH_OID;
+ };
+ sp_pkg2_hash: sp_pkg2_hash {
+ oid = SP_PKG2_HASH_OID;
+ };
+ sp_pkg3_hash: sp_pkg3_hash {
+ oid = SP_PKG3_HASH_OID;
+ };
+ sp_pkg4_hash: sp_pkg4_hash {
+ oid = SP_PKG4_HASH_OID;
+ };
+ };
+
+ plat_sp_content_cert: plat_sp_content_cert {
+ image-id = <PLAT_SP_CONTENT_CERT_ID>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ sp_pkg5_hash: sp_pkg5_hash {
+ oid = SP_PKG5_HASH_OID;
+ };
+ sp_pkg6_hash: sp_pkg6_hash {
+ oid = SP_PKG6_HASH_OID;
+ };
+ sp_pkg7_hash: sp_pkg7_hash {
+ oid = SP_PKG7_HASH_OID;
+ };
+ sp_pkg8_hash: sp_pkg8_hash {
+ oid = SP_PKG8_HASH_OID;
+ };
+ };
+#endif
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ hw_config {
+ image-id = <HW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&hw_config_hash>;
+ };
+
+ bl31_image {
+ image-id = <BL31_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_hash>;
+ };
+
+ soc_fw_config {
+ image-id = <SOC_FW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_config_hash>;
+ };
+
+ rmm_image {
+ image-id = <RMM_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&rmm_hash>;
+ };
+
+ bl32_image {
+ image-id = <BL32_IMAGE_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_hash>;
+ };
+
+ tos_fw_config {
+ image-id = <TOS_FW_CONFIG_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_config_hash>;
+ };
+
+ bl33_image {
+ image-id = <BL33_IMAGE_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_world_bl_hash>;
+ };
+
+ nt_fw_config {
+ image-id = <NT_FW_CONFIG_ID>;
+ hash = <&nt_fw_config_hash>;
+ };
+
+#if defined(SPD_spmd)
+ sp_pkg1 {
+ image-id = <SP_PKG1_ID>;
+ hash = <&sp_pkg1_hash>;
+ };
+
+ sp_pkg2 {
+ image-id = <SP_PKG2_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg2_hash>;
+ };
+
+ sp_pkg3 {
+ image-id = <SP_PKG3_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg3_hash>;
+ };
+
+ sp_pkg4 {
+ image-id = <SP_PKG4_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg4_hash>;
+ };
+
+ sp_pkg5 {
+ image-id = <SP_PKG5_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg5_hash>;
+ };
+
+ sp_pkg6 {
+ image-id = <SP_PKG6_ID>;
+ parent = <&wrong_parent>;
+ hash = <&sp_pkg6_hash>;
+ };
+
+ sp_pkg7 {
+ image-id = <SP_PKG7_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg7_hash>;
+ };
+
+ sp_pkg8 {
+ image-id = <SP_PKG8_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg8_hash>;
+ };
+#endif
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cca_nv_ctr: cca_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+
+ trusted_nv_ctr: trusted_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = TRUSTED_FW_NVCOUNTER_OID;
+ };
+
+ non_trusted_nv_ctr: non_trusted_nv_ctr {
+ id = <NON_TRUSTED_NV_CTR_ID>;
+ oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ swd_rot_pk: swd_rot_pk {
+ oid = SWD_ROT_PK_OID;
+ };
+ prot_pk: prot_pk {
+ oid = PROT_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi
new file mode 100644
index 0000000..5958f5d
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_ctr.dtsi
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing definition of
+ * nv counters
+ *
+ */
+
+#include <example/example.h>
+#include <example/example/example.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ example_cert: example_cert {
+ root-certificate;
+ image-id =<EXAMPLE_ID>;
+ signing-key = <&swd_rot_pk>;
+ antirollback-counter = <&example_ctr>;
+
+ example_hash: example_hash
+ {
+ oid = EXAMPLE_HASH_ID;
+ };
+
+ };
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ example {
+ image-id = <EXAMPLE_ID>;
+ parent = <&example_cert>;
+ hash = <&example_hash>;
+ };
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+};
+
+rot_keys {
+ example_pk: example_pk {
+ oid = EXAMPLE_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi b/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi
new file mode 100644
index 0000000..465a4c6
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_missing_root.dtsi
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that missing root certificate
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ core_swd_key_cert: core_swd_key_cert {
+ image-id = <CORE_SWD_KEY_CERT_ID>;
+ signing-key = <&swd_rot_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ core_swd_pk: core_swd_pk {
+ oid = CORE_SWD_PK_OID;
+ };
+ };
+
+ trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+ image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ tos_fw_hash: tos_fw_hash {
+ oid = TRUSTED_OS_FW_HASH_OID;
+ };
+ tos_fw_config_hash: tos_fw_config_hash {
+ oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+ };
+ };
+
+ plat_key_cert: plat_key_cert {
+ image-id = <PLAT_KEY_CERT_ID>;
+ signing-key = <&prot_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ plat_pk: plat_pk {
+ oid = PLAT_PK_OID;
+ };
+ };
+
+ non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+ image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+ parent = <&plat_key_cert>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ nt_world_bl_hash: nt_world_bl_hash {
+ oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+ };
+ nt_fw_config_hash: nt_fw_config_hash {
+ oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+ };
+ };
+
+#if defined(SPD_spmd)
+ sip_sp_content_cert: sip_sp_content_cert {
+ image-id = <SIP_SP_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ sp_pkg1_hash: sp_pkg1_hash {
+ oid = SP_PKG1_HASH_OID;
+ };
+ sp_pkg2_hash: sp_pkg2_hash {
+ oid = SP_PKG2_HASH_OID;
+ };
+ sp_pkg3_hash: sp_pkg3_hash {
+ oid = SP_PKG3_HASH_OID;
+ };
+ sp_pkg4_hash: sp_pkg4_hash {
+ oid = SP_PKG4_HASH_OID;
+ };
+ };
+
+ plat_sp_content_cert: plat_sp_content_cert {
+ image-id = <PLAT_SP_CONTENT_CERT_ID>;
+ parent = <&plat_key_cert>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ sp_pkg5_hash: sp_pkg5_hash {
+ oid = SP_PKG5_HASH_OID;
+ };
+ sp_pkg6_hash: sp_pkg6_hash {
+ oid = SP_PKG6_HASH_OID;
+ };
+ sp_pkg7_hash: sp_pkg7_hash {
+ oid = SP_PKG7_HASH_OID;
+ };
+ sp_pkg8_hash: sp_pkg8_hash {
+ oid = SP_PKG8_HASH_OID;
+ };
+ };
+#endif
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ hw_config {
+ image-id = <HW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&hw_config_hash>;
+ };
+
+ bl31_image {
+ image-id = <BL31_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_hash>;
+ };
+
+ soc_fw_config {
+ image-id = <SOC_FW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_config_hash>;
+ };
+
+ rmm_image {
+ image-id = <RMM_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&rmm_hash>;
+ };
+
+ bl32_image {
+ image-id = <BL32_IMAGE_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_hash>;
+ };
+
+ tos_fw_config {
+ image-id = <TOS_FW_CONFIG_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_config_hash>;
+ };
+
+ bl33_image {
+ image-id = <BL33_IMAGE_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_world_bl_hash>;
+ };
+
+ nt_fw_config {
+ image-id = <NT_FW_CONFIG_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_fw_config_hash>;
+ };
+
+#if defined(SPD_spmd)
+ sp_pkg1 {
+ image-id = <SP_PKG1_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg1_hash>;
+ };
+
+ sp_pkg2 {
+ image-id = <SP_PKG2_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg2_hash>;
+ };
+
+ sp_pkg3 {
+ image-id = <SP_PKG3_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg3_hash>;
+ };
+
+ sp_pkg4 {
+ image-id = <SP_PKG4_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg4_hash>;
+ };
+
+ sp_pkg5 {
+ image-id = <SP_PKG5_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg5_hash>;
+ };
+
+ sp_pkg6 {
+ image-id = <SP_PKG6_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg6_hash>;
+ };
+
+ sp_pkg7 {
+ image-id = <SP_PKG7_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg7_hash>;
+ };
+
+ sp_pkg8 {
+ image-id = <SP_PKG8_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg8_hash>;
+ };
+#endif
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cca_nv_ctr: cca_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+
+ trusted_nv_ctr: trusted_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = TRUSTED_FW_NVCOUNTER_OID;
+ };
+
+ non_trusted_nv_ctr: non_trusted_nv_ctr {
+ id = <NON_TRUSTED_NV_CTR_ID>;
+ oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ swd_rot_pk: swd_rot_pk {
+ oid = SWD_ROT_PK_OID;
+ };
+ prot_pk: prot_pk {
+ oid = PROT_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi b/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi
new file mode 100644
index 0000000..b761beb
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_invalid_undefined_parent.dtsi
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2024, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * This file provide a malformed CoT DT file that there
+ * are image/certificate that points to invalid parent
+ *
+ */
+
+#include <tools_share/cca_oid.h>
+#include <common/tbbr/tbbr_img_def.h>
+#include <common/nv_cntr_ids.h>
+
+cot {
+ manifests {
+ compatible = "arm, cert-descs";
+
+ cca_content_cert: cca_content_cert {
+ root-certificate;
+ image-id =<CCA_CONTENT_CERT_ID>;
+ antirollback-counter = <&cca_nv_ctr>;
+
+ tb_fw_hash: tb_fw_hash {
+ oid = TRUSTED_BOOT_FW_HASH_OID;
+ };
+ tb_fw_config_hash: tb_fw_config_hash {
+ oid = TRUSTED_BOOT_FW_CONFIG_HASH_OID;
+ };
+ hw_config_hash: hw_config_hash {
+ oid = HW_CONFIG_HASH_OID;
+ };
+ fw_config_hash: fw_config_hash {
+ oid = FW_CONFIG_HASH_OID;
+ };
+ soc_fw_hash: soc_fw_hash {
+ oid = SOC_AP_FW_HASH_OID;
+ };
+ soc_fw_config_hash: soc_fw_config_hash {
+ oid = SOC_FW_CONFIG_HASH_OID;
+ };
+ rmm_hash: rmm_hash {
+ oid = RMM_HASH_OID;
+ };
+ };
+
+ core_swd_key_cert: core_swd_key_cert {
+ root-certificate;
+ image-id = <CORE_SWD_KEY_CERT_ID>;
+ signing-key = <&swd_rot_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ core_swd_pk: core_swd_pk {
+ oid = CORE_SWD_PK_OID;
+ };
+ };
+
+ trusted_os_fw_content_cert: trusted_os_fw_content_cert {
+ image-id = <TRUSTED_OS_FW_CONTENT_CERT_ID>;
+ parent = <&core_swd_key_cert>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ tos_fw_hash: tos_fw_hash {
+ oid = TRUSTED_OS_FW_HASH_OID;
+ };
+ tos_fw_config_hash: tos_fw_config_hash {
+ oid = TRUSTED_OS_FW_CONFIG_HASH_OID;
+ };
+ };
+
+ plat_key_cert: plat_key_cert {
+ root-certificate;
+ image-id = <PLAT_KEY_CERT_ID>;
+ signing-key = <&prot_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ plat_pk: plat_pk {
+ oid = PLAT_PK_OID;
+ };
+ };
+
+ non_trusted_fw_content_cert: non_trusted_fw_content_cert {
+ image-id = <NON_TRUSTED_FW_CONTENT_CERT_ID>;
+ parent = <&wrong_parent>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ nt_world_bl_hash: nt_world_bl_hash {
+ oid = NON_TRUSTED_WORLD_BOOTLOADER_HASH_OID;
+ };
+ nt_fw_config_hash: nt_fw_config_hash {
+ oid = NON_TRUSTED_FW_CONFIG_HASH_OID;
+ };
+ };
+
+#if defined(SPD_spmd)
+ sip_sp_content_cert: sip_sp_content_cert {
+ image-id = <SIP_SP_CONTENT_CERT_ID>;
+ parent = <&wrong_parent>;
+ signing-key = <&core_swd_pk>;
+ antirollback-counter = <&trusted_nv_ctr>;
+
+ sp_pkg1_hash: sp_pkg1_hash {
+ oid = SP_PKG1_HASH_OID;
+ };
+ sp_pkg2_hash: sp_pkg2_hash {
+ oid = SP_PKG2_HASH_OID;
+ };
+ sp_pkg3_hash: sp_pkg3_hash {
+ oid = SP_PKG3_HASH_OID;
+ };
+ sp_pkg4_hash: sp_pkg4_hash {
+ oid = SP_PKG4_HASH_OID;
+ };
+ };
+
+ plat_sp_content_cert: plat_sp_content_cert {
+ image-id = <PLAT_SP_CONTENT_CERT_ID>;
+ signing-key = <&plat_pk>;
+ antirollback-counter = <&non_trusted_nv_ctr>;
+
+ sp_pkg5_hash: sp_pkg5_hash {
+ oid = SP_PKG5_HASH_OID;
+ };
+ sp_pkg6_hash: sp_pkg6_hash {
+ oid = SP_PKG6_HASH_OID;
+ };
+ sp_pkg7_hash: sp_pkg7_hash {
+ oid = SP_PKG7_HASH_OID;
+ };
+ sp_pkg8_hash: sp_pkg8_hash {
+ oid = SP_PKG8_HASH_OID;
+ };
+ };
+#endif
+ };
+
+ images {
+ compatible = "arm, img-descs";
+
+ hw_config {
+ image-id = <HW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&hw_config_hash>;
+ };
+
+ bl31_image {
+ image-id = <BL31_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_hash>;
+ };
+
+ soc_fw_config {
+ image-id = <SOC_FW_CONFIG_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&soc_fw_config_hash>;
+ };
+
+ rmm_image {
+ image-id = <RMM_IMAGE_ID>;
+ parent = <&cca_content_cert>;
+ hash = <&rmm_hash>;
+ };
+
+ bl32_image {
+ image-id = <BL32_IMAGE_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_hash>;
+ };
+
+ tos_fw_config {
+ image-id = <TOS_FW_CONFIG_ID>;
+ parent = <&trusted_os_fw_content_cert>;
+ hash = <&tos_fw_config_hash>;
+ };
+
+ bl33_image {
+ image-id = <BL33_IMAGE_ID>;
+ parent = <&non_trusted_fw_content_cert>;
+ hash = <&nt_world_bl_hash>;
+ };
+
+ nt_fw_config {
+ image-id = <NT_FW_CONFIG_ID>;
+ hash = <&nt_fw_config_hash>;
+ };
+
+#if defined(SPD_spmd)
+ sp_pkg1 {
+ image-id = <SP_PKG1_ID>;
+ hash = <&sp_pkg1_hash>;
+ };
+
+ sp_pkg2 {
+ image-id = <SP_PKG2_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg2_hash>;
+ };
+
+ sp_pkg3 {
+ image-id = <SP_PKG3_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg3_hash>;
+ };
+
+ sp_pkg4 {
+ image-id = <SP_PKG4_ID>;
+ parent = <&sip_sp_content_cert>;
+ hash = <&sp_pkg4_hash>;
+ };
+
+ sp_pkg5 {
+ image-id = <SP_PKG5_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg5_hash>;
+ };
+
+ sp_pkg6 {
+ image-id = <SP_PKG6_ID>;
+ parent = <&wrong_parent>;
+ hash = <&sp_pkg6_hash>;
+ };
+
+ sp_pkg7 {
+ image-id = <SP_PKG7_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg7_hash>;
+ };
+
+ sp_pkg8 {
+ image-id = <SP_PKG8_ID>;
+ parent = <&plat_sp_content_cert>;
+ hash = <&sp_pkg8_hash>;
+ };
+#endif
+ };
+};
+
+non_volatile_counters: non_volatile_counters {
+ compatible = "arm, non-volatile-counter";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cca_nv_ctr: cca_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = CCA_FW_NVCOUNTER_OID;
+ };
+
+ trusted_nv_ctr: trusted_nv_ctr {
+ id = <TRUSTED_NV_CTR_ID>;
+ oid = TRUSTED_FW_NVCOUNTER_OID;
+ };
+
+ non_trusted_nv_ctr: non_trusted_nv_ctr {
+ id = <NON_TRUSTED_NV_CTR_ID>;
+ oid = NON_TRUSTED_FW_NVCOUNTER_OID;
+ };
+};
+
+rot_keys {
+ swd_rot_pk: swd_rot_pk {
+ oid = SWD_ROT_PK_OID;
+ };
+ prot_pk: prot_pk {
+ oid = PROT_PK_OID;
+ };
+};
diff --git a/tools/cot_dt2c/tests/test_util.py b/tools/cot_dt2c/tests/test_util.py
new file mode 100644
index 0000000..b8e44d4
--- /dev/null
+++ b/tools/cot_dt2c/tests/test_util.py
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2024, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import os
+import sys
+
+from cot_dt2c.cli import *
+from click.testing import CliRunner
+
+def get_script_path():
+ return os.path.dirname(os.path.realpath(sys.argv[0]))
+
+def test_convert():
+ runner = CliRunner()
+ test_file = get_script_path() + "/test.dtsi"
+ test_output = get_script_path() + "/test.c"
+
+ result = runner.invoke(convert_to_c, [test_file, test_output])
+ try:
+ assert result.output == ""
+ except:
+ print("test convert fail")
+
+ try:
+ os.remove(test_output)
+ except OSError:
+ pass
+
+if __name__=="__main__":
+ test_convert()