diff --git a/Buildscripts/DevicetreeCompiler/compile.py b/Buildscripts/DevicetreeCompiler/compile.py index b5db6034c..9f21731e7 100644 --- a/Buildscripts/DevicetreeCompiler/compile.py +++ b/Buildscripts/DevicetreeCompiler/compile.py @@ -24,5 +24,6 @@ def print_help(): is_verbose = "--verbose" in sys.argv devicetree_yaml_config = args[0] output_path = args[1] - main(devicetree_yaml_config, output_path, is_verbose) + result = main(devicetree_yaml_config, output_path, is_verbose) + sys.exit(result) diff --git a/Buildscripts/DevicetreeCompiler/source/binding_files.py b/Buildscripts/DevicetreeCompiler/source/binding_files.py index d69733109..504036dda 100644 --- a/Buildscripts/DevicetreeCompiler/source/binding_files.py +++ b/Buildscripts/DevicetreeCompiler/source/binding_files.py @@ -1,4 +1,5 @@ import os +from .exception import DevicetreeException def find_bindings(directory_path: str) -> list[str]: yaml_files = [] @@ -14,6 +15,6 @@ def find_all_bindings(directory_paths: list[str]) -> list[str]: 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}") + raise DevicetreeException(f"No bindings found in {directory_path}") yaml_files += new_paths return yaml_files \ No newline at end of file diff --git a/Buildscripts/DevicetreeCompiler/source/binding_parser.py b/Buildscripts/DevicetreeCompiler/source/binding_parser.py index b6f7f116f..2b4889431 100644 --- a/Buildscripts/DevicetreeCompiler/source/binding_parser.py +++ b/Buildscripts/DevicetreeCompiler/source/binding_parser.py @@ -44,6 +44,7 @@ def parse_binding(file_path: str, binding_dirs: list[str]) -> Binding: type=details.get('type', 'unknown'), required=details.get('required', False), description=details.get('description', '').strip(), + default=details.get('default', None), ) properties_dict[name] = prop filename = os.path.basename(file_path) diff --git a/Buildscripts/DevicetreeCompiler/source/exception.py b/Buildscripts/DevicetreeCompiler/source/exception.py new file mode 100644 index 000000000..de70efd1f --- /dev/null +++ b/Buildscripts/DevicetreeCompiler/source/exception.py @@ -0,0 +1,3 @@ +class DevicetreeException(Exception): + def __init__(self, message): + super().__init__(message) diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index cf7f80f6c..c6d7685f2 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -1,7 +1,7 @@ import os.path from textwrap import dedent - from source.models import * +from .exception import DevicetreeException def write_include(file, include: IncludeC, verbose: bool): if verbose: @@ -26,9 +26,9 @@ def get_device_node_name_safe(device: Device): 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.node_name}") + raise DevicetreeException(f"Binding not found for {device.node_name}") if device_binding.compatible is None: - raise Exception(f"Couldn't find compatible binding for {device.node_name}") + raise DevicetreeException(f"Couldn't find compatible binding for {device.node_name}") compatible_safe = device_binding.compatible.split(",")[-1] return compatible_safe.replace("-", "_") @@ -41,7 +41,7 @@ def find_device_property(device: Device, name: str) -> DeviceProperty: 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.node_name}") + raise DevicetreeException(f"property 'compatible' not found in device {device.node_name}") for binding in bindings: if binding.compatible == compatible_property.value: return binding @@ -57,12 +57,14 @@ def find_phandle(devices: list[Device], phandle: str): for device in devices: if device.node_name == phandle or device.node_alias == phandle: return f"&{get_device_node_name_safe(device)}" - raise Exception(f"phandle '{phandle}' not found in device tree") + raise DevicetreeException(f"phandle '{phandle}' not found in device tree") def property_to_string(property: DeviceProperty, devices: list[Device]) -> str: type = property.type if type == "value": return property.value + elif type == "int": + return property.value elif type == "boolean": return "true" elif type == "text": @@ -72,29 +74,48 @@ def property_to_string(property: DeviceProperty, devices: list[Device]) -> str: elif type == "phandle": return find_phandle(devices, property.value) else: - raise Exception(f"property_to_string() has an unsupported type: {type}") + raise DevicetreeException(f"property_to_string() has an unsupported type: {type}") def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], devices: list[Device]) -> list: compatible_property = find_device_property(device, "compatible") if compatible_property is None: - raise Exception(f"Cannot find 'compatible' property for {device.node_name}") + raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}") device_binding = find_binding(compatible_property.value, bindings) if device_binding is None: - raise Exception(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'") + raise DevicetreeException(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'") # Filter out system properties binding_properties = [] + binding_property_names = set() for property in device_binding.properties: if property.name != "compatible": binding_properties.append(property) + binding_property_names.add(property.name) + + # Check for invalid properties in device + for device_property in device.properties: + if device_property.name in ["compatible"]: + continue + if device_property.name not in binding_property_names: + raise DevicetreeException(f"Device '{device.node_name}' has invalid property '{device_property.name}'") + # 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.node_name} doesn't have property '{binding_property.name}'") + if binding_property.type == "bool": + result[index] = "false" + elif binding_property.required: + raise DevicetreeException(f"device {device.node_name} doesn't have property '{binding_property.name}'") + elif binding_property.default is not None: + temp_prop = DeviceProperty( + name=binding_property.name, + type=binding_property.type, + value=binding_property.default + ) + result[index] = property_to_string(temp_prop, devices) else: - result[index] = '0' + raise DevicetreeException(f"Device {device.node_name} doesn't have property '{binding_property.name}' and no default value is set") else: result[index] = property_to_string(device_property, devices) return result @@ -121,7 +142,7 @@ def write_device_structs(file, device: Device, parent_device: Device, bindings: 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.node_name}") + raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}") node_name = get_device_node_name_safe(device) config_variable_name = f"{node_name}_config" if parent_device is not None: @@ -148,7 +169,7 @@ def write_device_init(file, device: Device, bindings: list[Binding], verbose: bo # 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.node_name}") + raise DevicetreeException(f"Cannot find 'compatible' property for {device.node_name}") # Type & instance names node_name = get_device_node_name_safe(device) device_variable = node_name diff --git a/Buildscripts/DevicetreeCompiler/source/main.py b/Buildscripts/DevicetreeCompiler/source/main.py index 97b7f0be1..cca885767 100644 --- a/Buildscripts/DevicetreeCompiler/source/main.py +++ b/Buildscripts/DevicetreeCompiler/source/main.py @@ -9,38 +9,44 @@ from source.binding_files import find_all_bindings from source.binding_parser import parse_binding from source.config import * +from source.exception import DevicetreeException -def main(config_path: str, output_path: str, verbose: bool): +def main(config_path: str, output_path: str, verbose: bool) -> int: 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}") + raise DevicetreeException(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:") + try: + 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: - 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) + bindings.append(parse_binding(binding_file, config.bindings)) + if verbose: + for binding in bindings: + pprint(binding) + generate(output_path, transformed, bindings, verbose) + return 0 + except DevicetreeException as caught: + print("\033[31mError: ", caught, "\033[0m") + return 1 diff --git a/Buildscripts/DevicetreeCompiler/source/models.py b/Buildscripts/DevicetreeCompiler/source/models.py index a865c698f..3b5747535 100644 --- a/Buildscripts/DevicetreeCompiler/source/models.py +++ b/Buildscripts/DevicetreeCompiler/source/models.py @@ -36,6 +36,7 @@ class BindingProperty: type: str required: bool description: str + default: object = None @dataclass class Binding: diff --git a/Buildscripts/DevicetreeCompiler/source/transformer.py b/Buildscripts/DevicetreeCompiler/source/transformer.py index bb70e3cde..ffd6441cf 100644 --- a/Buildscripts/DevicetreeCompiler/source/transformer.py +++ b/Buildscripts/DevicetreeCompiler/source/transformer.py @@ -4,6 +4,7 @@ from lark import Token from source.models import * from dataclasses import dataclass +from .exception import DevicetreeException def flatten_token_array(tokens: List[Token], name: str): result_list = list() @@ -23,7 +24,7 @@ def start(self, tokens): def dts_version(self, tokens: List[Token]): version = tokens[0].value if version != "dts-v1": - raise Exception(f"Unsupported DTS version: {version}") + raise DevicetreeException(f"Unsupported DTS version: {version}") return DtsVersion(version) def device(self, tokens: list): node_name = None @@ -46,12 +47,12 @@ def device_property(self, objects: List[object]): if (len(objects) == 1) or (objects[1] is None): return DeviceProperty(name, "boolean", True) if type(objects[1]) is not PropertyValue: - raise Exception(f"Object was not converted to PropertyValue: {objects[1]}") + raise DevicetreeException(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}") + raise DevicetreeException(f"Failed to convert token to PropertyValue: {token}") return token def PHANDLE(self, token: Token): return PropertyValue(type="phandle", value=token.value[1:]) diff --git a/Documentation/ideas.md b/Documentation/ideas.md index 41c63a426..ed7fed5bb 100644 --- a/Documentation/ideas.md +++ b/Documentation/ideas.md @@ -12,8 +12,6 @@ ## Higher Priority - Make a root device type so it can be discovered more easily. -- DTS/yaml: Consider support for default values. -- DTS: throw custom exceptions and catch them to show cleaner error messages. - When device.py selects a new device, it should automatically delete the build dirs (build/, cmake-*/) when it detects that the platform has changed. - Add font design tokens such as "regular", "title" and "smaller". Perhaps via the LVGL kernel module. - Add kernel listening mechanism so that the root device init can be notified when a device becomes available: @@ -69,8 +67,6 @@ ## Lower Priority -- Rename `Lock::lock()` and `Lock::unlock()` to `Lock::acquire()` and `Lock::release()`? -- Implement system suspend that turns off the screen - The boot button on some devices can be used as GPIO_NUM_0 at runtime - Localize all apps - Support hot-plugging SD card (note: this is not possible if they require the CS pin hack) diff --git a/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2c.yaml b/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2c.yaml index 553fbf805..fff784b74 100644 --- a/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2c.yaml +++ b/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2c.yaml @@ -7,16 +7,20 @@ compatible: "espressif,esp32-i2c" properties: port: type: int + required: true description: | The port number, defined by i2c_port_t. Depending on the hardware, these values are available: I2C_NUM_0, I2C_NUM_1, LP_I2C_NUM_0 clock-frequency: type: int + required: true description: Initial clock frequency in Hz pin-sda: type: int + required: true pin-scl: type: int + required: true pin-sda-pull-up: type: bool description: enable internal pull-up resistor for SDA pin diff --git a/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2s.yaml b/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2s.yaml index ee2712934..dec9e63fb 100644 --- a/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2s.yaml +++ b/Platforms/PlatformEsp32/Bindings/espressif,esp32-i2s.yaml @@ -21,13 +21,13 @@ properties: description: WS pin pin-data-out: type: int - required: true description: DATA OUT pin pin-data-in: type: int - required: true + required: false description: DATA IN pin pin-mclk: type: int - required: true + required: false + default: GPIO_PIN_NONE description: MCLK pin diff --git a/Platforms/PlatformEsp32/Bindings/espressif,esp32-spi.yaml b/Platforms/PlatformEsp32/Bindings/espressif,esp32-spi.yaml index d6555dbc1..4166042f4 100644 --- a/Platforms/PlatformEsp32/Bindings/espressif,esp32-spi.yaml +++ b/Platforms/PlatformEsp32/Bindings/espressif,esp32-spi.yaml @@ -25,12 +25,15 @@ properties: description: Clock pin pin-wp: type: int + default: GPIO_PIN_NONE description: WP (Data 2) pin pin-hd: type: int + default: GPIO_PIN_NONE description: HD (Data 3) pin max-transfer-size: type: int + default: 0 description: | Data transfer size limit in bytes. 0 means the platform decides the limit. diff --git a/Platforms/PlatformEsp32/Bindings/espressif,esp32-uart.yaml b/Platforms/PlatformEsp32/Bindings/espressif,esp32-uart.yaml index f39b11c64..c6a077e74 100644 --- a/Platforms/PlatformEsp32/Bindings/espressif,esp32-uart.yaml +++ b/Platforms/PlatformEsp32/Bindings/espressif,esp32-uart.yaml @@ -21,7 +21,9 @@ properties: description: RX pin pin-cts: type: int + default: GPIO_PIN_NONE description: CTS pin pin-rts: type: int + default: GPIO_PIN_NONE description: RTS pin