Implemented TactilityKernel and DevicetreeCompiler, updated licenses & copyrights (#452)
**New features** - Created a devicetree DTS and YAML parser in Python - Created new modules: - TactilityKernel (LGPL v3.0 license) - Platforms/PlatformEsp32 (LGPL v3.0 license) - Platforms/PlatformPosix (LGPL v3.0 license) - Tests/TactilityKernelTests Most boards have a placeholder DTS file, while T-Lora Pager has a few devices attached. **Licenses** Clarified licenses and copyrights better. - Add explanation about the intent behind them. - Added explanation about licenses for past and future subprojects - Added more details explanations with regards to the logo usage - Copied licenses to subprojects to make it more explicit
This commit is contained in:
committed by
GitHub
parent
0d16eb606f
commit
4b6ed871a9
@@ -0,0 +1,4 @@
|
||||
.venv/
|
||||
.idea/
|
||||
__pycache__/
|
||||
build/
|
||||
@@ -0,0 +1,195 @@
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import sys
|
||||
|
||||
from source.printing import print_error
|
||||
from source.main import main
|
||||
|
||||
def print_help():
|
||||
print("Usage: python compile.py [in_file] [out_path] [arguments]\n")
|
||||
print(f"\t[in_path] the path where the root devicetree.yaml file is")
|
||||
print(f"\t[out_path] output folder for C file output")
|
||||
print("")
|
||||
print("Optional arguments:\n")
|
||||
print("\t--help prints this help text")
|
||||
print("\t--verbose output debug info")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--help" in sys.argv:
|
||||
print_help()
|
||||
sys.exit()
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
if len(args) < 2:
|
||||
print_error("Missing argument")
|
||||
print_help()
|
||||
sys.exit()
|
||||
is_verbose = "--verbose" in sys.argv
|
||||
devicetree_yaml_config = args[0]
|
||||
output_path = args[1]
|
||||
main(devicetree_yaml_config, output_path, is_verbose)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
|
||||
def find_bindings(directory_path: str) -> list[str]:
|
||||
yaml_files = []
|
||||
for root, dirs, files in os.walk(directory_path):
|
||||
for file in files:
|
||||
if file.endswith(".yaml"):
|
||||
full_path = os.path.join(root, file)
|
||||
yaml_files.append(os.path.abspath(full_path))
|
||||
return yaml_files
|
||||
|
||||
def find_all_bindings(directory_paths: list[str]) -> list[str]:
|
||||
yaml_files = []
|
||||
for directory_path in directory_paths:
|
||||
new_paths = find_bindings(directory_path)
|
||||
if len(new_paths) == 0:
|
||||
raise Exception(f"No bindings found in {directory_path}")
|
||||
yaml_files += new_paths
|
||||
return yaml_files
|
||||
@@ -0,0 +1,57 @@
|
||||
import yaml
|
||||
import os
|
||||
from .models import Binding, BindingProperty
|
||||
|
||||
def parse_binding(file_path: str, binding_dirs: list[str]) -> Binding:
|
||||
with open(file_path, 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
description = data.get('description', '')
|
||||
bus = data.get('bus', None)
|
||||
properties_dict = {}
|
||||
|
||||
# Handle inclusions
|
||||
includes = data.get('include', [])
|
||||
all_includes = list(includes) # Copy for iteration
|
||||
for include_file in includes:
|
||||
include_path = None
|
||||
for binding_dir in binding_dirs:
|
||||
potential_path = os.path.join(binding_dir, include_file)
|
||||
if os.path.exists(potential_path):
|
||||
include_path = potential_path
|
||||
break
|
||||
|
||||
if not include_path:
|
||||
print(f"Warning: Could not find include file {include_file}")
|
||||
continue
|
||||
|
||||
parent_binding = parse_binding(include_path, binding_dirs)
|
||||
if not description and parent_binding.description:
|
||||
description = parent_binding.description
|
||||
if not bus and parent_binding.bus:
|
||||
bus = parent_binding.bus
|
||||
for prop in parent_binding.properties:
|
||||
properties_dict[prop.name] = prop
|
||||
for include in parent_binding.includes:
|
||||
all_includes.append(include)
|
||||
|
||||
# Parse local properties
|
||||
compatible = data.get('compatible', None)
|
||||
properties_raw = data.get('properties', {})
|
||||
for name, details in properties_raw.items():
|
||||
prop = BindingProperty(
|
||||
name=name,
|
||||
type=details.get('type', 'unknown'),
|
||||
required=details.get('required', False),
|
||||
description=details.get('description', '').strip(),
|
||||
)
|
||||
properties_dict[name] = prop
|
||||
filename = os.path.basename(file_path)
|
||||
return Binding(
|
||||
filename=filename,
|
||||
compatible=compatible,
|
||||
description=description.strip(),
|
||||
properties=list(properties_dict.values()),
|
||||
includes=all_includes,
|
||||
bus=bus
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
from dataclasses import dataclass, field
|
||||
import yaml
|
||||
import os
|
||||
|
||||
@dataclass
|
||||
class DeviceTreeConfig:
|
||||
dependencies: list[str] = field(default_factory=list)
|
||||
bindings: list[str] = field(default_factory=list)
|
||||
dts: str = ""
|
||||
|
||||
def parse_config(file_path: str, project_root: str) -> DeviceTreeConfig:
|
||||
"""
|
||||
Parses devicetree.yaml and recursively finds dependencies.
|
||||
Returns a list of DeviceTreeConfig objects in post-order (dependencies first).
|
||||
"""
|
||||
config = DeviceTreeConfig([], [], "")
|
||||
visited = set()
|
||||
|
||||
def _parse_recursive(current_path: str, is_root: bool):
|
||||
abs_path = os.path.abspath(current_path)
|
||||
if abs_path in visited:
|
||||
return
|
||||
visited.add(abs_path)
|
||||
|
||||
# Try to see if it's a directory and contains devicetree.yaml
|
||||
if os.path.isdir(abs_path):
|
||||
abs_path = os.path.join(abs_path, "devicetree.yaml")
|
||||
|
||||
with open(abs_path, 'r') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
# Handle dependencies before adding current config (post-order)
|
||||
deps = data.get("dependencies", [])
|
||||
for dep in deps:
|
||||
# Dependencies are relative to project_root
|
||||
dep_path = os.path.join(project_root, dep)
|
||||
_parse_recursive(dep_path, False)
|
||||
|
||||
if is_root:
|
||||
config.dependencies += deps
|
||||
dts_path = data.get("dts", "")
|
||||
config.dts = os.path.join(current_path, dts_path)
|
||||
|
||||
bindings = data.get("bindings", "")
|
||||
if bindings:
|
||||
bindings_resolved = os.path.join(current_path, bindings)
|
||||
config.bindings.append(bindings_resolved)
|
||||
|
||||
_parse_recursive(file_path, True)
|
||||
return config
|
||||
@@ -0,0 +1,9 @@
|
||||
def read_file(path: str):
|
||||
with open(path, "r") as file:
|
||||
result = file.read()
|
||||
return result
|
||||
|
||||
def write_file(path: str, content: str):
|
||||
with open(path, "w") as file:
|
||||
result = file.write(content)
|
||||
return result
|
||||
@@ -0,0 +1,217 @@
|
||||
import os.path
|
||||
from textwrap import dedent
|
||||
|
||||
from source.models import *
|
||||
|
||||
def write_include(file, include: IncludeC, verbose: bool):
|
||||
if verbose:
|
||||
print("Processing include:")
|
||||
print(f" {include.statement}")
|
||||
file.write(include.statement)
|
||||
file.write('\n')
|
||||
|
||||
def get_device_identifier_safe(device: Device):
|
||||
if device.identifier == "/":
|
||||
return "root"
|
||||
else:
|
||||
return device.identifier
|
||||
|
||||
def get_device_type_name(device: Device, bindings: list[Binding]):
|
||||
device_binding = find_device_binding(device, bindings)
|
||||
if device_binding is None:
|
||||
raise Exception(f"Binding not found for {device.identifier}")
|
||||
if device_binding.compatible is None:
|
||||
raise Exception(f"Couldn't find compatible binding for {device.identifier}")
|
||||
compatible_safe = device_binding.compatible.split(",")[-1]
|
||||
return compatible_safe.replace("-", "_")
|
||||
|
||||
def find_device_property(device: Device, name: str) -> DeviceProperty:
|
||||
for property in device.properties:
|
||||
if property.name == name:
|
||||
return property
|
||||
return None
|
||||
|
||||
def find_device_binding(device: Device, bindings: list[Binding]) -> Binding:
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"property 'compatible' not found in device {device.identifier}")
|
||||
for binding in bindings:
|
||||
if binding.compatible == compatible_property.value:
|
||||
return binding
|
||||
return None
|
||||
|
||||
def find_binding(compatible: str, bindings: list[Binding]) -> Binding:
|
||||
for binding in bindings:
|
||||
if binding.compatible == compatible:
|
||||
return binding
|
||||
return None
|
||||
|
||||
def property_to_string(property: DeviceProperty) -> str:
|
||||
type = property.type
|
||||
if type == "value":
|
||||
return property.value
|
||||
elif type == "text":
|
||||
return f"\"{property.value}\""
|
||||
elif type == "values":
|
||||
return "{ " + ",".join(property.value) + " }"
|
||||
else:
|
||||
raise Exception(f"property_to_string() has an unsupported type: {type}")
|
||||
|
||||
def resolve_parameters_from_bindings(device: Device, bindings: list[Binding]) -> list:
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
device_binding = find_binding(compatible_property.value, bindings)
|
||||
if device_binding is None:
|
||||
raise Exception(f"Binding not found for {device.identifier} and compatible '{compatible_property.value}'")
|
||||
# Filter out system properties
|
||||
binding_properties = []
|
||||
for property in device_binding.properties:
|
||||
if property.name != "compatible":
|
||||
binding_properties.append(property)
|
||||
# Allocate total expected configuration arguments
|
||||
result = [0] * len(binding_properties)
|
||||
for index, binding_property in enumerate(binding_properties):
|
||||
device_property = find_device_property(device, binding_property.name)
|
||||
if device_property is None:
|
||||
if binding_property.required:
|
||||
raise Exception(f"device {device.identifier} doesn't have property '{binding_property.name}'")
|
||||
else:
|
||||
result[index] = '0'
|
||||
else:
|
||||
result[index] = property_to_string(device_property)
|
||||
return result
|
||||
|
||||
def write_config(file, device: Device, bindings: list[Binding], type_name: str):
|
||||
device_identifier = get_device_identifier_safe(device)
|
||||
config_type = f"{type_name}_config_dt"
|
||||
config_variable_name = f"{device_identifier}_config"
|
||||
file.write(f"static const {config_type} {config_variable_name}" " = {\n")
|
||||
config_params = resolve_parameters_from_bindings(device, bindings)
|
||||
# Indent all params
|
||||
for index, config_param in enumerate(config_params):
|
||||
config_params[index] = f"\t{config_param}"
|
||||
# Join with command and newline
|
||||
if len(config_params) > 0:
|
||||
config_params_joined = ",\n".join(config_params)
|
||||
file.write(f"{config_params_joined}\n")
|
||||
file.write("};\n\n")
|
||||
|
||||
def write_device_structs(file, device: Device, parent_device: Device, bindings: list[Binding], verbose: bool):
|
||||
if verbose:
|
||||
print(f"Writing device struct for '{device.identifier}'")
|
||||
# Assemble some pre-requisites
|
||||
type_name = get_device_type_name(device, bindings)
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
identifier = get_device_identifier_safe(device)
|
||||
config_variable_name = f"{identifier}_config"
|
||||
if parent_device is not None:
|
||||
parent_identifier = get_device_identifier_safe(parent_device)
|
||||
parent_value = f"&{parent_identifier}"
|
||||
else:
|
||||
parent_value = "NULL"
|
||||
# Write config struct
|
||||
write_config(file, device, bindings, type_name)
|
||||
# Write device struct
|
||||
file.write(f"static struct Device {identifier}" " = {\n")
|
||||
file.write(f"\t.name = \"{device.identifier}\",\n") # Use original name
|
||||
file.write(f"\t.config = &{config_variable_name},\n")
|
||||
file.write(f"\t.parent = {parent_value},\n")
|
||||
file.write("};\n\n")
|
||||
# Child devices
|
||||
for child_device in device.devices:
|
||||
write_device_structs(file, child_device, device, bindings, verbose)
|
||||
|
||||
def write_device_init(file, device: Device, bindings: list[Binding], verbose: bool):
|
||||
if verbose:
|
||||
print(f"Processing device init code for '{device.identifier}'")
|
||||
# Assemble some pre-requisites
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
# Type & instance names
|
||||
identifier = get_device_identifier_safe(device)
|
||||
device_variable = identifier
|
||||
# Write device struct
|
||||
file.write(f"\tif (init_builtin_device(&{device_variable}, \"{compatible_property.value}\") != 0) return -1;\n")
|
||||
# Write children
|
||||
for child_device in device.devices:
|
||||
write_device_init(file, child_device, bindings, verbose)
|
||||
|
||||
def generate_devicetree_c(filename: str, items: list[object], bindings: list[Binding], verbose: bool):
|
||||
with open(filename, "w") as file:
|
||||
file.write(dedent('''\
|
||||
// Default headers
|
||||
#include <Tactility/Device.h>
|
||||
#include <Tactility/Driver.h>
|
||||
#include <Tactility/Log.h>
|
||||
// DTS headers
|
||||
'''))
|
||||
|
||||
# Write all headers first
|
||||
for item in items:
|
||||
if type(item) is IncludeC:
|
||||
write_include(file, item, verbose)
|
||||
file.write("\n")
|
||||
|
||||
file.write(dedent('''\
|
||||
#define TAG LOG_TAG(devicetree)
|
||||
|
||||
static int init_builtin_device(struct Device* device, const char* compatible) {
|
||||
struct Driver* driver = driver_find_compatible(compatible);
|
||||
if (driver == NULL) {
|
||||
LOG_E(TAG, "Can't find driver: %s", compatible);
|
||||
return -1;
|
||||
}
|
||||
device_construct(device);
|
||||
device_set_driver(device, driver);
|
||||
device_add(device);
|
||||
const int err = device_start(device);
|
||||
if (err != 0) {
|
||||
LOG_E(TAG, "Failed to start device %s with driver %s: error code %d", device->name, compatible, err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
'''))
|
||||
|
||||
# Then write all devices
|
||||
for item in items:
|
||||
if type(item) is Device:
|
||||
write_device_structs(file, item, None, bindings, verbose)
|
||||
# Init function body start
|
||||
file.write("int devices_builtin_init() {\n")
|
||||
# Init function body logic
|
||||
for item in items:
|
||||
if type(item) is Device:
|
||||
write_device_init(file, item, bindings, verbose)
|
||||
file.write("\treturn 0;\n")
|
||||
# Init function body end
|
||||
file.write("}\n")
|
||||
|
||||
def generate_devicetree_h(filename: str):
|
||||
with open(filename, "w") as file:
|
||||
file.write(dedent('''\
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int devices_builtin_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
'''))
|
||||
|
||||
def generate(output_path: str, items: list[object], bindings: list[Binding], verbose: bool):
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path)
|
||||
devicetree_c_filename = os.path.join(output_path, "devicetree.c")
|
||||
generate_devicetree_c(devicetree_c_filename, items, bindings, verbose)
|
||||
devicetree_h_filename = os.path.join(output_path, "devicetree.h")
|
||||
generate_devicetree_h(devicetree_h_filename)
|
||||
@@ -0,0 +1,46 @@
|
||||
%import common.DIGIT -> DIGIT
|
||||
%import common.LETTER -> LETTER
|
||||
%import common.HEXDIGIT -> HEXDIGIT
|
||||
%import common.SIGNED_INT -> SIGNED_INT
|
||||
%import common.WS -> WS
|
||||
%import common.SIGNED_NUMBER -> SIGNED_NUMBER
|
||||
%import common.ESCAPED_STRING -> ESCAPED_STRING
|
||||
%ignore WS
|
||||
|
||||
// Comment
|
||||
|
||||
COMMENT: /\/\*([^*]|\*+[^*\/])*\*+\//
|
||||
%ignore COMMENT
|
||||
|
||||
// Boolean
|
||||
|
||||
BOOLEAN: "true" | "false"
|
||||
|
||||
// Main
|
||||
|
||||
INCLUDE_C: /#include <[\w\/.\-]+>/
|
||||
|
||||
PROPERTY_NAME: /#?[a-zA-Z0-9_\-,]+/
|
||||
|
||||
QUOTE: "\""
|
||||
QUOTED_TEXT: QUOTE /[^"]+/ QUOTE
|
||||
quoted_text_array: QUOTED_TEXT ("," " "* QUOTED_TEXT)+
|
||||
HEX_NUMBER: "0x" HEXDIGIT+
|
||||
NUMBER: SIGNED_NUMBER | HEX_NUMBER
|
||||
PHANDLE: /&[0-9a-zA-Z\-]+/
|
||||
C_VARIABLE: /[0-9a-zA-Z_]+/
|
||||
VALUE: NUMBER | PHANDLE | C_VARIABLE
|
||||
value: VALUE
|
||||
values: VALUE+
|
||||
array: NUMBER+
|
||||
|
||||
property_value: quoted_text_array | QUOTED_TEXT | "<" value ">" | "<" values ">" | "[" array "]"
|
||||
device_property: PROPERTY_NAME ["=" property_value] ";"
|
||||
|
||||
DEVICE_IDENTIFIER: /[a-zA-Z0-9_\-\/@]+/
|
||||
|
||||
device: DEVICE_IDENTIFIER "{" (device | device_property)* "};"
|
||||
|
||||
dts_version: /[0-9a-zA-Z\-]+/
|
||||
|
||||
start: "/" dts_version "/;" INCLUDE_C* device+
|
||||
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
from lark import Lark
|
||||
|
||||
from source.files import *
|
||||
from source.transformer import *
|
||||
from source.generator import *
|
||||
from source.binding_files import find_all_bindings
|
||||
from source.binding_parser import parse_binding
|
||||
from source.config import *
|
||||
|
||||
def main(config_path: str, output_path: str, verbose: bool):
|
||||
print(f"Generating devicetree code\n config: {config_path}\n output: {output_path}")
|
||||
if not os.path.isdir(config_path):
|
||||
raise Exception(f"Directory not found: {config_path}")
|
||||
|
||||
config = parse_config(config_path, os.getcwd())
|
||||
if verbose:
|
||||
pprint(config)
|
||||
|
||||
project_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
grammar_path = os.path.join(project_dir, "grammar.lark")
|
||||
lark_data = read_file(grammar_path)
|
||||
dts_data = read_file(config.dts)
|
||||
lark = Lark(lark_data)
|
||||
parsed = lark.parse(dts_data)
|
||||
if verbose:
|
||||
print(parsed.pretty())
|
||||
transformed = DtsTransformer().transform(parsed)
|
||||
if verbose:
|
||||
pprint(transformed)
|
||||
binding_files = find_all_bindings(config.bindings)
|
||||
if verbose:
|
||||
print(f"Bindings found:")
|
||||
for binding_file in binding_files:
|
||||
print(f" {binding_file}")
|
||||
if verbose:
|
||||
print(f"Parsing bindings")
|
||||
bindings = []
|
||||
for binding_file in binding_files:
|
||||
bindings.append(parse_binding(binding_file, config.bindings))
|
||||
if verbose:
|
||||
for binding in bindings:
|
||||
pprint(binding)
|
||||
generate(output_path, transformed, bindings, verbose)
|
||||
@@ -0,0 +1,42 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class DtsVersion:
|
||||
version: str
|
||||
|
||||
@dataclass
|
||||
class Device:
|
||||
identifier: str
|
||||
properties: list
|
||||
devices: list
|
||||
|
||||
@dataclass
|
||||
class DeviceProperty:
|
||||
name: str
|
||||
type: str
|
||||
value: object
|
||||
|
||||
@dataclass
|
||||
class PropertyValue:
|
||||
type: str
|
||||
value: object
|
||||
|
||||
@dataclass
|
||||
class IncludeC:
|
||||
statement: str
|
||||
|
||||
@dataclass
|
||||
class BindingProperty:
|
||||
name: str
|
||||
type: str
|
||||
required: bool
|
||||
description: str
|
||||
|
||||
@dataclass
|
||||
class Binding:
|
||||
filename: str
|
||||
compatible: list[str]
|
||||
description: str
|
||||
properties: list[BindingProperty]
|
||||
includes: list[str]
|
||||
bus: str = None
|
||||
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
|
||||
if sys.platform == "win32":
|
||||
SHELL_COLOR_RED = ""
|
||||
SHELL_COLOR_ORANGE = ""
|
||||
SHELL_COLOR_RESET = ""
|
||||
else:
|
||||
SHELL_COLOR_RED = "\033[91m"
|
||||
SHELL_COLOR_ORANGE = "\033[93m"
|
||||
SHELL_COLOR_RESET = "\033[m"
|
||||
|
||||
def print_warning(message):
|
||||
print(f"{SHELL_COLOR_ORANGE}WARNING: {message}{SHELL_COLOR_RESET}")
|
||||
|
||||
def print_error(message):
|
||||
print(f"{SHELL_COLOR_RED}ERROR: {message}{SHELL_COLOR_RESET}")
|
||||
|
||||
def exit_with_error(message):
|
||||
print_error(message)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,67 @@
|
||||
from typing import List
|
||||
|
||||
from lark import Transformer
|
||||
from lark import Token
|
||||
from source.models import *
|
||||
|
||||
def flatten_token_array(tokens: List[Token], name: str):
|
||||
result_list = list()
|
||||
for token in tokens:
|
||||
result_list.append(token.value)
|
||||
return Token(name, result_list)
|
||||
|
||||
class DtsTransformer(Transformer):
|
||||
# Flatten the start node into a list
|
||||
def start(self, tokens):
|
||||
return tokens
|
||||
def dts_version(self, tokens: List[Token]):
|
||||
version = tokens[0].value
|
||||
if version != "dts-v1":
|
||||
raise Exception(f"Unsupported DTS version: {version}")
|
||||
return DtsVersion(version)
|
||||
def device(self, tokens: list):
|
||||
identifier = "UNKNOWN"
|
||||
properties = list()
|
||||
devices = list()
|
||||
for index, entry in enumerate(tokens):
|
||||
if index == 0:
|
||||
identifier = entry.value
|
||||
elif type(entry) is DeviceProperty:
|
||||
properties.append(entry)
|
||||
elif type(entry) is Device:
|
||||
devices.append(entry)
|
||||
return Device(identifier, properties, devices)
|
||||
def device_property(self, objects: List[object]):
|
||||
name = objects[0]
|
||||
if len(objects) == 1:
|
||||
# Boolean property with no value
|
||||
return DeviceProperty(name, "boolean", True)
|
||||
if type(objects[1]) is not PropertyValue:
|
||||
raise Exception(f"Object was not converted to PropertyValue: {objects[1]}")
|
||||
return DeviceProperty(name, objects[1].type, objects[1].value)
|
||||
def property_value(self, tokens: List):
|
||||
token = tokens[0]
|
||||
if type(token) is Token:
|
||||
raise Exception(f"Failed to convert token to PropertyValue: {token}")
|
||||
return token
|
||||
def values(self, object):
|
||||
return PropertyValue(type="values", value=object)
|
||||
def value(self, object):
|
||||
return PropertyValue(type="value", value=object[0])
|
||||
def array(self, object):
|
||||
return PropertyValue(type="array", value=object)
|
||||
def VALUE(self, token: Token):
|
||||
return token.value
|
||||
def NUMBER(self, token: Token):
|
||||
return token.value
|
||||
def PROPERTY_NAME(self, token: Token):
|
||||
return token.value
|
||||
def QUOTED_TEXT(self, token: Token):
|
||||
return PropertyValue("text", token.value[1:-1])
|
||||
def quoted_text_array(self, tokens: List[Token]):
|
||||
result_list = list()
|
||||
for token in tokens:
|
||||
result_list.append(token.value)
|
||||
return PropertyValue("text_array", result_list)
|
||||
def INCLUDE_C(self, token: Token):
|
||||
return IncludeC(token.value)
|
||||
Reference in New Issue
Block a user