# # 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 = [] deprecated = [] 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) 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, deprecate=False): 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 deprecated.append(b) prop_aliases[b] = label b = "DT_INST_{}_{}_{}".format(str(i), str_to_label(k), label_postfix) prop_aliases[b] = label if deprecate: deprecated.append(b) def add_prop_aliases(node_path, alias_label_function, prop_label, prop_aliases, deprecate=False): node_compat = get_compat(node_path) new_alias_prefix = 'DT_' for alias in aliases[node_path]: old_alias_label = alias_label_function(alias) new_alias_label = new_alias_prefix + 'ALIAS_' + old_alias_label new_alias_compat_label = new_alias_prefix + str_to_label(node_compat) + '_' + old_alias_label if new_alias_label != prop_label: prop_aliases[new_alias_label] = prop_label if deprecate: deprecated.append(new_alias_label) if new_alias_compat_label != prop_label: prop_aliases[new_alias_compat_label] = prop_label if deprecate: deprecated.append(new_alias_compat_label) if old_alias_names and old_alias_label != prop_label: prop_aliases[old_alias_label] = prop_label def get_binding(node_path): compat = reduced[node_path]['props'].get('compatible') if isinstance(compat, list): compat = compat[0] parent_path = get_parent_path(node_path) parent_compat = get_compat(parent_path) if parent_compat in bindings: parent_binding = bindings[parent_compat] # see if we're a sub-node if compat is None and 'sub-node' in parent_binding: return parent_binding['sub-node'] # look for a bus-specific binding if 'child' in parent_binding and 'bus' in parent_binding['child']: bus = parent_binding['child']['bus'] return bus_bindings[bus][compat] # No bus-specific binding found, look in the main dict. if compat: return bindings[compat] return None 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 child_to_parent_unmap(cell_parent, gpio_index): # This function returns a (gpio-controller, pin number) tuple from # cell_parent (identified as a 'nexus node', ie: has a 'gpio-map' # property) and gpio_index. # Note: Nexus nodes and gpio-map property are described in the # upcoming (presumably v0.3) Device Tree specification, chapter # 'Nexus nodes and Specifier Mapping'. # First, retrieve gpio-map as a list gpio_map = reduced[cell_parent]['props']['gpio-map'] # Before parsing, we need to know 'gpio-map' row size # gpio-map raws are encoded as follows: # [child specifier][gpio controller phandle][parent specifier] # child specifier field length is connector property #gpio-cells child_specifier_size = reduced[cell_parent]['props']['#gpio-cells'] # parent specifier field length is parent property #gpio-cells # Assumption 1: We assume parent #gpio-cells is constant across # the map, so we take the value of the first occurrence and apply # to the whole map. parent = phandles[gpio_map[child_specifier_size]] parent_specifier_size = reduced[parent]['props']['#gpio-cells'] array_cell_size = child_specifier_size + 1 + parent_specifier_size # Now that the length of each entry in 'gpio-map' is known, # look for a match with gpio_index for i in range(0, len(gpio_map), array_cell_size): entry = gpio_map[i:i+array_cell_size] if entry[0] == gpio_index: parent_controller_phandle = entry[child_specifier_size] # Assumption 2: We assume optional properties 'gpio-map-mask' # and 'gpio-map-pass-thru' are not specified. # So, for now, only the pin number (first value of the parent # specifier field) should be returned. parent_pin_number = entry[child_specifier_size+1] # Return gpio_controller and specifier pin return phandles[parent_controller_phandle], parent_pin_number # gpio_index did not match any entry in the gpio-map return None, None 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]] if 'gpio-map' in reduced[cell_parent]['props']: # Parent is a gpio 'nexus node' (ie has gpio-map). # Controller should be found in the map, using elem[1] as index. # Pin attribues (number, flag) will not be used in this function cell_parent, _ = child_to_parent_unmap(cell_parent, elem[1]) if cell_parent is None: raise Exception("No parent matching child specifier") 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]] if 'gpio-map' in reduced[cell_parent]['props']: # Parent is a gpio connector ie 'nexus node', ie has gpio-map). # Controller and pin number should be found in the connector map, # using elem[1] as index. # Parent pin flag is not used, so child flag(s) value (elem[2:]) # is kept as is. cell_parent, elem[1] = child_to_parent_unmap(cell_parent, elem[1]) if cell_parent is None: raise Exception("No parent matching child specifier") 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)