Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions Buildscripts/DevicetreeCompiler/source/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("-", "_")

Expand All @@ -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
Expand All @@ -46,24 +53,32 @@ 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
elif type == "text":
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:
Expand All @@ -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}"
Expand All @@ -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
Expand All @@ -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
Expand Down
12 changes: 7 additions & 5 deletions Buildscripts/DevicetreeCompiler/source/grammar.lark
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ BOOLEAN: "true" | "false"
// Main

INCLUDE_C: /#include <[\w\/.\-]+>/
DEFINE_C: /#define[^\n]+/

PROPERTY_NAME: /#?[a-zA-Z0-9_\-,]+/

Expand All @@ -29,20 +30,21 @@ 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\-]+/
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 "]"
property_value: quoted_text_array | QUOTED_TEXT | "<" value ">" | "<" values ">" | "[" array "]" | PHANDLE
device_property: PROPERTY_NAME ["=" property_value] ";"

DEVICE_IDENTIFIER: /[a-zA-Z0-9_\-\/@]+/
NODE_ALIAS: /[a-zA-Z0-9_\-\/@]+/
NODE_NAME: /[a-zA-Z0-9_\-\/@]+/

device: DEVICE_IDENTIFIER "{" (device | device_property)* "};"
device: (NODE_ALIAS ":")? NODE_NAME "{" (device | device_property)* "};"

dts_version: /[0-9a-zA-Z\-]+/

start: "/" dts_version "/;" INCLUDE_C* device+
start: "/" dts_version "/;" (INCLUDE_C | DEFINE_C)* device+
7 changes: 6 additions & 1 deletion Buildscripts/DevicetreeCompiler/source/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class DtsVersion:

@dataclass
class Device:
identifier: str
node_name: str
node_alias: str
properties: list
devices: list

Expand All @@ -25,6 +26,10 @@ class PropertyValue:
class IncludeC:
statement: str

@dataclass
class DefineC:
statement: str

@dataclass
class BindingProperty:
name: str
Expand Down
40 changes: 29 additions & 11 deletions Buildscripts/DevicetreeCompiler/source/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
from lark import Transformer
from lark import Token
from source.models import *
from dataclasses import dataclass

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)

@dataclass
class NodeNameAndAlias:
name: str
alias: str

class DtsTransformer(Transformer):
# Flatten the start node into a list
def start(self, tokens):
Expand All @@ -20,17 +26,20 @@ def dts_version(self, tokens: List[Token]):
raise Exception(f"Unsupported DTS version: {version}")
return DtsVersion(version)
def device(self, tokens: list):
identifier = "UNKNOWN"
node_name = None
node_alias = None
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)
child_devices = list()
for index, item in enumerate(tokens):
if type(item) is Token and item.type == 'NODE_NAME':
node_name = item.value
elif type(item) is Token and item.type == 'NODE_ALIAS':
node_alias = item.value
elif type(item) is DeviceProperty:
properties.append(item)
elif type(item) is Device:
child_devices.append(item)
return Device(node_name, node_alias, properties, child_devices)
def device_property(self, objects: List[object]):
name = objects[0]
# Boolean property has no value as the value is implied to be true
Expand All @@ -44,13 +53,20 @@ def property_value(self, tokens: List):
if type(token) is Token:
raise Exception(f"Failed to convert token to PropertyValue: {token}")
return token
def PHANDLE(self, token: Token):
return PropertyValue(type="phandle", value=token.value[1:])
def values(self, object):
return PropertyValue(type="values", value=object)
def value(self, object):
# PHANDLE is already converted to PropertyValue
if isinstance(object[0], PropertyValue):
return object[0]
return PropertyValue(type="value", value=object[0])
def array(self, object):
return PropertyValue(type="array", value=object)
def VALUE(self, token: Token):
if token.value.startswith("&"):
return PropertyValue(type="phandle", value=token.value[1:])
return token.value
def NUMBER(self, token: Token):
return token.value
Expand All @@ -64,4 +80,6 @@ def quoted_text_array(self, tokens: List[Token]):
result_list.append(token.value)
return PropertyValue("text_array", result_list)
def INCLUDE_C(self, token: Token):
return IncludeC(token.value)
return IncludeC(token.value)
def DEFINE_C(self, token: Token):
return DefineC(token.value)
4 changes: 2 additions & 2 deletions Devices/lilygo-tdeck/lilygo,tdeck.dts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
gpio-count = <49>;
};

i2c_internal {
i2c_internal: i2c0 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <18>;
pin-scl = <8>;
};

i2c_external {
i2c_external: i2c1 {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
Expand Down
3 changes: 2 additions & 1 deletion Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
pin-scl = <2>;
};

// ES8311 (also connected to I2C bus)
// ES8311
// TODO: init via I2C to enable audio playback
i2s0 {
compatible = "espressif,esp32-i2s";
port = <I2S_NUM_0>;
Expand Down
Loading