| # |
| # Copyright (c) 2017 Linaro |
| # Copyright (c) 2017 Bobby Noelte |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| # |
| |
| import sys |
| |
| from collections import defaultdict |
| |
| # globals |
| phandles = {} |
| aliases = defaultdict(list) |
| chosen = {} |
| reduced = {} |
| defs = {} |
| bindings = {} |
| bus_bindings = {} |
| binding_compats = [] |
| old_alias_names = False |
| |
| regs_config = { |
| 'zephyr,sram' : 'DT_SRAM', |
| 'zephyr,ccm' : 'DT_CCM' |
| } |
| |
| name_config = { |
| 'zephyr,console' : 'DT_UART_CONSOLE_ON_DEV_NAME', |
| 'zephyr,shell-uart' : 'DT_UART_SHELL_ON_DEV_NAME', |
| 'zephyr,bt-uart' : 'DT_BT_UART_ON_DEV_NAME', |
| 'zephyr,uart-pipe' : 'DT_UART_PIPE_ON_DEV_NAME', |
| 'zephyr,bt-mon-uart' : 'DT_BT_MONITOR_ON_DEV_NAME', |
| 'zephyr,uart-mcumgr' : 'DT_UART_MCUMGR_ON_DEV_NAME' |
| } |
| |
| |
| def str_to_label(s): |
| # Change ,-@/ to _ and uppercase |
| return s.replace('-', '_') \ |
| .replace(',', '_') \ |
| .replace('@', '_') \ |
| .replace('/', '_') \ |
| .upper() |
| |
| |
| def all_compats(node): |
| # Returns a set() of all 'compatible' strings that appear at or below |
| # 'node', skipping disabled nodes |
| |
| if node['props'].get('status') == 'disabled': |
| return set() |
| |
| compats = set() |
| |
| if 'compatible' in node['props']: |
| val = node['props']['compatible'] |
| if isinstance(val, list): |
| compats.update(val) |
| else: |
| compats.add(val) |
| |
| for child_node in node['children'].values(): |
| compats.update(all_compats(child_node)) |
| |
| return compats |
| |
| |
| def create_aliases(root): |
| if 'aliases' in root['children']: |
| for name, node_path in root['children']['aliases']['props'].items(): |
| aliases[node_path].append(name) |
| |
| # Treat alternate names as aliases |
| for node_path, node in reduced.items(): |
| if 'alt_name' in node: |
| aliases[node_path].append(node['alt_name']) |
| |
| |
| def get_compat(node_path): |
| # Returns the value of the 'compatible' property for the node at |
| # 'node_path'. Also checks the node's parent. |
| # |
| # Returns None if neither the node nor its parent has a 'compatible' |
| # property. |
| |
| compat = reduced[node_path]['props'].get('compatible') or \ |
| reduced[get_parent_path(node_path)]['props'].get('compatible') |
| |
| if isinstance(compat, list): |
| return compat[0] |
| |
| return compat |
| |
| |
| def create_chosen(root): |
| if 'chosen' in root['children']: |
| chosen.update(root['children']['chosen']['props']) |
| |
| |
| def create_phandles(root, name): |
| if root['props'].get('status') == 'disabled': |
| return |
| |
| if 'phandle' in root['props']: |
| phandles[root['props']['phandle']] = name |
| |
| if name != '/': |
| name += '/' |
| |
| for child_name, child_node in root['children'].items(): |
| create_phandles(child_node, name + child_name) |
| |
| |
| def insert_defs(node_path, new_defs, new_aliases): |
| for key in new_defs: |
| if key.startswith('DT_COMPAT_'): |
| node_path = 'compatibles' |
| |
| remove = [k for k in new_aliases if k in new_defs] |
| for k in remove: del new_aliases[k] |
| |
| if node_path in defs: |
| remove = [k for k in new_aliases if k in defs[node_path]] |
| for k in remove: del new_aliases[k] |
| defs[node_path]['aliases'].update(new_aliases) |
| defs[node_path].update(new_defs) |
| else: |
| new_defs['aliases'] = new_aliases |
| defs[node_path] = new_defs |
| |
| |
| # Dictionary where all keys default to 0. Used by create_reduced(). |
| last_used_id = defaultdict(int) |
| |
| |
| def create_reduced(node, path): |
| # Compress nodes list to nodes w/ paths, add interrupt parent |
| |
| if node['props'].get('status') == 'disabled': |
| return |
| |
| reduced[path] = node.copy() |
| reduced[path].pop('children', None) |
| |
| # Assign an instance ID for each compat |
| compat = node['props'].get('compatible') |
| if compat: |
| if type(compat) is not list: |
| compat = [compat] |
| |
| reduced[path]['instance_id'] = {} |
| for comp in compat: |
| reduced[path]['instance_id'][comp] = last_used_id[comp] |
| last_used_id[comp] += 1 |
| |
| # Flatten 'prop = <1 2>, <3 4>' (which turns into nested lists) to |
| # 'prop = <1 2 3 4>' |
| for val in node['props'].values(): |
| if isinstance(val, list) and isinstance(val[0], list): |
| # In-place modification |
| val[:] = [item for sublist in val for item in sublist] |
| |
| if node['children']: |
| if path != '/': |
| path += '/' |
| |
| for child_name, child_node in sorted(node['children'].items()): |
| create_reduced(child_node, path + child_name) |
| |
| |
| def node_label(node_path): |
| node_compat = get_compat(node_path) |
| def_label = str_to_label(node_compat) |
| if '@' in node_path: |
| # See if we have number we can convert |
| try: |
| unit_addr = int(node_path.split('@')[-1], 16) |
| (nr_addr_cells, nr_size_cells) = get_addr_size_cells(node_path) |
| unit_addr += translate_addr(unit_addr, node_path, |
| nr_addr_cells, nr_size_cells) |
| unit_addr = "%x" % unit_addr |
| except: |
| unit_addr = node_path.split('@')[-1] |
| def_label += '_' + str_to_label(unit_addr) |
| else: |
| def_label += '_' + str_to_label(node_path.split('/')[-1]) |
| return def_label |
| |
| |
| def get_parent_path(node_path): |
| # Turns /foo/bar into /foo. Returns None for /. |
| |
| if node_path == '/': |
| return None |
| |
| return '/'.join(node_path.split('/')[:-1]) or '/' |
| |
| |
| def find_parent_prop(node_path, prop): |
| parent_path = get_parent_path(node_path) |
| |
| if prop not in reduced[parent_path]['props']: |
| raise Exception("Parent of node " + node_path + |
| " has no " + prop + " property") |
| |
| return reduced[parent_path]['props'][prop] |
| |
| |
| # Get the #{address,size}-cells for a given node |
| def get_addr_size_cells(node_path): |
| parent_addr = get_parent_path(node_path) |
| |
| # The DT spec says that if #address-cells is missing default to 2 |
| # if #size-cells is missing default to 1 |
| nr_addr = reduced[parent_addr]['props'].get('#address-cells', 2) |
| nr_size = reduced[parent_addr]['props'].get('#size-cells', 1) |
| |
| return (nr_addr, nr_size) |
| |
| def translate_addr(addr, node_path, nr_addr_cells, nr_size_cells): |
| parent_path = get_parent_path(node_path) |
| |
| ranges = reduced[parent_path]['props'].get('ranges') |
| if not ranges: |
| return 0 |
| |
| if isinstance(ranges, list): |
| ranges = ranges.copy() # Modified in-place below |
| else: |
| # Empty value ('ranges;'), meaning the parent and child address spaces |
| # are the same |
| ranges = [] |
| |
| nr_p_addr_cells, nr_p_size_cells = get_addr_size_cells(parent_path) |
| |
| range_offset = 0 |
| while ranges: |
| child_bus_addr = 0 |
| parent_bus_addr = 0 |
| range_len = 0 |
| for x in range(nr_addr_cells): |
| val = ranges.pop(0) << (32 * (nr_addr_cells - x - 1)) |
| child_bus_addr += val |
| for x in range(nr_p_addr_cells): |
| val = ranges.pop(0) << (32 * (nr_p_addr_cells - x - 1)) |
| parent_bus_addr += val |
| for x in range(nr_size_cells): |
| range_len += ranges.pop(0) << (32 * (nr_size_cells - x - 1)) |
| # if we are outside of the range we don't need to translate |
| if child_bus_addr <= addr <= (child_bus_addr + range_len): |
| range_offset = parent_bus_addr - child_bus_addr |
| break |
| |
| parent_range_offset = translate_addr(addr + range_offset, |
| parent_path, nr_p_addr_cells, nr_p_size_cells) |
| range_offset += parent_range_offset |
| |
| return range_offset |
| |
| def enable_old_alias_names(enable): |
| global old_alias_names |
| old_alias_names = enable |
| |
| def add_compat_alias(node_path, label_postfix, label, prop_aliases): |
| if 'instance_id' in reduced[node_path]: |
| instance = reduced[node_path]['instance_id'] |
| for k in instance: |
| i = instance[k] |
| b = 'DT_' + str_to_label(k) + '_' + str(i) + '_' + label_postfix |
| prop_aliases[b] = label |
| |
| def add_prop_aliases(node_path, |
| alias_label_function, prop_label, prop_aliases): |
| node_compat = get_compat(node_path) |
| new_alias_prefix = 'DT_' + str_to_label(node_compat) |
| |
| for alias in aliases[node_path]: |
| old_alias_label = alias_label_function(alias) |
| new_alias_label = new_alias_prefix + '_' + old_alias_label |
| |
| if new_alias_label != prop_label: |
| prop_aliases[new_alias_label] = prop_label |
| if old_alias_names and old_alias_label != prop_label: |
| prop_aliases[old_alias_label] = prop_label |
| |
| def get_binding(node_path): |
| compat = get_compat(node_path) |
| |
| # For just look for the binding in the main dict |
| # if we find it here, return it, otherwise it best |
| # be in the bus specific dict |
| if compat in bindings: |
| return bindings[compat] |
| |
| parent_path = get_parent_path(node_path) |
| parent_compat = get_compat(parent_path) |
| |
| parent_binding = bindings[parent_compat] |
| |
| bus = parent_binding['child']['bus'] |
| binding = bus_bindings[bus][compat] |
| |
| return binding |
| |
| def get_binding_compats(): |
| return binding_compats |
| |
| def build_cell_array(prop_array): |
| index = 0 |
| ret_array = [] |
| |
| while index < len(prop_array): |
| handle = prop_array[index] |
| |
| if handle in {0, -1}: |
| ret_array.append([]) |
| index += 1 |
| else: |
| # get controller node (referenced via phandle) |
| cell_parent = phandles[handle] |
| |
| for prop in reduced[cell_parent]['props']: |
| if prop[0] == '#' and '-cells' in prop: |
| num_cells = reduced[cell_parent]['props'][prop] |
| break |
| |
| ret_array.append(prop_array[index:index+num_cells+1]) |
| |
| index += num_cells + 1 |
| |
| return ret_array |
| |
| |
| def extract_controller(node_path, prop, prop_values, index, |
| def_label, generic, handle_single=False): |
| |
| prop_def = {} |
| prop_alias = {} |
| |
| prop_array = build_cell_array(prop_values) |
| if handle_single: |
| prop_array = [prop_array[index]] |
| |
| for i, elem in enumerate(prop_array): |
| num_cells = len(elem) |
| |
| # if the entry is empty, skip |
| if num_cells == 0: |
| continue |
| |
| cell_parent = phandles[elem[0]] |
| l_cell = reduced[cell_parent]['props'].get('label') |
| |
| if l_cell is None: |
| continue |
| |
| l_base = [def_label] |
| |
| # Check is defined should be indexed (_0, _1) |
| if handle_single or i == 0 and len(prop_array) == 1: |
| # 0 or 1 element in prop_values |
| l_idx = [] |
| else: |
| l_idx = [str(i)] |
| |
| # Check node generation requirements |
| try: |
| generation = get_binding(node_path)['properties' |
| ][prop]['generation'] |
| except: |
| generation = '' |
| |
| if 'use-prop-name' in generation: |
| l_cellname = str_to_label(prop + '_' + 'controller') |
| else: |
| l_cellname = str_to_label(generic + '_' + 'controller') |
| |
| label = l_base + [l_cellname] + l_idx |
| |
| add_compat_alias(node_path, '_'.join(label[1:]), '_'.join(label), prop_alias) |
| prop_def['_'.join(label)] = "\"" + l_cell + "\"" |
| |
| #generate defs also if node is referenced as an alias in dts |
| if node_path in aliases: |
| add_prop_aliases( |
| node_path, |
| lambda alias: '_'.join([str_to_label(alias)] + label[1:]), |
| '_'.join(label), |
| prop_alias) |
| |
| insert_defs(node_path, prop_def, prop_alias) |
| |
| |
| def extract_cells(node_path, prop, prop_values, names, index, |
| def_label, generic, handle_single=False): |
| |
| prop_array = build_cell_array(prop_values) |
| if handle_single: |
| prop_array = [prop_array[index]] |
| |
| for i, elem in enumerate(prop_array): |
| num_cells = len(elem) |
| |
| # if the entry is empty, skip |
| if num_cells == 0: |
| continue |
| |
| cell_parent = phandles[elem[0]] |
| |
| try: |
| cell_yaml = get_binding(cell_parent) |
| except: |
| raise Exception( |
| "Could not find yaml description for " + |
| reduced[cell_parent]['name']) |
| |
| try: |
| name = names.pop(0).upper() |
| except: |
| name = '' |
| |
| # Get number of cells per element of current property |
| for props in reduced[cell_parent]['props']: |
| if props[0] == '#' and '-cells' in props: |
| if props in cell_yaml: |
| cell_yaml_names = props |
| else: |
| cell_yaml_names = '#cells' |
| try: |
| generation = get_binding(node_path)['properties'][prop |
| ]['generation'] |
| except: |
| generation = '' |
| |
| if 'use-prop-name' in generation: |
| l_cell = [str_to_label(str(prop))] |
| else: |
| l_cell = [str_to_label(str(generic))] |
| |
| l_base = [def_label] |
| # Check if #define should be indexed (_0, _1, ...) |
| if handle_single or i == 0 and len(prop_array) == 1: |
| # Less than 2 elements in prop_values |
| # Indexing is not needed |
| l_idx = [] |
| else: |
| l_idx = [str(i)] |
| |
| prop_def = {} |
| prop_alias = {} |
| |
| # Generate label for each field of the property element |
| for j in range(num_cells-1): |
| l_cellname = [str(cell_yaml[cell_yaml_names][j]).upper()] |
| if l_cell == l_cellname: |
| label = l_base + l_cell + l_idx |
| else: |
| label = l_base + l_cell + l_cellname + l_idx |
| label_name = l_base + [name] + l_cellname |
| add_compat_alias(node_path, '_'.join(label[1:]), '_'.join(label), prop_alias) |
| prop_def['_'.join(label)] = elem[j+1] |
| if name: |
| prop_alias['_'.join(label_name)] = '_'.join(label) |
| |
| # generate defs for node aliases |
| if node_path in aliases: |
| add_prop_aliases( |
| node_path, |
| lambda alias: '_'.join([str_to_label(alias)] + label[1:]), |
| '_'.join(label), |
| prop_alias) |
| |
| insert_defs(node_path, prop_def, prop_alias) |
| |
| |
| def err(msg): |
| # General error reporting helper. Prints a message to stderr and exits with |
| # status 1. |
| |
| sys.exit("error: " + msg) |