-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] scripts: extract_dts_includes.py: enhance #6761
Changes from 1 commit
2f201b2
ba4efe5
50e363c
5c1c642
3dcbce1
7f596d6
36a1808
ae4512f
ed09216
310c21f
b1d8d0f
44380cc
154137f
9384a46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# | ||
# Copyright (c) 2018 Bobby Noelte | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
|
||
from collections.abc import Mapping | ||
import json | ||
|
||
## | ||
# @brief Extended DTS database | ||
# | ||
# Database schema: | ||
# | ||
# _edts dict( | ||
# 'devices': dict(device-id : device-struct), | ||
# 'compatibles': dict(compatible : sorted list(device-id)), | ||
# 'device-types': dict(device-type : sorted list(device-id)), | ||
# ... | ||
# ) | ||
# | ||
# device-struct: dict( | ||
# 'device-id' : device-id, | ||
# 'device-type' : list(device-type) or device-type, | ||
# 'compatible' : list(compatible) or compatible, | ||
# 'label' : label, | ||
# property-name : property-value ... | ||
# ) | ||
# | ||
# Database types: | ||
# | ||
# device-id: <compatible>_<base address in hex>_<offset in hex>, | ||
# compatible: any of ['st,stm32-spi-fifo', ...] - 'compatibe' from <binding>.yaml | ||
# device-type: any of ['GPIO', 'SPI', 'CAN', ...] - 'id' from <binding>.yaml | ||
# label: any of ['UART_0', 'SPI_11', ...] - label directive from DTS | ||
# | ||
class EDTSDatabase(Mapping): | ||
|
||
def __init__(self, *args, **kw): | ||
self._edts = dict(*args, **kw) | ||
# setup basic database schema | ||
for edts_key in ('devices', 'compatibles', 'device-types'): | ||
if not edts_key in self._edts: | ||
self._edts[edts_key] = dict() | ||
|
||
def __getitem__(self, key): | ||
return self._edts[key] | ||
|
||
def __iter__(self): | ||
return iter(self._edts) | ||
|
||
def __len__(self): | ||
return len(self._edts) | ||
|
||
def convert_string_to_label(self, s): | ||
# Transmute ,-@/ to _ | ||
s = s.replace("-", "_") | ||
s = s.replace(",", "_") | ||
s = s.replace("@", "_") | ||
s = s.replace("/", "_") | ||
# Uppercase the string | ||
s = s.upper() | ||
return s | ||
|
||
def device_id(self, compatible, base_address, offset): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is 'offset'? I think the function should take node_address and compatible into account. And while I don't have any example yet, I think there could be more address level than that, so it should be taken into account upfront: <compatible>_<addr_1>_<addr_2>_<...>_<addr_n> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took offset only as an assumption from the string you provided as @galak ´s request. You may also call it index, ... Maybe @galak can explain what he would like device id to be build from and look like. I don´t think it is important how device id will be generated as long as it is unique. My first proposal was to just use the 'label' property as this is unique for all activated devices. In my view device_id is just a unique reference to a device struct. It should not be used for other purposes (like generating define labels from it). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll let @galak provide more information here |
||
# sanitize | ||
base_address = int(base_address) | ||
offset = int(offset) | ||
compatibe = self.convert_string_to_label(compatible.strip()) | ||
return "{}_{:08X}_{:02X}".format(compatible, base_address, offset) | ||
|
||
def device_id_by_label(self, label): | ||
for device in self._edts['devices']: | ||
if label == device['label']: | ||
return device['device-id'] | ||
|
||
def _update_device_compatible(self, device_id, compatible): | ||
if compatible not in self._edts['compatibles']: | ||
self._edts['compatibles'][compatible] = list() | ||
if device_id not in self._edts['compatibles'][compatible]: | ||
self._edts['compatibles'][compatible].append(device_id) | ||
self._edts['compatibles'][compatible].sort() | ||
|
||
def _update_device_type(self, device_id, device_type): | ||
if device_type not in self._edts['device-types']: | ||
self._edts['device-types'][device_type] = list() | ||
if device_id not in self._edts['device-types'][device_type]: | ||
self._edts['device-types'][device_type].append(device_id) | ||
self._edts['device-types'][device_type].sort() | ||
|
||
## | ||
# @brief Insert property value for the device of the given device id. | ||
# | ||
# @param device_id | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using device_id will impose to generate device id each time before calling insert_device_property. *: compatible could be computed from node_address, but requires to know 'reduced' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There may also be a helper function in extract_dts_includes:
This can not be part of EDTSDatabase as this requires reduced to be available which breaks data encapsulation. There can well be a function in extract_dts_includes that provides this, e.g.
|
||
# @param property_path Path of the property to access | ||
# (e.g. 'reg/0', 'interrupts/prio', 'label', ...) | ||
# @param property_value value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way it works today is that we enter a top level property and a dict associated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would become:
|
||
# | ||
def insert_device_property(self, device_id, property_path, property_value): | ||
# special properties | ||
if property_path == 'compatible': | ||
self._update_device_compatible(device_id, property_value) | ||
elif property_path == 'device-type': | ||
self._update_device_type(device_id, property_value) | ||
|
||
# normal property management | ||
if device_id not in self._edts['devices']: | ||
self._edts['devices'][device_id] = dict() | ||
self._edts['devices'][device_id]['device-id'] = device_id | ||
if property_path == 'device-id': | ||
# already set | ||
return | ||
keys = property_path.strip("'").split('/') | ||
property_access_point = self._edts['devices'][device_id] | ||
for i in range(0, len(keys)): | ||
if i < len(keys) - 1: | ||
# there are remaining keys | ||
if keys[i] not in property_access_point: | ||
property_access_point[keys[i]] = dict() | ||
property_access_point = property_access_point[key[i]] | ||
else: | ||
# we have to set the property value | ||
if keys[i] in property_access_point: | ||
# There is already a value set | ||
current_value = property_access_point[keys[i]] | ||
if not isinstance(current_value, list): | ||
current_value = [current_value, ] | ||
if isinstance(property_value, list): | ||
property_value = current_value.extend(property_value) | ||
else: | ||
property_value = current_value.append(property_value) | ||
property_access_point[keys[i]] = property_value | ||
|
||
## | ||
# | ||
# @return list of devices that are compatible | ||
def compatible_devices(self, compatible): | ||
devices = list() | ||
for device_id in self._edts['compatibles'][compatible]: | ||
devices.append(self._edts['devices'][device_id]) | ||
return devices | ||
|
||
## | ||
# | ||
# @return list of device ids of activated devices that are compatible | ||
def compatible_devices_id(self, compatible): | ||
return self._edts['compatibles'].get(compatible, []) | ||
|
||
## | ||
# @brief Get device tree property value for the device of the given device id. | ||
# | ||
# | ||
# @param device_id | ||
# @param property_path Path of the property to access | ||
# (e.g. 'reg/0', 'interrupts/prio', 'device_id', ...) | ||
# @return property value | ||
# | ||
def device_property(self, device_id, property_path, default="<unset>"): | ||
property_value = self._edts['devices'][device_id] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be updated vs the latest version. |
||
for key in property_path.strip("'").split('/'): | ||
if isinstance(property_value, list): | ||
# TODO take into account prop with more than 1 elements of cell_size > 1 | ||
if isinstance(property_value[0], dict): | ||
property_value = property_value[0] | ||
try: | ||
property_value = property_value[str(key)] | ||
except TypeError: | ||
property_value = property_value[int(key)] | ||
except KeyError: | ||
# we should have a dict | ||
if isinstance(property_value, dict): | ||
# look for key in labels | ||
for x in range(0, len(property_value['labels'])): | ||
if property_value['labels'][x] == key: | ||
property_value = property_value['data'][x] | ||
break | ||
else: | ||
return "Dict was expected here" | ||
if property_value is None: | ||
if default == "<unset>": | ||
default = \ | ||
"Device tree property {} not available in {}[{}]".format(property_path, compatible, device_index) | ||
return default | ||
return property_value | ||
|
||
def load(self, file_path): | ||
with open(file_path, "r") as load_file: | ||
self._edts = json.load(load_file) | ||
|
||
def save(self, file_path): | ||
with open(file_path, "w") as save_file: | ||
json.dump(self._edts, save_file, indent = 4) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For clarity, it would be nice to split between producer and consumer functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to by the function names:
I´m open to better method names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So a clear split in the file would be good. get_... for consumer could be nice as well
What about sub classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK will go for mixin classes like codegen uses to separate the different functions.