Devicetree DTS alias support (#483)

* **New Features**
  * Phandle support for device-to-device property references.
  * Recognition of #define-style declarations in device trees.
  * Device nodes may include optional aliases alongside node names.

* **Improvements**
  * Flatter, consistent device hierarchy processing for generation.
  * Error and log messages now reference node names for clearer diagnostics.

* **Other**
  * Added a static ESP32-based device tree and minor DTS comment updates.
This commit is contained in:
Ken Van Hoeylandt
2026-02-05 23:10:11 +01:00
committed by GitHub
parent ecc0a9c076
commit 626d0d9776
7 changed files with 172 additions and 52 deletions
@@ -10,18 +10,25 @@ def write_include(file, include: IncludeC, verbose: bool):
file.write(include.statement)
file.write('\n')
def get_device_identifier_safe(device: Device):
if device.identifier == "/":
def write_define(file, define: DefineC, verbose: bool):
if verbose:
print("Processing define:")
print(f" {define.statement}")
file.write(define.statement)
file.write('\n')
def get_device_node_name_safe(device: Device):
if device.node_name == "/":
return "root"
else:
return device.identifier
return device.node_name.replace("-", "_")
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}")
raise Exception(f"Binding not found for {device.node_name}")
if device_binding.compatible is None:
raise Exception(f"Couldn't find compatible binding for {device.identifier}")
raise Exception(f"Couldn't find compatible binding for {device.node_name}")
compatible_safe = device_binding.compatible.split(",")[-1]
return compatible_safe.replace("-", "_")
@@ -34,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.identifier}")
raise Exception(f"property 'compatible' not found in device {device.node_name}")
for binding in bindings:
if binding.compatible == compatible_property.value:
return binding
@@ -46,7 +53,13 @@ def find_binding(compatible: str, bindings: list[Binding]) -> Binding:
return binding
return None
def property_to_string(property: DeviceProperty) -> str:
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")
def property_to_string(property: DeviceProperty, devices: list[Device]) -> str:
type = property.type
if type == "value":
return property.value
@@ -54,16 +67,18 @@ def property_to_string(property: DeviceProperty) -> str:
return f"\"{property.value}\""
elif type == "values":
return "{ " + ",".join(property.value) + " }"
elif type == "phandle":
return find_phandle(devices, 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:
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.identifier}")
raise Exception(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.identifier} and compatible '{compatible_property.value}'")
raise Exception(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'")
# Filter out system properties
binding_properties = []
for property in device_binding.properties:
@@ -75,19 +90,19 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding]) ->
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}'")
raise Exception(f"device {device.node_name} doesn't have property '{binding_property.name}'")
else:
result[index] = '0'
else:
result[index] = property_to_string(device_property)
result[index] = property_to_string(device_property, devices)
return result
def write_config(file, device: Device, bindings: list[Binding], type_name: str):
device_identifier = get_device_identifier_safe(device)
def write_config(file, device: Device, bindings: list[Binding], devices: list[Device], type_name: str):
node_name = get_device_node_name_safe(device)
config_type = f"{type_name}_config_dt"
config_variable_name = f"{device_identifier}_config"
config_variable_name = f"{node_name}_config"
file.write(f"static const {config_type} {config_variable_name}" " = {\n")
config_params = resolve_parameters_from_bindings(device, bindings)
config_params = resolve_parameters_from_bindings(device, bindings, devices)
# Indent all params
for index, config_param in enumerate(config_params):
config_params[index] = f"\t{config_param}"
@@ -97,50 +112,63 @@ def write_config(file, device: Device, bindings: list[Binding], type_name: str):
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):
def write_device_structs(file, device: Device, parent_device: Device, bindings: list[Binding], devices: list[Device], verbose: bool):
if verbose:
print(f"Writing device struct for '{device.identifier}'")
print(f"Writing device struct for '{device.node_name}'")
# 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"
raise Exception(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:
parent_identifier = get_device_identifier_safe(parent_device)
parent_value = f"&{parent_identifier}"
parent_node_name = get_device_node_name_safe(parent_device)
parent_value = f"&{parent_node_name}"
else:
parent_value = "NULL"
# Write config struct
write_config(file, device, bindings, type_name)
write_config(file, device, bindings, devices, 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"static struct Device {node_name}" " = {\n")
file.write(f"\t.name = \"{device.node_name}\",\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)
write_device_structs(file, child_device, device, bindings, devices, verbose)
def write_device_init(file, device: Device, bindings: list[Binding], verbose: bool):
if verbose:
print(f"Processing device init code for '{device.identifier}'")
print(f"Processing device init code for '{device.node_name}'")
# 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}")
raise Exception(f"Cannot find 'compatible' property for {device.node_name}")
# Type & instance names
identifier = get_device_identifier_safe(device)
device_variable = identifier
node_name = get_device_node_name_safe(device)
device_variable = node_name
# Write device struct
file.write("\t{ " f"&{device_variable}, \"{compatible_property.value}\"" " },\n")
# Write children
for child_device in device.devices:
write_device_init(file, child_device, bindings, verbose)
# Walk the tree and gather all devices
def gather_devices(device: Device, output: list[Device]):
output.append(device)
for child_device in device.devices:
gather_devices(child_device, output)
def generate_devicetree_c(filename: str, items: list[object], bindings: list[Binding], verbose: bool):
# Create a cache for looking up device names and aliases easily
# We still want to traverse it as a tree during code generation because of parent-setting
devices = list()
for item in items:
if type(item) is Device:
gather_devices(item, devices)
with open(filename, "w") as file:
file.write(dedent('''\
// Default headers
@@ -152,12 +180,14 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
for item in items:
if type(item) is IncludeC:
write_include(file, item, verbose)
elif type(item) is DefineC:
write_define(file, item, verbose)
file.write("\n")
# Then write all devices
for item in items:
if type(item) is Device:
write_device_structs(file, item, None, bindings, verbose)
write_device_structs(file, item, None, bindings, devices, verbose)
# Init function body start
file.write("struct CompatibleDevice devicetree_devices[] = {\n")
# Init function body logic